refactor(plan): remove generic mode abstraction

This commit is contained in:
Tianyi Cui
2026-07-22 16:57:23 +08:00
parent 92da23270d
commit f4185122dc
61 changed files with 990 additions and 1161 deletions

View File

@@ -1,6 +1,6 @@
# tui-agent
The full-screen interactive coding agent: DeepSeek V4, local bash and filesystem tools, compaction, subagents, workflows and fresh-agent Ralph iteration, `todo_write`, session modes (`/plan` enters plan mode, which has a reviewed exit), timeout/spill policy, and [`@deepseek-ai/dsh-tui-demo`](../../packages/examples/tui-demo).
The full-screen interactive coding agent: DeepSeek V4, local bash and filesystem tools, compaction, subagents, workflows and fresh-agent Ralph iteration, `todo_write`, plan mode (`/plan` enters and `exit_plan_mode` reviews the exit), timeout/spill policy, and [`@deepseek-ai/dsh-tui-demo`](../../packages/examples/tui-demo).
## Run it

View File

@@ -47,8 +47,8 @@ flowchart LR
cfg --> plugin_tui_tool_ralph
plugin_tui_tool_todo["tool-todo<br/>@deepseek-ai/dsh-tool-todo"]
cfg --> plugin_tui_tool_todo
plugin_tui_mode["mode<br/>@deepseek-ai/dsh-mode"]
cfg --> plugin_tui_mode
plugin_tui_plan_mode["plan-mode<br/>@deepseek-ai/dsh-plan-mode"]
cfg --> plugin_tui_plan_mode
plugin_tui_fs_local["fs-local<br/>@deepseek-ai/dsh-fs-local"]
cfg --> plugin_tui_fs_local
plugin_tui_fs_policy["fs-policy<br/>@deepseek-ai/dsh-fs-policy"]
@@ -83,7 +83,7 @@ flowchart LR
| `tool-workflow` | `@deepseek-ai/dsh-tool-workflow` |
| `tool-ralph` | `@deepseek-ai/dsh-tool-ralph` |
| `tool-todo` | `@deepseek-ai/dsh-tool-todo` |
| `mode` | `@deepseek-ai/dsh-mode` |
| `plan-mode` | `@deepseek-ai/dsh-plan-mode` |
| `fs-local` | `@deepseek-ai/dsh-fs-local` |
| `fs-policy` | `@deepseek-ai/dsh-fs-policy` |
| `tool-fs` | `@deepseek-ai/dsh-tool-fs` |

View File

@@ -88,15 +88,12 @@
- id: tool-todo
name: '@deepseek-ai/dsh-tool-todo'
# Session modes (the shipped `plan` definition): the TUI's command registry
# gains the plugin-registered /plan [message] command, and the exit review
# rides the TUI's user-interaction provider.
- id: mode
name: '@deepseek-ai/dsh-mode'
# Plan mode gives the TUI a plugin-owned /plan [message] command; the exit
# review rides the TUI's user-interaction provider.
- id: plan-mode
name: '@deepseek-ai/dsh-plan-mode'
config:
modes:
plan:
section: |
section: |
You are in plan mode. Stay in plan mode until exit_plan_mode succeeds or the user switches the session mode. Imperative language to implement changes means plan the implementation, not execute it. A user's conversational agreement — including an answer confirming something you asked — approves nothing and does not end plan mode; fold the confirmed decision into the plan and submit it through exit_plan_mode.
Explore first. Use non-mutating reads, searches, static analysis, and checks to ground the plan in the actual repository. Do not edit or write files, change configuration, run formatters or code generation that rewrites tracked files, commit, or otherwise carry out the plan. Prefer existing functions and patterns over new machinery.

View File

@@ -15,12 +15,10 @@
- id: token-meter
name: '@deepseek-ai/dsh-token-meter'
- id: mode
name: '@deepseek-ai/dsh-mode'
- id: plan-mode
name: '@deepseek-ai/dsh-plan-mode'
config:
modes:
plan:
section: 'Stay in plan mode for this scripted TUI test.'
section: 'Stay in plan mode for this scripted TUI test.'
- id: tui-agent
name: '@deepseek-ai/dsh-tui-demo'

View File

@@ -15,7 +15,7 @@ import * as FsPolicy from '@deepseek-ai/dsh-fs-policy'
import * as ToolFs from '@deepseek-ai/dsh-tool-fs'
import * as LlmDeepSeek from '@deepseek-ai/dsh-llm-deepseek'
import { installLlmReplay, parseSessionLog } from '@deepseek-ai/dsh-llm-replay'
import ModesService, { PLAN_MODE } from '@deepseek-ai/dsh-mode'
import PlanModeService from '@deepseek-ai/dsh-plan-mode'
import TokenMeterService from '@deepseek-ai/dsh-token-meter'
import type { Session, SessionEvent } from '@deepseek-ai/dsh-session'
import { SessionId } from '@deepseek-ai/dsh-session'
@@ -56,7 +56,7 @@ const SCENARIOS: Scenario[] = [
name: 'multi-turn-conversation',
composition: 'native',
expectedTools: [],
expectedEventCounts: { 'mode/set': 1 },
expectedEventCounts: { 'plan/mode': 1 },
enterPlanMode: true,
recorded: true,
},
@@ -209,7 +209,7 @@ async function mountScenarioContext(
await ctx.plugin(ToolRalph)
await ctx.plugin(CommandService)
if (scenario.enterPlanMode === true) {
await ctx.plugin(ModesService, { modes: { plan: { section: 'Snapshot plan mode instructions.' } } })
await ctx.plugin(PlanModeService, { section: 'Snapshot plan mode instructions.' })
}
if (scenario.composition === 'code' || scenario.composition === 'advanced') {
await ctx.plugin(WorkerCodeRuntime, {})
@@ -298,13 +298,13 @@ async function runScenario(scenario: Scenario): Promise<ScenarioResult> {
expect(events.filter(event => event.type === type), `${scenario.name} must emit ${type}`).toHaveLength(count)
}
if (scenario.enterPlanMode === true) {
expect(ctx.modes.get(agent)).toEqual({ current: PLAN_MODE })
const modeSet = events.find(event => event.type === 'mode/set')
expect(ctx.planMode.get(agent)).toEqual({ active: true })
const planMode = events.find(event => event.type === 'plan/mode')
const firstHeader = events.find(event => event.type === 'request/header')
if (modeSet === undefined || firstHeader === undefined) {
throw new Error('plan-mode command snapshot needs a mode/set before its first request/header')
if (planMode === undefined || firstHeader === undefined) {
throw new Error('plan-mode command snapshot needs plan/mode before its first request/header')
}
expect(modeSet.seq).toBeLessThan(firstHeader.seq)
expect(planMode.seq).toBeLessThan(firstHeader.seq)
expect(firstHeader.data.header.system).toContain('Snapshot plan mode instructions.')
const firstMessage = events.find(event => event.type === 'user/message')
expect(firstMessage?.data.content).toEqual([{ type: 'text', text: prompts[0] }])