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:
Yichen Jiang
2026-07-30 20:15:40 +08:00
477 changed files with 11033 additions and 4650 deletions

View File

@@ -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

View File

@@ -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

View File

@@ -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, () => {

View File

@@ -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()