Merge origin/master into codex/invariant-service-seam
# Conflicts: # docs/config-catalog.md # docs/event-producer-consumer.md # docs/module-graph.md # packages/examples/agent-spine-demo/README.md # packages/examples/agent-spine-demo/package.json # packages/examples/agent-spine-demo/src/index.ts # packages/examples/tui-demo/package.json # packages/support/invariants/src/scoped-events.generated.ts # packages/ui/acp/package.json # packages/ui/tui/package.json # packages/ui/tui/tests/tui.spec.ts # pnpm-lock.yaml
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
# @deepseek-ai/dsh-tui-demo
|
||||
|
||||
The full-screen terminal app: a Cordis plugin that composes [`@deepseek-ai/dsh-agent-spine-demo`](../agent-spine-demo/README.md), JSONL persistence, keyboard-backed user interaction, a pre-created `main` agent, and [`@deepseek-ai/dsh-tui`](../../ui/tui/README.md). Its `bin` boots a leaf `cordis.yml`.
|
||||
The full-screen terminal app: a Cordis plugin that composes [`@deepseek-ai/dsh-agent-spine-demo`](../agent-spine-demo/README.md), persisted same-session goals, the human-command registry and `/goal` producer, JSONL persistence, keyboard-backed user interaction, a pre-created `main` agent, and [`@deepseek-ai/dsh-tui`](../../ui/tui/README.md). Its `bin` boots a leaf `cordis.yml`.
|
||||
|
||||
Use [`@deepseek-ai/dsh-cli-demo`](../cli-demo/README.md) for pipes, scripts, and other non-interactive runs. This package requires a TTY pair and has no line-oriented fallback.
|
||||
|
||||
@@ -9,6 +9,8 @@ Use [`@deepseek-ai/dsh-cli-demo`](../cli-demo/README.md) for pipes, scripts, and
|
||||
| Plugin | Why it is here |
|
||||
|---|---|
|
||||
| `@deepseek-ai/dsh-agent-spine-demo` | Shared services, model-facing tools, and one configured `main` agent |
|
||||
| `@deepseek-ai/dsh-commands` | Human-only discovery and dispatch consumed by the TUI and command plugins |
|
||||
| `@deepseek-ai/dsh-command-goal` | Direct `/goal` status and mutation over the spine's persisted-goal stack |
|
||||
| `@deepseek-ai/dsh-session-persistence-jsonl` | Durable session log under `persistenceRoot` |
|
||||
| `@deepseek-ai/dsh-user-interaction` | Provider-neutral human question service |
|
||||
| `@deepseek-ai/dsh-tui` | Full-screen transcript, editor, tool cards, plan, and question overlays |
|
||||
@@ -30,6 +32,7 @@ Swappable LLM, bash, filesystem, and other capability providers remain in the le
|
||||
| `skills` | owner defaults | Skill registry, local provider, and tool config |
|
||||
| `toolBash` | owner defaults | Model-facing bash tool config |
|
||||
| `toolTasks` | owner defaults | Background-task control-tool config, or `false` |
|
||||
| `goals` | owner defaults | Persisted goal-domain and model-tool config; `false` removes the goal stack and `/goal` producer |
|
||||
| `workspaceContext` | required | Workspace-instruction config, or `false` |
|
||||
| `persistenceRoot` | `./.sessions` | JSONL persistence root |
|
||||
| `persistenceCompression` | `'zstd'` | JSONL artifact encoding (`'zstd'` or raw `'none'`) |
|
||||
@@ -70,7 +73,7 @@ Fresh runs mint a `main-session-<uuid>` session id and pass it to both the TUI a
|
||||
|
||||
#### What the model sees
|
||||
|
||||
Each non-empty editor submission becomes a user message; a submission during a running turn becomes steering. The shared spine contributes the configured persona, workspace instructions, skill catalog, and visible tool schemas. TUI rendering itself is not model-visible.
|
||||
Each non-empty non-command editor submission becomes a user message; a submission during a running turn becomes steering. Slash-command input and output remain human-only, while accepted `/goal` mutations append domain-owned model-visible state. The shared spine contributes the configured persona, workspace instructions, skill catalog, goal controls, and visible tool schemas. TUI rendering itself is not model-visible.
|
||||
|
||||
#### Token effect
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@deepseek-ai/dsh-tui-demo",
|
||||
"description": "Full-screen terminal app: agent spine + JSONL persistence + pi-tui front door + pre-created main agent",
|
||||
"description": "Full-screen terminal app: agent spine + persisted goals + human commands + JSONL persistence + pi-tui front door + pre-created main agent",
|
||||
"version": "0.0.1",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
@@ -40,6 +40,8 @@
|
||||
"@deepseek-ai/dsh-app-boot": "^0.0.1",
|
||||
"@deepseek-ai/dsh-agent": "^0.0.1",
|
||||
"@deepseek-ai/dsh-agent-loop": "^0.0.1",
|
||||
"@deepseek-ai/dsh-commands": "^0.0.1",
|
||||
"@deepseek-ai/dsh-command-goal": "^0.0.1",
|
||||
"@deepseek-ai/dsh-agent-spine-demo": "^0.0.1",
|
||||
"@deepseek-ai/dsh-invariants": "^0.0.1",
|
||||
"@deepseek-ai/dsh-llm": "^0.0.1",
|
||||
@@ -59,6 +61,8 @@
|
||||
"@deepseek-ai/dsh-app-boot": "workspace:^",
|
||||
"@deepseek-ai/dsh-agent": "workspace:^",
|
||||
"@deepseek-ai/dsh-agent-loop": "workspace:^",
|
||||
"@deepseek-ai/dsh-commands": "workspace:^",
|
||||
"@deepseek-ai/dsh-command-goal": "workspace:^",
|
||||
"@deepseek-ai/dsh-agent-spine-demo": "workspace:^",
|
||||
"@deepseek-ai/dsh-invariants": "workspace:^",
|
||||
"@deepseek-ai/dsh-llm": "workspace:^",
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
/**
|
||||
* Full-screen terminal app: the default agent spine ({@link @deepseek-ai/dsh-agent-spine-demo})
|
||||
* plus JSONL persistence, keyboard-backed user interaction, and one pre-created
|
||||
* agent whose exact session identity the TUI drives. Swappable adapters,
|
||||
* executors, optional tools, and HMR stay in the leaf. This Loader plugin
|
||||
* plus persisted goals, human commands, JSONL persistence, keyboard-backed
|
||||
* user interaction, and one pre-created agent whose exact session identity the
|
||||
* TUI drives. Swappable adapters, executors, optional tools, and HMR stay in the leaf. This Loader plugin
|
||||
* intentionally exposes named exports only; a default export would hide its
|
||||
* `Config` schema (see docs/postmortem/0001).
|
||||
* @module @deepseek-ai/dsh-tui-demo
|
||||
@@ -13,6 +13,8 @@ import { randomUUID } from 'node:crypto'
|
||||
import z from 'schemastery'
|
||||
import { SessionId } from '@deepseek-ai/dsh-session'
|
||||
import ToolRegistry, { type Config as ToolsConfig } from '@deepseek-ai/dsh-tools'
|
||||
import CommandService from '@deepseek-ai/dsh-commands'
|
||||
import * as commandGoal from '@deepseek-ai/dsh-command-goal'
|
||||
import * as agentCore from '@deepseek-ai/dsh-agent-spine-demo'
|
||||
import * as workspaceContext from '@deepseek-ai/dsh-workspace-context'
|
||||
import SessionPersistenceJsonl, {
|
||||
@@ -57,6 +59,8 @@ export interface Config {
|
||||
toolBash?: NonNullable<agentCore.Config['toolBash']>
|
||||
/** Generic background-task controls forwarded through agent-spine-demo; set false to omit them. */
|
||||
toolTasks?: NonNullable<agentCore.Config['toolTasks']>
|
||||
/** Persisted same-session goals; owner defaults enable them, or false disables the stack and command. */
|
||||
goals?: agentCore.GoalConfig | false
|
||||
/** Persisted session id to resume instead of creating a fresh session. */
|
||||
resumeSessionId?: string
|
||||
/** Controls automatic AGENTS.md/CLAUDE.md loading; configure a byte budget or set `false`. */
|
||||
@@ -82,6 +86,7 @@ export const Config: z<Config> = z.object({
|
||||
skills: agentCore.SkillConfigSchema,
|
||||
toolBash: agentCore.ToolBashConfigSchema,
|
||||
toolTasks: z.union([z.const(false), agentCore.ToolTasksConfigSchema]),
|
||||
goals: z.union([z.const(false), agentCore.GoalConfigSchema]),
|
||||
resumeSessionId: z.string(),
|
||||
workspaceContext: z.union([z.const(false), workspaceContext.Config]).required(),
|
||||
})
|
||||
@@ -97,6 +102,9 @@ export const Config: z<Config> = z.object({
|
||||
export function composeTuiApp(ctx: Context, config: Config): void {
|
||||
const resumeSessionId = config.resumeSessionId === '' ? undefined : config.resumeSessionId
|
||||
const sessionId = SessionId(resumeSessionId ?? `main-session-${randomUUID()}`)
|
||||
const goals = config.goals ?? {}
|
||||
ctx.plugin(CommandService)
|
||||
if (goals !== false) ctx.plugin(commandGoal)
|
||||
ctx.plugin(SessionPersistenceJsonl, {
|
||||
root: config.persistenceRoot ?? DEFAULT_PERSISTENCE_ROOT,
|
||||
...(config.persistenceCompression === undefined ? {} : { compression: config.persistenceCompression }),
|
||||
@@ -109,6 +117,7 @@ export function composeTuiApp(ctx: Context, config: Config): void {
|
||||
})
|
||||
ctx.plugin(agentCore, {
|
||||
...agentCore.pickSpineConfig(config),
|
||||
goals,
|
||||
agents: [{
|
||||
id: SessionId('main'),
|
||||
provider: config.provider,
|
||||
|
||||
@@ -41,18 +41,22 @@ describe('dsh-tui-demo app', () => {
|
||||
})
|
||||
|
||||
expect(calls.map(call => call.name)).toEqual([
|
||||
'CommandService',
|
||||
'command-goal',
|
||||
'SessionPersistenceJsonl',
|
||||
'UserInteractionService',
|
||||
'ui-tui',
|
||||
'agent-spine-demo',
|
||||
'tool-ask-user',
|
||||
])
|
||||
expect(calls[0]?.config).toEqual({ root: '/tmp/tui-sessions', compression: 'none' })
|
||||
const tuiConfig = calls[2]?.config as { sessionId: string }
|
||||
expect(calls[0]?.config).toBeUndefined()
|
||||
expect(calls[2]?.config).toEqual({ root: '/tmp/tui-sessions', compression: 'none' })
|
||||
const tuiConfig = calls[4]?.config as { sessionId: string }
|
||||
expect(tuiConfig).toMatchObject({ welcome: 'TUI ready', color: false, maxToolOutputLines: 3 })
|
||||
expect(tuiConfig.sessionId).toMatch(/^main-session-[0-9a-f-]{36}$/)
|
||||
const spineConfig = calls[3]?.config as {
|
||||
const spineConfig = calls[5]?.config as {
|
||||
readonly agents: Array<Record<string, unknown>>
|
||||
readonly goals: Record<string, never>
|
||||
readonly maxParallelToolCalls: number
|
||||
readonly persona: string
|
||||
readonly toolOrder: string[]
|
||||
@@ -63,6 +67,7 @@ describe('dsh-tui-demo app', () => {
|
||||
persona: 'test persona',
|
||||
toolOrder: ['zulu', TOOL_ORDER_REST],
|
||||
tools: { mode: 'code' },
|
||||
goals: {},
|
||||
})
|
||||
expect(spineConfig.agents[0]).toMatchObject({
|
||||
id: 'main',
|
||||
@@ -82,9 +87,9 @@ describe('dsh-tui-demo app', () => {
|
||||
workspaceContext: false,
|
||||
})
|
||||
|
||||
expect(calls[0]?.config).toEqual({ root: './.sessions' })
|
||||
expect(calls[2]?.config).toEqual({ welcome: 'ready.', sessionId: 'persisted-session' })
|
||||
expect((calls[3]?.config as { agents: Array<Record<string, unknown>> }).agents[0]).toMatchObject({
|
||||
expect(calls[2]?.config).toEqual({ root: './.sessions' })
|
||||
expect(calls[4]?.config).toEqual({ welcome: 'ready.', sessionId: 'persisted-session' })
|
||||
expect((calls[5]?.config as { agents: Array<Record<string, unknown>> }).agents[0]).toMatchObject({
|
||||
id: 'main',
|
||||
resumeSessionId: 'persisted-session',
|
||||
})
|
||||
@@ -96,13 +101,16 @@ describe('dsh-tui-demo app', () => {
|
||||
provider: 'mock',
|
||||
model: 'mock-model',
|
||||
resumeSessionId: '',
|
||||
goals: false,
|
||||
workspaceContext: false,
|
||||
})
|
||||
|
||||
const tuiConfig = calls[2]?.config as { sessionId: string }
|
||||
const tuiConfig = calls[3]?.config as { sessionId: string }
|
||||
expect(tuiConfig.sessionId).toMatch(/^main-session-[0-9a-f-]{36}$/)
|
||||
expect((calls[3]?.config as { agents: Array<Record<string, unknown>> }).agents[0])
|
||||
expect((calls[4]?.config as { agents: Array<Record<string, unknown>> }).agents[0])
|
||||
.toMatchObject({ sessionId: tuiConfig.sessionId })
|
||||
expect(calls.map(call => call.name)).not.toContain('command-goal')
|
||||
expect(calls[4]?.config).toMatchObject({ goals: false })
|
||||
})
|
||||
|
||||
it('has the namespace-plugin export shape so the Loader keeps its schema', () => {
|
||||
|
||||
@@ -26,6 +26,12 @@
|
||||
{
|
||||
"path": "../../core/session"
|
||||
},
|
||||
{
|
||||
"path": "../../ui/commands"
|
||||
},
|
||||
{
|
||||
"path": "../../goal/command-goal"
|
||||
},
|
||||
{
|
||||
"path": "../agent-spine-demo"
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user