fix(agent): address inspection and staging review feedback

This commit is contained in:
Aamer Akhter committed 2026-10-24 22:37:18 -04:00
1 parent a24b0ec7bb
commit 4c2f050a96
24 files changed
+517 -116

No files matched your search

+37
View File
@@ -117,3 +117,40 @@ it.each([
}
}
);
it('attributes an owned rollout among 320 unrelated full-size metadata prefixes', async () => {
const f = await fixture();
const path = await f.put(A);
await Promise.all(
Array.from({ length: 320 }, (_, n) => {
const id = `10000000-1111-4111-8111-${String(n).padStart(12, '0')}`;
return f.put(id, { originator: 'codex_cli', instructions: 'x'.repeat(5000) });
})
);
expect(await findExactCodexConversation(f.options)).toEqual({ id: A, path });
expect(
await findExactCodexConversation(f.options, { maxEntries: 8192, maxBytes: 1048576, maxPrefixBytes: 4096 })
).toBeUndefined();
});
it.each(['workspace', 'metadata'] as const)(
'probes %s bounded past the cap before canonical filesystem reads',
async (where) => {
const { vi } = await import('vitest');
const probe = await import('../src/utils/bounded-path-probe.js');
const f = await fixture();
const alias = join(f.root, 'workspace-alias');
await symlink(f.root, alias);
await f.put(A, { cwd: where === 'metadata' ? alias : f.root });
const blocked = where === 'workspace' ? f.root : alias;
const spy = vi
.spyOn(probe, 'probePathKind')
.mockImplementation(async (path) => (path === blocked ? 'unknown' : 'directory'));
try {
expect(await findExactCodexConversation(f.options)).toBeUndefined();
expect(spy).toHaveBeenCalledWith(blocked, { pastCap: true });
} finally {
spy.mockRestore();
}
}
);
+19
View File
@@ -159,3 +159,22 @@ it('keeps an incomplete UTF8 sequence pending until its JSONL record is complete
await appendFile(file, row.subarray(split));
expect((await reader.read(file, scope, 100, first.cursor)).evidence).toBe(row.toString('utf8'));
});
it('treats a full incremental page as pageable rather than a blocked oversized record', async () => {
await writeFile(file, '{}\n');
const initial = await reader.read(file, scope, 100);
await appendFile(file, '{}\n{}\n');
const page = await reader.read(file, scope, 3, initial.cursor);
expect(page.evidence).toBe('{}\n');
expect(page.truncated).toBe(true);
expect(page.blockedReason).toBeUndefined();
const next = await reader.read(file, scope, 3, page.cursor);
expect(next.evidence).toBe('{}\n');
});
it('still labels the first selected oversized tail record after omitted older bytes', async () => {
await writeFile(file, '{}\n' + JSON.stringify({ text: 'x'.repeat(100) }) + '\n');
const page = await reader.read(file, scope, 10);
expect(page.evidence).toBe('');
expect(page.blockedReason).toBe('record-exceeds-output-budget');
});
+155 -18
View File
@@ -7,13 +7,19 @@ it('fences writes and reused-pid pane lifecycles without persisting prompt conte
let screen = '› \n GPT-6-Luna default · ~/case\n';
const mux = {
isAvailable: () => true,
capturePaneBuffer: () =>
formatPaneSnapshot(screen.trimEnd().split('\n'), {
capturePaneBuffer: (
_name: string,
_pane?: string,
opts?: { capturedGeometry?: { cols: number; rows: number } }
) => {
if (opts) opts.capturedGeometry = { cols: 100, rows: 20 };
return formatPaneSnapshot(screen.trimEnd().split('\n'), {
cols: 100,
rows: 20,
cursorX: screen.startsWith('› hello') ? 7 : 2,
cursorY: 0,
}),
});
},
sendInput: async () => true,
};
const session = new Session({
@@ -49,8 +55,19 @@ it('checks conditional Enter at the dispatch boundary and rejects concurrent bro
const writes: string[] = [];
const mux = {
isAvailable: () => true,
capturePaneBuffer: () =>
formatPaneSnapshot(['› hello', ' GPT-6-Luna default · ~/case'], { cols: 100, rows: 20, cursorX: 7, cursorY: 0 }),
capturePaneBuffer: (
_name: string,
_pane?: string,
opts?: { capturedGeometry?: { cols: number; rows: number } }
) => {
if (opts) opts.capturedGeometry = { cols: 100, rows: 20 };
return formatPaneSnapshot(['› hello', ' GPT-6-Luna default · ~/case'], {
cols: 100,
rows: 20,
cursorX: 7,
cursorY: 0,
});
},
sendInput: async (_id: string, text: string, options?: { beforeDispatch?: () => boolean }) => {
await new Promise<void>((resolve) => {
dispatch = resolve;
@@ -104,13 +121,19 @@ it('cancels an older automatic submit verifier before staging an identical promp
const writes: string[] = [];
const mux = {
isAvailable: () => true,
capturePaneBuffer: () =>
formatPaneSnapshot([`› ${composer}`, ' GPT-6-Luna default · ~/case'], {
capturePaneBuffer: (
_name: string,
_pane?: string,
opts?: { capturedGeometry?: { cols: number; rows: number } }
) => {
if (opts) opts.capturedGeometry = { cols: 100, rows: 20 };
return formatPaneSnapshot([`› ${composer}`, ' GPT-6-Luna default · ~/case'], {
cols: 100,
rows: 20,
cursorX: 2 + composer.length,
cursorY: 0,
}),
});
},
capturePaneText: () => `› ${composer}\n GPT-6-Luna default · ~/case`,
sendInput: async (_id: string, text: string) => {
writes.push(text);
@@ -141,8 +164,19 @@ it('routes automatic verifier Enter through the shared guarded input boundary',
const writes: string[] = [];
const mux = {
isAvailable: () => true,
capturePaneBuffer: () =>
formatPaneSnapshot(['› hello', ' GPT-6-Luna default · ~/case'], { cols: 100, rows: 20, cursorX: 7, cursorY: 0 }),
capturePaneBuffer: (
_name: string,
_pane?: string,
opts?: { capturedGeometry?: { cols: number; rows: number } }
) => {
if (opts) opts.capturedGeometry = { cols: 100, rows: 20 };
return formatPaneSnapshot(['› hello', ' GPT-6-Luna default · ~/case'], {
cols: 100,
rows: 20,
cursorX: 7,
cursorY: 0,
});
},
capturePaneText: () => '› hello\n GPT-6-Luna default · ~/case',
sendInput: async (_id: string, text: string, options?: { beforeDispatch?: () => boolean }) => {
if (options?.beforeDispatch) {
@@ -180,8 +214,19 @@ it('refuses legacy one-shot prompt entrypoints during a guarded submission befor
const writes: string[] = [];
const mux = {
isAvailable: () => true,
capturePaneBuffer: () =>
formatPaneSnapshot(['› hello', ' GPT-6-Luna default · ~/case'], { cols: 100, rows: 20, cursorX: 7, cursorY: 0 }),
capturePaneBuffer: (
_name: string,
_pane?: string,
opts?: { capturedGeometry?: { cols: number; rows: number } }
) => {
if (opts) opts.capturedGeometry = { cols: 100, rows: 20 };
return formatPaneSnapshot(['› hello', ' GPT-6-Luna default · ~/case'], {
cols: 100,
rows: 20,
cursorX: 7,
cursorY: 0,
});
},
sendInput: async (_id: string, text: string, options?: { beforeDispatch?: () => boolean }) => {
await new Promise<void>((resolve) => {
release = resolve;
@@ -259,7 +304,14 @@ it('uses fresh normalized pane rows for working/dialog denial before exact compo
mode: 'codex',
mux: {
isAvailable: () => true,
capturePaneBuffer: capture,
capturePaneBuffer: (
_name: string,
_pane?: string,
opts?: { capturedGeometry?: { cols: number; rows: number } }
) => {
if (opts) opts.capturedGeometry = { cols: 100, rows: 20 };
return capture();
},
capturePaneText: () => {
throw Error('must use same frame');
},
@@ -287,13 +339,19 @@ it('keeps raw user input synchronous and preserves receipts for focus/mouse/noop
mode: 'codex',
mux: {
isAvailable: () => true,
capturePaneBuffer: () =>
formatPaneSnapshot(['› hello', ' GPT-6-Luna default · ~/case'], {
capturePaneBuffer: (
_name: string,
_pane?: string,
opts?: { capturedGeometry?: { cols: number; rows: number } }
) => {
if (opts) opts.capturedGeometry = { cols: 100, rows: 20 };
return formatPaneSnapshot(['› hello', ' GPT-6-Luna default · ~/case'], {
cols: 100,
rows: 20,
cursorX: 7,
cursorY: 0,
}),
});
},
},
muxSession: { muxName: 'codeman-12345678', sessionId: 'test', createdAt: 1, pid: 1 },
} as unknown as ConstructorParameters<typeof Session>[0]);
@@ -319,11 +377,90 @@ it('keeps raw user input synchronous and preserves receipts for focus/mouse/noop
session.owner = 'other';
session.owner = undefined;
expect(session.inspectionGeneration).not.toBe(generation);
const priorResize = session.inspectionGeneration;
const priorResize = session.guardedInputGeneration;
session.resize(80, 24);
expect(session.inspectionGeneration).not.toBe(priorResize);
expect(session.guardedInputGeneration).not.toBe(priorResize);
} finally {
access.ptyProcess = null;
await session.stop(false);
}
});
it('refuses a message that would wrap using the actual captured indent and width before any write', async () => {
const writes: string[] = [];
const session = new Session({
workingDir: '/tmp',
mode: 'codex',
mux: {
isAvailable: () => true,
capturePaneBuffer: (
_name: string,
_pane: string,
opts: { capturedGeometry?: { cols: number; rows: number } }
) => {
if (opts) opts.capturedGeometry = { cols: 30, rows: 20 };
return formatPaneSnapshot([' › ', ' GPT-6 default · ~/case'], {
cols: 30,
rows: 20,
cursorX: 6,
cursorY: 0,
});
},
sendInput: async (_id: string, text: string) => {
writes.push(text);
return true;
},
},
muxSession: { muxName: 'codeman-12345678', sessionId: 'test', createdAt: 1, pid: 1 },
} as unknown as ConstructorParameters<typeof Session>[0]);
const { GuardedStageStore } = await import('../src/agent-staging.js');
try {
const store = new GuardedStageStore();
const target = {
id: session.id,
snapshot: () => session.guardedInputSnapshot(),
write: (text: string) => session.stageGuardedInput(text),
submit: (snap: Parameters<Session['submitGuardedInput']>[0]) => session.submitGuardedInput(snap),
};
await expect(store.stage(target, 'alice', 'x'.repeat(24))).rejects.toThrow();
expect(writes).toEqual([]);
} finally {
await session.stop(false);
}
});
it('keeps inspection generations stable across resize but invalidates a staged snapshot', async () => {
const session = new Session({ workingDir: '/tmp', mode: 'codex' });
const access = session as unknown as { ptyProcess: { resize(cols: number, rows: number): void } | null };
access.ptyProcess = { resize: () => {} };
try {
const inspection = session.inspectionGeneration;
const staging = session.guardedInputGeneration;
session.resize(80, 24);
expect(session.inspectionGeneration).toBe(inspection);
expect(session.guardedInputGeneration).not.toBe(staging);
} finally {
access.ptyProcess = null;
await session.stop(false);
}
});
it('records a verifier re-press as the latest submit time', async () => {
vi.useFakeTimers();
vi.setSystemTime(10000);
const session = new Session({
workingDir: '/tmp',
mode: 'codex',
mux: { isAvailable: () => true, capturePaneText: () => '› hello\n', sendInput: async () => true },
muxSession: { muxName: 'codeman-12345678', sessionId: 'test', createdAt: 1, pid: 1 },
} as unknown as ConstructorParameters<typeof Session>[0]);
try {
await session.writeViaMux('hello\r');
const initial = session.lastSubmitAt;
await vi.advanceTimersByTimeAsync(2000);
expect(session.lastSubmitAt).toBe(initial + 2000);
} finally {
await session.stop(false);
vi.useRealTimers();
}
});
+18 -1
View File
@@ -1,6 +1,7 @@
/** @fileoverview Guarded staging one-shot and content-verification safety. */
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';
const sha = (s: string) => createHash('sha256').update(s).digest('hex');
let store: GuardedStageStore;
@@ -21,7 +22,8 @@ beforeEach(() => {
store = new GuardedStageStore(() => time, 2, 1000);
target = {
id: 's',
snapshot: () => (composer === null ? null : { generation, revision, composerSha: sha(composer) }),
snapshot: () =>
composer === null ? null : { generation, revision, composerSha: sha(composer), availableColumns: 100 },
submit: async () => target.write('\r'),
write: async (text) => {
writes.push(text);
@@ -151,3 +153,18 @@ it('reserves receipt capacity before concurrent stages type', async () => {
release!();
await first;
});
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');
}
);
it('refuses messages that reach the available pane columns before typing', async () => {
target.snapshot = () => ({ generation, revision, composerSha: sha(composer ?? ''), availableColumns: 4 });
for (const text of ['four', '界界']) await expect(store.stage(target, 'alice', text)).rejects.toThrow();
expect(writes).toEqual([]);
await expect(store.stage(target, 'alice', 'abc')).resolves.toMatchObject({ submitted: false });
});
+28 -11
View File
@@ -1,6 +1,6 @@
/** @fileoverview Agent inspection and guarded input HTTP contracts (no live ports). */
import { afterEach, describe, expect, it } from 'vitest';
import { registerSessionRoutes } from '../../src/web/routes/session-routes.js';
import { registerAgentSessionRoutes } from '../../src/web/routes/agent-session-routes.js';
import { createRouteTestHarness, type RouteTestHarness } from './_route-test-utils.js';
let harness: RouteTestHarness;
@@ -9,7 +9,7 @@ afterEach(async () => {
});
describe('agent session routes', () => {
it('returns an explicitly bounded pane snapshot when no attributed transcript exists', async () => {
harness = await createRouteTestHarness(registerSessionRoutes);
harness = await createRouteTestHarness(registerAgentSessionRoutes);
const session = [...harness.ctx.sessions.values()][0];
const result = await harness.app.inject({ url: `/api/sessions/${session.id}/inspect?maxBytes=20` });
expect(result.statusCode).toBe(200);
@@ -19,7 +19,7 @@ describe('agent session routes', () => {
expect(Buffer.byteLength(body.data.evidence)).toBeLessThanOrEqual(20);
});
it('refuses guarded staging for an uncharacterized composer', async () => {
harness = await createRouteTestHarness(registerSessionRoutes);
harness = await createRouteTestHarness(registerAgentSessionRoutes);
const session = [...harness.ctx.sessions.values()][0];
const result = await harness.app.inject({
method: 'POST',
@@ -33,7 +33,9 @@ describe('agent session routes', () => {
it('checks ownership before inspection, stage and submit', async () => {
process.env.CODEMAN_MULTIUSER = '1';
try {
harness = await createRouteTestHarness(registerSessionRoutes, { authUser: { username: 'alice', role: 'user' } });
harness = await createRouteTestHarness(registerAgentSessionRoutes, {
authUser: { username: 'alice', role: 'user' },
});
const session = [...harness.ctx.sessions.values()][0];
session.owner = 'bob';
for (const request of [
@@ -52,7 +54,7 @@ it('checks ownership before inspection, stage and submit', async () => {
}
});
it('validates output budgets and refuses to resume a pane fallback', async () => {
harness = await createRouteTestHarness(registerSessionRoutes);
harness = await createRouteTestHarness(registerAgentSessionRoutes);
const session = [...harness.ctx.sessions.values()][0];
session.terminalBuffer = '😀é'.repeat(20);
for (const value of ['0', '-1', '65537', '1.5', 'NaN'])
@@ -74,7 +76,7 @@ it('validates output budgets and refuses to resume a pane fallback', async () =>
it('uses real session writes to stage without Enter and refuses intervening API input', async () => {
const { Session } = await import('../../src/session.js');
const { formatPaneSnapshot } = await import('../../src/tmux-manager.js');
harness = await createRouteTestHarness(registerSessionRoutes);
harness = await createRouteTestHarness(registerAgentSessionRoutes);
let composer = '';
const writes: string[] = [];
const session = new Session({
@@ -83,13 +85,19 @@ it('uses real session writes to stage without Enter and refuses intervening API
mode: 'codex',
mux: {
isAvailable: () => true,
capturePaneBuffer: () =>
formatPaneSnapshot([`› ${composer}`, ' GPT-6-Luna default · ~/case'], {
capturePaneBuffer: (
_name: string,
_pane?: string,
opts?: { capturedGeometry?: { cols: number; rows: number } }
) => {
if (opts) opts.capturedGeometry = { cols: 100, rows: 20 };
return formatPaneSnapshot([`› ${composer}`, ' GPT-6-Luna default · ~/case'], {
cols: 100,
rows: 20,
cursorX: 2 + composer.length,
cursorY: 0,
}),
});
},
sendInput: async (_id: string, text: string, options?: { beforeDispatch?: () => boolean }) => {
if (options?.beforeDispatch && !options.beforeDispatch()) return false;
writes.push(text);
@@ -157,7 +165,9 @@ it('reads only a strictly attributed Codex rollout and binds HTTP cursors to its
const row =
JSON.stringify({ type: 'session_meta', payload: { id, cwd: root, originator: `codeman_${session.id}` } }) + '\n';
await writeFile(join(home, 'sessions', `rollout-${id}.jsonl`), row + '{"text":"hello"}\n');
harness = await createRouteTestHarness(registerSessionRoutes, { authUser: { username: 'alice', role: 'admin' } });
harness = await createRouteTestHarness(registerAgentSessionRoutes, {
authUser: { username: 'alice', role: 'admin' },
});
(harness.ctx.sessions as unknown as Map<string, Session>).set(session.id, session);
try {
const first = await harness.app.inject({
@@ -172,6 +182,13 @@ it('reads only a strictly attributed Codex rollout and binds HTTP cursors to its
expect(data.cursor).not.toContain(home);
const next = await harness.app.inject({ url: `/api/sessions/${session.id}/inspect?since=${data.cursor}` });
expect(next.json().data.evidence).toBe('');
const access = session as unknown as { ptyProcess: { resize(cols: number, rows: number): void } | null };
access.ptyProcess = { resize: () => {} };
session.resize(80, 24);
access.ptyProcess = null;
expect(
(await harness.app.inject({ url: `/api/sessions/${session.id}/inspect?since=${data.cursor}` })).statusCode
).toBe(200);
session.owner = 'changed-owner';
expect(
(await harness.app.inject({ url: `/api/sessions/${session.id}/inspect?since=${data.cursor}` })).statusCode
@@ -202,7 +219,7 @@ it('refuses a stale launch-resume cursor once a new managed rollout conflicts wi
const row = (id: string, originator: string) =>
JSON.stringify({ type: 'session_meta', payload: { id, cwd: root, originator } }) + '\n';
await writeFile(join(home, 'sessions', `rollout-${old}.jsonl`), row(old, 'original-launch'));
harness = await createRouteTestHarness(registerSessionRoutes);
harness = await createRouteTestHarness(registerAgentSessionRoutes);
(harness.ctx.sessions as unknown as Map<string, Session>).set(session.id, session);
try {
const initial = (await harness.app.inject({ url: `/api/sessions/${session.id}/inspect` })).json().data;
+10
View File
@@ -326,3 +326,13 @@ describe('sendInput line breaks', () => {
expect(answer.ran[1]).toMatch(/ send-keys -t "codeman-abc12345" Enter$/);
});
});
it('terminates literal operands in both sendInputToPane branches', async () => {
const { readFile } = await import('node:fs/promises');
const src = await readFile(new URL('../src/tmux-manager.ts', import.meta.url), 'utf8');
const start = src.indexOf(' sendInputToPane(');
const body = src.slice(start, src.indexOf('\n }\n', start));
const literal = body.match(/send-keys[^`\n]*-l[^`\n]*/g) ?? [];
expect(literal).toHaveLength(2);
for (const command of literal) expect(command).toContain(' -l -- ');
});
+28
View File
@@ -40,6 +40,34 @@ describe('WebServer on an ephemeral port', () => {
expect(ctx.port).toBe(socketPort());
});
it('registers bounded agent routes with the server route modules', async () => {
const { Session } = await import('../src/session.js');
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);
try {
const prefix = `http://127.0.0.1:${server.boundPort}/api/v1/sessions/${session.id}`;
const inspected = await fetch(prefix + '/inspect');
expect(inspected.status).toBe(200);
expect((await inspected.json()).data.source).toBe('pane-snapshot');
for (const [verb, body] of [
['stage', { message: 'hello' }],
['submit', { stageId: '11111111-1111-4111-8111-111111111111', confirmSha: 'a'.repeat(64) }],
] as const) {
const response = await fetch(prefix + '/' + verb, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(body),
});
expect(response.status).toBe(409);
expect((await response.json()).errorCode).toBe('CONFLICT');
}
} finally {
sessions.delete(session.id);
await session.stop(false);
}
});
it('exports the real port to the panes as CODEMAN_API_URL', () => {
expect(process.env.CODEMAN_API_URL).toBe(`http://127.0.0.1:${socketPort()}`);
});