Merge pull request #385 from irisitymichaelgrundberg/fix/work-detection-external-clis

Merging with follow-up fixes applied on master: workingLine routed through compileVersionRegex() in both the schema refine and _workingLinePattern(), the Codex footer matched case-insensitively on the E, plus the doc passages that stated the retired Claude-mode-only rule.
This commit is contained in:
Ark0N
2026-09-07 22:41:25 +02:00
committed by GitHub
5 changed files with 183 additions and 29 deletions
+20
View File
@@ -274,6 +274,26 @@ const capabilitiesSchema = z
effort: z.boolean(),
agentSkillInjection: z.boolean(),
statusLineTelemetry: z.boolean(),
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.
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'),
})
.strict()
.optional(),
model: z
.object({ source: z.enum(['flag', 'claude-settings-file', 'none']), param: z.string().optional() })
.strict(),
+12
View File
@@ -183,6 +183,13 @@ const CLAUDE: CliEntry = {
},
capabilities: {
external: false,
// The historical hard-coded pair, now stated as data. `workingLine` matches both the
// `✻ Actualizing… (39s · ↓ 2.0k tokens)` status line and the bare `esc to interrupt`
// footer, because tmux repaints partially and only one of the two may land in a chunk.
workDetect: {
promptGlyph: '❯',
workingLine: String.raw`…\s*\((?:\d+h\s+)?(?:\d+m\s+)?\d+s\b|esc to interrupt`,
},
requiresMux: false,
// Claude installs Codeman's own hooks block into every workspace it runs in, so its
// stop/idle signals are unconditional — no per-session veto, unlike deepseek's bridge.
@@ -418,6 +425,11 @@ const CODEX: CliEntry = {
},
capabilities: {
...agentDefaults(),
// Codex draws `› Ask Codex to do anything` on its composer row and
// `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' },
transcript: 'codex-rollout',
altScreen: 'strip-full',
echo: { policy: 'predict', anchor: { kind: 'cursor' }, predictProfile: 'codex' },
+23
View File
@@ -306,6 +306,29 @@ export interface CliCapabilities {
* independent — see this interface's own doc comment.
*/
external: boolean;
/**
* How to read this CLI's own TUI for whether it is mid-turn.
*
* Codeman infers a working agent from the pane, so the two strings it needs are the
* ones that differ per CLI: the glyph on the composer row, and the status line the CLI
* draws while a turn runs. Holding them here is what lets a non-Claude CLI report work
* at all — `external` used to gate the whole detector, so every external CLI reported
* itself permanently idle even mid-turn.
*
* `promptGlyph` only ARMS the idle confirmation and is never on its own evidence that a
* turn ended, because a CLI redraws its composer throughout a turn. `workingLine` is
* the evidence, and `_confirmIdle` consults it before believing the pane went quiet.
*
* An entry that omits this field keeps Codeman's historical behaviour: the Claude glyph
* arms the confirmation and the Claude working line answers it. Leave it out for a CLI
* whose TUI nobody has characterised, and its sessions report work exactly as before.
*/
workDetect?: {
/** The glyph this CLI draws on its composer row, e.g. Claude's `❯`, Codex's `›`. */
promptGlyph: string;
/** Source of a regex matching the status line this CLI draws while a turn runs. */
workingLine: string;
};
/** No direct-PTY fallback: the CLI must run inside tmux (secrets ride tmux setenv). */
requiresMux: boolean;
/**