Merge branch 'codex/simp-ui-identity-residue' into codex/simp-hide-concrete-agent-loop

This commit is contained in:
Tianyi Cui
2026-07-14 02:29:34 +08:00
13 changed files with 51 additions and 28 deletions
+8 -4
View File
@@ -179,6 +179,7 @@ flowchart TD
pkg_workflow --> pkg_agent pkg_workflow --> pkg_agent
pkg_workflow --> pkg_brand pkg_workflow --> pkg_brand
pkg_workflow --> pkg_llm pkg_workflow --> pkg_llm
pkg_workflow --> pkg_session
pkg_tools --> pkg_agent pkg_tools --> pkg_agent
pkg_tools --> pkg_code_runtime pkg_tools --> pkg_code_runtime
pkg_tools --> pkg_llm pkg_tools --> pkg_llm
@@ -215,6 +216,7 @@ flowchart TD
pkg_subagent --> pkg_agent pkg_subagent --> pkg_agent
pkg_subagent --> pkg_llm pkg_subagent --> pkg_llm
pkg_subagent --> pkg_scope pkg_subagent --> pkg_scope
pkg_subagent --> pkg_session
pkg_subagent --> pkg_tools pkg_subagent --> pkg_tools
pkg_tool_web --> pkg_llm pkg_tool_web --> pkg_llm
pkg_tool_web --> pkg_system_prompt pkg_tool_web --> pkg_system_prompt
@@ -265,6 +267,7 @@ flowchart TD
pkg_agent_core --> pkg_tools pkg_agent_core --> pkg_tools
pkg_subagent_acp --> pkg_agent pkg_subagent_acp --> pkg_agent
pkg_subagent_acp --> pkg_llm pkg_subagent_acp --> pkg_llm
pkg_subagent_acp --> pkg_session
pkg_subagent_acp --> pkg_subagent pkg_subagent_acp --> pkg_subagent
pkg_subagent_acp --> pkg_subagent_subprocess pkg_subagent_acp --> pkg_subagent_subprocess
pkg_subagent_inprocess --> pkg_agent pkg_subagent_inprocess --> pkg_agent
@@ -285,6 +288,7 @@ flowchart TD
pkg_hooks_claude --> pkg_tools pkg_hooks_claude --> pkg_tools
pkg_subagent_mock --> pkg_agent pkg_subagent_mock --> pkg_agent
pkg_subagent_mock --> pkg_llm pkg_subagent_mock --> pkg_llm
pkg_subagent_mock --> pkg_session
pkg_subagent_mock --> pkg_subagent pkg_subagent_mock --> pkg_subagent
pkg_jsonrpc --> pkg_agent pkg_jsonrpc --> pkg_agent
pkg_jsonrpc --> pkg_llm pkg_jsonrpc --> pkg_llm
@@ -362,14 +366,14 @@ flowchart TD
| [`invariants`](../packages/support/invariants) | `support` | [`agent`](../packages/core/agent), [`llm`](../packages/llm/llm), [`scope`](../packages/core/scope), [`session`](../packages/core/session) | | [`invariants`](../packages/support/invariants) | `support` | [`agent`](../packages/core/agent), [`llm`](../packages/llm/llm), [`scope`](../packages/core/scope), [`session`](../packages/core/session) |
| [`user-approval`](../packages/ui/user-approval) | `ui` | [`agent`](../packages/core/agent), [`brand`](../packages/util/brand), [`llm`](../packages/llm/llm), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`system-prompt`](../packages/core/system-prompt) | | [`user-approval`](../packages/ui/user-approval) | `ui` | [`agent`](../packages/core/agent), [`brand`](../packages/util/brand), [`llm`](../packages/llm/llm), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`system-prompt`](../packages/core/system-prompt) |
| [`user-interaction`](../packages/ui/user-interaction) | `ui` | [`agent`](../packages/core/agent), [`llm`](../packages/llm/llm) | | [`user-interaction`](../packages/ui/user-interaction) | `ui` | [`agent`](../packages/core/agent), [`llm`](../packages/llm/llm) |
| [`workflow`](../packages/workflow/workflow) | `workflow` | [`agent`](../packages/core/agent), [`brand`](../packages/util/brand), [`llm`](../packages/llm/llm) | | [`workflow`](../packages/workflow/workflow) | `workflow` | [`agent`](../packages/core/agent), [`brand`](../packages/util/brand), [`llm`](../packages/llm/llm), [`session`](../packages/core/session) |
| [`tools`](../packages/core/tools) | `core` | [`agent`](../packages/core/agent), [`code-runtime`](../packages/code-runtime/code-runtime), [`llm`](../packages/llm/llm), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`system-prompt`](../packages/core/system-prompt), [`user-approval`](../packages/ui/user-approval) | | [`tools`](../packages/core/tools) | `core` | [`agent`](../packages/core/agent), [`code-runtime`](../packages/code-runtime/code-runtime), [`llm`](../packages/llm/llm), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`system-prompt`](../packages/core/system-prompt), [`user-approval`](../packages/ui/user-approval) |
| [`bash-sandbox`](../packages/bash/bash-sandbox) | `bash` | [`bash`](../packages/bash/bash), [`bash-local`](../packages/bash/bash-local), [`sandbox`](../packages/sandbox/sandbox) | | [`bash-sandbox`](../packages/bash/bash-sandbox) | `bash` | [`bash`](../packages/bash/bash), [`bash-local`](../packages/bash/bash-local), [`sandbox`](../packages/sandbox/sandbox) |
| [`agent-loop`](../packages/core/agent-loop) | `core` | [`agent`](../packages/core/agent), [`llm`](../packages/llm/llm), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`session-persistence`](../packages/session-persistence/session-persistence), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools) | | [`agent-loop`](../packages/core/agent-loop) | `core` | [`agent`](../packages/core/agent), [`llm`](../packages/llm/llm), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`session-persistence`](../packages/session-persistence/session-persistence), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools) |
| [`tool-bash`](../packages/bash/tool-bash) | `bash` | [`agent`](../packages/core/agent), [`bash`](../packages/bash/bash), [`llm`](../packages/llm/llm), [`sandbox`](../packages/sandbox/sandbox), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools), [`user-approval`](../packages/ui/user-approval) | | [`tool-bash`](../packages/bash/tool-bash) | `bash` | [`agent`](../packages/core/agent), [`bash`](../packages/bash/bash), [`llm`](../packages/llm/llm), [`sandbox`](../packages/sandbox/sandbox), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools), [`user-approval`](../packages/ui/user-approval) |
| [`tool-fs`](../packages/fs/tool-fs) | `fs` | [`fs`](../packages/fs/fs), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools) | | [`tool-fs`](../packages/fs/tool-fs) | `fs` | [`fs`](../packages/fs/fs), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools) |
| [`tool-skill`](../packages/skill/tool-skill) | `skill` | [`agent`](../packages/core/agent), [`llm`](../packages/llm/llm), [`skill`](../packages/skill/skill), [`tools`](../packages/core/tools) | | [`tool-skill`](../packages/skill/tool-skill) | `skill` | [`agent`](../packages/core/agent), [`llm`](../packages/llm/llm), [`skill`](../packages/skill/skill), [`tools`](../packages/core/tools) |
| [`subagent`](../packages/subagent/subagent) | `subagent` | [`agent`](../packages/core/agent), [`llm`](../packages/llm/llm), [`scope`](../packages/core/scope), [`tools`](../packages/core/tools) | | [`subagent`](../packages/subagent/subagent) | `subagent` | [`agent`](../packages/core/agent), [`llm`](../packages/llm/llm), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`tools`](../packages/core/tools) |
| [`tool-web`](../packages/web/tool-web) | `web` | [`llm`](../packages/llm/llm), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools), [`web`](../packages/web/web) | | [`tool-web`](../packages/web/tool-web) | `web` | [`llm`](../packages/llm/llm), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools), [`web`](../packages/web/web) |
| [`timeout-policy`](../packages/timeout/timeout-policy) | `timeout` | [`llm`](../packages/llm/llm), [`timeout`](../packages/util/timeout), [`tools`](../packages/core/tools) | | [`timeout-policy`](../packages/timeout/timeout-policy) | `timeout` | [`llm`](../packages/llm/llm), [`timeout`](../packages/util/timeout), [`tools`](../packages/core/tools) |
| [`tool-todo`](../packages/todo/tool-todo) | `todo` | [`agent`](../packages/core/agent), [`session`](../packages/core/session), [`tools`](../packages/core/tools) | | [`tool-todo`](../packages/todo/tool-todo) | `todo` | [`agent`](../packages/core/agent), [`session`](../packages/core/session), [`tools`](../packages/core/tools) |
@@ -380,11 +384,11 @@ flowchart TD
| [`repeat-tool-guard`](../packages/guard/repeat-tool-guard) | `guard` | [`agent`](../packages/core/agent), [`tools`](../packages/core/tools) | | [`repeat-tool-guard`](../packages/guard/repeat-tool-guard) | `guard` | [`agent`](../packages/core/agent), [`tools`](../packages/core/tools) |
| [`tool-workflow`](../packages/workflow/tool-workflow) | `workflow` | [`agent`](../packages/core/agent), [`llm`](../packages/llm/llm), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools), [`workflow`](../packages/workflow/workflow) | | [`tool-workflow`](../packages/workflow/tool-workflow) | `workflow` | [`agent`](../packages/core/agent), [`llm`](../packages/llm/llm), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools), [`workflow`](../packages/workflow/workflow) |
| [`agent-core`](../packages/core/agent-core) | `core` | [`agent`](../packages/core/agent), [`agent-loop`](../packages/core/agent-loop), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`skill`](../packages/skill/skill), [`skill-local`](../packages/skill/skill-local), [`system-prompt`](../packages/core/system-prompt), [`tool-bash`](../packages/bash/tool-bash), [`tool-skill`](../packages/skill/tool-skill), [`tools`](../packages/core/tools) | | [`agent-core`](../packages/core/agent-core) | `core` | [`agent`](../packages/core/agent), [`agent-loop`](../packages/core/agent-loop), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`skill`](../packages/skill/skill), [`skill-local`](../packages/skill/skill-local), [`system-prompt`](../packages/core/system-prompt), [`tool-bash`](../packages/bash/tool-bash), [`tool-skill`](../packages/skill/tool-skill), [`tools`](../packages/core/tools) |
| [`subagent-acp`](../packages/subagent/subagent-acp) | `subagent` | [`agent`](../packages/core/agent), [`llm`](../packages/llm/llm), [`subagent`](../packages/subagent/subagent), [`subagent-subprocess`](../packages/subagent/subagent-subprocess) | | [`subagent-acp`](../packages/subagent/subagent-acp) | `subagent` | [`agent`](../packages/core/agent), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`subagent`](../packages/subagent/subagent), [`subagent-subprocess`](../packages/subagent/subagent-subprocess) |
| [`subagent-inprocess`](../packages/subagent/subagent-inprocess) | `subagent` | [`agent`](../packages/core/agent), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`subagent`](../packages/subagent/subagent), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools) | | [`subagent-inprocess`](../packages/subagent/subagent-inprocess) | `subagent` | [`agent`](../packages/core/agent), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`subagent`](../packages/subagent/subagent), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools) |
| [`tool-subagent`](../packages/subagent/tool-subagent) | `subagent` | [`agent`](../packages/core/agent), [`llm`](../packages/llm/llm), [`subagent`](../packages/subagent/subagent), [`tools`](../packages/core/tools) | | [`tool-subagent`](../packages/subagent/tool-subagent) | `subagent` | [`agent`](../packages/core/agent), [`llm`](../packages/llm/llm), [`subagent`](../packages/subagent/subagent), [`tools`](../packages/core/tools) |
| [`hooks-claude`](../packages/hooks/hooks-claude) | `hooks` | [`agent`](../packages/core/agent), [`hook-protocol`](../packages/hooks/hook-protocol), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`subagent`](../packages/subagent/subagent), [`tools`](../packages/core/tools) | | [`hooks-claude`](../packages/hooks/hooks-claude) | `hooks` | [`agent`](../packages/core/agent), [`hook-protocol`](../packages/hooks/hook-protocol), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`subagent`](../packages/subagent/subagent), [`tools`](../packages/core/tools) |
| [`subagent-mock`](../packages/support/subagent-mock) | `support` | [`agent`](../packages/core/agent), [`llm`](../packages/llm/llm), [`subagent`](../packages/subagent/subagent) | | [`subagent-mock`](../packages/support/subagent-mock) | `support` | [`agent`](../packages/core/agent), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`subagent`](../packages/subagent/subagent) |
| [`jsonrpc`](../packages/ui/jsonrpc) | `ui` | [`agent`](../packages/core/agent), [`llm`](../packages/llm/llm), [`llm-deepseek`](../packages/llm/llm-deepseek), [`session`](../packages/core/session), [`subagent`](../packages/subagent/subagent) | | [`jsonrpc`](../packages/ui/jsonrpc) | `ui` | [`agent`](../packages/core/agent), [`llm`](../packages/llm/llm), [`llm-deepseek`](../packages/llm/llm-deepseek), [`session`](../packages/core/session), [`subagent`](../packages/subagent/subagent) |
| [`workflow-workerthread`](../packages/workflow/workflow-workerthread) | `workflow` | [`agent`](../packages/core/agent), [`brand`](../packages/util/brand), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`subagent`](../packages/subagent/subagent), [`tools`](../packages/core/tools), [`workflow`](../packages/workflow/workflow) | | [`workflow-workerthread`](../packages/workflow/workflow-workerthread) | `workflow` | [`agent`](../packages/core/agent), [`brand`](../packages/util/brand), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`subagent`](../packages/subagent/subagent), [`tools`](../packages/core/tools), [`workflow`](../packages/workflow/workflow) |
| [`subagent-fork`](../packages/subagent/subagent-fork) | `subagent` | [`agent`](../packages/core/agent), [`session`](../packages/core/session), [`subagent`](../packages/subagent/subagent), [`subagent-inprocess`](../packages/subagent/subagent-inprocess) | | [`subagent-fork`](../packages/subagent/subagent-fork) | `subagent` | [`agent`](../packages/core/agent), [`session`](../packages/core/session), [`subagent`](../packages/subagent/subagent), [`subagent-inprocess`](../packages/subagent/subagent-inprocess) |
@@ -14,7 +14,7 @@ Session itself repeated the same fact as `Session.id` and `Session.header.id`. C
## Decision ## Decision
An agent's registry id equals its session id. `CreateAgentOptions` accepts one `sessionId` used for both final registry entries; resume registers the agent under `resumeSessionId`; subagent creation mints one combined id; and `Session.id` derives from `header.id`. The existing creation transaction, final-entry collision checks, and exact-entry detach semantics remain; maps and fields whose sole job was translating between the ids are gone. An agent's registry id equals its session id. `CreateAgentOptions` accepts one `sessionId` used for both final registry entries; resume registers the agent under `resumeSessionId`; in-process and ACP subagent creation use the child session id; and `Session.id` derives from `header.id`. The existing creation transaction, final-entry collision checks, and exact-entry detach semantics remain; maps and fields whose sole job was translating between the ids are gone.
The config-driven path keeps `agents[].id` as a stable configuration label, not a live routing identity. A fresh start mints the combined id `${label}-session-${randomUUID()}` so durable restarts do not collide; `resumeSessionId` instead supplies the exact combined identity to load and register. Logs may use the stable label while all live and durable lookups use the one `SessionId`. The config-driven path keeps `agents[].id` as a stable configuration label, not a live routing identity. A fresh start mints the combined id `${label}-session-${randomUUID()}` so durable restarts do not collide; `resumeSessionId` instead supplies the exact combined identity to load and register. Logs may use the stable label while all live and durable lookups use the one `SessionId`.
@@ -28,7 +28,7 @@ The config-driven path keeps `agents[].id` as a stable configuration label, not
- Agent create/resume and subagent creation carry one identity, and `Session` stores it in one place. - Agent create/resume and subagent creation carry one identity, and `Session` stores it in one place.
- The creation transaction retains final-entry collision, exact-entry detach, rollback, and quiescence coverage without identity-specific lifecycle state. - The creation transaction retains final-entry collision, exact-entry detach, rollback, and quiescence coverage without identity-specific lifecycle state.
- ACP, stdio, hooks, bash ownership, persistence, and lineage use the shared `SessionId` directly. ACP verifies exact `Agent` ownership from the forward session map; JSON-RPC caches only disposable-child parent lineage. - ACP, stdio, hooks, bash ownership, persistence, and lineage use the shared `SessionId` directly. The ACP subagent backend uses the child server's returned session id as its run id; the ACP bridge verifies exact `Agent` ownership from the forward session map; and JSON-RPC caches only local disposable-child parent lineage while leaving remote runs outside its local-session notification pair.
- The config-driven resume-or-create policy is explicit and covered across a durable restart. - The config-driven resume-or-create policy is explicit and covered across a durable restart.
- A production listener search kept `agent/created`/`agent/disposed` and their publication semantics. - A production listener search kept `agent/created`/`agent/disposed` and their publication semantics.
- Typecheck, coverage, snapshots, doc-sync, module-graph verification, build, and hygiene pass. - Typecheck, coverage, snapshots, doc-sync, module-graph verification, build, and hygiene pass.
@@ -24,6 +24,7 @@
"peerDependencies": { "peerDependencies": {
"@deepseek-ai/dsh-agent": "^0.0.1", "@deepseek-ai/dsh-agent": "^0.0.1",
"@deepseek-ai/dsh-llm": "^0.0.1", "@deepseek-ai/dsh-llm": "^0.0.1",
"@deepseek-ai/dsh-session": "^0.0.1",
"@deepseek-ai/dsh-subagent": "^0.0.1", "@deepseek-ai/dsh-subagent": "^0.0.1",
"@deepseek-ai/dsh-subagent-subprocess": "^0.0.1", "@deepseek-ai/dsh-subagent-subprocess": "^0.0.1",
"cordis": "^4.0.0-rc.6" "cordis": "^4.0.0-rc.6"
@@ -35,6 +36,7 @@
"devDependencies": { "devDependencies": {
"@deepseek-ai/dsh-agent": "workspace:^", "@deepseek-ai/dsh-agent": "workspace:^",
"@deepseek-ai/dsh-llm": "workspace:^", "@deepseek-ai/dsh-llm": "workspace:^",
"@deepseek-ai/dsh-session": "workspace:^",
"@deepseek-ai/dsh-subagent": "workspace:^", "@deepseek-ai/dsh-subagent": "workspace:^",
"@deepseek-ai/dsh-subagent-subprocess": "workspace:^", "@deepseek-ai/dsh-subagent-subprocess": "workspace:^",
"@cordisjs/plugin-loader": "^1.0.0-rc.4", "@cordisjs/plugin-loader": "^1.0.0-rc.4",
+9 -7
View File
@@ -23,7 +23,6 @@
*/ */
import { spawn } from 'node:child_process' import { spawn } from 'node:child_process'
import { randomUUID } from 'node:crypto'
import { Readable, Writable } from 'node:stream' import { Readable, Writable } from 'node:stream'
import { import {
ClientSideConnection, ClientSideConnection,
@@ -190,8 +189,6 @@ function toError(value: unknown): Error {
* @returns the ready run handle for the child subprocess. * @returns the ready run handle for the child subprocess.
*/ */
export async function startAcpRun(request: SubagentStartRequest, spec: AcpRunSpec): Promise<SubagentRun> { export async function startAcpRun(request: SubagentStartRequest, spec: AcpRunSpec): Promise<SubagentRun> {
const id = SessionId(randomUUID())
if (request.signal.aborted) throw new Error('subagent request was aborted before the ACP child started') if (request.signal.aborted) throw new Error('subagent request was aborted before the ACP child started')
// Spawn the child ACP agent. stdin = ACP request channel, stdout = ACP // Spawn the child ACP agent. stdin = ACP request channel, stdout = ACP
@@ -260,7 +257,7 @@ export async function startAcpRun(request: SubagentStartRequest, spec: AcpRunSpe
), ),
) )
let sessionId: string | undefined let sessionId: SessionId | undefined
// Resolves when a cancel is requested, so `result` can settle `aborted` even // Resolves when a cancel is requested, so `result` can settle `aborted` even
// if the child never cooperates with `session/cancel` (it ignores the notify, // if the child never cooperates with `session/cancel` (it ignores the notify,
// or the prompt wedges). The result path races this against the ACP drive: the // or the prompt wedges). The result path races this against the ACP drive: the
@@ -309,7 +306,7 @@ export async function startAcpRun(request: SubagentStartRequest, spec: AcpRunSpe
clientCapabilities: {}, clientCapabilities: {},
}) })
const session = await conn.newSession({ cwd: spec.cwd, mcpServers: [] }) const session = await conn.newSession({ cwd: spec.cwd, mcpServers: [] })
sessionId = session.sessionId sessionId = SessionId(session.sessionId)
if (flags.cancelled) throw new Error('subagent cancelled before the ACP session started') if (flags.cancelled) throw new Error('subagent cancelled before the ACP session started')
})(), })(),
spawnFailed.then((err): never => { throw err }), spawnFailed.then((err): never => { throw err }),
@@ -321,6 +318,11 @@ export async function startAcpRun(request: SubagentStartRequest, spec: AcpRunSpe
if (flags.cancelled) throw new Error('subagent request was aborted before the ACP child started') if (flags.cancelled) throw new Error('subagent request was aborted before the ACP child started')
throw toError(error) throw toError(error)
} }
// The startup race can fulfill only after newSession assigned the id; this
// guard keeps that cross-closure invariant explicit for TypeScript.
/* v8 ignore next */
if (sessionId === undefined) throw new Error('ACP child published without a session id')
const runId = sessionId
const result: Promise<SubagentResult> = (async (): Promise<SubagentResult> => { const result: Promise<SubagentResult> = (async (): Promise<SubagentResult> => {
try { try {
@@ -332,7 +334,7 @@ export async function startAcpRun(request: SubagentStartRequest, spec: AcpRunSpe
// succeeds, transport/process failure rejects the in-flight prompt RPC. // succeeds, transport/process failure rejects the in-flight prompt RPC.
const prompt = async (): Promise<SubagentResult> => { const prompt = async (): Promise<SubagentResult> => {
// The startup phase cannot fulfill without assigning the session id. // The startup phase cannot fulfill without assigning the session id.
const promptResult = await conn.prompt({ sessionId: sessionId as string, prompt: toAcpPrompt(request.prompt) }) const promptResult = await conn.prompt({ sessionId: runId, prompt: toAcpPrompt(request.prompt) })
return { output: collectOutput(), stopReason: acpStopReason(promptResult.stopReason) } return { output: collectOutput(), stopReason: acpStopReason(promptResult.stopReason) }
} }
return await Promise.race([ return await Promise.race([
@@ -366,7 +368,7 @@ export async function startAcpRun(request: SubagentStartRequest, spec: AcpRunSpe
let disposal: Promise<void> | undefined let disposal: Promise<void> | undefined
return { return {
id, id: runId,
result, result,
dispose(): Promise<void> { dispose(): Promise<void> {
if (disposal !== undefined) return disposal if (disposal !== undefined) return disposal
@@ -99,7 +99,7 @@ function makeAgent(conn: AgentSideConnection): Agent {
writeFileSync(NEWSESSION_GATE.ready, 'at-newSession') writeFileSync(NEWSESSION_GATE.ready, 'at-newSession')
while (!existsSync(NEWSESSION_GATE.go)) await new Promise(r => setTimeout(r, 10)) while (!existsSync(NEWSESSION_GATE.go)) await new Promise(r => setTimeout(r, 10))
} }
return { sessionId: randomUUID() } return { sessionId: process.env.MOCK_SESSION_ID ?? randomUUID() }
}, },
authenticate(_params: AuthenticateRequest): Promise<void> { authenticate(_params: AuthenticateRequest): Promise<void> {
// No auth methods advertised; nothing to do. // No auth methods advertised; nothing to do.
@@ -122,8 +122,9 @@ describe('buildChildEnv', () => {
describe('dsh-subagent-acp', () => { describe('dsh-subagent-acp', () => {
it('drives a child process to completion and returns its streamed output', async () => { it('drives a child process to completion and returns its streamed output', async () => {
const ctx = await setup({ MOCK_TEXT: 'hello from acp child', MOCK_STOP: 'end_turn' }) const ctx = await setup({ MOCK_TEXT: 'hello from acp child', MOCK_STOP: 'end_turn', MOCK_SESSION_ID: 'acp-child-session' })
const run = await ctx.subagents.start('acp', request('do X')) const run = await ctx.subagents.start('acp', request('do X'))
expect(run.id).toBe('acp-child-session')
const result = await run.result const result = await run.result
expect(result.stopReason).toBe('completed') expect(result.stopReason).toBe('completed')
expect(text(result.output)).toBe('hello from acp child') expect(text(result.output)).toBe('hello from acp child')
+2
View File
@@ -25,6 +25,7 @@
"@deepseek-ai/dsh-agent": "^0.0.1", "@deepseek-ai/dsh-agent": "^0.0.1",
"@deepseek-ai/dsh-llm": "^0.0.1", "@deepseek-ai/dsh-llm": "^0.0.1",
"@deepseek-ai/dsh-scope": "^0.0.1", "@deepseek-ai/dsh-scope": "^0.0.1",
"@deepseek-ai/dsh-session": "^0.0.1",
"@deepseek-ai/dsh-tools": "^0.0.1", "@deepseek-ai/dsh-tools": "^0.0.1",
"cordis": "^4.0.0-rc.6" "cordis": "^4.0.0-rc.6"
}, },
@@ -32,6 +33,7 @@
"@deepseek-ai/dsh-agent": "workspace:^", "@deepseek-ai/dsh-agent": "workspace:^",
"@deepseek-ai/dsh-llm": "workspace:^", "@deepseek-ai/dsh-llm": "workspace:^",
"@deepseek-ai/dsh-scope": "workspace:^", "@deepseek-ai/dsh-scope": "workspace:^",
"@deepseek-ai/dsh-session": "workspace:^",
"@deepseek-ai/dsh-tools": "workspace:^", "@deepseek-ai/dsh-tools": "workspace:^",
"cordis": "^4.0.0-rc.6" "cordis": "^4.0.0-rc.6"
} }
@@ -24,6 +24,7 @@
"peerDependencies": { "peerDependencies": {
"@deepseek-ai/dsh-agent": "^0.0.1", "@deepseek-ai/dsh-agent": "^0.0.1",
"@deepseek-ai/dsh-llm": "^0.0.1", "@deepseek-ai/dsh-llm": "^0.0.1",
"@deepseek-ai/dsh-session": "^0.0.1",
"@deepseek-ai/dsh-subagent": "^0.0.1", "@deepseek-ai/dsh-subagent": "^0.0.1",
"cordis": "^4.0.0-rc.6" "cordis": "^4.0.0-rc.6"
}, },
@@ -33,6 +34,7 @@
"devDependencies": { "devDependencies": {
"@deepseek-ai/dsh-agent": "workspace:^", "@deepseek-ai/dsh-agent": "workspace:^",
"@deepseek-ai/dsh-llm": "workspace:^", "@deepseek-ai/dsh-llm": "workspace:^",
"@deepseek-ai/dsh-session": "workspace:^",
"@deepseek-ai/dsh-subagent": "workspace:^", "@deepseek-ai/dsh-subagent": "workspace:^",
"@cordisjs/plugin-loader": "^1.0.0-rc.4", "@cordisjs/plugin-loader": "^1.0.0-rc.4",
"cordis": "^4.0.0-rc.6" "cordis": "^4.0.0-rc.6"
+1 -1
View File
@@ -4,7 +4,7 @@ The **SDK server plugin** (`jsonrpc`): mounting it serves a stdio JSON-RPC serve
## Wiring ## Wiring
`inject: ['agents']` — the server creates one agent per SDK `sessionId` (get-or-create on `session/prompt`). A subagent's shared agent/session id supplies `subagent.finished.childSessionId` directly; the server caches only parent lineage because the child may be disposed before `subagent/end`. The LLM seam is read opportunistically via `ctx.get('llm')` (not injected): when `initialize.model` has no registered adapter, the plugin mounts `dsh-llm-deepseek` for it (credentials from `$DEEPSEEK_API_KEY` / `$DEEPSEEK_BASE_URL`); a config-registered adapter for the model wins. Everything else — persistence, the tool stacks, the adapter set — comes from the surrounding `cordis.yml`. `inject: ['agents']` — the server creates one agent per SDK `sessionId` (get-or-create on `session/prompt`). A local subagent's shared agent/session id supplies `subagent.finished.childSessionId` directly; the server caches only parent lineage because the child may be disposed before `subagent/end`. Runs from remote providers are not reported through this local-session notification pair because they create no local `session/created`/`subagent.started` edge. The LLM seam is read opportunistically via `ctx.get('llm')` (not injected): when `initialize.model` has no registered adapter, the plugin mounts `dsh-llm-deepseek` for it (credentials from `$DEEPSEEK_API_KEY` / `$DEEPSEEK_BASE_URL`); a config-registered adapter for the model wins. Everything else — persistence, the tool stacks, the adapter set — comes from the surrounding `cordis.yml`.
## Config ## Config
+7 -1
View File
@@ -104,8 +104,14 @@ export class HarnessSdkServer {
})) }))
this.disposers.push(ctx.on('subagent/end', (info: SubagentRunEndInfo) => { this.disposers.push(ctx.on('subagent/end', (info: SubagentRunEndInfo) => {
const agent = this.ctx.agents.get(info.id) const agent = this.ctx.agents.get(info.id)
const parentSessionId = this.subagentParents.get(info.id) ?? agent?.session.header.parentSession const cachedParentSessionId = this.subagentParents.get(info.id)
this.subagentParents.delete(info.id) this.subagentParents.delete(info.id)
// This protocol reports LOCAL child sessions, paired with the
// session/created-driven subagent.started notification above. A remote
// provider may use a real remote SessionId for its run, but that session
// does not exist in this harness and therefore has no paired start event.
if (cachedParentSessionId === undefined && agent === undefined) return
const parentSessionId = cachedParentSessionId ?? agent?.session.header.parentSession
this.transport.notify('subagent.finished', { this.transport.notify('subagent.finished', {
provider: info.provider, provider: info.provider,
agentId: String(info.id), agentId: String(info.id),
+5 -11
View File
@@ -303,7 +303,7 @@ describe('HarnessSdkServer', () => {
} }
}) })
it('falls back to live lineage and treats the shared id as the child session id', async () => { it('falls back to live lineage and ignores runs without a local child session', async () => {
const storageDir = await mkdtemp(join(tmpdir(), 'dsh-jsonrpc-subagent-fallback-')) const storageDir = await mkdtemp(join(tmpdir(), 'dsh-jsonrpc-subagent-fallback-'))
const ctx = await makeHarness(storageDir) const ctx = await makeHarness(storageDir)
let parentHandle: AgentHandle | undefined let parentHandle: AgentHandle | undefined
@@ -367,16 +367,10 @@ describe('HarnessSdkServer', () => {
stopReason: 'error', stopReason: 'error',
}, },
}) })
expect(transport.notifications).toContainEqual({ expect(transport.notifications.some(n =>
method: 'subagent.finished', n.method === 'subagent.finished'
params: { && n.params?.agentId === 'missing-child-agent',
provider: 'fork', )).toBe(false)
agentId: 'missing-child-agent',
childSessionId: 'missing-child-agent',
status: 'error',
stopReason: 'error',
},
})
await server.shutdown() await server.shutdown()
} finally { } finally {
+1
View File
@@ -25,6 +25,7 @@
"@deepseek-ai/dsh-agent": "^0.0.1", "@deepseek-ai/dsh-agent": "^0.0.1",
"@deepseek-ai/dsh-brand": "^0.0.1", "@deepseek-ai/dsh-brand": "^0.0.1",
"@deepseek-ai/dsh-llm": "^0.0.1", "@deepseek-ai/dsh-llm": "^0.0.1",
"@deepseek-ai/dsh-session": "^0.0.1",
"cordis": "^4.0.0-rc.6" "cordis": "^4.0.0-rc.6"
}, },
"devDependencies": { "devDependencies": {
+9
View File
@@ -827,6 +827,9 @@ importers:
'@deepseek-ai/dsh-scope': '@deepseek-ai/dsh-scope':
specifier: workspace:^ specifier: workspace:^
version: link:../../core/scope version: link:../../core/scope
'@deepseek-ai/dsh-session':
specifier: workspace:^
version: link:../../core/session
'@deepseek-ai/dsh-tools': '@deepseek-ai/dsh-tools':
specifier: workspace:^ specifier: workspace:^
version: link:../../core/tools version: link:../../core/tools
@@ -852,6 +855,9 @@ importers:
'@deepseek-ai/dsh-llm': '@deepseek-ai/dsh-llm':
specifier: workspace:^ specifier: workspace:^
version: link:../../llm/llm version: link:../../llm/llm
'@deepseek-ai/dsh-session':
specifier: workspace:^
version: link:../../core/session
'@deepseek-ai/dsh-subagent': '@deepseek-ai/dsh-subagent':
specifier: workspace:^ specifier: workspace:^
version: link:../subagent version: link:../subagent
@@ -1085,6 +1091,9 @@ importers:
'@deepseek-ai/dsh-llm': '@deepseek-ai/dsh-llm':
specifier: workspace:^ specifier: workspace:^
version: link:../../llm/llm version: link:../../llm/llm
'@deepseek-ai/dsh-session':
specifier: workspace:^
version: link:../../core/session
'@deepseek-ai/dsh-subagent': '@deepseek-ai/dsh-subagent':
specifier: workspace:^ specifier: workspace:^
version: link:../../subagent/subagent version: link:../../subagent/subagent