fix(subagent): route explicit DSH_* config entries onto the managed env channel

The acp run passed config.env wholesale into the spawn spec's ordinary
channel, which rejects the reserved DSH_* namespace — a deployment fact
like DSH_PERMISSION_MODE (the acp-agent example's own knob, used by the
with-key e2e) crashed the spawn. The run now splits DSH_* entries onto
dshEnv, where the scrubbed base expects current facts to arrive. New
layering test drives the split through the real seam via a MOCK_ECHO_ENV
knob on the mock server; README env prose updated (en+zh, re-recorded).
This commit is contained in:
Tianyi Cui
2026-07-27 01:04:37 +08:00
parent 23e82fdfb0
commit fced51d4eb
6 changed files with 45 additions and 8 deletions

View File

@@ -4,6 +4,9 @@
* fully scripted by environment variables — no model, no network:
*
* - `MOCK_TEXT` — the assistant text it streams as one `agent_message_chunk`.
* - `MOCK_ECHO_ENV` — if set to a variable NAME, stream that variable's value
* (or `<NAME unset>`) instead of MOCK_TEXT — asserts what
* environment actually reached the child process.
* - `MOCK_STOP` — the ACP `StopReason` it returns from `prompt`
* (`end_turn` default, or `max_tokens`/`refusal`/…).
* - `MOCK_HANG` — if `1`, `prompt` never resolves on its own (it waits for
@@ -67,7 +70,12 @@ import {
type StopReason,
} from '@agentclientprotocol/sdk'
const TEXT = process.env.MOCK_TEXT ?? 'mock child answer'
// When MOCK_ECHO_ENV names a variable, stream that variable's value in place
// of MOCK_TEXT — lets a test assert exactly what env reached this process.
const echoEnvName = process.env.MOCK_ECHO_ENV
const TEXT = echoEnvName !== undefined
? process.env[echoEnvName] ?? `<${echoEnvName} unset>`
: process.env.MOCK_TEXT ?? 'mock child answer'
const ECHO_CWD = process.env.MOCK_ECHO_CWD === '1'
const STOP = (process.env.MOCK_STOP ?? 'end_turn') as StopReason
const HANG = process.env.MOCK_HANG === '1'

View File

@@ -119,6 +119,21 @@ describe('child env layering (through the subprocess seam)', () => {
delete process.env.ACP_TEST_AMBIENT_SECRET_TOKEN
}
})
it('routes explicit DSH_* config entries onto the managed channel', async () => {
// A deployment sets child-harness facts like DSH_PERMISSION_MODE in
// config.env; the run must split them onto the seam's managed channel
// (the ordinary channel rejects the reserved namespace) and the child
// must still see the value.
const ctx = await setup({ MOCK_ECHO_ENV: 'DSH_ACP_TEST_FACT', DSH_ACP_TEST_FACT: 'managed' })
const parent = { id: 'parent', session: { header: { cwd: process.cwd() } } } as unknown as Agent
const run = await ctx.subagents.start('acp', { prompt: [{ type: 'text' as const, text: 'p' }], parent, signal: new AbortController().signal })
const result = await run.result
await run.dispose()
const text = result.output.filter(b => b.type === 'text').map(b => (b as { text: string }).text).join('')
expect(text).toBe('managed')
await ctx.fiber.dispose()
})
})
describe('cwd resolution', () => {