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:
@@ -25,7 +25,8 @@ import {
|
||||
import type { ContentBlock } from '@deepseek-ai/dsh-llm'
|
||||
import { SessionId } from '@deepseek-ai/dsh-session'
|
||||
import type { SubagentResult, SubagentRun, SubagentStartRequest, SubagentStopReason } from '@deepseek-ai/dsh-subagent'
|
||||
import type { SubprocessHandle, SubprocessSpawnSpec } from '@deepseek-ai/dsh-subprocess'
|
||||
import { DSH_ENV_PREFIX } from '@deepseek-ai/dsh-subprocess'
|
||||
import type { DshEnvironmentKey, SubprocessHandle, SubprocessSpawnSpec } from '@deepseek-ai/dsh-subprocess'
|
||||
|
||||
/** Fixed response to child permission requests: reject by default, or select the first allow option. */
|
||||
export type PermissionPolicy = 'allow' | 'reject'
|
||||
@@ -49,6 +50,9 @@ export interface AcpRunSpec {
|
||||
* `DEEPSEEK_API_KEY`). Merged on top of the subprocess seam's scrubbed
|
||||
* parent env. A value here is forwarded even if its name matches the
|
||||
* credential-scrub pattern (an explicit opt-in for the child's own creds).
|
||||
* Explicit `DSH_*` entries are deployment-owned facts for the child harness
|
||||
* (e.g. `DSH_PERMISSION_MODE`) and ride the seam's managed channel, which
|
||||
* the scrubbed base reserves for current values.
|
||||
*/
|
||||
env: Record<string, string>
|
||||
/**
|
||||
@@ -166,13 +170,23 @@ export async function startAcpRun(request: SubagentStartRequest, spec: AcpRunSpe
|
||||
|
||||
// Keep diagnostics on parent stderr ('inherit'); only ACP output contributes
|
||||
// to the result. The seam's scrub drops ambient credentials while spec.env
|
||||
// (the child's own key) merges after it.
|
||||
// (the child's own key) merges after it. Explicit DSH_* entries are the
|
||||
// deployment's facts for the child and take the managed channel — the
|
||||
// ordinary channel rejects that reserved namespace.
|
||||
const env: Record<string, string> = {}
|
||||
const dshEnv: Record<DshEnvironmentKey, string> = {}
|
||||
const isDshKey = (key: string): key is DshEnvironmentKey => key.startsWith(DSH_ENV_PREFIX)
|
||||
for (const [key, value] of Object.entries(spec.env)) {
|
||||
if (isDshKey(key)) dshEnv[key] = value
|
||||
else env[key] = value
|
||||
}
|
||||
const child = spec.spawn({
|
||||
argv: [spec.command, ...spec.args],
|
||||
cwd: spec.cwd,
|
||||
stdio: { stdin: 'pipe', stdout: 'pipe', stderr: 'inherit' },
|
||||
graceMs: spec.disposeGraceMs,
|
||||
env: spec.env,
|
||||
env,
|
||||
dshEnv,
|
||||
})
|
||||
/* v8 ignore start -- 'pipe' dispositions expose both streams by the seam contract; defensive. */
|
||||
if (child.stdin === undefined || child.stdout === undefined) {
|
||||
|
||||
Reference in New Issue
Block a user