fix(ui): surface declarative startup failures
This commit is contained in:
@@ -11,7 +11,7 @@ This package owns the terminal channel only. It injects `agents` and `userIntera
|
||||
| `welcome` | `ready.` | Banner printed before the first prompt |
|
||||
| `agent` | `main` | Agent id driven by stdin and observed for EOF shutdown |
|
||||
|
||||
The plugin seeds display labels from the live agent registry, then tracks `agent/created` and `agent/disposed` so HMR and externally managed agents render consistently. Disposal closes readline and unregisters every listener/provider through Cordis effects.
|
||||
The plugin seeds display labels from the live agent registry, then tracks `agent/created` and `agent/disposed` so HMR and externally managed agents render consistently. While waiting for its configured agent, it also observes retained and live `agent/start-failed` notifications; a matching failure is printed and exits with status 1 instead of waiting forever. Disposal closes readline and unregisters every listener/provider through Cordis effects.
|
||||
|
||||
```yaml
|
||||
- id: stdio
|
||||
|
||||
@@ -13,7 +13,7 @@ import { createInterface } from 'node:readline'
|
||||
import type { Readable, Writable } from 'node:stream'
|
||||
import type { Context } from 'cordis'
|
||||
import z from 'schemastery'
|
||||
import { AgentId } from '@deepseek-ai/dsh-agent'
|
||||
import { AgentId, observeAgentStart } from '@deepseek-ai/dsh-agent'
|
||||
import {
|
||||
UserInteractionError,
|
||||
type AskUserQuestionAnswer,
|
||||
@@ -363,14 +363,12 @@ export function createStdioChat(ctx: Context, config: Config, runtime: StdioRunt
|
||||
*/
|
||||
export function mountStdio(ctx: Context, config: Config, runtime: StdioRuntime): void {
|
||||
const agentId = AgentId(config.agent ?? 'main')
|
||||
if (ctx.agents.get(agentId) !== undefined) {
|
||||
createStdioChat(ctx, config, runtime)
|
||||
return
|
||||
}
|
||||
const dispose = ctx.on('agent/created', (agent) => {
|
||||
if (agent.id !== agentId) return
|
||||
dispose()
|
||||
createStdioChat(ctx, config, runtime)
|
||||
observeAgentStart(ctx, agentId, {
|
||||
onStarted: () => { createStdioChat(ctx, config, runtime) },
|
||||
onFailed: (error) => {
|
||||
runtime.output.write(`ui-stdio: agent "${agentId}" failed to start: ${error.message}\n`)
|
||||
runtime.exit(1)
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ import { Readable, Writable } from 'node:stream'
|
||||
import { describe, expect, it, vi } from 'vitest'
|
||||
import { Context } from 'cordis'
|
||||
import type { Agent, AgentStatus } from '@deepseek-ai/dsh-agent'
|
||||
import AgentRegistry from '@deepseek-ai/dsh-agent'
|
||||
import AgentRegistry, { AgentId } from '@deepseek-ai/dsh-agent'
|
||||
import type { ContentBlock, StreamChunk } from '@deepseek-ai/dsh-llm'
|
||||
import type { Session, SessionEvent } from '@deepseek-ai/dsh-session'
|
||||
import UserInteractionService from '@deepseek-ai/dsh-user-interaction'
|
||||
@@ -111,6 +111,42 @@ describe('mountStdio readiness', () => {
|
||||
await fiber.dispose()
|
||||
})
|
||||
|
||||
it('prints a matching live startup failure and exits instead of waiting forever', async () => {
|
||||
const ctx = new Context()
|
||||
await ctx.plugin(AgentRegistry)
|
||||
await ctx.plugin(UserInteractionService)
|
||||
const { runtime, out, exit } = makeRuntime()
|
||||
const fiber = await ctx.plugin(Object.assign((inner: Context) => {
|
||||
mountStdio(inner, CONFIG, runtime)
|
||||
}, { inject: ['agents', 'userInteraction'] }))
|
||||
|
||||
ctx.agents.reportStartFailure(AgentId('other'), new Error('other failed'))
|
||||
expect(out.text()).toBe('')
|
||||
expect(exit).not.toHaveBeenCalled()
|
||||
ctx.agents.reportStartFailure(AgentId('main'), new Error('resume failed'))
|
||||
expect(out.text()).toBe('ui-stdio: agent "main" failed to start: resume failed\n')
|
||||
expect(exit).toHaveBeenCalledWith(1)
|
||||
|
||||
ctx.agents.register(makeAgent('main'))
|
||||
expect(out.text()).toBe('ui-stdio: agent "main" failed to start: resume failed\n')
|
||||
await fiber.dispose()
|
||||
})
|
||||
|
||||
it('prints a retained startup failure for a late-mounted stdio channel', async () => {
|
||||
const ctx = new Context()
|
||||
await ctx.plugin(AgentRegistry)
|
||||
await ctx.plugin(UserInteractionService)
|
||||
ctx.agents.reportStartFailure(AgentId('main'), new Error('already failed'))
|
||||
const { runtime, out, exit } = makeRuntime()
|
||||
const fiber = await ctx.plugin(Object.assign((inner: Context) => {
|
||||
mountStdio(inner, CONFIG, runtime)
|
||||
}, { inject: ['agents', 'userInteraction'] }))
|
||||
|
||||
expect(out.text()).toBe('ui-stdio: agent "main" failed to start: already failed\n')
|
||||
expect(exit).toHaveBeenCalledWith(1)
|
||||
await fiber.dispose()
|
||||
})
|
||||
|
||||
it('opens immediately when the configured agent already exists', async () => {
|
||||
const ctx = new Context()
|
||||
await ctx.plugin(AgentRegistry)
|
||||
|
||||
Reference in New Issue
Block a user