mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-10-02 13:39:41 +02:00
fix(cli-registry): guard workDetect.workingLine like every other config regex
#385 made the composer glyph and the working status line per-CLI registry data, which is right, but `workingLine` arrived as a config-supplied regex validated with a bare `new RegExp()`. That skips `compileVersionRegex()`, the helper the registry uses for exactly this: a `~/.codeman/clis.json` override can set the field, the compiled pattern is run against every accumulated PTY chunk and every pane capture, and a nested quantifier there backtracks on the event loop for the whole server rather than one session. Route it through the helper in both places, which are not redundant: the schema refine rejects the entry at LOAD time so a bad pattern never reaches a session, and `_workingLinePattern()` compiles through the same helper so the runtime cannot hold a pattern the schema would have refused. The helper returns null instead of throwing, so the Claude-pattern fallback stops being a try/catch and becomes structural. Both shipped patterns compile unchanged, and Claude's is behaviourally identical to CLAUDE_WORKING_LINE_PATTERN. Also match the Codex footer case-insensitively on the E. It was characterised against codex-cli 0.152.1, which prints a lowercase `esc`; a version capitalising it would make the whole fix silently inert, since the pane would simply never look like it was working. Docs: CLAUDE.md, architecture-invariants and cli-registry.md all still stated the Claude-mode-only rule this PR retires, and none of them named the new capability or the regex guard. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -13,7 +13,7 @@
|
||||
*/
|
||||
|
||||
import { z } from 'zod';
|
||||
import { TOKEN_PATTERNS } from './patterns.js';
|
||||
import { compileVersionRegex, TOKEN_PATTERNS } from './patterns.js';
|
||||
import { isKnownLauncherProfile, isKnownSetenvProfile } from './profiles.js';
|
||||
|
||||
/** A bare CLI id: lowercase, starts with a letter, at most 24 chars. Also used as a CSS/URL token. */
|
||||
@@ -277,20 +277,17 @@ const capabilitiesSchema = z
|
||||
workDetect: z
|
||||
.object({
|
||||
promptGlyph: z.string().min(1).max(8),
|
||||
// Compiled per session, so a broken pattern must fail at LOAD time rather than
|
||||
// throw inside the PTY data handler.
|
||||
// Config-supplied regex, so it goes through the same guard as `version.regex`:
|
||||
// ~/.codeman/clis.json can set this, and the compiled pattern runs on the PTY
|
||||
// hot path, where a nested quantifier would be a ReDoS against the event loop.
|
||||
// A broken pattern must also fail at LOAD time rather than inside a data handler.
|
||||
workingLine: z
|
||||
.string()
|
||||
.min(1)
|
||||
.max(400)
|
||||
.refine((src) => {
|
||||
try {
|
||||
new RegExp(src);
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}, 'workingLine must be a valid regular expression'),
|
||||
.refine(
|
||||
(src) => compileVersionRegex(src) !== null,
|
||||
'workingLine must be a regex compileVersionRegex() accepts: at most 200 characters, no nested quantifiers'
|
||||
),
|
||||
})
|
||||
.strict()
|
||||
.optional(),
|
||||
|
||||
@@ -429,7 +429,7 @@ const CODEX: CliEntry = {
|
||||
// `Working (2m 49s • esc to interrupt)` above it while a turn runs. It animates no
|
||||
// braille spinner, and it never prints `esc to interrupt` at rest, so that phrase
|
||||
// alone separates a running turn from an idle one.
|
||||
workDetect: { promptGlyph: '›', workingLine: 'esc to interrupt' },
|
||||
workDetect: { promptGlyph: '›', workingLine: '[Ee]sc to interrupt' },
|
||||
transcript: 'codex-rollout',
|
||||
altScreen: 'strip-full',
|
||||
echo: { policy: 'predict', anchor: { kind: 'cursor' }, predictProfile: 'codex' },
|
||||
|
||||
+6
-7
@@ -106,6 +106,7 @@ import {
|
||||
import { DEFAULT_TMUX_HISTORY_LIMIT } from './config/terminal-history.js';
|
||||
import { EXEC_TIMEOUT_MS } from './config/exec-timeout.js';
|
||||
import { getCli } from './config/cli-registry/registry.js';
|
||||
import { compileVersionRegex } from './config/cli-registry/patterns.js';
|
||||
import { resolveSessionCliVersion } from './utils/cli-resolver.js';
|
||||
import {
|
||||
buildInteractiveArgs,
|
||||
@@ -2469,13 +2470,11 @@ export class Session extends EventEmitter {
|
||||
private _workingLinePattern(): RegExp {
|
||||
if (this._workingLineRe === undefined) {
|
||||
const src = getCli(this.mode)?.capabilities.workDetect?.workingLine;
|
||||
// The schema validates `workingLine` at load time, so a throw here would mean a
|
||||
// registry that never loaded. Falling back beats taking the session down.
|
||||
try {
|
||||
this._workingLineRe = src ? new RegExp(src) : CLAUDE_WORKING_LINE_PATTERN;
|
||||
} catch {
|
||||
this._workingLineRe = CLAUDE_WORKING_LINE_PATTERN;
|
||||
}
|
||||
// Same guard the schema applies, not a second opinion: `compileVersionRegex()` is
|
||||
// what keeps a nested quantifier out of this pattern, and this one runs on the PTY
|
||||
// hot path. It returns null rather than throwing, and Claude's pattern is the
|
||||
// fallback every session used before the registry carried one.
|
||||
this._workingLineRe = (src ? compileVersionRegex(src) : null) ?? CLAUDE_WORKING_LINE_PATTERN;
|
||||
}
|
||||
return this._workingLineRe;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user