Merge branch 'codex/simp-hide-concrete-agent-loop' into codex/simp-hide-subagent-internals

This commit is contained in:
Tianyi Cui
2026-07-18 14:27:59 +08:00
60 changed files with 1041 additions and 242 deletions
@@ -21,8 +21,6 @@ import { acpStopReason, acpContentText, DEFAULT_DISPOSE_EOF_GRACE_MS, DEFAULT_DI
*/
const mockServer = fileURLToPath(new URL('./mock-acp-server.ts', import.meta.url))
const tsxLoader = fileURLToPath(import.meta.resolve('tsx'))
const repoTsconfig = fileURLToPath(new URL('../../../../tsconfig.json', import.meta.url))
/** A throwaway parent Agent — the ACP backend ignores it, but the seam requires one. */
const fakeParent = { id: 'parent', session: { header: {} } } as unknown as Agent
@@ -46,11 +44,9 @@ async function setup(mockEnv: SetupEnv = {}, permission: 'allow' | 'reject' = 'r
await ctx.plugin(acp, {
providerName: 'acp',
command: process.execPath,
args: ['--import', tsxLoader, mockServer],
args: [mockServer],
permission,
// The mock-server scripting vars must reach the child; TSX_TSCONFIG_PATH lets
// tsx resolve @deepseek-ai/* from a child cwd outside the repo.
env: { ...mockEnv, TSX_TSCONFIG_PATH: repoTsconfig },
env: mockEnv,
})
return ctx
}
@@ -62,7 +58,7 @@ function text(blocks: { type: string; text?: string }[]): string {
/**
* Poll until `file` exists (the mock touches it once its prompt is in flight),
* so a cancel test waits on a CONDITION rather than an arbitrary timeout — the
* subprocess cold-start under tsx is variable, and a fixed sleep both flakes and
* subprocess cold-start is variable, and a fixed sleep both flakes and
* slows the suite. Fails loud if the child never signals readiness.
*/
async function waitForFile(file: string, timeoutMs = 5000): Promise<void> {
@@ -200,14 +196,13 @@ describe('dsh-subagent-acp', () => {
try {
await expect(startAcpRun(request(), {
command: process.execPath,
args: ['--import', tsxLoader, mockServer],
args: [mockServer],
cwd: process.cwd(),
permission: 'reject',
env: {
MOCK_MISSING_SESSION_ID: '1',
MOCK_FLUSH_ON_EOF: flushed,
MOCK_FLUSH_DELAY_MS: '20',
TSX_TSCONFIG_PATH: repoTsconfig,
},
disposeEofGraceMs: 1000,
disposeGraceMs: 100,
@@ -229,10 +224,10 @@ describe('dsh-subagent-acp', () => {
try {
const spec: AcpRunSpec = {
command: process.execPath,
args: ['--import', tsxLoader, mockServer],
args: [mockServer],
cwd: process.cwd(),
permission: 'reject',
env: { MOCK_TRAP_SIGTERM: '1', MOCK_TEXT: 'x', MOCK_READY_FILE: ready, TSX_TSCONFIG_PATH: repoTsconfig },
env: { MOCK_TRAP_SIGTERM: '1', MOCK_TEXT: 'x', MOCK_READY_FILE: ready },
// Short on BOTH tiers: the trap ignores EOF and SIGTERM, so dispose must
// burn the EOF window, then the SIGTERM window, then SIGKILL — keep each
// small so the whole ladder finishes well within the 4000ms bound.
@@ -272,7 +267,7 @@ describe('dsh-subagent-acp', () => {
try {
const spec: AcpRunSpec = {
command: process.execPath,
args: ['--import', tsxLoader, mockServer],
args: [mockServer],
cwd: process.cwd(),
permission: 'reject',
// MOCK_HANG so the prompt never resolves on its own — we tear down a live
@@ -281,7 +276,7 @@ describe('dsh-subagent-acp', () => {
// wider grace.
env: {
MOCK_HANG: '1', MOCK_TEXT: 'x', MOCK_READY_FILE: ready,
MOCK_FLUSH_ON_EOF: flushed, MOCK_FLUSH_DELAY_MS: '400', TSX_TSCONFIG_PATH: repoTsconfig,
MOCK_FLUSH_ON_EOF: flushed, MOCK_FLUSH_DELAY_MS: '400',
},
disposeEofGraceMs: 2000,
disposeGraceMs: 50,
@@ -312,12 +307,12 @@ describe('dsh-subagent-acp', () => {
try {
const spec: AcpRunSpec = {
command: process.execPath,
args: ['--import', tsxLoader, mockServer],
args: [mockServer],
cwd: process.cwd(),
permission: 'reject',
env: {
MOCK_HANG: '1', MOCK_IGNORE_EOF: '1', MOCK_TEXT: 'x',
MOCK_READY_FILE: ready, MOCK_SIGTERM_FILE: sigterm, TSX_TSCONFIG_PATH: repoTsconfig,
MOCK_READY_FILE: ready, MOCK_SIGTERM_FILE: sigterm,
},
// Tiny EOF grace so the ignored-EOF window elapses fast, then SIGTERM.
disposeEofGraceMs: 150,
@@ -436,9 +431,9 @@ describe('dsh-subagent-acp', () => {
await ctx.plugin(acp, {
providerName: 'acp',
command: process.execPath,
args: ['--import', tsxLoader, mockServer],
args: [mockServer],
permission: 'reject',
env: { MOCK_TRAP_SIGTERM: '1', MOCK_TEXT: 'x', MOCK_READY_FILE: ready, TSX_TSCONFIG_PATH: repoTsconfig },
env: { MOCK_TRAP_SIGTERM: '1', MOCK_TEXT: 'x', MOCK_READY_FILE: ready },
disposeEofGraceMs: 150,
disposeGraceMs: 150,
})
@@ -487,10 +482,10 @@ describe('dsh-subagent-acp', () => {
request(),
{
command: process.execPath,
args: ['--import', tsxLoader, mockServer],
args: [mockServer],
cwd: process.cwd(),
permission: 'reject',
env: { MOCK_CRASH_ON_PROMPT: '1', TSX_TSCONFIG_PATH: repoTsconfig },
env: { MOCK_CRASH_ON_PROMPT: '1' },
disposeEofGraceMs: DEFAULT_DISPOSE_EOF_GRACE_MS,
disposeGraceMs: DEFAULT_DISPOSE_GRACE_MS,
onError: (error, stopReason) => { errors.push({ message: error.message, stopReason }) },
@@ -525,10 +520,10 @@ describe('dsh-subagent-acp', () => {
request(),
{
command: process.execPath,
args: ['--import', tsxLoader, mockServer],
args: [mockServer],
cwd: process.cwd(),
permission: 'reject',
env: { MOCK_CRASH_ON_PROMPT: '1', TSX_TSCONFIG_PATH: repoTsconfig },
env: { MOCK_CRASH_ON_PROMPT: '1' },
disposeEofGraceMs: DEFAULT_DISPOSE_EOF_GRACE_MS,
disposeGraceMs: DEFAULT_DISPOSE_GRACE_MS,
onError: () => { throw new Error('sink boom') },