Merge remote-tracking branch 'origin/master' into worktree/remove-expose-internals
This commit is contained in:
@@ -5,6 +5,8 @@ import { CallId, LlmAdapter } from '@deepseek-ai/dsh-llm'
|
||||
const CONTROL_PROBE = '\u001b]2;MODEL_CONTROLLED\u0007\u001b[999CMODEL_CURSOR\u009b31mMODEL_C1'
|
||||
const INITIAL_TEXT = `I need one decision before I continue. ${CONTROL_PROBE}`
|
||||
const FINAL_TEXT = 'Decision received. Scripted TUI run complete.'
|
||||
const DEFAULT_MODE_PROBE = 'Confirm the scripted run left plan mode.'
|
||||
const DEFAULT_MODE_TEXT = 'Default mode confirmed.'
|
||||
// The `skill` scenario types `/skill:scripted-skill`; the manual-invocation front
|
||||
// door delivers the loaded skill as a user turn wrapped in `<skill name="…">`. The
|
||||
// body marker below lives in the fixture skill, so echoing it back proves the whole
|
||||
@@ -53,6 +55,13 @@ class ScriptedTuiAdapter extends LlmAdapter {
|
||||
.filter(block => block.type === 'text')
|
||||
.map(block => block.text)
|
||||
.join('\n')
|
||||
if (lastText.includes(DEFAULT_MODE_PROBE)) {
|
||||
if (options.system?.includes('Stay in plan mode for this scripted TUI test.')) {
|
||||
throw new Error('the scripted TUI request retained plan guidance after /plan off')
|
||||
}
|
||||
for (const chunk of textChunks(DEFAULT_MODE_TEXT)) yield chunk
|
||||
return
|
||||
}
|
||||
if (lastText.includes(SKILL_BLOCK_OPEN)) {
|
||||
const ack = lastText.includes(SKILL_BODY_MARKER)
|
||||
? SKILL_RECEIVED_TEXT
|
||||
|
||||
@@ -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=28 bufferRow=28
|
||||
cursor hidden column=1 viewportRow=33 bufferRow=33
|
||||
buffer
|
||||
0| " DEEPSEEK HARNESS"
|
||||
style 1-8 fg=bright-blue bold
|
||||
@@ -11,8 +11,8 @@ buffer
|
||||
2| " deepseek-v4-flash • main-session"
|
||||
style 1-34 dim
|
||||
3| <blank>
|
||||
4| " Entering plan mode (applies from the next step). "
|
||||
style 1-48 fg=bright-black
|
||||
4| " Entering plan mode (applies from the next step). Use /plan off to leave. "
|
||||
style 1-72 fg=bright-black
|
||||
5| <blank>
|
||||
6| "▌ "
|
||||
style 0-0 fg=bright-blue
|
||||
@@ -33,31 +33,38 @@ buffer
|
||||
style 1-9 fg=bright-magenta bold
|
||||
15| " ONE "
|
||||
16| <blank>
|
||||
17| "▌ "
|
||||
17| " Leaving plan mode (applies from the next step). "
|
||||
style 1-47 fg=bright-black
|
||||
18| <blank>
|
||||
19| " Context · plan-mode "
|
||||
style 1-19 dim
|
||||
20| " The user switched this session back to the default mode. "
|
||||
style 1-56 fg=bright-black
|
||||
21| <blank>
|
||||
22| "▌ "
|
||||
style 0-0 fg=bright-blue
|
||||
18| "▌ You "
|
||||
23| "▌ You "
|
||||
style 0-0 fg=bright-blue
|
||||
style 2-4 fg=bright-blue bold
|
||||
19| "▌ Reply with exactly the word: TWO. No tools. "
|
||||
24| "▌ Reply with exactly the word: TWO. No tools. "
|
||||
style 0-0 fg=bright-blue
|
||||
20| "▌ "
|
||||
25| "▌ "
|
||||
style 0-0 fg=bright-blue
|
||||
21| <blank>
|
||||
22| " Reasoning "
|
||||
26| <blank>
|
||||
27| " Reasoning "
|
||||
style 1-9 fg=bright-black italic
|
||||
23| " The user wants me to reply with exactly the word \"TWO\" and no tools. "
|
||||
28| " The user wants me to reply with exactly the word \"TWO\" and no tools. "
|
||||
style 1-68 fg=bright-black italic
|
||||
24| <blank>
|
||||
25| " Assistant "
|
||||
29| <blank>
|
||||
30| " Assistant "
|
||||
style 1-9 fg=bright-magenta bold
|
||||
26| " TWO "
|
||||
27| "────────────────────────────────────────────────────────────────────────────────────────────────────"
|
||||
31| " TWO "
|
||||
32| "────────────────────────────────────────────────────────────────────────────────────────────────────"
|
||||
style 0-99 dim
|
||||
28| " "
|
||||
33| " "
|
||||
style 1-1 inverse
|
||||
29| "────────────────────────────────────────────────────────────────────────────────────────────────────"
|
||||
34| "────────────────────────────────────────────────────────────────────────────────────────────────────"
|
||||
style 0-99 dim
|
||||
30| "deepseek-v4-flash /workspace/project ↑2.9k ↓41 cache 49% 3% co"
|
||||
35| "deepseek-v4-flash /workspace/project ↑2.9k ↓41 cache 49% 3% co"
|
||||
style 0-92 dim
|
||||
style 95-99 dim
|
||||
31-35| <blank>
|
||||
|
||||
@@ -87,14 +87,16 @@ describe('tui-agent keyless smoke (real Loader tree in a PTY)', () => {
|
||||
const output = await smoke({
|
||||
label: 'tui-agent boot',
|
||||
actions: [
|
||||
{ waitFor: 'main-session-', send: '/plan\r' },
|
||||
{ waitFor: 'Entering plan mode (applies from the next step).', send: '/exit\r' },
|
||||
{ waitFor: 'main-session-', send: '/plan' },
|
||||
{ waitFor: '[off|message] — Enter or leave plan mode', send: '\r' },
|
||||
{ waitFor: 'Entering plan mode (applies from the next step). Use /plan off to leave.', 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).')
|
||||
expect(output).toContain('[off|message] — Enter or leave plan mode')
|
||||
expect(output).toContain('Entering plan mode (applies from the next step). Use /plan off to leave.')
|
||||
// Borderless: no box-drawing frame around the banner.
|
||||
expect(output).not.toContain('╭')
|
||||
expect(output).not.toContain('╮')
|
||||
@@ -120,12 +122,16 @@ describe('tui-agent keyless smoke (real Loader tree in a PTY)', () => {
|
||||
// window title as `<session title> — <configured title>` via OSC 0.
|
||||
// Gating /status on it keeps the assertion race-free; the diagnostics
|
||||
// card is then exercised through the same real Loader/PTY composition.
|
||||
{ waitFor: 'scripted session title — DeepSeek Harness', send: '/status\r' },
|
||||
{ waitFor: 'scripted session title — DeepSeek Harness', send: '/plan off\r' },
|
||||
{ waitFor: 'Leaving plan mode (applies from the next step).', send: 'Confirm the scripted run left plan mode.\r' },
|
||||
{ waitFor: 'Default mode confirmed.', send: '/status\r' },
|
||||
{ waitFor: 'Session status', send: '/exit\r' },
|
||||
],
|
||||
})
|
||||
expect(output).toContain('I need one decision before I continue.')
|
||||
expect(output).toContain('Entering plan mode (applies from the next step).')
|
||||
expect(output).toContain('Entering plan mode (applies from the next step). Use /plan off to leave.')
|
||||
expect(output).toContain('Leaving plan mode (applies from the next step).')
|
||||
expect(output).toContain('Default mode confirmed.')
|
||||
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`)
|
||||
|
||||
@@ -47,6 +47,7 @@ interface Scenario {
|
||||
expectedEventCounts?: Record<string, number>
|
||||
childSessions?: number
|
||||
enterPlanMode?: boolean
|
||||
leavePlanModeAfterFirstTurn?: boolean
|
||||
recorded: boolean
|
||||
seedWorkspace?: boolean
|
||||
/**
|
||||
@@ -62,8 +63,9 @@ const SCENARIOS: Scenario[] = [
|
||||
name: 'multi-turn-conversation',
|
||||
composition: 'native',
|
||||
expectedTools: [],
|
||||
expectedEventCounts: { 'plan/mode': 1 },
|
||||
expectedEventCounts: { 'plan/mode': 2 },
|
||||
enterPlanMode: true,
|
||||
leavePlanModeAfterFirstTurn: true,
|
||||
recorded: true,
|
||||
},
|
||||
{
|
||||
@@ -294,6 +296,12 @@ async function runScenario(scenario: Scenario): Promise<ScenarioResult> {
|
||||
remainingPrompts = prompts.slice(1)
|
||||
}
|
||||
|
||||
if (scenario.leavePlanModeAfterFirstTurn === true) {
|
||||
terminal.send('/plan off')
|
||||
terminal.send('\r')
|
||||
await settleTerminal(terminal)
|
||||
}
|
||||
|
||||
for (const prompt of remainingPrompts) {
|
||||
terminal.send(prompt)
|
||||
terminal.send('\r')
|
||||
@@ -310,7 +318,9 @@ 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.planMode.get(agent)).toEqual({ active: true })
|
||||
expect(ctx.planMode.get(agent)).toEqual({
|
||||
active: scenario.leavePlanModeAfterFirstTurn !== true,
|
||||
})
|
||||
const planMode = events.find(event => event.type === 'plan/mode')
|
||||
if (planMode === undefined || firstHeader === undefined) {
|
||||
throw new Error('plan-mode command snapshot needs plan/mode before its first request/header')
|
||||
@@ -320,6 +330,20 @@ async function runScenario(scenario: Scenario): Promise<ScenarioResult> {
|
||||
const firstMessage = events.find(event => event.type === 'user/message')
|
||||
expect(firstMessage?.data.content).toEqual([{ type: 'text', text: prompts[0] }])
|
||||
}
|
||||
if (scenario.leavePlanModeAfterFirstTurn === true) {
|
||||
const planModes = events.filter(event => event.type === 'plan/mode')
|
||||
expect(planModes.map(event => event.data.active)).toEqual([true, false])
|
||||
const headers = events.filter(event => event.type === 'request/header')
|
||||
const exit = planModes[1]
|
||||
const afterExit = headers[1]
|
||||
if (exit === undefined || afterExit === undefined) {
|
||||
throw new Error('active plan exit snapshot needs a committed exit and changed request header')
|
||||
}
|
||||
expect(exit.seq).toBeLessThan(afterExit.seq)
|
||||
expect(afterExit.data.header.system).not.toContain('Snapshot plan mode instructions.')
|
||||
expect(events.filter(event => event.type === 'context/message').map(event => event.data.content))
|
||||
.toContainEqual([{ type: 'text', text: 'The user switched this session back to the default mode.' }])
|
||||
}
|
||||
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