fix: surface config startup failures

This commit is contained in:
Tianyi Cui
2026-07-14 11:23:38 +08:00
parent e8f1bd41f5
commit c1fb97c63a
16 changed files with 151 additions and 21 deletions

View File

@@ -102,16 +102,23 @@ export const Config: z<Config> = z.object({
})
/**
* Compose the spine with the stdio front door. The console logger comes first
* (infra), then the agent-core bundle pre-creating one agent from this app's
* `model`/`resumeSessionId` with the deployment `persona`, then the JSONL
* backend, then the readline UI rendering that object as `main`. The `hmr` dev-reload plugin is
* a leaf concern (see the module doc), so it is not mounted here.
* Compose the spine with the stdio front door. Console logging, persistence,
* and user interaction mount first; the readline UI then waits on the agent
* registry and subscribes to config-start failures before agent-core can start
* the configured identity. The ask-user tool waits on the completed spine.
* The `hmr` dev-reload plugin is a leaf concern (see the module doc), so it is
* not mounted here.
*/
export function apply(ctx: Context, config: Config): void {
const resumeSessionId = config.resumeSessionId === '' ? undefined : config.resumeSessionId
const sessionId = SessionId(resumeSessionId ?? `main-session-${randomUUID()}`)
ctx.plugin(ConsoleExporter)
ctx.plugin(SessionPersistenceJsonl, { root: config.persistenceRoot ?? './.sessions' })
ctx.plugin(UserInteractionService)
ctx.plugin(uiStdio, {
welcome: config.welcome ?? 'ready.',
sessionId,
})
ctx.plugin(agentCore, {
...config.persona !== undefined ? { persona: config.persona } : {},
...config.toolOrder !== undefined ? { toolOrder: config.toolOrder } : {},
@@ -124,11 +131,5 @@ export function apply(ctx: Context, config: Config): void {
}],
...config.skills !== undefined ? { skills: config.skills } : {},
})
ctx.plugin(SessionPersistenceJsonl, { root: config.persistenceRoot ?? './.sessions' })
ctx.plugin(UserInteractionService)
ctx.plugin(toolAskUser)
ctx.plugin(uiStdio, {
welcome: config.welcome ?? 'ready.',
sessionId,
})
}

View File

@@ -21,6 +21,7 @@ import type { Readable, Writable } from 'node:stream'
import type { Context } from 'cordis'
import z from 'schemastery'
import type { Agent } from '@deepseek-ai/dsh-agent'
import type {} from '@deepseek-ai/dsh-agent-loop'
import {
UserInteractionError,
type AskUserQuestionAnswer,
@@ -173,6 +174,7 @@ export function createStdioChat(ctx: Context, config: Config, runtime: StdioRunt
const queuedInput: string[] = []
let targetReady = target !== undefined
let hadReadyTarget = targetReady
let failedStartup: { error: unknown } | undefined
const submit = (agent: Agent, text: string): void => {
submittedWork = true
@@ -187,6 +189,7 @@ export function createStdioChat(ctx: Context, config: Config, runtime: StdioRunt
if (!matchesConfiguredIdentity(agent)) return
target = agent
targetReady = false
failedStartup = undefined
})
const disposeSessionStartListener = ctx.on('agent/session-start', (agent) => {
if (agent !== target) return
@@ -220,6 +223,18 @@ export function createStdioChat(ctx: Context, config: Config, runtime: StdioRunt
exitTimer = setTimeout(() => { exit(0) }, 200)
}
const disposeStartupFailedListener = ctx.on('agent-loop/config-start-failed', (sessionId, error) => {
if (sessionId !== config.sessionId || targetReady) return
failedStartup = { error }
const dropped = queuedInput.length
queuedInput.length = 0
submittedWork = sawRunning
if (dropped > 0) {
ctx.logger.error(`ui-stdio: main agent failed to start; dropped queued stdin (${dropped} line(s)): ${String(error)}`)
}
maybeExit()
})
const disposeStatusListener = ctx.on('agent/status', (subject, status) => {
if (subject !== target) return
if (status === 'running') sawRunning = true
@@ -374,6 +389,10 @@ export function createStdioChat(ctx: Context, config: Config, runtime: StdioRunt
}
const text = line.trim()
if (!text) return
if (failedStartup !== undefined) {
ctx.logger.error(`ui-stdio: main agent failed to start; dropped queued stdin (1 line(s)): ${String(failedStartup.error)}`)
return
}
const agent = target
if (agent === undefined || !targetReady) {
// Initial exact-id restoration is asynchronous. Preserve input until
@@ -407,6 +426,7 @@ export function createStdioChat(ctx: Context, config: Config, runtime: StdioRunt
disposeCreatedListener()
disposeSessionStartListener()
disposeDisposedListener()
disposeStartupFailedListener()
reader.close()
}
}, 'ui-stdio')