mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-10-10 01:09:43 +02:00
f79f530f938c959c01d97085598e7ef81739b460
10
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
7d8c188f83 |
fix(gemini): read Gemini CLI's composer bar and spinner line so a turn can end
A gemini session stayed "working" for good after its first turn. Its braille spinner trips the generic SPINNER_PATTERN and marks the pane working, but only a composer glyph arms the idle confirmation and gemini declared none, so it fell back to Claude's `❯`, which gemini never draws. Measured on live Gemini CLI 0.63.0 panes (capture-pane every 300 ms through real turns with a shell call at 40, 120 and 200 columns, YOLO and default approval mode, plus the raw PTY stream). The turns ran against a local stand-in for the Gemini API (GOOGLE_GEMINI_BASE_URL, which Codeman's custom endpoint support already sets), since the CLI's TUI does not depend on the backend and no account is needed for it: - The TUI repaints its whole bottom region every frame, composer included, and the composer sits between a `▄` bar and a `▀` bar; the submitted prompt is echoed between the same bars. The `▀` bar arms the idle check: every repaint carries it, tmux's reattach repaint too. The composer's prompt character is no good: it follows the approval mode (`*` in YOLO), and its `>` also starts the echoed prompt, which would make the submit verifier read a submitted prompt as stranded and press Enter again. - While a turn runs a line `⠦ Thinking... (esc to cancel, 6s)` animates about every 80 ms (largest gap mid-turn: 214 ms). The label can be any loading phrase, so the working line is the `(esc to cancel, <n>` suffix, or a spinner frame opening a line for when a long phrase wraps that suffix. Nothing at rest matches either. - A tool confirmation (default mode) replaces the composer, stops the spinner and the pane goes silent (3.9 s gap), so it reads as idle. Verified on an isolated instance from this branch: a YOLO turn emitted one session:working (+170 ms) and one session:idle (2.5 s after the last output); a default-mode turn went working -> idle while the confirmation waited -> working once allowed -> idle at the end; after a server restart four restored gemini panes went busy -> idle in about 4 s; a fresh launch settled in 3 s. The launch-settle and uncharacterised-CLI tests that used gemini as their example of a CLI without work detection now use grok and deepseek. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> |
||
|
|
112c533ac7 |
fix(omp): read omp's status bar so an omp turn can end
An omp session stayed "working" for good once a turn started, the same latch pi had: omp's braille spinner trips the SPINNER_PATTERN fast path, and only a composer glyph arms the idle confirmation. omp declared none, so it fell back to Claude's `❯`, which omp never draws once its setup wizard is done. Measured on live omp 18.8.6 and 18.0.11 panes, holding a turn open against a local endpoint that never answers: the input row is `╰─ <text>` and is redrawn at submit, at the end of a turn, at launch and on reattach. While a turn runs, the status bar's leading `π` becomes a braille spinner plus the elapsed time (` ⠼ 14s > ⬢ model > ...`; 18.0.11 pads it with two spaces, past a minute it reads `1m`), with a `⎋ Working…` row above it. The registry entry now names the input row as the glyph and either working signal as the working line. The glyph also switches the submit verifier on for omp, which reads the input row the way it reads Claude's composer. A prompt sent mid-turn goes to omp's Steering queue and clears the row, so the verifier stands down. Text left in the row after an Enter is the one case it re-presses. Verified end to end on a sandboxed instance (own HOME and PATH, omp 18.8.6): session:idle at launch, session:working during a turn, session:idle about 3 s after it ended, and a restored pane settled idle about 3 s after a server restart. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> |
||
|
|
e7d661158b |
fix(opencode): read opencode's composer bar and footer spinner so a turn can end
An opencode session that ran a tool stayed "working" for good. The running
tool row draws a braille spinner (`⠋ Sleep for 12 seconds...`), which trips
the generic SPINNER_PATTERN and marks the pane working, but only a composer
glyph arms the idle confirmation and opencode declared none, so it fell back
to Claude's `❯`, which opencode never draws. Measured on an isolated
instance: a 16 s turn latched busy/isWorking for the rest of the session.
A text-only turn had the opposite problem and never showed as working.
Measured on live opencode 1.3.0 panes (capture-pane every 250-300 ms through
real turns at 40, 60, 120 and 200 columns, plus the raw PTY stream):
- Every composer row starts with a `┃` bar, and the submitted prompt lands in
the transcript with the same bar, so a turn's first repaint arms the idle
check, and tmux's reattach repaint does the same for a restored pane.
- While a turn runs the footer row starts with an 8-cell knight-rider
spinner, `⬝■■■■■■⬝ esc interrupt`, redrawn about every 40 ms (largest
gap mid-turn: 121 ms). At rest the TUI is silent and nothing on screen
draws a `⬝`/`■` run, the 200-column sidebar included.
- The working line is the spinner run, `[⬝■]{8}`, not the label: tmux ships
`esc` and `interrupt` as separate words joined by cursor moves, so the
label never reaches the stream detector, and at 40 columns the footer
wraps it to `esc` / `interr` / `upt`. All 344 spinner chunks of a turn
match the run after Codeman's ANSI strip.
- A pending permission prompt replaces the composer and stops the spinner,
so it reads as idle (waiting on the user).
- The last `┃` row on screen is the composer's agent/model row, or the
permission box's closing bar, never the prompt text, so the submit
verifier stands down and can never press Enter into a dialog.
Verified on an isolated instance from this branch: a 15 s tool turn emitted
exactly one session:working (+271 ms) and one session:idle (3 s after the
spinner stopped); a permission prompt read idle and the allowed turn went
working -> idle; after a server restart both restored opencode panes (one
at rest, one on a permission prompt) went busy -> idle in about 4 s; a
fresh launch reached an open page as idle in 3 s.
The launch-settle tests that used opencode as their example of a CLI
without work detection now use gemini and antigravity.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
|
||
|
|
db168cdbe5 |
fix(session): never settle a just-prompted pane idle at launch
|
||
|
|
b539780f33 |
fix(pi): read pi's composer rule so a pi turn can end
A pi session stayed "working" for good once a turn started. pi's braille spinner trips the SPINNER_PATTERN fast path, which marks the pane working, but only a composer glyph arms the idle confirmation and pi declared none, so it fell back to Claude's `❯`, which pi never draws. Measured on beta136: an errored turn stayed busy/isWorking for 3+ minutes after pi was back at rest. pi has no composer glyph. Measured on a live pi 1.1.0 pane (capture-pane every 250 ms through a turn): its composer sits between two `─` rules, and while a turn runs it rewrites the top rule as `── ⠏ Working ───` on every frame. The registry entry now names the rule as the glyph that arms the check and a spinner frame inside it as the working line. The same glyph settles a reattached pi pane (a restored pane gets no launch timer): tmux's reattach repaint carries `─`, which arms the confirmation. The submit verifier reads the last rule, finds no prompt text and stands down, so it can never press Enter on a pi pane. Verified on an isolated instance from this branch: a real pi turn emitted session:working then session:idle, and after a server restart the restored pi pane went busy -> idle in about 3 s. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> |
||
|
|
40560aced1 |
fix(session): announce when a fresh or re-attached agent pane goes idle
A fresh codex, pi or opencode tile could spin "working" forever while the pane sat at its composer. The external-CLI launch timer set the status to idle WITHOUT an event, only `needsRefresh`. When the launch paint never marked the pane working, the later idle confirmation found the status already idle and announced nothing either, so every open browser kept the `busy` from the spawn broadcast. A reload fixed it, which is why only open pages were stuck. Measured on the 1.36.0 beta: w8 (codex) had `lastPromptTime: 0`, i.e. no idle edge ever, and a page held a fresh pi session at `busy` while GET /api/sessions said `idle`. pi and opencode hit it on every launch (no work detection, so the timer is all they have); codex only when its launch paint lost the race against the timer. - `_concludeIdle()` is now the one place a pane is concluded idle: status, working flag and prompt stamp change together and `idle` is emitted (session:idle + state broadcast). `_confirmIdle()` uses it too. - The launch settle (`_settlePaneStartup`) concludes a pane still in its spawn-time `busy`. A pane already working is left to `_confirmIdle()` only when its CLI declares `capabilities.workDetect`; for the rest the timer is the only thing that can settle it, so it also clears a working flag a launch spinner glyph latched. - `_armPaneSettle()` also arms it for a RESTORED pane (Codeman restart, auto-reattach, tile Attach) of a CLI without work detection (at this commit shell, opencode, gemini, antigravity, pi, grok, deepseek and omp), which used to stay `busy` server-side with nothing to clear it. Restored claude and codex panes stay on their composer glyph, so a restart mid-turn is never called idle. No `needsRefresh` there: an attach refetches by itself. Pre-existing since the OpenCode integration, not a regression of this release. Restore-path gap reported by the opencode and pi sessions working the same symptom. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> |
||
|
|
f1b7283393 |
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> |
||
|
|
51957e2ed4 |
fix(session): let each CLI declare how its own pane shows work
A Codex session reported `isWorking: false` for its entire life, including
mid-turn. Codeman has four paths that mark a session working, and all four were
inert for Codex:
- The spinner fast path tests eight braille frames, and Codex animates none.
- The activity-streak fallback was wrapped in `!isExternalCliMode(mode)`.
- The pane probe inside `_confirmIdle` would have matched, since Codex prints
`esc to interrupt`, but arming it required the literal glyph `❯` and Codex
draws `›` on its composer row.
- The text detector sat inside `_processExpensiveParsers`, whose first statement
returns early for an external CLI.
Add an optional `workDetect: { promptGlyph, workingLine }` to CliCapabilities,
so the two strings that differ per CLI are registry data rather than constants
in the detector. Claude declares its existing pair and behaves as before. Codex
declares `›` and `esc to interrupt`. The text detector moves above the
external-CLI early return, guarded on the descriptor so a CLI without one still
skips the ANSI strip that the early return used to save it.
A CLI that declares no descriptor falls back to Claude's pair, and the
activity-streak gate now reads "has a descriptor, or is not external", so the
plain shell mode keeps the behaviour it had.
Rewrite the test that asserted the old premise in its own comment, so it makes
the same guarantee for a genuinely uncharacterised CLI, and add Codex coverage
built from verbatim pane captures on Codex CLI 0.152.1.
|
||
|
|
cb9149879d |
restore the activity stamp across restarts: the quiet ordering no longer flattens on deploy
Root cause of the reviewer's mass-bump measurement (17 of 17 sessions with an identical lastActivityAt): every restart restamps all sessions in the constructor loop, and the boot auto-attach's repaint re-bumps the rest within the same second. A 12-minute steady-state sample shows NO ambient mass bump, so restarts are the whole story, and Codeman restarts on every deploy. The stamp now has a display twin: recovery threads the previous run's lastActivityAt from state.json into the wire-visible stamp (getter + toState), and a 15s settle window keeps the attach repaint from overwriting it. Real actions (input, task assignment, respawn) always write through. The private stamp keeps its boot-anchored semantics untouched, because the idle confirmation reads it as how long the pane has been quiet. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
b03780dfd2 |
fix(session): decide working/idle from the pane, not the composer redraw
Every working Claude session reported `status: "idle"` about two seconds into its turn. Measured on live workers: two sessions mid-tool-call at 13 and 17 minutes both read `idle` while their panes showed `✻ Actualizing… (13m 23s · ↓ 47.5k tokens)`. Two things had drifted apart: 1. The working indicator changed. Claude animates the glyph through `· ✢ ✳ ∗ ✻ ✽` and randomizes the gerund per turn, so neither SPINNER_PATTERN (braille, no longer drawn) nor the keyword list (Thinking/Writing/Reading/Running) matches a turn anymore. 2. A `❯` sighting is not the end of a turn. Claude redraws the composer roughly once a second all the way through one, and that redraw armed the "2s later, call it idle" timer. Matching the new status line in the STREAM does not fix it either: tmux ships partial repaints, so the complete line reached the PTY about once every 20 seconds while the `❯` arrived every second. So the decision moves off the stream: - An unbroken run of repaints marks a turn as started. Sampled once a second for 12s over six live sessions, the two working ones produced output in 12/12 windows and the four idle ones in 0/12. Pure helpers in session-activity.ts carry the thresholds. - Idle now needs the pane to go quiet AND the screen to agree. `_confirmIdle()` asks tmux what is rendered (new `capturePaneText()`, one plain `capture-pane`, floored at 1.5s per session and only ever at a transition) and re-checks every 5s while the screen still shows work. A turn can sit silent for tens of seconds inside one tool call, so silence alone proves nothing. - The same screen check vetoes keystroke echo, which is a steady stream of repaints too but is not work. CLAUDE_WORKING_LINE_PATTERN matches the `… (elapsed)` shape rather than the glyph, because the FINISHED line (`✻ Cooked for 2m 49s`) carries the same glyph and would otherwise pin a session at working forever. Claude mode only. An external CLI has no `❯`, so nothing would arm the confirmation and such a session would latch busy. respawn-patterns.hasWorkingPattern() had the same blind spot (its gerund list cannot see "Actualizing"), so it takes the pattern as an extra signal. That can only make respawn less eager, never more. Idle now lands about 3 to 5 seconds after a turn ends instead of 2 seconds into one. Verified end to end against a live worker, sampled against the CLI's own "esc to interrupt" footer as independent ground truth: busy for all 25s of a turn, idle 3s after it ended. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |