Merge branch 'worktree-llm-dynamic-config' into worktree-llm-web-config
# Conflicts: # apps/cli/cordis.yml # apps/cli/package.json # apps/cli/tests/tui-keyless-smoke.e2e.ts # apps/web/tests/details-session-lifecycle.e2e.ts # apps/web/tests/snapshots/code-mode-round/ui.expected.md # apps/web/tests/snapshots/cordis-tool-round/ui.expected.md # apps/web/tests/snapshots/fresh-round-trip/ui.expected.md # apps/web/tests/snapshots/lifecycle-chrome/hero.expected.md # apps/web/tests/snapshots/lifecycle-chrome/reloaded.expected.md # apps/web/tests/snapshots/live-interactions/cancel.expected.md # apps/web/tests/snapshots/live-interactions/error-auth.expected.md # apps/web/tests/snapshots/live-interactions/retry.expected.md # apps/web/tests/snapshots/message-actions/ui.expected.md # apps/web/tests/snapshots/question-composer/answered.expected.md # apps/web/tests/snapshots/seeded-history/ui.expected.md # apps/web/tests/snapshots/steering/mid-steer.expected.md # apps/web/tests/snapshots/steering/settled.expected.md # docs/cordis-catalog/events.md # docs/cordis-catalog/services.md # docs/event-producer-consumer.md # docs/user/guide/config.i18n.yaml # docs/user/guide/config.md # docs/user/guide/config.zh.md # docs/user/guide/index.i18n.yaml # docs/user/guide/index.md # docs/user/guide/index.zh.md # examples/acp-agent/tests/snapshots/subagent-fork/session.1.jsonl # examples/acp-agent/tests/snapshots/subagent-mixed/session.2.jsonl # examples/cordis-agent/cordis.yml # examples/cordis-agent/tests/cordis-tools.e2e.ts # examples/headless-agent/tests/semantic-checkpoint-snapshots/tool-outcome-unknown/session.expected.jsonl # examples/headless-agent/tests/subagent-inheritance-snapshots/parent-override/parent.expected.jsonl # examples/tui-agent/code-mode.cordis.yml # examples/tui-agent/cordis.yml # packages/examples/tui-demo/README.md # packages/examples/tui-demo/README.zh.md # packages/host/apiproxy/README.i18n.yaml # packages/pty/tool-bash-persistent/README.i18n.yaml # packages/ui/tui/tests/snapshots/status-diagnostics-narrow.expected.txt # packages/ui/tui/tests/snapshots/status-diagnostics.expected.txt # pnpm-lock.yaml # scripts/snapshots/python-sdk-single-exe/advanced/result.json # scripts/snapshots/python-sdk-single-exe/advanced/session.1.jsonl # scripts/snapshots/python-sdk-single-exe/advanced/session.2.jsonl # scripts/snapshots/python-sdk-single-exe/advanced/session.jsonl
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
terminal 56x36 buffer=normal length=45 base=9 viewport=9
|
||||
terminal 56x36 buffer=normal length=44 base=8 viewport=8
|
||||
lifecycle started=1 stopped=0 progress=inactive
|
||||
title "Inspect session diagnostics — DSH snapshot"
|
||||
cursor hidden column=7 viewportRow=35 bufferRow=44
|
||||
cursor hidden column=7 viewportRow=35 bufferRow=43
|
||||
buffer
|
||||
0| " DEEPSEEK HARNESS"
|
||||
style 1-8 fg=bright-magenta bold
|
||||
@@ -37,87 +37,84 @@ buffer
|
||||
style 0-0 dim
|
||||
style 3-12 dim
|
||||
style 55-55 dim
|
||||
15| "│ Model: deepseek-official/deepseek-v4-pro │"
|
||||
15| "│ Model: deepseek/deepseek-v4-pro (effort │"
|
||||
style 0-0 dim
|
||||
style 3-12 dim
|
||||
style 40-55 dim
|
||||
16| "│ default; reasoning blocks shown) │"
|
||||
style 0-0 dim
|
||||
style 15-46 dim
|
||||
style 55-55 dim
|
||||
17| "│ │"
|
||||
style 0-0 dim
|
||||
style 55-55 dim
|
||||
18| "│ Agent: idle · 8 events · 1 turn · 1 step · 1 │"
|
||||
style 0-0 dim
|
||||
style 3-12 dim
|
||||
style 55-55 dim
|
||||
16| "│ (effort default; reasoning blocks │"
|
||||
style 0-0 dim
|
||||
style 15-55 dim
|
||||
17| "│ shown) │"
|
||||
style 0-0 dim
|
||||
style 15-20 dim
|
||||
style 55-55 dim
|
||||
18| "│ │"
|
||||
19| "│ tool call │"
|
||||
style 0-0 dim
|
||||
style 55-55 dim
|
||||
19| "│ Agent: idle · 7 events · 1 turn · 1 step · 1 │"
|
||||
20| "│ │"
|
||||
style 0-0 dim
|
||||
style 55-55 dim
|
||||
21| "│ Tokens: 1,250 input + 340 output │"
|
||||
style 0-0 dim
|
||||
style 3-12 dim
|
||||
style 55-55 dim
|
||||
20| "│ tool call │"
|
||||
style 0-0 dim
|
||||
style 55-55 dim
|
||||
21| "│ │"
|
||||
style 0-0 dim
|
||||
style 55-55 dim
|
||||
22| "│ Tokens: 1,250 input + 340 output │"
|
||||
style 0-0 dim
|
||||
style 3-12 dim
|
||||
style 55-55 dim
|
||||
23| "│ KV cache: [███████████░░░░░] 67% hit (3,000 read │"
|
||||
22| "│ KV cache: [███████████░░░░░] 67% hit (3,000 read │"
|
||||
style 0-0 dim
|
||||
style 3-12 dim
|
||||
style 15-15 dim
|
||||
style 16-26 fg=bright-magenta
|
||||
style 27-32 dim
|
||||
style 55-55 dim
|
||||
24| "│ + 250 write) │"
|
||||
23| "│ + 250 write) │"
|
||||
style 0-0 dim
|
||||
style 55-55 dim
|
||||
25| "│ Context: [█████░░░░░░░░░░░] 33% used (42,000 / │"
|
||||
24| "│ Context: [█████░░░░░░░░░░░] 33% used (42,000 / │"
|
||||
style 0-0 dim
|
||||
style 3-12 dim
|
||||
style 15-15 dim
|
||||
style 16-20 fg=bright-magenta
|
||||
style 21-32 dim
|
||||
style 55-55 dim
|
||||
26| "│ 128,000) │"
|
||||
25| "│ 128,000) │"
|
||||
style 0-0 dim
|
||||
style 55-55 dim
|
||||
27| "│ │"
|
||||
26| "│ │"
|
||||
style 0-0 dim
|
||||
style 55-55 dim
|
||||
28| "│ Created: 2026-07-22 09:10:11 UTC │"
|
||||
27| "│ Created: 2026-07-22 09:10:11 UTC │"
|
||||
style 0-0 dim
|
||||
style 3-12 dim
|
||||
style 55-55 dim
|
||||
29| "│ Active: 2026-07-22 09:10:11 UTC │"
|
||||
28| "│ Active: 2026-07-22 09:10:11 UTC │"
|
||||
style 0-0 dim
|
||||
style 3-12 dim
|
||||
style 55-55 dim
|
||||
30| "╰──────────────────────────────────────────────────────╯"
|
||||
29| "╰──────────────────────────────────────────────────────╯"
|
||||
style 0-55 dim
|
||||
31| <blank>
|
||||
32| "System prompt "
|
||||
30| <blank>
|
||||
31| "System prompt "
|
||||
style 0-12 fg=bright-magenta bold
|
||||
33| "You are an AI agent powered by the DeepSeek Harness SDK."
|
||||
34| " "
|
||||
35| "Paths prefixed with @ are files explicitly referenced by"
|
||||
36| "the user. Use the read tool when their contents are "
|
||||
37| "needed; do not claim to have inspected a file before "
|
||||
38| "reading it. "
|
||||
39| <blank>
|
||||
40| "Registered tools "
|
||||
32| "You are an AI agent powered by the DeepSeek Harness SDK."
|
||||
33| " "
|
||||
34| "Paths prefixed with @ are files explicitly referenced by"
|
||||
35| "the user. Use the read tool when their contents are "
|
||||
36| "needed; do not claim to have inspected a file before "
|
||||
37| "reading it. "
|
||||
38| <blank>
|
||||
39| "Registered tools "
|
||||
style 0-15 fg=bright-magenta bold
|
||||
41| "read, write "
|
||||
42| <blank>
|
||||
43| "/workspace/project (tui-staging) deepseek-v4-pro ↑1.3k"
|
||||
40| "read, write "
|
||||
41| <blank>
|
||||
42| "/workspace/project (tui-staging) deepseek-v4-pro ↑1.3k"
|
||||
style 0-17 fg=bright-magenta bold
|
||||
style 18-31 dim
|
||||
style 34-48 dim
|
||||
style 51-55 dim
|
||||
44| " dsh > "
|
||||
43| " dsh > "
|
||||
style 1-3 fg=bright-magenta bold
|
||||
style 5-6 dim
|
||||
style 7-7 inverse
|
||||
|
||||
@@ -21,68 +21,68 @@ buffer
|
||||
style 0-2 fg=bright-magenta bold underline
|
||||
9| "inspect this session "
|
||||
10| <blank>
|
||||
11| "╭─ Session status ────────────────────────────────────────────────────────────────────────╮"
|
||||
11| "╭─ Session status ───────────────────────────────────────────────────────────────╮"
|
||||
style 0-2 dim
|
||||
style 3-16 fg=bright-magenta bold
|
||||
style 17-90 dim
|
||||
12| "│ Session: main-session │"
|
||||
style 17-81 dim
|
||||
12| "│ Session: main-session │"
|
||||
style 0-0 dim
|
||||
style 3-12 dim
|
||||
style 90-90 dim
|
||||
13| "│ Title: Inspect session diagnostics │"
|
||||
style 81-81 dim
|
||||
13| "│ Title: Inspect session diagnostics │"
|
||||
style 0-0 dim
|
||||
style 3-12 dim
|
||||
style 90-90 dim
|
||||
14| "│ Directory: /workspace/project │"
|
||||
style 81-81 dim
|
||||
14| "│ Directory: /workspace/project │"
|
||||
style 0-0 dim
|
||||
style 3-12 dim
|
||||
style 90-90 dim
|
||||
15| "│ Model: deepseek-official/deepseek-v4-pro (effort default; reasoning blocks shown) │"
|
||||
style 81-81 dim
|
||||
15| "│ Model: deepseek/deepseek-v4-pro (effort default; reasoning blocks shown) │"
|
||||
style 0-0 dim
|
||||
style 3-12 dim
|
||||
style 49-88 dim
|
||||
style 90-90 dim
|
||||
16| "│ │"
|
||||
style 40-79 dim
|
||||
style 81-81 dim
|
||||
16| "│ │"
|
||||
style 0-0 dim
|
||||
style 90-90 dim
|
||||
17| "│ Agent: idle · 7 events · 1 turn · 1 step · 1 tool call │"
|
||||
style 81-81 dim
|
||||
17| "│ Agent: idle · 8 events · 1 turn · 1 step · 1 tool call │"
|
||||
style 0-0 dim
|
||||
style 3-12 dim
|
||||
style 90-90 dim
|
||||
18| "│ │"
|
||||
style 81-81 dim
|
||||
18| "│ │"
|
||||
style 0-0 dim
|
||||
style 90-90 dim
|
||||
19| "│ Tokens: 1,250 input + 340 output │"
|
||||
style 81-81 dim
|
||||
19| "│ Tokens: 1,250 input + 340 output │"
|
||||
style 0-0 dim
|
||||
style 3-12 dim
|
||||
style 90-90 dim
|
||||
20| "│ KV cache: [███████████░░░░░] 67% hit (3,000 read + 250 write) │"
|
||||
style 81-81 dim
|
||||
20| "│ KV cache: [███████████░░░░░] 67% hit (3,000 read + 250 write) │"
|
||||
style 0-0 dim
|
||||
style 3-12 dim
|
||||
style 15-15 dim
|
||||
style 16-26 fg=bright-magenta
|
||||
style 27-32 dim
|
||||
style 90-90 dim
|
||||
21| "│ Context: [█████░░░░░░░░░░░] 33% used (42,000 / 128,000) │"
|
||||
style 81-81 dim
|
||||
21| "│ Context: [█████░░░░░░░░░░░] 33% used (42,000 / 128,000) │"
|
||||
style 0-0 dim
|
||||
style 3-12 dim
|
||||
style 15-15 dim
|
||||
style 16-20 fg=bright-magenta
|
||||
style 21-32 dim
|
||||
style 90-90 dim
|
||||
22| "│ │"
|
||||
style 81-81 dim
|
||||
22| "│ │"
|
||||
style 0-0 dim
|
||||
style 90-90 dim
|
||||
23| "│ Created: 2026-07-22 09:10:11 UTC │"
|
||||
style 81-81 dim
|
||||
23| "│ Created: 2026-07-22 09:10:11 UTC │"
|
||||
style 0-0 dim
|
||||
style 3-12 dim
|
||||
style 90-90 dim
|
||||
24| "│ Active: 2026-07-22 09:10:11 UTC │"
|
||||
style 81-81 dim
|
||||
24| "│ Active: 2026-07-22 09:10:11 UTC │"
|
||||
style 0-0 dim
|
||||
style 3-12 dim
|
||||
style 90-90 dim
|
||||
25| "╰─────────────────────────────────────────────────────────────────────────────────────────╯"
|
||||
style 0-90 dim
|
||||
style 81-81 dim
|
||||
25| "╰────────────────────────────────────────────────────────────────────────────────╯"
|
||||
style 0-81 dim
|
||||
26| <blank>
|
||||
27| "System prompt "
|
||||
style 0-12 fg=bright-magenta bold
|
||||
|
||||
@@ -837,6 +837,9 @@ describe('TUI terminal-state snapshots', () => {
|
||||
{ type: 'step/end', seq: 5, time: Date.parse(`${day}T00:00:06Z`), data: { turn: 1, step: 1 } },
|
||||
{ type: 'turn/end', seq: 6, time: Date.parse(`${day}T00:00:07Z`), data: { turn: 1, reason: { kind: 'completed' } } },
|
||||
{ type: 'session/title', seq: 7, time: Date.parse(`${day}T00:00:08Z`), data: { title, messageSeqs: [1], source: { kind: 'fallback' } } },
|
||||
// A prior pickup, dated well after the work: the picker must still
|
||||
// show the work's date, not the pickup's.
|
||||
{ type: 'session/end-seed', seq: 8, time: Date.parse('2026-07-23T07:59:00.000Z'), data: {} },
|
||||
],
|
||||
})
|
||||
const harness = await setupSnapshot({
|
||||
@@ -906,6 +909,12 @@ describe('TUI terminal-state snapshots', () => {
|
||||
messageSeqs: [1],
|
||||
source: { kind: 'fallback' },
|
||||
})
|
||||
// Renders over a boundary-bearing log. It cannot pin the exclusion:
|
||||
// `/status` appends its own `command/run` first, so the boundary is
|
||||
// never the tail here. The other two call sites pin it.
|
||||
dateNow.mockReturnValue(Date.parse('2026-07-22T10:10:11.000Z'))
|
||||
session.append('session/end-seed', {})
|
||||
dateNow.mockReturnValue(Date.parse('2026-07-22T09:10:11.000Z'))
|
||||
},
|
||||
}, { columns: 92, rows: 32 })
|
||||
await renderAfter(harness, () => {
|
||||
|
||||
@@ -29,6 +29,7 @@ import SessionReferenceService, { formatSessionReferenceMention } from '@deepsee
|
||||
import type {} from '@deepseek-ai/dsh-llm-retry'
|
||||
import {
|
||||
createTuiChat,
|
||||
disposeRootAndExit,
|
||||
FILE_REFERENCE_PROMPT,
|
||||
mountTui,
|
||||
renderSkillInvocation,
|
||||
@@ -502,6 +503,41 @@ describe('goodbye message and /resume', () => {
|
||||
await dispose(result)
|
||||
})
|
||||
|
||||
it('allows a transient session-query state but rejects a terminal state', async () => {
|
||||
let queryCtx: Context | undefined
|
||||
let listCalls = 0
|
||||
const result = await setup({
|
||||
cwd: '/workspace',
|
||||
async configureContext(ctx) {
|
||||
await ctx.plugin({
|
||||
apply(child: Context) {
|
||||
queryCtx = child
|
||||
child.provide('sessionQuery', {
|
||||
listSessions: async () => { listCalls++; return [] },
|
||||
} as never)
|
||||
},
|
||||
})
|
||||
},
|
||||
})
|
||||
if (queryCtx === undefined) throw new Error('query provider did not mount')
|
||||
const activeState = queryCtx.fiber.state
|
||||
queryCtx.fiber.state = 0
|
||||
result.terminal.send('/resume')
|
||||
result.terminal.send('\r')
|
||||
await tick(); await tick()
|
||||
expect(listCalls).toBe(1)
|
||||
result.terminal.send('\u001B')
|
||||
await tick()
|
||||
queryCtx.fiber.state = 5
|
||||
result.terminal.send('/resume')
|
||||
result.terminal.send('\r')
|
||||
await tick()
|
||||
expect(result.terminal.output).toContain('session query is not mounted')
|
||||
expect(listCalls).toBe(1)
|
||||
queryCtx.fiber.state = activeState
|
||||
await dispose(result)
|
||||
})
|
||||
|
||||
it('keeps persisted query records readable without a persistence service', async () => {
|
||||
const target = header('query-only-persisted', 10, '/workspace')
|
||||
const result = await setup({
|
||||
@@ -3349,14 +3385,14 @@ describe('pi-tui chat lifecycle and transcript', () => {
|
||||
result.terminal.send('/model alpha/shared')
|
||||
result.terminal.send('\r')
|
||||
await vi.waitFor(() => {
|
||||
expect(result.terminal.output.slice(providerDefaultOutput)).toContain('Reasoning effort: provider default.')
|
||||
expect(result.terminal.output.slice(providerDefaultOutput)).toContain('Reasoning effort: Default.')
|
||||
})
|
||||
result.terminal.send('/model')
|
||||
result.terminal.send('\r')
|
||||
await vi.waitFor(() => {
|
||||
expect(result.terminal.output.slice(providerDefaultOutput)).toContain('Select model')
|
||||
})
|
||||
expect(result.terminal.output.slice(providerDefaultOutput)).toContain('Alpha Shared — provider default')
|
||||
expect(result.terminal.output.slice(providerDefaultOutput)).toContain('Alpha Shared — Default')
|
||||
result.terminal.send('\x1b[Z')
|
||||
await tick()
|
||||
expect(result.terminal.output.slice(providerDefaultOutput)).toContain('Alpha Shared — Standard')
|
||||
@@ -3375,10 +3411,10 @@ describe('pi-tui chat lifecycle and transcript', () => {
|
||||
expect(result.terminal.output.slice(resetDefaultOutput)).toContain('Alpha Shared — Ultra — current')
|
||||
result.terminal.send('\x1b[Z')
|
||||
await tick()
|
||||
expect(result.terminal.output.slice(resetDefaultOutput)).toContain('Alpha Shared — provider default')
|
||||
expect(result.terminal.output.slice(resetDefaultOutput)).toContain('Alpha Shared — Default')
|
||||
result.terminal.send('\r')
|
||||
await tick()
|
||||
expect(result.terminal.output.slice(resetDefaultOutput)).toContain('Reasoning effort: provider default.')
|
||||
expect(result.terminal.output.slice(resetDefaultOutput)).toContain('Reasoning effort: Default.')
|
||||
const explicitResetSeed: LlmCallConfig = {
|
||||
provider: 'beta',
|
||||
model: 'b1',
|
||||
@@ -4962,6 +4998,59 @@ describe('TUI extension service', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('application exit', () => {
|
||||
it('disposes the root fiber rather than only the TUI child before exiting', async () => {
|
||||
const rootDispose = vi.fn(() => Promise.resolve())
|
||||
const childDispose = vi.fn(() => Promise.resolve())
|
||||
const ctx = {
|
||||
root: { fiber: { dispose: rootDispose } },
|
||||
fiber: { dispose: childDispose },
|
||||
} as unknown as Context
|
||||
const exit = vi.fn()
|
||||
disposeRootAndExit(ctx, 7, exit)
|
||||
await Promise.resolve()
|
||||
expect(rootDispose).toHaveBeenCalledOnce()
|
||||
expect(childDispose).not.toHaveBeenCalled()
|
||||
expect(exit).toHaveBeenCalledOnce()
|
||||
expect(exit).toHaveBeenCalledWith(7)
|
||||
})
|
||||
|
||||
it('forces exit when root disposal does not settle', async () => {
|
||||
vi.useFakeTimers()
|
||||
try {
|
||||
let settle!: () => void
|
||||
const disposal = new Promise<void>((resolve) => { settle = resolve })
|
||||
const ctx = {
|
||||
root: { fiber: { dispose: () => disposal } },
|
||||
} as unknown as Context
|
||||
const exit = vi.fn()
|
||||
disposeRootAndExit(ctx, 9, exit)
|
||||
await vi.advanceTimersByTimeAsync(4_999)
|
||||
expect(exit).not.toHaveBeenCalled()
|
||||
await vi.advanceTimersByTimeAsync(1)
|
||||
expect(exit).toHaveBeenCalledOnce()
|
||||
expect(exit).toHaveBeenCalledWith(9)
|
||||
settle()
|
||||
await disposal
|
||||
await Promise.resolve()
|
||||
expect(exit).toHaveBeenCalledOnce()
|
||||
} finally {
|
||||
vi.useRealTimers()
|
||||
}
|
||||
})
|
||||
|
||||
it('exits after a rejected root disposal without an unhandled rejection', async () => {
|
||||
const ctx = {
|
||||
root: { fiber: { dispose: () => Promise.reject(new Error('cleanup failed')) } },
|
||||
} as unknown as Context
|
||||
const exit = vi.fn()
|
||||
disposeRootAndExit(ctx, 5, exit)
|
||||
await Promise.resolve()
|
||||
await Promise.resolve()
|
||||
expect(exit).toHaveBeenCalledWith(5)
|
||||
})
|
||||
})
|
||||
|
||||
describe('terminal mounting', () => {
|
||||
it('starts immediately when the configured agent already exists', async () => {
|
||||
const ctx = new Context()
|
||||
|
||||
Reference in New Issue
Block a user