fix(goal): close human command surface gaps

This commit is contained in:
Tianyi Cui
2026-07-20 12:11:06 +08:00
parent 79d41dfaa9
commit 491dafc785
12 changed files with 47 additions and 28 deletions

View File

@@ -12,7 +12,7 @@ A terminal chat always wants the same cluster, so the package owns it rather tha
|---|---|
| `@deepseek-ai/dsh-agent-spine-demo` | the spine, pre-creating a `main` agent from this app's provider/model pair with `process.cwd()` as the fresh session cwd and carrying its `persona` |
| `@deepseek-ai/dsh-commands` | the human-command registry consumed by the TUI front door and optional command plugins |
| `@deepseek-ai/dsh-command-goal` | the direct `/goal` producer; the app enables the spine's persisted-goal stack with it |
| `@deepseek-ai/dsh-command-goal` | the direct `/goal` producer mounted only for the TUI front door; readline retains the model-mediated goal path |
| `@deepseek-ai/dsh-session-persistence-jsonl` | durable JSONL session log under `persistenceRoot` |
| `@deepseek-ai/dsh-user-interaction` | the human question/answer seam used by confirmation tools |
| `@deepseek-ai/dsh-tool-ask-user` | the model-facing `ask_user_question` tool |
@@ -38,7 +38,7 @@ The leaf `cordis.yml` supplies only the **swappable backends** — an LLM adapte
| `skills` | owner defaults | registry-cache, local-provider, and model-facing skill-tool config, routed through `dsh-agent-spine-demo` |
| `toolBash` | owner defaults | model-facing bash config routed through `dsh-agent-spine-demo`, including bash's producer-local `enableRunInBackground` |
| `toolTasks` | owner defaults | generic `task_output` wait bounds routed through `dsh-agent-spine-demo` |
| `goals` | owner defaults | persisted goal-domain and model-tool config; `false` removes the goal stack and `/goal` producer |
| `goals` | owner defaults | persisted goal-domain and model-tool config; `false` removes the goal stack and the TUI `/goal` producer |
| `persistenceRoot` | `./.sessions` | the JSONL backend's root directory |
| `welcome` | `ready.` | terminal banner / TUI subtitle |
| `ui` | `{ mode: 'auto' }` | terminal mode (`auto` / `readline` / `tui`) and nested TUI presentation config |

View File

@@ -101,7 +101,7 @@ export interface Config {
toolBash?: NonNullable<agentCore.Config['toolBash']>
/** Generic background-task controls forwarded through agent-core; set false to omit their tool surface. */
toolTasks?: NonNullable<agentCore.Config['toolTasks']>
/** Persisted same-session goals; owner defaults enable them, or false disables the stack and command. */
/** Persisted same-session goals; owner defaults enable them, or false disables the stack and TUI command. */
goals?: agentCore.GoalConfig | false
/**
* If set, the pre-created agent RESUMES this persisted session id instead of
@@ -152,7 +152,7 @@ export function composeTerminalApp(ctx: Context, config: Config, isTTY: boolean)
const goals = config.goals ?? {}
if (mode === 'readline') ctx.plugin(ConsoleExporter)
ctx.plugin(CommandService)
if (goals !== false) ctx.plugin(commandGoal)
if (mode === 'tui' && goals !== false) ctx.plugin(commandGoal)
ctx.plugin(SessionPersistenceJsonl, { root: config.persistenceRoot ?? DEFAULT_PERSISTENCE_ROOT })
ctx.plugin(UserInteractionService)
if (mode === 'tui') {

View File

@@ -118,17 +118,25 @@ describe('dsh-stdio-demo app', () => {
calls.length = 0
stdioAgent.composeTerminalApp(ctx, {
provider: 'mock', model: 'mock', workspaceContext: false, goals: false, ui: { mode: 'readline' },
provider: 'mock', model: 'mock', workspaceContext: false, goals: false, ui: { mode: 'tui' },
}, true)
expect(calls.map(call => call.name)).toContain('ui-tui')
expect(calls.map(call => call.name)).not.toContain('command-goal')
expect(calls.find(call => call.name === 'agent-spine-demo')?.config).toMatchObject({ goals: false })
calls.length = 0
stdioAgent.composeTerminalApp(ctx, {
provider: 'mock', model: 'mock', workspaceContext: false, ui: { mode: 'readline' },
}, false)
expect(calls.map(call => call.name)).toContain('ui-stdio')
expect(calls.map(call => call.name)).toContain('ConsoleExporter')
expect(calls.map(call => call.name)).not.toContain('ui-tui')
expect(calls.map(call => call.name)).not.toContain('command-goal')
expect(calls.find(call => call.name === 'agent-spine-demo')?.config).toMatchObject({ goals: false })
expect(calls.find(call => call.name === 'agent-spine-demo')?.config).toMatchObject({ goals: {} })
})
it('composes the spine + front-door cluster and pre-creates the main agent', async () => {
const ctx = await mount({ provider: 'mock', model: 'mock', persona: 'hi', persistenceRoot: '/tmp/dsh-stdio-demo-spec', skills: await isolatedSkillsConfig(), workspaceContext: false })
const ctx = await mount({ provider: 'mock', model: 'mock', persona: 'hi', persistenceRoot: '/tmp/dsh-stdio-demo-spec', skills: await isolatedSkillsConfig(), workspaceContext: false, ui: { mode: 'readline' } })
// The spine services (brought up by the agent-spine-demo bundle) are all present.
expect(ctx.get('agents')).toBeDefined()
expect(ctx.get('agentLoop')).toBeDefined()
@@ -145,7 +153,7 @@ describe('dsh-stdio-demo app', () => {
expect(agent?.id).toBe(agent?.session.id)
expect(agent?.id).toMatch(/^main-session-/)
expect(agent?.session.header.cwd).toBe(process.cwd())
expect(ctx.commands.find(agent!, 'tui', 'goal')).toBeDefined()
expect(ctx.commands.find(agent!, 'tui', 'goal')).toBeUndefined()
await ctx.fiber.dispose()
})