refactor: remove UI identity translations

This commit is contained in:
Tianyi Cui
2026-07-14 02:09:09 +08:00
parent 709cc7200e
commit 61136b22bb
12 changed files with 100 additions and 110 deletions

View File

@@ -4,7 +4,7 @@ The **SDK server plugin** (`jsonrpc`): mounting it serves a stdio JSON-RPC serve
## Wiring
`inject: ['agents']` — the server creates one agent per SDK `sessionId` (get-or-create on `session/prompt`) and demuxes `subagent/end` through the registry. 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 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`.
## Config

View File

@@ -58,11 +58,6 @@ interface SessionRecord {
activePrompt: boolean
}
interface SubagentRecord {
childSessionId: string
parentSessionId: string | undefined
}
/**
* The SDK server over a booted harness context. Constructing it subscribes to
* the context's `session/event`, `session/created`, `agent/created`, and
@@ -76,7 +71,7 @@ export class HarnessSdkServer {
private llmFiber: { dispose(): Promise<void> } | undefined
private readonly sessions = new Map<string, SessionRecord>()
private readonly sessionCreations = new Map<string, Promise<SessionRecord>>()
private readonly subagentSessions = new Map<string, SubagentRecord>()
private readonly subagentParents = new Map<SessionId, SessionId>()
private readonly disposers: (() => void)[] = []
private shutdownTask: Promise<Record<string, never>> | undefined
private shuttingDown = false
@@ -100,29 +95,22 @@ export class HarnessSdkServer {
childSessionId: String(session.id),
})
}))
// Cache agent → session lineage on creation: by the time `subagent/end`
// fires the child agent may already be disposed and gone from the registry.
// Cache parent lineage on creation: by the time `subagent/end` fires the
// child agent may already be disposed and gone from the registry. The child
// session id needs no cache because it is the shared agent/session id.
this.disposers.push(ctx.on('agent/created', (agent) => {
this.subagentSessions.set(String(agent.id), {
childSessionId: String(agent.session.id),
parentSessionId: agent.session.header.parentSession === undefined
? undefined
: String(agent.session.header.parentSession),
})
const parentSessionId = agent.session.header.parentSession
if (parentSessionId !== undefined) this.subagentParents.set(agent.id, parentSessionId)
}))
this.disposers.push(ctx.on('subagent/end', (info: SubagentRunEndInfo) => {
const rec = this.subagentSessions.get(String(info.id))
const agent = this.ctx.agents.get(info.id)
const childSessionId = rec?.childSessionId ?? (agent === undefined ? undefined : String(agent.session.id))
const parentSessionId = rec?.parentSessionId ?? (
agent?.session.header.parentSession === undefined ? undefined : String(agent.session.header.parentSession)
)
if (childSessionId === undefined) return
const parentSessionId = this.subagentParents.get(info.id) ?? agent?.session.header.parentSession
this.subagentParents.delete(info.id)
this.transport.notify('subagent.finished', {
provider: info.provider,
agentId: String(info.id),
...(parentSessionId === undefined ? {} : { parentSessionId }),
childSessionId,
...(parentSessionId === undefined ? {} : { parentSessionId: String(parentSessionId) }),
childSessionId: String(info.id),
status: info.stopReason === 'completed' ? 'ok' : 'error',
stopReason: info.stopReason,
...(info.lastAssistantMessage === undefined ? {} : { lastAssistantMessage: info.lastAssistantMessage }),
@@ -195,7 +183,7 @@ export class HarnessSdkServer {
this.sessionCreations.clear()
const records = [...this.sessions.values()]
this.sessions.clear()
this.subagentSessions.clear()
this.subagentParents.clear()
const failures: unknown[] = []
while (this.disposers.length > 0) {
try {

View File

@@ -272,6 +272,9 @@ describe('HarnessSdkServer', () => {
meta: { cwd: storageDir, parentSession: SessionId('main') },
agentOptions: { model: 'deepseek' },
})
// The backend may dispose the child before publishing its run outcome;
// only the cached parent lineage should be needed at this point.
await handle.dispose()
await settleSubagent(ctx, parentHandle.agent, {
provider: 'spawn',
id: SessionId('child-session'),
@@ -292,7 +295,6 @@ describe('HarnessSdkServer', () => {
},
})
await handle.dispose()
await parentHandle.dispose()
await server.shutdown()
} finally {
@@ -301,7 +303,7 @@ describe('HarnessSdkServer', () => {
}
})
it('falls back to live agent lineage for uncached subagent end events', async () => {
it('falls back to live lineage and treats the shared id as the child session id', async () => {
const storageDir = await mkdtemp(join(tmpdir(), 'dsh-jsonrpc-subagent-fallback-'))
const ctx = await makeHarness(storageDir)
let parentHandle: AgentHandle | undefined
@@ -365,10 +367,16 @@ describe('HarnessSdkServer', () => {
stopReason: 'error',
},
})
expect(transport.notifications.some(n =>
n.method === 'subagent.finished'
&& n.params?.agentId === 'missing-child-agent',
)).toBe(false)
expect(transport.notifications).toContainEqual({
method: 'subagent.finished',
params: {
provider: 'fork',
agentId: 'missing-child-agent',
childSessionId: 'missing-child-agent',
status: 'error',
stopReason: 'error',
},
})
await server.shutdown()
} finally {