feat(agent): make coordination routes opt-in and address review

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.
This commit is contained in:
Aamer Akhter committed 2026-10-24 22:54:17 -04:00
1 parent 1a2c3be0ea
commit c78635a3d3
32 files changed
+848 -124

No files matched your search

+83 -2
View File
@@ -1,9 +1,13 @@
/** @fileoverview Strict bounded native rollout attribution without viewer heuristics. */
import { afterEach, expect, it } from 'vitest';
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
import { mkdtemp, mkdir, writeFile, rm, symlink } from 'node:fs/promises';
import { join } from 'node:path';
import { tmpdir } from 'node:os';
import { findExactCodexConversation, resolveEffectiveCodexHome } from '../src/agent-codex-identity.js';
import {
__clearCodexAttributionMemo,
findExactCodexConversation,
resolveEffectiveCodexHome,
} from '../src/agent-codex-identity.js';
const roots: string[] = [];
afterEach(async () => {
await Promise.all(roots.splice(0).map((root) => rm(root, { recursive: true, force: true })));
@@ -154,3 +158,80 @@ it.each(['workspace', 'metadata'] as const)(
}
}
);
describe('remembered attribution on a large, never-pruned store', () => {
beforeEach(() => __clearCodexAttributionMemo());
const OWNED = '33333333-3333-4333-8333-333333333333';
const NEWER = '44444444-4444-4444-8444-444444444444';
/** codex's own layout: sessions/YYYY/MM/DD/rollout-<ts>-<uuid>.jsonl */
async function dated(f: Awaited<ReturnType<typeof fixture>>, day: string, id: string, payload: object = {}) {
const dir = join(f.home, 'sessions', ...day.split('/'));
await mkdir(dir, { recursive: true });
const path = join(dir, `rollout-${day.replaceAll('/', '-')}T00-00-00-${id}.jsonl`);
await writeFile(
path,
JSON.stringify({
type: 'session_meta',
payload: { id, cwd: f.root, originator: 'codeman_managed', ...payload },
}) + '\n'
);
return path;
}
/** `count` unrelated 19 KiB rollouts (a real session_meta size) spread over older days. */
async function unrelated(f: Awaited<ReturnType<typeof fixture>>, count: number) {
const instructions = 'x'.repeat(19 * 1024);
for (let start = 0; start < count; start += 200) {
await Promise.all(
Array.from({ length: Math.min(200, count - start) }, (_, k) => {
const n = start + k;
const day = `2026/${String(1 + (n % 9)).padStart(2, '0')}/${String(1 + (n % 28)).padStart(2, '0')}`;
return dated(f, day, `10000000-1111-4111-8111-${String(n).padStart(12, '0')}`, {
originator: 'codex_cli',
instructions,
});
})
);
}
}
const small = { maxEntries: 20, maxBytes: 1048576 };
it('attributes one owned rollout beside 2,100 unrelated full-size rollouts', async () => {
const f = await fixture();
await unrelated(f, 2100);
const path = await dated(f, '2026/10/24', OWNED);
expect(await findExactCodexConversation(f.options)).toEqual({ id: OWNED, path });
}, 60_000);
it('re-verifies only the remembered file on a later call instead of re-walking the store', async () => {
const f = await fixture();
await unrelated(f, 300);
const path = await dated(f, '2026/10/24', OWNED);
expect(await findExactCodexConversation(f.options)).toEqual({ id: OWNED, path });
// 20 entries cannot cover 300 rollouts in 9 months of day directories: only the
// remembered head plus the directories not older than it fit.
expect(await findExactCodexConversation(f.options, small)).toEqual({ id: OWNED, path });
__clearCodexAttributionMemo();
expect(await findExactCodexConversation(f.options, small)).toBeUndefined();
});
it('still catches a newer owned rollout (codex /new) after remembering one', async () => {
const f = await fixture();
await unrelated(f, 50);
const path = await dated(f, '2026/10/24', OWNED);
expect(await findExactCodexConversation(f.options)).toEqual({ id: OWNED, path });
await dated(f, '2026/10/25', NEWER);
expect(await findExactCodexConversation(f.options, small)).toBeUndefined();
expect(await findExactCodexConversation(f.options)).toBeUndefined();
});
it('drops the remembered file when it is replaced, and walks again', async () => {
const f = await fixture();
const path = await dated(f, '2026/10/24', OWNED);
expect(await findExactCodexConversation(f.options)).toEqual({ id: OWNED, path });
await rm(path);
await dated(f, '2026/10/24', OWNED, { originator: 'codex_cli' });
expect(await findExactCodexConversation(f.options)).toBeUndefined();
const moved = await dated(f, '2026/10/23', NEWER);
expect(await findExactCodexConversation(f.options)).toEqual({ id: NEWER, path: moved });
});
});
+90
View File
@@ -0,0 +1,90 @@
/**
* @fileoverview The synced `agentCoordinationEnabled` setting (default OFF) that gates the
* experimental inspect/stage/submit routes: the strict settings schema admits it, App
* Settings loads and saves it under the same control id, and its row reads in Chinese
* (zh-CN) with no English left beyond the command and product names. The route gate itself
* is pinned in test/routes/agent-session-routes.test.ts.
*
* Port: N/A.
*/
import { readFileSync } from 'node:fs';
import { resolve } from 'node:path';
import vm from 'node:vm';
import { JSDOM } from 'jsdom';
import { afterAll, describe, expect, it } from 'vitest';
import { SettingsUpdateSchema } from '../src/web/schemas.js';
const PUBLIC = resolve(import.meta.dirname, '../src/web/public');
const I18N = readFileSync(resolve(PUBLIC, 'i18n.js'), 'utf8');
const INDEX = readFileSync(resolve(PUBLIC, 'index.html'), 'utf8');
const SETTINGS_UI = readFileSync(resolve(PUBLIC, 'settings-ui.js'), 'utf8');
const ID = 'appSettingsAgentCoordination';
type Api = { configure(o: Record<string, string>): void; start(): void };
/** What may stay Latin: the product, CLI, key and protocol names and the three commands. */
const leftover = (text: string) =>
text.replace(/\b(Codex|Enter|HTTP|inspect|stage|submit)\b/g, '').match(/[A-Za-z]+/g) ?? [];
function rowStrings(doc: Document): { label: string; description: string } {
const row = doc.getElementById(ID)!.closest('.set-row')!;
return {
label: row.querySelector('.set-row-label')!.textContent!.trim(),
description: row.querySelector('.set-row-desc')!.textContent!.trim(),
};
}
const source = new JSDOM(INDEX);
const english = rowStrings(source.window.document);
const dom = new JSDOM(INDEX, { runScripts: 'outside-only', url: 'http://localhost/' });
vm.runInContext(I18N, dom.getInternalVMContext(), { filename: 'i18n.js' });
const api = (dom.window as unknown as { CodemanI18n: Api }).CodemanI18n;
api.start();
api.configure({ language: 'zh-CN' });
const chinese = rowStrings(dom.window.document);
afterAll(() => {
source.window.close();
dom.window.close();
});
describe('agentCoordinationEnabled', () => {
it('is a boolean the strict settings schema accepts, and nothing else', () => {
expect(SettingsUpdateSchema.safeParse({ agentCoordinationEnabled: true }).success).toBe(true);
expect(SettingsUpdateSchema.safeParse({ agentCoordinationEnabled: false }).success).toBe(true);
expect(SettingsUpdateSchema.safeParse({ agentCoordinationEnabled: 'yes' }).success).toBe(false);
});
it('has one checkbox in App Settings, loaded only from an explicit true and saved by id', () => {
expect(INDEX.split(`id="${ID}"`)).toHaveLength(2);
expect(source.window.document.getElementById(ID)!.getAttribute('type')).toBe('checkbox');
expect(SETTINGS_UI).toContain(
`document.getElementById('${ID}').checked = settings.agentCoordinationEnabled === true;`
);
expect(SETTINGS_UI).toContain(`agentCoordinationEnabled: document.getElementById('${ID}').checked,`);
});
it('sits in the synced Cross-session features group', () => {
const group = source.window.document.getElementById(ID)!.closest('.set-group')!;
expect(group.querySelector('.set-group-head h4')!.textContent).toBe('Cross-session features');
expect(group.querySelector('.set-group-head .set-scope')!.textContent).toBe('synced');
});
it('finds the row (the translation check is not vacuous)', () => {
expect(english.label).toBe('Experimental agent coordination');
expect(english.description).toMatch(/^Lets agents read another session's output/);
});
it('translates the label and the description, with no English left', () => {
for (const key of ['label', 'description'] as const) {
expect(chinese[key]).not.toBe(english[key]);
expect(leftover(chinese[key])).toEqual([]);
}
});
it('adds each key to the dictionary once', () => {
for (const text of [english.label, english.description]) {
expect(I18N.split(`'${text}'`).length + I18N.split(`"${text}"`).length - 2).toBe(1);
}
});
});
+31 -4
View File
@@ -2,7 +2,18 @@
import { beforeEach, expect, it } from 'vitest';
import { createHash } from 'node:crypto';
import { CODEX_REASONING_EFFORTS } from '../src/types/session.js';
import { GuardedStageStore, type GuardedInputTarget } from '../src/agent-staging.js';
import {
GuardedStageStore,
guardedComposerChrome,
readGuardedComposer,
readGuardedSnapshot,
type GuardedInputTarget,
} from '../src/agent-staging.js';
import { getCli, listClis } from '../src/config/cli-registry/registry.js';
/** Codex's composer chrome exactly as the registry declares it: no copy of the glyph or footer here. */
const CODEX = guardedComposerChrome(getCli('codex'))!;
const readGuardedCodexComposer = (screen: string) => readGuardedComposer(screen, CODEX);
const readGuardedCodexSnapshot = (snapshot: string) => readGuardedSnapshot(snapshot, CODEX);
const sha = (s: string) => createHash('sha256').update(s).digest('hex');
let store: GuardedStageStore;
let time: number;
@@ -110,7 +121,6 @@ it('does not issue a receipt on failed staging or intervening input during its w
});
it('reads exact composer spaces and refuses wrapped or modal frames', async () => {
const { readGuardedCodexComposer } = await import('../src/agent-staging.js');
expect(readGuardedCodexComposer('› hello world\n GPT-6-Luna default · ~/case\n')).toBe('hello world');
expect(
readGuardedCodexComposer(
@@ -122,7 +132,6 @@ it('reads exact composer spaces and refuses wrapped or modal frames', async () =
expect(readGuardedCodexComposer('› hello\n')).toBeNull();
});
it('requires capture cursor geometry, rejecting invisible trailing edits and moved cursors', async () => {
const { readGuardedCodexSnapshot } = await import('../src/agent-staging.js');
const { formatPaneSnapshot } = await import('../src/tmux-manager.js');
const frame = (text: string, x: number, y = 0) =>
formatPaneSnapshot([`› ${text}`, ' GPT-6-Luna default · ~/case'], { cols: 100, rows: 20, cursorX: x, cursorY: y });
@@ -157,7 +166,6 @@ it('reserves receipt capacity before concurrent stages type', async () => {
it.each([...CODEX_REASONING_EFFORTS, 'default'])(
'accepts the shared Codex effort word %s in the footer',
async (word) => {
const { readGuardedCodexComposer } = await import('../src/agent-staging.js');
expect(readGuardedCodexComposer(`› hello\n GPT-6 ${word} · ~/case\n`)).toBe('hello');
}
);
@@ -168,3 +176,22 @@ it('refuses messages that reach the available pane columns before typing', async
expect(writes).toEqual([]);
await expect(store.stage(target, 'alice', 'abc')).resolves.toMatchObject({ submitted: false });
});
it('takes the composer glyph and footer from the registry, and only codex declares a footer', () => {
const codex = getCli('codex')!.capabilities.workDetect!;
expect(CODEX.promptGlyph).toBe(codex.promptGlyph);
expect(CODEX.footerLine.source).toBe(codex.composerFooterLine);
// Another glyph drives the same reader: nothing in it is codex-specific.
const other = { promptGlyph: '>>', footerLine: CODEX.footerLine };
expect(readGuardedComposer('>> hello\n GPT-6 default · ~/case\n', other)).toBe('hello');
expect(readGuardedComposer('› hello\n GPT-6 default · ~/case\n', other)).toBeNull();
const declaring = listClis()
.filter((entry) => entry.capabilities.workDetect?.composerFooterLine)
.map((entry) => entry.id);
expect(declaring).toEqual(['codex']);
expect(guardedComposerChrome(getCli('claude'))).toBeNull();
// A pattern the ReDoS guard refuses yields no chrome, so no guarded input.
const unsafe = structuredClone(getCli('codex')!);
unsafe.capabilities.workDetect!.composerFooterLine = '(a+)+b';
expect(guardedComposerChrome(unsafe)).toBeNull();
});
+33
View File
@@ -92,3 +92,36 @@ it('registers required file and confirmation options and bounded inspect flags',
?.mandatory
).toBe(true);
});
it('shows the opt-in 403 verbatim on inspect, stage and submit while the setting is off', async () => {
const dir = await mkdtemp(join(tmpdir(), 'agent-stage-cli-'));
try {
const path = join(dir, 'message');
await writeFile(path, 'hello');
const message =
'Experimental agent coordination (inspect, stage, submit) is disabled. Turn on "Experimental agent coordination" in Settings (agentCoordinationEnabled) and save first.';
for (const json of [false, true]) {
const lines: string[] = [];
const refusing: AgentDeps = {
ctx: { apiUrl: 'http://localhost:1', selfId: SELF },
json,
now: () => 1,
io: { out: (line) => lines.push(line), err: (line) => lines.push(line) },
request: async () => ({
status: 403,
text: '',
json: { success: false, error: message, errorCode: 'FORBIDDEN' },
}),
};
expect(await agentInspect(refusing, { id: OTHER, maxBytes: 10 })).toBe(1);
expect(await agentStage(refusing, { id: OTHER, messageFile: path })).toBe(1);
expect(await agentSubmit(refusing, { id: OTHER, stageId: OTHER, confirmSha: 'a'.repeat(64) })).toBe(1);
expect(lines).toHaveLength(3);
for (const line of lines) {
const shown = json ? (JSON.parse(line) as { success: boolean; error: string }).error : line;
expect(shown).toContain(`FORBIDDEN: ${message} (HTTP 403)`);
}
}
} finally {
await rm(dir, { recursive: true, force: true });
}
});
+21
View File
@@ -184,6 +184,27 @@ describe('workDetect.workingLine is guarded like every other config regex', () =
}
});
it('holds the optional composerFooterLine to the same guard', () => {
// Run over the rows under the composer each time guarded input reads the pane.
for (const composerFooterLine of ['(a+)+b', 'a'.repeat(201), '([unclosed', '']) {
expectRejected((e) => {
(e.capabilities as Record<string, unknown>).workDetect = {
promptGlyph: '>',
workingLine: 'working',
composerFooterLine,
};
}, 'guarded input tests it against pane rows a clis.json override can supply');
}
});
it('accepts every shipped composerFooterLine', () => {
for (const entry of STOCK_CLIS) {
const src = entry.capabilities.workDetect?.composerFooterLine;
if (!src) continue;
expect(compileVersionRegex(src), `${entry.id} declares a composerFooterLine the guard refuses`).not.toBeNull();
}
});
it('holds the optional blockedLine to the same guard', () => {
// Run over the foot of a capture at every settle and on a timer while a dialog is
// up, and a clis.json override can set it.
+109 -2
View File
@@ -1,11 +1,118 @@
/** @fileoverview Agent inspection and guarded input HTTP contracts (no live ports). */
import { afterEach, describe, expect, it } from 'vitest';
import { registerAgentSessionRoutes } from '../../src/web/routes/agent-session-routes.js';
import { mkdirSync, rmSync, writeFileSync } from 'node:fs';
import { dirname } from 'node:path';
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import {
AGENT_COORDINATION_DISABLED_MESSAGE,
registerAgentSessionRoutes,
} from '../../src/web/routes/agent-session-routes.js';
import { InspectionReader } from '../../src/agent-inspection.js';
import { SETTINGS_PATH } from '../../src/web/route-helpers.js';
import { createRouteTestHarness, type RouteTestHarness } from './_route-test-utils.js';
let harness: RouteTestHarness;
/** The routes are opt-in (`agentCoordinationEnabled`, default OFF); undefined = key absent. */
function setEnabled(on: boolean | undefined): void {
mkdirSync(dirname(SETTINGS_PATH), { recursive: true });
writeFileSync(SETTINGS_PATH, JSON.stringify(on === undefined ? {} : { agentCoordinationEnabled: on }));
}
beforeEach(() => setEnabled(true));
afterEach(async () => {
await harness?.app.close();
rmSync(SETTINGS_PATH, { force: true });
});
describe('the agentCoordinationEnabled gate', () => {
const requests = (id: string) => [
{ method: 'GET' as const, url: `/api/sessions/${id}/inspect` },
{ method: 'GET' as const, url: `/api/sessions/${id}/inspect?maxBytes=0` },
{ method: 'POST' as const, url: `/api/sessions/${id}/stage`, payload: { message: 'hello' } },
{
method: 'POST' as const,
url: `/api/sessions/${id}/submit`,
payload: { stageId: '11111111-1111-4111-8111-111111111111', confirmSha: 'a'.repeat(64) },
},
];
it.each([
['absent', undefined],
['false', false],
] as const)('answers 403 naming the setting on every route while it is %s', async (_label, value) => {
setEnabled(value);
harness = await createRouteTestHarness(registerAgentSessionRoutes);
const session = [...harness.ctx.sessions.values()][0];
// A session id that does not exist is refused the same way: the gate runs before lookup.
for (const request of [...requests(session.id), ...requests('no-such-session')]) {
const result = await harness.app.inject(request);
expect(result.statusCode).toBe(403);
expect(result.json()).toEqual({
success: false,
error: AGENT_COORDINATION_DISABLED_MESSAGE,
errorCode: 'FORBIDDEN',
});
}
expect(AGENT_COORDINATION_DISABLED_MESSAGE).toContain('agentCoordinationEnabled');
expect(session.writeBuffer).toEqual([]);
});
it('answers on every route once it is on, with no restart', async () => {
setEnabled(false);
harness = await createRouteTestHarness(registerAgentSessionRoutes);
const session = [...harness.ctx.sessions.values()][0];
expect((await harness.app.inject(requests(session.id)[0])).statusCode).toBe(403);
setEnabled(true);
const [inspect, invalid, stage, submit] = await Promise.all(
requests(session.id).map((request) => harness.app.inject(request))
);
expect(inspect.statusCode).toBe(200);
expect(inspect.json().data.source).toBe('pane-snapshot');
expect(invalid.statusCode).toBe(400);
// The mock session has no characterized composer, so the guards refuse: the route answered.
expect(stage.statusCode).toBe(409);
expect(submit.statusCode).toBe(409);
});
});
it('reports a non-cursor inspection failure as OPERATION_FAILED and logs only the session and error code', async () => {
const { mkdtemp, mkdir, writeFile, rm } = await import('node:fs/promises');
const { tmpdir } = await import('node:os');
const { join } = await import('node:path');
const { Session } = await import('../../src/session.js');
const root = await mkdtemp(join(tmpdir(), 'agent-inspect-fail-'));
const home = join(root, 'codex');
await mkdir(join(home, 'sessions'), { recursive: true });
const session = new Session({
id: 'inspect-fail',
workingDir: root,
mode: 'codex',
envOverrides: { CODEX_HOME: home },
});
const id = '11111111-1111-4111-8111-111111111111';
const path = join(home, 'sessions', `rollout-${id}.jsonl`);
await writeFile(
path,
JSON.stringify({ type: 'session_meta', payload: { id, cwd: root, originator: `codeman_${session.id}` } }) + '\n'
);
harness = await createRouteTestHarness(registerAgentSessionRoutes);
(harness.ctx.sessions as unknown as Map<string, Session>).set(session.id, session);
const read = vi
.spyOn(InspectionReader.prototype, 'read')
.mockRejectedValue(Object.assign(new Error(`EACCES: permission denied, open '${path}'`), { code: 'EACCES' }));
const warn = vi.spyOn(console, 'warn').mockImplementation(() => {});
try {
const result = await harness.app.inject({ url: `/api/sessions/${session.id}/inspect` });
expect(read).toHaveBeenCalled();
expect(result.statusCode).toBe(422);
expect(result.json().errorCode).toBe('OPERATION_FAILED');
expect(result.body).not.toContain(path);
const logged = warn.mock.calls.map((call) => call.join(' ')).join('\n');
expect(logged).toContain(session.id);
expect(logged).toContain('EACCES');
expect(logged).not.toContain(home);
} finally {
read.mockRestore();
warn.mockRestore();
await session.stop(false);
await rm(root, { recursive: true, force: true });
}
});
describe('agent session routes', () => {
it('returns an explicitly bounded pane snapshot when no attributed transcript exists', async () => {
+25
View File
@@ -45,8 +45,32 @@ describe('WebServer on an ephemeral port', () => {
const session = new Session({ id: 'agent-route-wiring', workingDir: process.env.HOME!, mode: 'shell' });
const sessions = (server as unknown as { sessions: Map<string, typeof session> }).sessions;
sessions.set(session.id, session);
const { mkdirSync, rmSync, writeFileSync } = await import('node:fs');
const { dirname } = await import('node:path');
const { SETTINGS_PATH } = await import('../src/web/route-helpers.js');
const setEnabled = (on: boolean) => {
mkdirSync(dirname(SETTINGS_PATH), { recursive: true });
writeFileSync(SETTINGS_PATH, JSON.stringify({ agentCoordinationEnabled: on }));
};
try {
const prefix = `http://127.0.0.1:${server.boundPort}/api/v1/sessions/${session.id}`;
// Opt-in: off, the /api/v1 alias of every route refuses with 403 FORBIDDEN.
setEnabled(false);
for (const [method, verb] of [
['GET', 'inspect'],
['POST', 'stage'],
['POST', 'submit'],
] as const) {
const refused = await fetch(prefix + '/' + verb, {
method,
...(method === 'POST' ? { headers: { 'Content-Type': 'application/json' }, body: '{}' } : {}),
});
expect(refused.status).toBe(403);
const body = await refused.json();
expect(body.errorCode).toBe('FORBIDDEN');
expect(body.error).toContain('agentCoordinationEnabled');
}
setEnabled(true);
const inspected = await fetch(prefix + '/inspect');
expect(inspected.status).toBe(200);
expect((await inspected.json()).data.source).toBe('pane-snapshot');
@@ -63,6 +87,7 @@ describe('WebServer on an ephemeral port', () => {
expect((await response.json()).errorCode).toBe('CONFLICT');
}
} finally {
rmSync(SETTINGS_PATH, { force: true });
sessions.delete(session.id);
await session.stop(false);
}