Merge latest master into codex/llm-error-recovery-rfc
# Conflicts: # docs/architecture.md # docs/config-catalog.md # docs/cordis-catalog/events.md # docs/event-producer-consumer.md # docs/module-graph.md # docs/persistence-catalog.md # packages/cordis/tool-cordis/src/api-catalog.ts # packages/core/agent-loop/src/loop.ts # packages/core/agent/src/types.ts # packages/examples/agent-spine-demo/README.md # packages/examples/stdio-demo/README.md # packages/examples/stdio-demo/src/index.ts # packages/llm/llm-deepseek/README.md # packages/llm/llm-deepseek/src/adapter.ts # packages/llm/llm-deepseek/tests/adapter.spec.ts # packages/llm/llm/README.md # packages/llm/llm/src/error.ts # packages/llm/llm/tests/service.spec.ts # packages/sandbox/sandbox-policy/tsconfig.json # packages/ui/stdio/README.md # packages/ui/stdio/src/index.ts # packages/ui/stdio/tests/stdio.spec.ts # packages/ui/tui/src/index.ts # pnpm-lock.yaml # website/zh-CN/api/harness/events.md # website/zh-CN/api/harness/llm.md
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
# @deepseek-ai/dsh-tui
|
||||
|
||||
The interactive terminal front door for DeepSeek Harness agents, built on [`@earendil-works/pi-tui`](https://www.npmjs.com/package/@earendil-works/pi-tui). It requires stdin and stdout TTYs; scripts and Loader pipes should compose [`@deepseek-ai/dsh-stdio`](../stdio/README.md) instead.
|
||||
The interactive terminal front door for DeepSeek Harness agents, built on [`@earendil-works/pi-tui`](https://www.npmjs.com/package/@earendil-works/pi-tui). It requires stdin and stdout TTYs; scripts and Loader pipes should use the headless [`@deepseek-ai/dsh-cli-demo`](../../examples/cli-demo/README.md) app instead.
|
||||
|
||||
The implemented [TUI feature Agent Note](../../../.agents/notes/implemented/feature/2026-07-17-dedicated-full-screen-tui-front-door.md) owns the front-door decision; the [terminal-state snapshot Agent Note](../../../.agents/notes/implemented/testing/2026-07-18-tui-terminal-state-snapshots.md) owns its verification strategy.
|
||||
|
||||
@@ -77,4 +77,4 @@ Append-only; newly visible content follows the reusable request prefix and does
|
||||
|
||||
- **One configured session owns the transcript and editor** — questions from other agents can still use the shared overlay provider, but session rendering and prompt input remain bound to `sessionId`.
|
||||
- **Tool cards are text terminal presentations** — terminal, diff, and generic cards use tool-owned titles/content, but session content currently has no image block for inline image rendering.
|
||||
- **Non-TTY operation is intentionally unsupported** — app bundles that need automation must select `dsh-stdio` before mounting this plugin rather than expecting an internal fallback.
|
||||
- **Non-TTY operation is intentionally unsupported** — automation must use the headless app rather than expecting an internal fallback.
|
||||
|
||||
@@ -35,6 +35,7 @@ import type { Context } from 'cordis'
|
||||
import z from 'schemastery'
|
||||
import type { Agent, AgentStatus } from '@deepseek-ai/dsh-agent'
|
||||
import type {} from '@deepseek-ai/dsh-agent-loop'
|
||||
import { errorChain } from '@deepseek-ai/dsh-llm'
|
||||
import type { ContentBlock, StreamChunk, TokenUsage } from '@deepseek-ai/dsh-llm'
|
||||
import type {} from '@deepseek-ai/dsh-llm-retry'
|
||||
import { SessionId, type Session, type SessionEvent, type TodoItem } from '@deepseek-ai/dsh-session'
|
||||
@@ -192,15 +193,6 @@ function displayText(text: string): string {
|
||||
`\\x${control.charCodeAt(0).toString(16).padStart(2, '0')}`)
|
||||
}
|
||||
|
||||
/** Render an arbitrary failure without allowing hostile coercion to escape the UI boundary. */
|
||||
function renderThrown(value: unknown): string {
|
||||
try {
|
||||
return String(value)
|
||||
} catch {
|
||||
return '<unrenderable thrown value>'
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Theme-agnostic palette built from the standard 16-color ANSI set plus SGR
|
||||
* attributes, which every terminal remaps to its active color scheme. Body
|
||||
@@ -1300,7 +1292,9 @@ export function createTuiChat(
|
||||
const disposeError = ctx.on('agent/error', (subject, turn, step, error) => {
|
||||
if (subject !== agent) return
|
||||
liveErrors.add(`${turn}:${step}`)
|
||||
appendNotice(error.message, 'error')
|
||||
// Full cause chain: wrapper messages like `fetch failed` carry the
|
||||
// actionable transport detail on `cause`.
|
||||
appendNotice(errorChain(error), 'error')
|
||||
})
|
||||
const disposeAgent = ctx.on('agent/disposed', (subject) => {
|
||||
if (subject !== agent) return
|
||||
@@ -1367,7 +1361,7 @@ export function mountTui(ctx: Context, config: Config, runtime: TuiRuntime): voi
|
||||
if (settled || failedSessionId !== sessionId) return
|
||||
settled = true
|
||||
stopWaiting()
|
||||
runtime.terminal.write(displayText(`ui-tui: session "${sessionId}" failed to start: ${renderThrown(error)}\n`))
|
||||
runtime.terminal.write(displayText(`ui-tui: session "${sessionId}" failed to start: ${errorChain(error)}\n`))
|
||||
runtime.exit(1)
|
||||
}
|
||||
|
||||
@@ -1379,10 +1373,10 @@ export function mountTui(ctx: Context, config: Config, runtime: TuiRuntime): voi
|
||||
|
||||
/** Cordis entry point using the process terminal; explicit TUI composition requires a TTY pair. */
|
||||
/* v8 ignore start -- production process wiring; fake-terminal tests cover mountTui/createTuiChat,
|
||||
and the repl-agent PTY smoke covers the real entry */
|
||||
and the tui-agent PTY smoke covers the real entry */
|
||||
export function apply(ctx: Context, config: Config): void {
|
||||
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
||||
throw new Error('ui-tui: both stdin and stdout must be TTYs; use @deepseek-ai/dsh-stdio for pipes')
|
||||
throw new Error('ui-tui: both stdin and stdout must be TTYs; use @deepseek-ai/dsh-cli-demo for non-interactive runs')
|
||||
}
|
||||
mountTui(ctx, config, {
|
||||
terminal: new ProcessTerminal(),
|
||||
|
||||
@@ -937,7 +937,7 @@ describe('terminal mounting', () => {
|
||||
expect(terminal.output).toBe('')
|
||||
expect(exit).not.toHaveBeenCalled()
|
||||
ctx.emit('agent-loop/config-start-failed', SessionId('main-session'), new Error('resume \u001b]2;failure-controlled\u0007'))
|
||||
expect(terminal.output).toBe('ui-tui: session "main-session" failed to start: Error: resume \\x1b]2;failure-controlled\\x07\n')
|
||||
expect(terminal.output).toBe('ui-tui: session "main-session" failed to start: resume \\x1b]2;failure-controlled\\x07\n')
|
||||
expect(exit).toHaveBeenCalledWith(1)
|
||||
|
||||
const session = ctx.sessions.create(SessionId('main-session'))
|
||||
@@ -965,7 +965,7 @@ describe('terminal mounting', () => {
|
||||
})
|
||||
|
||||
expect(terminal.started).toBe(0)
|
||||
expect(terminal.output).toBe('ui-tui: session "main-session" failed to start: <unrenderable thrown value>\n')
|
||||
expect(terminal.output).toBe('ui-tui: session "main-session" failed to start: <unrenderable value>\n')
|
||||
expect(exit).toHaveBeenCalledWith(1)
|
||||
await ctx.fiber.dispose()
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user