mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-11-15 10:59:32 +01:00
Merge pull request #694: feat(agent): COD-529 add bounded inspection and guarded submission
Conflicts resolved by keeping both sides: - CLAUDE.md (Agent CLI): #694's opening (an HTTP client, the experimental agent-session-routes behind agentCoordinationEnabled) with #708's clause on the directory spawn and its two refusals. - docs/api-reference.md (codeman agent): #708's spawn paragraph and --json shape, with #694's inspect/stage/submit in the verb list and the guarded endpoints named before "adds no route for the other verbs". - src/cli-agent.ts (@fileoverview): #708's quick-start / directory spawn routes and #694's experimental routes in one endpoint list; the two node:fs/promises imports (#708's realpath+stat, #694's open+stat) merged into one. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
commit
5908cc51fd
50 files changed
+5408
-116
No files matched your search
@@ -384,6 +384,35 @@ const capabilitiesSchema = z
|
||||
// marker but never the footer. Still bounded, since it is also how much text the
|
||||
// pattern runs over.
|
||||
blockedLines: z.number().int().min(1).max(24).optional(),
|
||||
// Same guard again: run over the rows under the composer each time guarded
|
||||
// input reads the pane, and ~/.codeman/clis.json can set it.
|
||||
composerFooterLine: z
|
||||
.string()
|
||||
.min(1)
|
||||
.refine(
|
||||
(src) => compileVersionRegex(src) !== null,
|
||||
'composerFooterLine must be a regex compileVersionRegex() accepts: at most 200 characters, no nested quantifiers'
|
||||
)
|
||||
.optional(),
|
||||
// Same guard again: run over the rows just above the composer by the same reads.
|
||||
composerPopupLine: z
|
||||
.string()
|
||||
.min(1)
|
||||
.refine(
|
||||
(src) => compileVersionRegex(src) !== null,
|
||||
'composerPopupLine must be a regex compileVersionRegex() accepts: at most 200 characters, no nested quantifiers'
|
||||
)
|
||||
.optional(),
|
||||
// Single ASCII punctuation characters, never letters, digits or whitespace: each one
|
||||
// refuses every message that starts (or whose last word starts) with it.
|
||||
composerCommandPrefixes: z
|
||||
.string()
|
||||
.regex(/^[!-/:-@[-`{-~]{1,8}$/, 'composerCommandPrefixes must be 1 to 8 ASCII punctuation characters')
|
||||
.optional(),
|
||||
composerCompletionSigils: z
|
||||
.string()
|
||||
.regex(/^[!-/:-@[-`{-~]{1,8}$/, 'composerCompletionSigils must be 1 to 8 ASCII punctuation characters')
|
||||
.optional(),
|
||||
})
|
||||
.strict()
|
||||
// A window with nothing to search is a typo, not a configuration. Refused at LOAD
|
||||
|
||||
@@ -13,6 +13,14 @@
|
||||
import type { CliEntry } from './types.js';
|
||||
import { CODEX_REASONING_EFFORTS } from '../../types/session.js';
|
||||
|
||||
/**
|
||||
* Codex's footer effort words: every CODEX_REASONING_EFFORTS level plus `default` (what
|
||||
* codex prints when no effort is configured), as one regex alternation. Shared by the two
|
||||
* patterns that read that footer (`modelDetect.screenLine` and
|
||||
* `workDetect.composerFooterLine`), so a new level is a one-place edit.
|
||||
*/
|
||||
const CODEX_EFFORT_WORDS = [...CODEX_REASONING_EFFORTS, 'default'].join('|');
|
||||
|
||||
const HOME_DIRS = {
|
||||
local: '~/.local/bin',
|
||||
usrLocal: '/usr/local/bin',
|
||||
@@ -734,6 +742,34 @@ const CODEX: CliEntry = {
|
||||
watchingLines: 4,
|
||||
blockedLine: String.raw`(?:^|\n) {0,4}› \d\. [\s\S]*\n {2}(?:Press enter to confirm|tab (?:to add|or esc to clear) notes|enter continue)(?:[^\n]|\n {2}(?=[a-z←])){0,120}(?:cancel|interrupt|quit|back|answer|all)$`,
|
||||
blockedLines: 20,
|
||||
// The rows codex draws under an idle composer, tested one at a time by guarded
|
||||
// stage/submit (agent-staging.ts): the model footer (` gpt-5.6-terra default ·
|
||||
// ~/case`, effort words from CODEX_EFFORT_WORDS as in modelDetect below), the
|
||||
// `? for shortcuts` / `← for agents` hint row 0.162.0 adds at rest, and the older
|
||||
// `NN% context left` footer. Indented, so the `›` composer row itself never matches.
|
||||
composerFooterLine: String.raw`^\s+(?:\d+% context left|\? for shortcuts|← for agents|\S+ (?:${CODEX_EFFORT_WORDS}) · )`,
|
||||
// Read from codex-rs/tui/src/bottom_pane at rust-v0.162.0 (chat_composer/
|
||||
// popup_state.rs, composer_layout.rs and the popups' render code and snapshots):
|
||||
// every suggestion popup renders ABOVE the composer and keeps the footer below it,
|
||||
// so the footer alone cannot tell `› /stat` with the command list open from an
|
||||
// idle draft, and Enter then runs the highlighted `/status`. What sits in the
|
||||
// block just above the composer (past its blank top padding row):
|
||||
// command list (`/`) `› /status show current session ...`
|
||||
// ` /statusline configure which items ...`
|
||||
// skill and mention lists ` enter insert · esc close` (key-hint row)
|
||||
// (`$`, `@` with mentions on) ` enter/tab insert · esc close · ↑/↓ select · ←/→ filter`
|
||||
// file search (`@`) ` loading...` / ` no matches`, else bare path
|
||||
// rows, which no pattern can tell from prose
|
||||
// The last case is why stage also refuses the triggers themselves (below).
|
||||
composerPopupLine: String.raw`^ {0,2}(?:enter(?:/tab)? insert · esc close|loading\.\.\.$|no matches$)|^[› ] /[\w:-]+ {2,}\S`,
|
||||
// Same source. A draft starting with `/` is a slash command (the popup opens while
|
||||
// the caret is in its name, and Enter runs a command either way), and one starting
|
||||
// with `!` is a shell command (codex switches its prompt to `!` and runs it). Popups
|
||||
// open for the word under the caret only: `@` (file search, or the unified mention
|
||||
// list) and `$` (skills and mentions; whether a given `$word` opens it depends on
|
||||
// codex's skill catalog, so every `$` word is refused).
|
||||
composerCommandPrefixes: '/!',
|
||||
composerCompletionSigils: '@$',
|
||||
},
|
||||
// The footer under the composer, measured on a live 0.147.0 pane:
|
||||
// ` gpt-5.6-terra default · ~/codeman-cases/th-scratch` (model, reasoning effort,
|
||||
@@ -757,7 +793,7 @@ const CODEX: CliEntry = {
|
||||
// and the `›` composer, and a forged footer-shaped transcript line is not followed by
|
||||
// an indented row, so it is not read.
|
||||
modelDetect: {
|
||||
screenLine: String.raw`(?:^|\n) {2}([A-Za-z0-9][\w.:/@+-]{0,79}) (?:${[...CODEX_REASONING_EFFORTS, 'default'].join('|')}) · [^\n]*(?:\n {2}[^\n]*)?$`,
|
||||
screenLine: String.raw`(?:^|\n) {2}([A-Za-z0-9][\w.:/@+-]{0,79}) (?:${CODEX_EFFORT_WORDS}) · [^\n]*(?:\n {2}[^\n]*)?$`,
|
||||
screenLines: 2,
|
||||
},
|
||||
// App Settings → Codex model / reasoning effort (synced), filled into a LOCAL launch's
|
||||
|
||||
@@ -432,6 +432,39 @@ export interface CliCapabilities {
|
||||
* this is sized for the narrowest pane the CLI was measured at. Default 1.
|
||||
*/
|
||||
blockedLines?: number;
|
||||
/**
|
||||
* Source of a regex every non-blank row BELOW an idle composer must match (at most
|
||||
* three rows): the footer and hint rows this CLI draws there and nowhere else. It is
|
||||
* what lets the experimental guarded stage/submit routes (`agent-staging.ts`) prove
|
||||
* that the `promptGlyph` row they read is this CLI's own composer, at rest, and not
|
||||
* a dialog option or a transcript line the agent printed. A CLI that omits it never
|
||||
* takes guarded input: stage and submit refuse it. Tested one row at a time.
|
||||
*/
|
||||
composerFooterLine?: string;
|
||||
/**
|
||||
* Source of a regex matching a row of a suggestion popup this CLI draws directly
|
||||
* ABOVE its composer (a slash-command list, a file or mention search, its key-hint
|
||||
* row). The footer below such a composer is unchanged, so without this guarded
|
||||
* stage/submit would read the popup frame as an idle composer, and Enter would
|
||||
* accept the highlighted entry instead of sending the draft. Tested one row at a
|
||||
* time against the contiguous block just above the composer row, never further
|
||||
* up the pane: a match there refuses the frame. See `readGuardedComposer()`.
|
||||
*/
|
||||
composerPopupLine?: string;
|
||||
/**
|
||||
* Characters that, as the first character of a draft, make Enter run a command
|
||||
* rather than send a prompt (codex: `/` slash commands, `!` shell commands).
|
||||
* Guarded stage refuses such a message before typing; commands go through the
|
||||
* ordinary input route.
|
||||
*/
|
||||
composerCommandPrefixes?: string;
|
||||
/**
|
||||
* Characters that, starting the word under the caret, open a completion popup
|
||||
* whose highlighted entry Enter accepts (codex: `@` file and mention search, `$`
|
||||
* skills). Guarded stage refuses a message whose LAST word starts with one,
|
||||
* before typing: the caret ends there, and the popup follows the caret.
|
||||
*/
|
||||
composerCompletionSigils?: string;
|
||||
};
|
||||
/**
|
||||
* How many columns this CLI indents its transcript body by, so a copy taken from its
|
||||
|
||||
Reference in new issue
Block a user