mirror of
https://github.com/deepseek-ai/deepseek-harness
synced 2026-08-15 21:04:50 +00:00
fix: retain stdio target across agent replacement
This commit is contained in:
@@ -95,13 +95,20 @@ export function createStdioChat(ctx: Context, config: Config, runtime: StdioRunt
|
||||
const welcome = config.welcome ?? 'ready.'
|
||||
const { input, output, exit } = runtime
|
||||
|
||||
// This app owns exactly one pre-created agent. Hold the live object directly:
|
||||
// its per-run id is intentionally fresh, while `main` remains only the
|
||||
// terminal's fixed display label.
|
||||
let target: Agent | undefined = ctx.agents.list()[0]
|
||||
ctx.on('agent/created', (agent) => { target ??= agent })
|
||||
// This app owns one root agent. Hold the live object directly: its per-run id
|
||||
// is intentionally fresh, while `main` remains only the terminal's fixed
|
||||
// display label. HMR may publish the replacement before old teardown emits
|
||||
// disposed, so a target disposal reselects the surviving root from the live
|
||||
// registry instead of leaving the terminal detached. Fork children carry
|
||||
// parentSession lineage and must never become the terminal target.
|
||||
const rootAgent = (): Agent | undefined =>
|
||||
ctx.agents.list().find(agent => agent.session.header.parentSession === undefined)
|
||||
let target: Agent | undefined = rootAgent()
|
||||
ctx.on('agent/created', (agent) => {
|
||||
if (target === undefined && agent.session.header.parentSession === undefined) target = agent
|
||||
})
|
||||
ctx.on('agent/disposed', (agent) => {
|
||||
if (target === agent) target = undefined
|
||||
if (target === agent) target = rootAgent()
|
||||
})
|
||||
|
||||
// Transcript rendering off the durable `session/event` feed — the assistant
|
||||
|
||||
@@ -230,6 +230,27 @@ describe('createStdioChat rendering', () => {
|
||||
expect(out.text()).toContain('[main turn 1] ')
|
||||
})
|
||||
|
||||
it('retargets a surviving root when HMR publishes it before disposing the old root', async () => {
|
||||
const { ctx, input } = await setup()
|
||||
const oldRoot = makeAgent('old-root')
|
||||
const child = makeAgent('child')
|
||||
;(child.session.header as { parentSession?: string }).parentSession = oldRoot.id
|
||||
const replacement = makeAgent('replacement')
|
||||
const disposeOld = ctx.agents.register(oldRoot)
|
||||
ctx.agents.register(child)
|
||||
ctx.agents.register(replacement)
|
||||
|
||||
// The replacement's created edge arrived while oldRoot was still targeted.
|
||||
// Once oldRoot is removed, registry order is child then replacement; the
|
||||
// UI must skip the surviving child and route input to the replacement root.
|
||||
disposeOld()
|
||||
input.feed('after hmr')
|
||||
await new Promise(resolve => setImmediate(resolve))
|
||||
|
||||
expect(child.sent).toEqual([])
|
||||
expect(replacement.sent).toEqual([[{ type: 'text', text: 'after hmr' }]])
|
||||
})
|
||||
|
||||
it('renders tool/call and tool/result session events', async () => {
|
||||
const { ctx, out } = await setup()
|
||||
const session = {} as Session
|
||||
|
||||
Reference in New Issue
Block a user