Merge remote-tracking branch 'origin/master' into worktree/pr504-retarget-latest-master
# Conflicts: # docs/event-producer-consumer.md # examples/tui-agent/README.md # examples/tui-agent/composition.md # examples/tui-agent/cordis.yml # examples/tui-agent/tests/snapshots/multi-turn-conversation/terminal.expected.txt # examples/tui-agent/tests/tui-keyless-smoke.e2e.ts # packages/ui/tui/README.md # vitest.config.ts
This commit is contained in:
@@ -15,6 +15,11 @@
|
||||
- id: token-meter
|
||||
name: '@deepseek-ai/dsh-token-meter'
|
||||
|
||||
- id: plan-mode
|
||||
name: '@deepseek-ai/dsh-plan-mode'
|
||||
config:
|
||||
section: 'Stay in plan mode for this scripted TUI test.'
|
||||
|
||||
- id: tui-agent
|
||||
name: '@deepseek-ai/dsh-tui-demo'
|
||||
config:
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
terminal 100x36 buffer=normal length=36 base=0 viewport=0
|
||||
lifecycle started=1 stopped=0 progress=inactive
|
||||
title "Reply with exactly the word: — DSH TUI snapshot"
|
||||
cursor hidden column=1 viewportRow=26 bufferRow=26
|
||||
cursor hidden column=1 viewportRow=28 bufferRow=28
|
||||
buffer
|
||||
0| " DEEPSEEK HARNESS"
|
||||
style 1-8 fg=bright-blue bold
|
||||
@@ -11,50 +11,53 @@ buffer
|
||||
2| " deepseek-v4-flash • main-session"
|
||||
style 1-34 dim
|
||||
3| <blank>
|
||||
4| "▌ "
|
||||
4| " Entering plan mode (applies from the next step). "
|
||||
style 1-48 fg=bright-black
|
||||
5| <blank>
|
||||
6| "▌ "
|
||||
style 0-0 fg=bright-blue
|
||||
5| "▌ You "
|
||||
7| "▌ You "
|
||||
style 0-0 fg=bright-blue
|
||||
style 2-4 fg=bright-blue bold
|
||||
6| "▌ Reply with exactly the word: ONE. No tools. "
|
||||
8| "▌ Reply with exactly the word: ONE. No tools. "
|
||||
style 0-0 fg=bright-blue
|
||||
7| "▌ "
|
||||
9| "▌ "
|
||||
style 0-0 fg=bright-blue
|
||||
8| <blank>
|
||||
9| " Reasoning "
|
||||
10| <blank>
|
||||
11| " Reasoning "
|
||||
style 1-9 fg=bright-black italic
|
||||
10| " The user wants me to reply with exactly the word \"ONE\" and use no tools. "
|
||||
12| " The user wants me to reply with exactly the word \"ONE\" and use no tools. "
|
||||
style 1-72 fg=bright-black italic
|
||||
11| <blank>
|
||||
12| " Assistant "
|
||||
13| <blank>
|
||||
14| " Assistant "
|
||||
style 1-9 fg=bright-magenta bold
|
||||
13| " ONE "
|
||||
14| <blank>
|
||||
15| "▌ "
|
||||
15| " ONE "
|
||||
16| <blank>
|
||||
17| "▌ "
|
||||
style 0-0 fg=bright-blue
|
||||
16| "▌ You "
|
||||
18| "▌ You "
|
||||
style 0-0 fg=bright-blue
|
||||
style 2-4 fg=bright-blue bold
|
||||
17| "▌ Reply with exactly the word: TWO. No tools. "
|
||||
19| "▌ Reply with exactly the word: TWO. No tools. "
|
||||
style 0-0 fg=bright-blue
|
||||
18| "▌ "
|
||||
20| "▌ "
|
||||
style 0-0 fg=bright-blue
|
||||
19| <blank>
|
||||
20| " Reasoning "
|
||||
21| <blank>
|
||||
22| " Reasoning "
|
||||
style 1-9 fg=bright-black italic
|
||||
21| " The user wants me to reply with exactly the word \"TWO\" and no tools. "
|
||||
23| " The user wants me to reply with exactly the word \"TWO\" and no tools. "
|
||||
style 1-68 fg=bright-black italic
|
||||
22| <blank>
|
||||
23| " Assistant "
|
||||
24| <blank>
|
||||
25| " Assistant "
|
||||
style 1-9 fg=bright-magenta bold
|
||||
24| " TWO "
|
||||
25| "────────────────────────────────────────────────────────────────────────────────────────────────────"
|
||||
style 0-99 dim
|
||||
26| " "
|
||||
style 1-1 inverse
|
||||
26| " TWO "
|
||||
27| "────────────────────────────────────────────────────────────────────────────────────────────────────"
|
||||
style 0-99 dim
|
||||
28| "deepseek-v4-flash /workspace/project ↑2.9k ↓41 cache 49% 3% co"
|
||||
28| " "
|
||||
style 1-1 inverse
|
||||
29| "────────────────────────────────────────────────────────────────────────────────────────────────────"
|
||||
style 0-99 dim
|
||||
30| "deepseek-v4-flash /workspace/project ↑2.9k ↓41 cache 49% 3% co"
|
||||
style 0-92 dim
|
||||
style 95-99 dim
|
||||
29-35| <blank>
|
||||
31-35| <blank>
|
||||
|
||||
@@ -70,17 +70,21 @@ const SELECT_PRO_MODEL = [
|
||||
] as const
|
||||
|
||||
describe('tui-agent keyless smoke (real Loader tree in a PTY)', () => {
|
||||
it('boots pi-tui, sweeps the borderless banner in, accepts /exit, and restores the terminal', async () => {
|
||||
it('boots pi-tui, sweeps the borderless banner in, enters plan mode, and restores the terminal', async () => {
|
||||
// With no configured welcome the borderless banner sweeps in left-to-right;
|
||||
// the detail line's session id (`main-session-<uuid>`) renders only once
|
||||
// the sweep reaches it, so it marks a settled banner.
|
||||
const output = await smoke({
|
||||
label: 'tui-agent boot',
|
||||
actions: [{ waitFor: 'main-session-', send: '/exit\r' }],
|
||||
actions: [
|
||||
{ waitFor: 'main-session-', send: '/plan\r' },
|
||||
{ waitFor: 'Entering plan mode (applies from the next step).', send: '/exit\r' },
|
||||
],
|
||||
})
|
||||
expect(output).toContain('DEEPSEEK')
|
||||
expect(output).toContain('HARNESS')
|
||||
expect(output).toContain('main-session-')
|
||||
expect(output).toContain('Entering plan mode (applies from the next step).')
|
||||
// Borderless: no box-drawing frame around the banner.
|
||||
expect(output).not.toContain('╭')
|
||||
expect(output).not.toContain('╮')
|
||||
@@ -94,7 +98,7 @@ describe('tui-agent keyless smoke (real Loader tree in a PTY)', () => {
|
||||
configPath: scriptedConfigPath,
|
||||
actions: [
|
||||
...SELECT_PRO_MODEL,
|
||||
{ waitFor: 'Model selected: tui-scripted/tui-scripted-model-pro.', send: 'exercise the TUI\r' },
|
||||
{ waitFor: 'Model selected: tui-scripted/tui-scripted-model-pro.', send: '/plan exercise the TUI\r' },
|
||||
{ waitFor: 'How should the scripted run proceed?', send: '\r' },
|
||||
{ waitFor: 'Decision received. Scripted TUI run complete.', send: '' },
|
||||
// Session title: the first user message drives the first-message-llm
|
||||
@@ -108,6 +112,7 @@ describe('tui-agent keyless smoke (real Loader tree in a PTY)', () => {
|
||||
],
|
||||
})
|
||||
expect(output).toContain('I need one decision before I continue.')
|
||||
expect(output).toContain('Entering plan mode (applies from the next step).')
|
||||
expect(output).toContain(String.raw`\x1b]2;MODEL_CONTROLLED\x07`)
|
||||
expect(output).toContain(String.raw`\x1b[999CMODEL_CURSOR`)
|
||||
expect(output).toContain(String.raw`\x9b31mMODEL_C1`)
|
||||
|
||||
@@ -15,6 +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 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'
|
||||
@@ -45,6 +46,7 @@ interface Scenario {
|
||||
expectedTools: string[]
|
||||
expectedEventCounts?: Record<string, number>
|
||||
childSessions?: number
|
||||
enterPlanMode?: boolean
|
||||
recorded: boolean
|
||||
seedWorkspace?: boolean
|
||||
/**
|
||||
@@ -60,6 +62,8 @@ const SCENARIOS: Scenario[] = [
|
||||
name: 'multi-turn-conversation',
|
||||
composition: 'native',
|
||||
expectedTools: [],
|
||||
expectedEventCounts: { 'plan/mode': 1 },
|
||||
enterPlanMode: true,
|
||||
recorded: true,
|
||||
},
|
||||
{
|
||||
@@ -213,6 +217,9 @@ async function mountScenarioContext(
|
||||
await ctx.plugin(ToolWorkflow)
|
||||
await ctx.plugin(ToolRalph)
|
||||
await ctx.plugin(CommandService)
|
||||
if (scenario.enterPlanMode === true) {
|
||||
await ctx.plugin(PlanModeService, { section: 'Snapshot plan mode instructions.' })
|
||||
}
|
||||
if (scenario.composition === 'code' || scenario.composition === 'advanced') {
|
||||
await ctx.plugin(WorkerCodeRuntime, {})
|
||||
}
|
||||
@@ -277,7 +284,17 @@ async function runScenario(scenario: Scenario): Promise<ScenarioResult> {
|
||||
})
|
||||
await settleTerminal(terminal)
|
||||
|
||||
for (const prompt of prompts) {
|
||||
let remainingPrompts = prompts
|
||||
if (scenario.enterPlanMode === true) {
|
||||
const firstPrompt = prompts[0]!
|
||||
terminal.send(`/plan ${firstPrompt}`)
|
||||
terminal.send('\r')
|
||||
await agent.whenIdle()
|
||||
await settleTerminal(terminal)
|
||||
remainingPrompts = prompts.slice(1)
|
||||
}
|
||||
|
||||
for (const prompt of remainingPrompts) {
|
||||
terminal.send(prompt)
|
||||
terminal.send('\r')
|
||||
await agent.whenIdle()
|
||||
@@ -289,6 +306,18 @@ async function runScenario(scenario: Scenario): Promise<ScenarioResult> {
|
||||
for (const [type, count] of Object.entries(scenario.expectedEventCounts ?? {})) {
|
||||
expect(events.filter(event => event.type === type), `${scenario.name} must emit ${type}`).toHaveLength(count)
|
||||
}
|
||||
if (scenario.enterPlanMode === true) {
|
||||
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 (planMode === undefined || firstHeader === undefined) {
|
||||
throw new Error('plan-mode command snapshot needs plan/mode before its first request/header')
|
||||
}
|
||||
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] }])
|
||||
}
|
||||
expect(events.filter(event => event.type === 'tool/result').every(event => !event.data.isError)).toBe(true)
|
||||
expect(events.filter(event => event.type === 'turn/end').every(event => event.data.reason.kind !== 'error')).toBe(true)
|
||||
if (scenario.name === 'dynamic-workflow' || scenario.name === 'cordis-dynamic-toolchain') {
|
||||
|
||||
Reference in New Issue
Block a user