Merge remote-tracking branch 'origin/master' into xtr/agent-loop-message-machine
# Conflicts: # .agents/notes/implemented/feature/2026-06-14-acp-agent-client-protocol.md # .agents/notes/implemented/feature/2026-06-14-acp-agent-client-protocol.zh.md # .agents/notes/implemented/feature/2026-06-30-hook-bridges.i18n.yaml # .agents/notes/implemented/feature/2026-06-30-interception-seams.i18n.yaml # .agents/notes/implemented/feature/2026-07-06-sandbox.i18n.yaml # .agents/notes/implemented/feature/2026-07-06-sandbox.md # .agents/notes/implemented/feature/2026-07-06-sandbox.zh.md # .agents/notes/implemented/feature/2026-07-19-model-facing-goal-tools.i18n.yaml # .agents/notes/implemented/feature/2026-07-19-same-session-goal-round-driver.i18n.yaml # .agents/notes/implemented/feature/2026-07-25-workspace-ui-product-flow.i18n.yaml # .agents/notes/implemented/simplification/2026-07-02-remove-stream-chunk-mirror.i18n.yaml # docs/architecture.i18n.yaml # docs/cookbook/adding-a-tool.i18n.yaml # docs/cookbook/extension-cookbook.i18n.yaml # docs/core-data-structures/llm-streaming.i18n.yaml # docs/core-data-structures/session.i18n.yaml # docs/core-data-structures/tools.i18n.yaml # docs/event-producer-consumer.md # docs/persistence-catalog.md # examples/acp-agent/tests/snapshots/cordis-inspect-jsdoc/stdout.expected.jsonl # examples/acp-agent/tests/snapshots/escalation-approved/session.jsonl # examples/acp-agent/tests/snapshots/escalation-rejected/session.jsonl # examples/acp-agent/tests/snapshots/fs-escalation-approved/session.jsonl # examples/acp-agent/tests/snapshots/hook-cc-pretool-ask/session.jsonl # examples/acp-agent/tests/snapshots/permission-switching/session.jsonl # examples/acp-agent/tests/snapshots/plan-mode-reject/session.jsonl # examples/acp-agent/tests/snapshots/plan-mode/session.jsonl # examples/acp-agent/tests/snapshots/session-sandbox-root/session.jsonl # packages/context/session-reference/README.md # packages/core/agent-loop/tests/agent.spec.ts # packages/hooks/hooks-claude/tests/coverage-cases.ts # packages/host/runtime/tests/host-runtime.spec.ts # packages/llm/llm-retry/tests/retry.spec.ts # packages/session-persistence/session-persistence/src/coordinator.ts # packages/support/acp-snapshot/README.md # packages/support/acp-snapshot/src/normalize.ts # packages/ui/acp/acp-feature-support.md # packages/ui/acp/src/codec.ts # packages/ui/acp/src/index.ts # packages/ui/acp/tests/bridge.spec.ts # packages/ui/acp/tests/codec.spec.ts # packages/ui/acp/tests/config-options.spec.ts # packages/ui/acp/tests/dispose.spec.ts # packages/ui/acp/tests/edges.spec.ts # packages/ui/acp/tests/stream-update.spec.ts # packages/ui/acp/tests/turns.spec.ts
This commit is contained in:
@@ -96,6 +96,6 @@ Append-only; newly visible content follows the reusable request prefix and does
|
||||
- **A fresh process per run** — persistent-process pooling is a future optimization ([the seam Agent Note](../../../.agents/notes/implemented/feature/2026-06-21-subagent-capability-seam.md)).
|
||||
- **Local workspaces only** — the resolved cwd is a local path handed to a child on the same machine; workspace mapping for a remote ACP agent would need its own backend capability and is not designed here.
|
||||
- **No optional start-time capabilities** — this provider cannot apply the local harness's `outputSchema`, depth cap, tool filter, or persona inside the remote process, so it advertises none and the service rejects requests that require them.
|
||||
- **Only `agent_message_chunk` text is collected** — the child's tool-call activity, thought chunks, and plan updates are not surfaced to the parent.
|
||||
- **Only committed `agent_message_chunk` text is collected** — the automation server keeps reasoning, tool activity, plans, and other trace data in the child session log rather than emitting them on ACP.
|
||||
- **Permission prompts are auto-answered** (`permission: allow | reject`) — no human is surfaced a child's `session/request_permission` in this cut.
|
||||
- **No snapshot-tier replay coverage** (`TODO(acp-subagent-replay)`) — an ACP child is its own process with its own replay shape, deferred.
|
||||
|
||||
@@ -102,7 +102,7 @@ describe.skipIf(!process.env.DEEPSEEK_API_KEY)('ACP backend with-key e2e (drive
|
||||
await run.dispose()
|
||||
|
||||
expect(result.stopReason).toBe('completed')
|
||||
// Verify the WORLD: the child process actually wrote the file in its cwd.
|
||||
// Assert the filesystem effect independently of the model response.
|
||||
const proof = await readFile(join(workdir, 'proof.txt'), 'utf8')
|
||||
expect(proof).toContain('ACP_CHILD_WAS_HERE')
|
||||
}, 180_000)
|
||||
|
||||
@@ -6,14 +6,7 @@ import type { Context } from 'cordis'
|
||||
import { spawnHarness, waitForIdle } from './harness.ts'
|
||||
import { SessionId } from '@deepseek-ai/dsh-session'
|
||||
|
||||
/**
|
||||
* With-key smoke for the in-process spawn backend: a REAL parent agent delegates
|
||||
* to a REAL child (via the `subagent` tool → spawn backend) that uses the REAL
|
||||
* bash tool to write a file, and we verify the WORLD (the file on disk) — not
|
||||
* the agent's self-report. This is the "green units, broken product" guard:
|
||||
* mocks prove the plumbing, only a real model proves a parent can actually drive
|
||||
* a child to do real work. Key-gated (self-skips without DEEPSEEK_API_KEY).
|
||||
*/
|
||||
/** Key-gated smoke for a real parent delegating filesystem work to a real child. */
|
||||
|
||||
let ctx: Context | undefined
|
||||
let workdir: string | undefined
|
||||
@@ -37,7 +30,7 @@ describe.skipIf(!process.env.DEEPSEEK_API_KEY)('spawn backend with-key smoke', (
|
||||
+ 'After the subagent finishes, tell me it is done.' }], source: { kind: 'user' } })
|
||||
await waitForIdle(ctx, parent)
|
||||
|
||||
// Verify the WORLD: the child actually wrote the file.
|
||||
// Assert the filesystem effect independently of the model response.
|
||||
const proof = await readFile(join(workdir, 'proof.txt'), 'utf8')
|
||||
expect(proof).toContain('SUBAGENT_WAS_HERE')
|
||||
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
/**
|
||||
* Subagent seam vocabulary: the request/result/capability types a
|
||||
* {@link SubagentProvider} consumes and produces. No runtime code — types
|
||||
* only, per the package convention.
|
||||
* Request, result, and capability contracts for {@link SubagentProvider}.
|
||||
*
|
||||
* @module @deepseek-ai/dsh-subagent/types
|
||||
*/
|
||||
@@ -17,7 +15,7 @@ export type SubagentRunId = Branded<'SubagentRunId'>
|
||||
|
||||
/**
|
||||
* Brand a string as a {@link SubagentRunId}.
|
||||
* @param id - the raw id string (the service mints UUIDs; tests may pass fixtures).
|
||||
* @param id - the raw run id.
|
||||
* @returns the same string, branded.
|
||||
*/
|
||||
export function SubagentRunId(id: string): SubagentRunId {
|
||||
@@ -30,16 +28,13 @@ export function SubagentRunId(id: string): SubagentRunId {
|
||||
* is rejected with a typed error rather than accepted-then-ignored (the "fail loud, no silent
|
||||
* degradation" rule). These static flags cover features needed before a run exists; runtime
|
||||
* capabilities such as steering and resume are optional {@link SubagentRun} methods whose presence
|
||||
* is the capability.
|
||||
* is the capability. Each flag corresponds one-to-one to a {@link SubagentStartRequest} option:
|
||||
* `depthLimit` to `maxDepth`; the other names match.
|
||||
*/
|
||||
export interface SubagentCapabilities {
|
||||
/** Honor {@link SubagentStartRequest.outputSchema} (structured final output). */
|
||||
readonly outputSchema: boolean
|
||||
/** Enforce {@link SubagentStartRequest.maxDepth} (recursion cap). */
|
||||
readonly depthLimit: boolean
|
||||
/** Enforce {@link SubagentStartRequest.toolFilter} (child tool scoping). */
|
||||
readonly toolFilter: boolean
|
||||
/** Honor {@link SubagentStartRequest.persona} (a per-child persona). */
|
||||
readonly persona: boolean
|
||||
}
|
||||
|
||||
@@ -50,16 +45,12 @@ export interface SubagentCapabilities {
|
||||
* passes it to {@link SubagentProvider.start}.
|
||||
*/
|
||||
export interface SubagentStartRequest {
|
||||
/** The task/prompt for the child agent (a user message in the child session). */
|
||||
/** Content delivered as the child's user message. */
|
||||
readonly prompt: ContentBlock[]
|
||||
/**
|
||||
* The spawning ("parent") agent — the one whose tool call started this
|
||||
* subagent. REQUIRED: in-process backends read `parent.session.header` for
|
||||
* the working directory, the `parentSession` lineage to stamp on the child,
|
||||
* and the parent's delegation depth. The out-of-process backend (ACP) reads
|
||||
* exactly one field — the session header's cwd, the child's workspace when
|
||||
* no deployment `cwd` override is configured; nothing else crosses the
|
||||
* process boundary.
|
||||
* The spawning agent. In-process providers derive workspace, lineage, and
|
||||
* delegation depth from its durable session state. ACP reads only its cwd,
|
||||
* and only when no deployment `cwd` override is configured.
|
||||
*/
|
||||
readonly parent: Agent
|
||||
/**
|
||||
@@ -70,7 +61,6 @@ export interface SubagentStartRequest {
|
||||
* afterward.
|
||||
*/
|
||||
readonly signal: AbortSignal
|
||||
/** Per-child agent options (model and plugin-defined extension fields). */
|
||||
readonly agentOptions?: AgentOptions
|
||||
/**
|
||||
* Object-rooted JSON Schema within `assertObjectJsonSchema`'s enforced subset. Start rejects
|
||||
@@ -112,9 +102,9 @@ export interface SubagentStartRequest {
|
||||
export interface SubagentStopReasonMap {
|
||||
/** The child finished its turn normally. */
|
||||
completed: 'completed'
|
||||
/** The run was cancelled by its request signal or by disposal. */
|
||||
/** Cancelled through the request signal or disposal. */
|
||||
aborted: 'aborted'
|
||||
/** The child failed (model error, transport error). */
|
||||
/** Model or transport failure. */
|
||||
error: 'error'
|
||||
/** The child hit its token ceiling before finishing. */
|
||||
'max-tokens': 'max-tokens'
|
||||
@@ -170,9 +160,8 @@ export interface SubagentRun {
|
||||
*/
|
||||
readonly result: Promise<SubagentResult>
|
||||
/**
|
||||
* Cancel remaining work, reach child quiescence, and release the run's
|
||||
* resources (in-process: dispose the owned agent and remove its session;
|
||||
* ACP: kill and reap the subprocess). Idempotent.
|
||||
* Cancel remaining work, reach child quiescence, and release resources.
|
||||
* Idempotent.
|
||||
*/
|
||||
dispose(): Promise<void>
|
||||
/**
|
||||
@@ -188,12 +177,9 @@ export interface SubagentRun {
|
||||
}
|
||||
|
||||
/**
|
||||
* A subagent backend: one transport for running a child agent (in-process
|
||||
* spawn/fork, ACP to another process, …). Implementations register under a
|
||||
* unique name via {@link SubagentService.registerProvider}; multiple providers
|
||||
* coexist in one context (unlike the single-implementation bash seam). The
|
||||
* Providers are trusted same-process implementations; callers treat their
|
||||
* descriptors and returned values as borrowed immutable data.
|
||||
* One registered transport for running child agents. Providers are trusted
|
||||
* same-process implementations; callers treat descriptors and returned values
|
||||
* as borrowed immutable data.
|
||||
*/
|
||||
export interface SubagentProvider {
|
||||
/** Unique registry name (e.g. `spawn`, `fork`, `acp`). */
|
||||
|
||||
Reference in New Issue
Block a user