Repository navigation
Conversation
|
Thank you @aakhter for this. It adds bounded, cursor-paged inspection of another session's output and a guarded stage/submit flow that types a prompt into a local Codex composer without Enter and submits it once against a hash-confirmed receipt. The guard design is careful, the tests are thorough, and typecheck, lint, format and the rest of the static checks are green here. Before going further, one design point: this adds three endpoints under Three things make the main paths fail on ordinary setups, and each is a small fix:
Smaller items:
Once the API question is settled and the three numbered items are in, I will review it again. |
|
The concrete review findings are addressed in For the design question, I propose an experimental first phase and opened Discussion #695. Commit This labeling/help follow-up passed 94 focused tests plus typecheck, lint, format and skill/plugin validation. Route organization remains available for revision in the Discussion; I am not treating the experimental proposal as maintainer approval. |
|
Thank you @aakhter for the follow-up. To restate what this does: it adds bounded, cursor-paged inspection of another session's output and a guarded stage/submit flow that types one line into a verified local Codex composer and later sends exactly one Enter against a hash-confirmed receipt. Every item from my first review is in, and I checked each one:
A few small things to fold in while the design question is open:
What happens next: the API shape (routes under |
|
@aakhter, the design question is answered on #695: #695 (comment) In short: the three routes stay where they are under |
Gate inspect, stage and submit behind the synced agentCoordinationEnabled setting (default OFF, App Settings > Cross-session features, zh-CN strings). While off, all three routes answer 403 FORBIDDEN naming the setting, checked before session lookup; the CLI verbs print it verbatim. Remember the attributed Codex rollout per session and re-verify only its head on later calls, rescanning only date directories not older than it for a /new conflict. The first walk reads 1 KiB prefixes first, so a store of 2,100+ unrelated rollouts still attributes. Answer unexpected inspection errors with 422 OPERATION_FAILED and log the session id and error code only. Read the guarded composer glyph and footer from the CLI registry: codex workDetect.promptGlyph and a new workDetect.composerFooterLine capability, compiled through compileVersionRegex. Codex footer effort words are one shared list with modelDetect.screenLine. Fix the wiki API reference link, frame the versioning policy entry as a case of the experimental rule, join the CLAUDE.md Agent CLI paragraph, and update the changeset, skill reference and architecture notes.
d381bee to
c78635a
Compare
|
Thanks @Ark0N. Rebased onto master; #677 already carries the Design (#695):
Your 2026-10-23 items:
The changeset, api-reference, architecture-invariants, Settings-Reference wiki page, packaged skill (and plugin mirror) and the |
|
Thank you @aakhter for this round. It adds the experimental, opt-in Everything from the earlier rounds is in and I checked it: the I also ran it against a real Codex 0.162.0 pane on an isolated instance. Inspect attributed the rollout (
Small things to fold in:
With the stage retry and the popup refusal in, the rest of this is in good shape. |
Stage now re-reads the pane after typing (every 50 ms, up to 1 s) and issues a receipt only for a frame with the same generation, the input revision its own write moved, and a composer hash equal to the message's. Codex repaints 30 to 60 ms after the keys, so the first capture was often stale and stage refused with the text already typed. Every refusal after typing now says the message may be in the composer and points at the Ctrl+U recovery. A visible-frame capture reads the screen and the caret in one tmux invocation, with the old two calls as the fallback. Codex draws its suggestion popups above the composer and keeps the footer, so a staged /stat verified and submit ran /status. Stage now refuses a leading / or ! and a last word starting with @ or $ before typing, and stage and submit refuse a frame with popup rows just above the composer. The pattern and characters are registry data (composerPopupLine, composerCommandPrefixes, composerCompletionSigils), read from the codex 0.162.0 bottom_pane sources. Submit waits until 250 ms after the staged keys: codex turns an Enter within 120 ms of a typed burst into a newline. POST .../input answers 409 with its seq returned while a guarded submit holds the input lease, instead of 200 for bytes it never wrote. Also align the attribution memo comment with the code (a verified file whose incremental scan fails is not re-walked, since the full walk reads a superset), and list stage as a fromUser source in CLAUDE.md.
|
Thanks for running it on a real 0.162.0 pane; both findings were real. Pushed as one commit on top (no rebase; still merges cleanly with master). 1. Stage refusing after typing. Fixed at both ends:
2. Popups. Both done, from
Small things:
Live-pane differences I went through (from source, not a live codex; I did not run one, to avoid network and billing):
Gates: typecheck, lint, format, frontend syntax, public assets, browser excludes, plugin mirror and CLI catalogue checks pass, and the new tests fail when each fix is reverted. |
|
Thank you @aakhter for this round. It adds the experimental, opt-in Both live-pane findings from the last round are fixed the way I asked: stage now polls for a frame whose composer hash equals the message, and the trigger refusal and popup-frame check are registry data. The small items (the after-typing wording, the One thing needs a fix before merge:
Two small ones for the same push:
Optional nit, With item 1 and its test in, this is ready to merge; items 2 and 3 can come in the same push. |
The combined capture-pane and cursor query swallowed a thrown error and the caller then ran the same capture again, so a hung tmux blocked the event loop for two 5 s timeouts on every visible-frame capture, which serves every GET .../terminal load. A thrown error now reaches the capture's own catch; only output that splitFrameAndCursor cannot split falls back to the two separate calls. The capture runs through a captureRunner() seam, like inputRunner(), so the tests count the tmux calls each path makes.
Cursors shared one global FIFO of 1,024 and were never retired on use, so one agent polling every second evicted every other caller's cursors after about 17 minutes. Each owner and session now keeps its newest 8 cursors, older ones retired first, under the same global backstop; the cursor just used stays among the newest, so a retry of the last call still works.
The input lease is held across the awaited tmux Enter, so WebSocket frames keep arriving while it is held. A tagged frame (seq N) was refused and its seq rolled back, but the browser's WebSocket path does not wait for ACKs: seq N+1 landed after the lease and moved the watermark past N, and the redelivered N was then dropped as a duplicate. The keystroke was lost. Input that arrives during the lease is now held and written in arrival order when the lease is released, whatever the submit's outcome. A WebSocket frame is applied and ACKed only once written; managed writes (writeViaMux) and named keys (send-key) wait for their turn the same way, and a held write that is still being sent holds everything behind it. A keystroke held before the guard runs still moves the input revision, so it still invalidates the Enter. The hold is capped per lease (256 inputs, 1 MiB): past it the WebSocket is closed with 1013 and nothing later from it is applied, so the browser resends every unACKed frame in order, and send-key answers 409 CONFLICT instead of logging a tmux failure as INTERNAL_ERROR. POST .../input keeps its 409 for the lease plus the held input's drain; its browser drainer is serialized. Docs: correct the api-reference paragraph that said these keystrokes are redelivered, and describe the capture fallback and the per-scope cursor cap from the previous two commits in api-reference, architecture-invariants and the changeset.
|
Thanks @Ark0N. All four items are in, three commits on top of 2832a03 (no rebase; still merges cleanly with master).
Same class, also fixed:
Swept and left as is: resize frames were never refused (they move the geometry revision); paste, the mobile accessory keys and voice dictation reach the session as WebSocket input frames, so they are covered by item 1; the legacy Trade-off, stated plainly: cron's Mutation checks (fix reverted, tests that fail): WebSocket route not holding 9; the route calling the hold without its defensive check 11 ( Gates: typecheck, lint, format:check, check:frontend-syntax, check:public-assets (58 files), check:browser-excludes and the plugin sync check are green; prettier is clean on the changed tests. Focused tests: 18 files, 587 tests passed. |
|
Thank you @aakhter for this round. The PR adds the experimental, opt-in All four items from my last review are in, and I checked each one:
Typecheck, lint, format, frontend syntax, browser excludes, public assets, the plugin mirror, the CLI catalogue, the 22 focused test files and the full Two small things are left, both only reachable with the setting on and input landing within the few milliseconds of a submit:
Both are small. I will apply them on top at merge time unless you push them first. |
Agents coordinating Codeman sessions currently choose between reading broad, repeated terminal history and sending a prompt with Enter immediately. This adds experimental, opt-in bounded evidence reads and a separate, verifiable stage/submit flow to the agent CLI and the authenticated API.
agent inspect: bounded UTF-8 evidence with opaque, expiring cursors tied to the caller, session, pane lifecycle generation and attributed transcript. A local Codex session reads strictly attributed rollout records; other targets return an explicitly labelled pane snapshot. Attribution remembers the attributed file per session and re-verifies only its head on later calls, so a large~/.codex/sessionsstore is walked once. Cursors are capped per owner and session (newest 8) under the global 1,024.agent stage/agent submit: type one printable line without Enter, then submit it once against a hash-confirmed receipt, after checking the exact composer, caret, lifecycle, input revision and geometry. Failed or ambiguous submits consume the receipt and require inspection before retrying.Opt-in and experimental (per Discussion #695): the three routes stay under
/api/v1(and/api) with the experimental label indocs/versioning-policy.md, behind the synced settingagentCoordinationEnabled, default OFF. While it is off, each route answers403 FORBIDDENnaming the setting, before session lookup or body validation; turning it on needs no restart. App Settings carries the toggle (with its zh-CN string).Live-pane behaviour (measured on a real Codex 0.162.0 pane and tmux 3.4):
capturePaneBuffer()reads the frame and the caret in one tmux call; it falls back to two calls only when the combined output cannot be split, and a thrown error is not retried./or!, or a last word starting with@or$. A popup row above the composer also fails the frame check. Both are registry data on the codex entry (workDetect.composerPopupLine,composerCommandPrefixes,composerCompletionSigils; the footer isworkDetect.composerFooterLine). Slash commands go throughsend.POST .../inputanswers409 CONFLICT(nothing written) during the lease, which the browser's durable queue retries.Errors: an unexpected inspection read error is
422 OPERATION_FAILED, logged with the session id and error code, never the path. A consumed receipt stays409.Initial guarded submission supports a recognised local Codex composer only. Wrapped, modal, working, remote/container or unverifiable composers are refused. Codex launch metadata cannot authoritatively track every in-CLI
/resume; callers check the returned conversation identity. Direct tmux edits stay outside Codeman's input serialisation. Known trade-off: a cron job inpastemode on the same Codex pane writes text and Enter as two writes, so a guarded submit landing between them makes that run report as failed with the text left on the composer.Docs:
docs/api-reference.md,docs/versioning-policy.md,docs/cli-registry.md,docs/architecture-invariants.md,CLAUDE.md, the README, the wiki pages Driving-Codeman-From-An-Agent (absolute links) and Settings-Reference, CLI help, theskills/codemanreference and its synced plugin mirror, and the changeset.Verification on
2aad9391:send-keys -l --hunk from fix(tmux): send prompts that start with a hyphen literally #677 is dropped.npm run test:ci: 12,728 passed; the only failures are the 6 real-tmux cases intest/tmux-prefix-table.test.ts, which fail the same way on plain origin/master on this machine.test/guarded-lease-held-input.test.ts, the stage settle window, popup frames, the 403 gate and the combined capture) each fail with their fix reverted.