fix(test): make session replay portable in CI

This commit is contained in:
Tianyi Cui
2026-08-24 21:37:14 +08:00
parent e4a3918e87
commit d1e8f4672e
25 changed files with 175 additions and 115 deletions
@@ -2,5 +2,5 @@
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write .agents/notes/implemented/testing/2026-08-24-session-log-snapshot-corpus.md
2026-08-24-session-log-snapshot-corpus.md: 30dad40098e5d1da08052384374425cad595a1ba
2026-08-24-session-log-snapshot-corpus.zh.md: 49edffc2a63486030ace8e76455e9bae209ee902
2026-08-24-session-log-snapshot-corpus.md: eb77f74e85ad97f74df48ecbf831579b6a643cbf
2026-08-24-session-log-snapshot-corpus.zh.md: 5aff7fe8d04d8bf04f5cdfd443a7ed121dcbad25
@@ -40,6 +40,7 @@ Workspace inputs remain scenario-local. A mutating scenario compares a complete
- Committed session fixtures are redaction fixed points, contain no system-prompt or tool-schema bulk, and retain exactly one pin per header class.
- Mutating scenarios verify their final workspace externally.
- Owner-local process expectations use `*.expected.e2e.ts` and a separate built-output gate.
- Source and built adapters install replay-only packages in isolated profile fallbacks; distinct prompt-section orders keep their request headers byte-identical.
- Source and built launch modes, browser replay, SDK projections, packaged Python runtime cases, documentation gates, and repository hygiene pass.
## Consequences
@@ -40,6 +40,7 @@ Workspace 输入继续归各场景本地所有。变更文件的场景比较完
- 提交的会话 fixture 是脱敏固定点,不含 system prompt 或工具 schema 正文,并为每个 header 类保留且仅保留一个 pin。
- 变更内容的场景从外部验证最终 workspace。
- 所属位置的进程预期使用 `*.expected.e2e.ts`,并由单独的构建产物门禁运行。
- 源码与构建适配器在隔离的 profile fallback 中安装仅回放包;不同的提示词 section 顺序值使两种模式的请求 header 保持字节一致。
- 源码和构建启动模式、浏览器回放、SDK 投影、打包 Python 运行时场景、文档门禁和仓库卫生检查通过。
## Consequences
+2 -2
View File
@@ -127,8 +127,8 @@ describe('web e2e: approval takeover keeps its actions reachable', () => {
await assertFinalWorkspaceSnapshot(SNAPSHOT_DIR, join(scaffold.workspaceCwd, 'workspace'))
return
}
// The denied attempt contains platform-specific OS text, so direct state
// and DOM assertions cover the answered outcome.
// Direct state and DOM assertions cover the answered outcome beyond the
// pending panel's expected output.
expect(JSON.stringify(sessionEvents.filter(e => e.type === 'approval/decided').at(-1)))
.toContain('allowed-once')
const written = await readFile(join(scaffold.workspaceCwd, 'workspace', 'notes.txt'), 'utf8')
+8 -4
View File
@@ -26,10 +26,10 @@ const MID_EXPECTED = join(SNAPSHOT_DIR, 'mid-steer.expected.md')
const SETTLED_EXPECTED = join(SNAPSHOT_DIR, 'settled.expected.md')
const MODE = webSnapshotMode()
// The question composer replaces the textarea, so fill → Queue row → Steer
// must finish inside the first replay chunk window. At 15 ms that window is
// shorter than Playwright's round trips; 50 ms supplies test-only headroom,
// while larger values lengthen all three replay scenarios linearly.
const REPLAY_PACE_MS = 50
// starts only after request/context and must finish before the first replay
// chunk. The compact canonical call plus 500 ms pacing gives loaded CI enough
// time without stretching a long provider-authored chunk sequence.
const REPLAY_PACE_MS = 500
const PROMPT = 'Use the ask_user_question tool to ask me exactly one question with id "checkpoint", question "Ready to continue?", header "Checkpoint", and options labeled "Yes" and "No". After I answer, reply with one short sentence acknowledging my answer and stop.'
const STEER = 'Interjection: include the word BANANA in your final reply.'
@@ -101,6 +101,10 @@ describe('web e2e: mid-turn steering lands durably and visibly', () => {
const settled = scaffold.whenTurnSettled(MODE === 'record' ? 180_000 : 30_000)
await input.fill(PROMPT)
await input.press('Enter')
await expect.poll(
() => sessionEvents.some(event => event.type === 'request/context'),
{ timeout: 10_000 },
).toBe(true)
// Enter remains the Queue gesture. The row action then atomically moves
// this exact occurrence into the current turn's steering outbox.
+1 -1
View File
@@ -33,7 +33,7 @@ function requireAgent(exec: ToolExecution): Agent {
/** Register the Cordis tools and explicit `@pluginId` context injection. */
export function apply(ctx: Context): void {
ctx.systemPrompt.section({ name: 'tool:cordis', order: 115, text: CORDIS_SYSTEM_PROMPT })
ctx.systemPrompt.section({ name: 'tool:cordis', order: 115.5, text: CORDIS_SYSTEM_PROMPT })
for (const provider of hostInspectProviders(ctx)) {
ctx.effect(() => ctx.cordisInspect.register(provider), `tool-cordis: inspect ${provider.manifest.id}`)
}
@@ -449,6 +449,7 @@ function inferStartedSubagents(
for (const leaf of leaves) {
for (const match of leaf.matchAll(/started subagent ([^\s"'<>]+)/g)) {
const id = match[1]
/* v8 ignore next -- the fixed regular expression always has capture group 1. */
if (id === undefined || liveSessionIds.includes(id)) continue
const index = liveSessionIds.findIndex((value, candidate) => candidate > 0 && value === undefined)
if (index < 0) return
@@ -1246,7 +1246,7 @@ describe('installLlmReplay (per-session keying)', () => {
const options: GenerateOptions = {
...live('live-parent'),
messages: [createUserMessage({
content: [{ type: 'text', text: 'started subagent live-child-before-call' }],
content: [{ type: 'text', text: 'started subagent live-child-before-call started subagent live-child-before-call' }],
source: { kind: 'user' },
})],
}
@@ -1257,6 +1257,21 @@ describe('installLlmReplay (per-session keying)', () => {
])
})
it('ignores started-subagent text after every recorded session has bound', async () => {
const parentFile = writeSession('session.jsonl', { id: '{{session:1}}', createdAt: 1 }, [TEXT_CHUNKS])
const ctx = new Context()
await ctx.plugin(LlmRuntime)
installLlmReplay(ctx, { file: parentFile })
expect(await drain(ctx.llm.stream({
...live('live-parent'),
messages: [createUserMessage({
content: [{ type: 'text', text: 'started subagent unrecorded-child' }],
source: { kind: 'user' },
})],
}))).toEqual(TEXT_CHUNKS)
})
it('treats a call with no sessionId as the single anonymous (primary) session', async () => {
const parentFile = writeSession('session.jsonl', { id: 'p', createdAt: 1 }, [TEXT_CHUNKS])
const ctx = new Context()
@@ -397,7 +397,7 @@ function linkProfilePackage(source: string, cwd: string, packageName: string): v
mkdirSync(dirname(link), { recursive: true })
if (existsSync(link)) {
if (realpathSync(link) !== packageDir) {
throw new Error(`ACP profile package ${packageName} resolves to two directories`)
throw new Error(`snapshot profile package ${packageName} resolves to two directories`)
}
return
}
@@ -348,6 +348,7 @@ export function parseSnapshotManifest(source: string, path = 'snapshot.yml'): Sn
...(session === undefined ? {} : { session }),
}
} catch (error) {
/* v8 ignore next -- every parser and validator above throws Error instances. */
throw new Error(`session-snapshot: ${path}: ${error instanceof Error ? error.message : String(error)}`)
}
}
@@ -1338,12 +1338,10 @@ export function defineAcpSnapshotSuite(options: SnapshotSuiteOptions): void {
expect(result.sessionLogs.length, 'this scenario must persist one log per session fixture').toBe(fixtureFiles.length)
const harvested = result.sessionLogs.map(log => log.content)
const fixtures = await Promise.all(fixtureFiles.map(file => readFile(join(dir, file), 'utf8')))
const fixtureHeaders = fixtures.map(fixture => JSON.parse(
fixture.split('\n').find(line => line.trim() !== '') ?? '{}',
) as { id?: unknown; cwd?: unknown })
const fixtureContexts = fixtures.map(fixtureContext)
const fixtureCtx: NormalizeContext = {
sessionIds: fixtureHeaders.flatMap(header => typeof header.id === 'string' ? [header.id] : []),
cwd: typeof fixtureHeaders[0]?.cwd === 'string' ? fixtureHeaders[0].cwd : '\0no-cwd\0',
sessionIds: fixtureContexts.flatMap(context => context.sessionIds),
cwd: (fixtureContexts[0] as NormalizeContext).cwd,
}
const actualSnapshots = normalizeSessionSnapshots(harvested, ctx)
const expectedSnapshots = normalizeSessionSnapshots(fixtures, fixtureCtx)
@@ -78,7 +78,7 @@ export async function captureWorkspaceSnapshot(
const visit = async (directory: string, segments: readonly string[]): Promise<WorkspaceSnapshotEntry[]> => {
const entries = (await readdir(directory, { withFileTypes: true }))
.filter(entry => segments.length > 0 || !ignoredRootEntries.has(entry.name))
.sort((left, right) => left.name < right.name ? -1 : left.name > right.name ? 1 : 0)
.sort((left, right) => Buffer.compare(Buffer.from(left.name), Buffer.from(right.name)))
const captured: WorkspaceSnapshotEntry[] = []
for (const entry of entries) {
const childSegments = [...segments, entry.name]
@@ -93,11 +93,8 @@ export async function captureWorkspaceSnapshot(
captured.push(content === undefined
? { path, kind: 'binary', base64: bytes.toString('base64') }
: { path, kind: 'text', content })
} else if (entry.isSymbolicLink()) {
captured.push({ path, kind: 'symlink', target: await readlink(absolute) })
} else {
/* v8 ignore next -- ordinary Git workspaces contain only files, directories, and links. */
throw new Error(`session-snapshot: unsupported workspace entry ${JSON.stringify(path)}`)
captured.push({ path, kind: 'symlink', target: await readlink(absolute) })
}
}
return captured
@@ -1,2 +1,4 @@
version: 1
profile: acp
workspace:
final: true
@@ -0,0 +1 @@
prepared at runtime
@@ -242,7 +242,7 @@ describe('runScenario', () => {
agent: { ...profileAgent, configPath: conflictPatch },
cwd: dir,
env: { DSH_SNAPSHOT: 'record', DSH_SNAPSHOT_FILE: fixtureFile },
})).toThrow('ACP profile package conflict-package resolves to two directories')
})).toThrow('snapshot profile package conflict-package resolves to two directories')
const invalidPatch = join(dir, 'invalid.cordis.yml')
await writeFile(invalidPatch, 'not: a-list\n')
@@ -53,4 +53,32 @@ describe('session snapshot identity redaction', () => {
expect(redacted[0]).toContain('session {{session:2}}')
expect(redactSessionSnapshotIds(redacted)).toEqual(redacted)
})
it('classifies semantic text plus command, RPC, and retry identity fields', () => {
const semanticMessage = '88888888-8888-4888-8888-888888888888'
const anonymousUser = '99999999-9999-4999-8999-999999999999'
const retryId = 'aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa'
const source = [
JSON.stringify({ type: 'not-a-session', data: { value: 'plain' } }),
JSON.stringify({
type: 'example',
data: {
commandId: 'command-7',
rpcId: 'rpc-9',
retryId,
requestId: 'stable-readable-id',
text: `Retain this as message ${semanticMessage}. Anonymous user: ${anonymousUser}`,
},
}),
].join('\n')
const [redacted] = redactSessionSnapshotIds([source])
expect(redacted).toContain('"commandId":"{{command:1}}"')
expect(redacted).toContain('"rpcId":"{{rpc:1}}"')
expect(redacted).toContain('"retryId":"{{retry:1}}"')
expect(redacted).toContain('as message {{message:1}}')
expect(redacted).toContain('Anonymous user: {{id:1}}')
expect(redacted).toContain('"requestId":"stable-readable-id"')
expect(redacted?.endsWith('\n')).toBe(false)
})
})
@@ -26,6 +26,7 @@ describe('snapshot manifest', () => {
it('parses composition, recording, header, and exceptional replay metadata', () => {
expect(parseSnapshotManifest([
'version: 1',
'scenario: sdk-case',
'profile: sdk',
'composition: continuable-subagent',
'recording: authored',
@@ -56,6 +57,7 @@ describe('snapshot manifest', () => {
'',
].join('\n'))).toEqual({
version: 1,
scenario: 'sdk-case',
profile: 'sdk',
composition: 'continuable-subagent',
recording: 'authored',
@@ -80,6 +82,38 @@ describe('snapshot manifest', () => {
})
})
it('parses independently optional header and input fields', () => {
expect(parseSnapshotManifest([
'version: 1',
'profile: headless',
'header:',
' class: default',
'input:',
' task: Run once.',
'',
].join('\n'))).toEqual({
version: 1,
profile: 'headless',
header: { class: 'default' },
input: { task: 'Run once.' },
})
expect(parseSnapshotManifest([
'version: 1',
'profile: sdk',
'input:',
' attachments:',
' - id: sha256:one',
' mediaType: image/png',
' data: AQ==',
'',
].join('\n'))).toEqual({
version: 1,
profile: 'sdk',
input: { attachments: [{ id: 'sha256:one', mediaType: 'image/png', data: 'AQ==' }] },
})
})
it.each([
['', 'manifest must be a mapping'],
['version: 2\nprofile: acp\n', 'manifest.version must equal 1'],
@@ -91,6 +125,7 @@ describe('snapshot manifest', () => {
['version: 1\nprofile: acp\nheader:\n class: base\n pin: false\n', 'manifest.header.pin must equal true when present'],
['version: 1\nprofile: acp\nheader:\n class: base\n childToolSchemas: [1, 1]\n', 'manifest.header.childToolSchemas must be an array of unique positive integers'],
['version: 1\nprofile: acp\nheader:\n class: base\n changes: -1\n', 'manifest.header.changes must be a non-negative integer'],
['version: 1\nprofile: acp\nheader:\n class: base\n systemPromptSource: ../bad\n', 'manifest.header.systemPromptSource must be a lower-kebab-case name or corpus-relative path'],
['version: 1\nprofile: acp\nreplay:\n override: false\n', 'manifest.replay.override must equal true'],
['version: 1\nprofile: acp\nplatform: windows\n', 'manifest.platform must be posix or pwsh'],
['version: 1\nprofile: acp\npermission: root\n', 'manifest.permission must be read-only, workspace-write, or danger-full-access'],
@@ -101,6 +136,10 @@ describe('snapshot manifest', () => {
['version: 1\nprofile: acp\ninput:\n task: ""\n', 'manifest.input.task must be a non-empty string when present'],
['version: 1\nprofile: acp\ninput: {}\n', 'manifest.input must declare task or attachments'],
['version: 1\nprofile: acp\ninput:\n attachments: []\n', 'manifest.input.attachments must be a non-empty array'],
['version: 1\nprofile: acp\ninput:\n attachments:\n - id: raw\n mediaType: image/png\n data: AQ==\n', 'manifest.input.attachments[0].id must start with sha256:'],
['version: 1\nprofile: acp\ninput:\n attachments:\n - id: sha256:one\n mediaType: image\n data: AQ==\n', 'manifest.input.attachments[0].mediaType must be a MIME type'],
['version: 1\nprofile: acp\ninput:\n attachments:\n - id: sha256:one\n mediaType: image/png\n data: ""\n', 'manifest.input.attachments[0].data must be non-empty base64'],
['version: 1\nprofile: acp\ninput:\n attachments:\n - id: sha256:one\n mediaType: image/png\n data: AQ==\n - id: sha256:one\n mediaType: image/png\n data: Ag==\n', 'manifest.input.attachments must have unique ids'],
['version: 1\nprofile: acp\nsession: {}\n', 'manifest.session.source must be a non-empty string'],
['version: 1\nprofile: acp\nsession:\n source: /tmp/session.jsonl\n', 'manifest.session.source must be a relative POSIX path'],
['version: 1\nprofile: acp\nsession:\n source: ..\\session.jsonl\n', 'manifest.session.source must be a relative POSIX path'],
@@ -3,8 +3,8 @@
"kind": "chunks",
"chunks": [
{ "type": "block-start", "index": 0, "blockType": "tool-call" },
{ "type": "tool-call-delta", "index": 0, "id": "call_wait", "name": "bash", "argumentsDelta": "{\"command\":\"node -e \\\"require('node:fs').writeFileSync('started.txt', 'started'); setInterval(() => {}, 1000)\\\"\",\"description\":\"Wait until cancellation\"}" },
{ "type": "block-end", "index": 0, "block": { "type": "tool-call", "id": "call_wait", "name": "bash", "arguments": "{\"command\":\"node -e \\\"require('node:fs').writeFileSync('started.txt', 'started'); setInterval(() => {}, 1000)\\\"\",\"description\":\"Wait until cancellation\"}" } },
{ "type": "tool-call-delta", "index": 0, "id": "call_wait", "name": "bash", "argumentsDelta": "{\"command\":\"node -e \\\"const fs=require('node:fs'); fs.writeFileSync('started.tmp', 'started'); fs.renameSync('started.tmp', 'started.txt'); setInterval(() => {}, 1000)\\\"\",\"description\":\"Wait until cancellation\"}" },
{ "type": "block-end", "index": 0, "block": { "type": "tool-call", "id": "call_wait", "name": "bash", "arguments": "{\"command\":\"node -e \\\"const fs=require('node:fs'); fs.writeFileSync('started.tmp', 'started'); fs.renameSync('started.tmp', 'started.txt'); setInterval(() => {}, 1000)\\\"\",\"description\":\"Wait until cancellation\"}" } },
{ "type": "block-start", "index": 1, "blockType": "tool-call" },
{ "type": "tool-call-delta", "index": 1, "id": "call_skipped", "name": "bash", "argumentsDelta": "{\"command\":\"printf skipped > skipped.txt\",\"description\":\"Write skipped marker\"}" },
{ "type": "block-end", "index": 1, "block": { "type": "tool-call", "id": "call_skipped", "name": "bash", "arguments": "{\"command\":\"printf skipped > skipped.txt\",\"description\":\"Write skipped marker\"}" } },
@@ -12,15 +12,15 @@
{"type":"request/header","data":{"header":{"config":{"provider":"deepseek-official","model":"deepseek-v4-flash"},"system":"{{system}}","tools":"{{tools}}"},"reason":"initial"}}
{"type":"request/context","data":{"provider":"deepseek-official","model":"deepseek-v4-flash"}}
{"type":"assistant/chunk","data":{"turn":1,"step":1,"chunk":{"type":"block-start","index":0,"blockType":"tool-call"}}}
{"type":"assistant/chunk","data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":0,"id":"call_wait","name":"bash","argumentsDelta":"{\"command\":\"node -e \\\"require('node:fs').writeFileSync('started.txt', 'started'); setInterval(() => {}, 1000)\\\"\",\"description\":\"Wait until cancellation\"}"}}}
{"type":"assistant/chunk","data":{"turn":1,"step":1,"chunk":{"type":"block-end","index":0,"block":{"type":"tool-call","id":"call_wait","name":"bash","arguments":"{\"command\":\"node -e \\\"require('node:fs').writeFileSync('started.txt', 'started'); setInterval(() => {}, 1000)\\\"\",\"description\":\"Wait until cancellation\"}"}}}}
{"type":"assistant/chunk","data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":0,"id":"call_wait","name":"bash","argumentsDelta":"{\"command\":\"node -e \\\"const fs=require('node:fs'); fs.writeFileSync('started.tmp', 'started'); fs.renameSync('started.tmp', 'started.txt'); setInterval(() => {}, 1000)\\\"\",\"description\":\"Wait until cancellation\"}"}}}
{"type":"assistant/chunk","data":{"turn":1,"step":1,"chunk":{"type":"block-end","index":0,"block":{"type":"tool-call","id":"call_wait","name":"bash","arguments":"{\"command\":\"node -e \\\"const fs=require('node:fs'); fs.writeFileSync('started.tmp', 'started'); fs.renameSync('started.tmp', 'started.txt'); setInterval(() => {}, 1000)\\\"\",\"description\":\"Wait until cancellation\"}"}}}}
{"type":"assistant/chunk","data":{"turn":1,"step":1,"chunk":{"type":"block-start","index":1,"blockType":"tool-call"}}}
{"type":"assistant/chunk","data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":1,"id":"call_skipped","name":"bash","argumentsDelta":"{\"command\":\"printf skipped > skipped.txt\",\"description\":\"Write skipped marker\"}"}}}
{"type":"assistant/chunk","data":{"turn":1,"step":1,"chunk":{"type":"block-end","index":1,"block":{"type":"tool-call","id":"call_skipped","name":"bash","arguments":"{\"command\":\"printf skipped > skipped.txt\",\"description\":\"Write skipped marker\"}"}}}}
{"type":"assistant/chunk","data":{"turn":1,"step":1,"chunk":{"type":"usage","usage":{"inputTokens":10,"outputTokens":10}}}}
{"type":"assistant/chunk","data":{"turn":1,"step":1,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}}
{"type":"assistant/message","data":{"turn":1,"step":1,"message":{"role":"assistant","content":[{"type":"tool-call","id":"call_wait","name":"bash","arguments":"{\"command\":\"node -e \\\"require('node:fs').writeFileSync('started.txt', 'started'); setInterval(() => {}, 1000)\\\"\",\"description\":\"Wait until cancellation\"}"},{"type":"tool-call","id":"call_skipped","name":"bash","arguments":"{\"command\":\"printf skipped > skipped.txt\",\"description\":\"Write skipped marker\"}"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{message:3}}"},"usage":{"inputTokens":10,"outputTokens":10}},"sourceEventSeqs":[12,13,14,15,16,17,18,19],"surfaceOp":"append"}
{"type":"tool/call","data":{"turn":1,"step":1,"callId":"call_wait","name":"bash","arguments":"{\"command\":\"node -e \\\"require('node:fs').writeFileSync('started.txt', 'started'); setInterval(() => {}, 1000)\\\"\",\"description\":\"Wait until cancellation\"}"}}
{"type":"assistant/message","data":{"turn":1,"step":1,"message":{"role":"assistant","content":[{"type":"tool-call","id":"call_wait","name":"bash","arguments":"{\"command\":\"node -e \\\"const fs=require('node:fs'); fs.writeFileSync('started.tmp', 'started'); fs.renameSync('started.tmp', 'started.txt'); setInterval(() => {}, 1000)\\\"\",\"description\":\"Wait until cancellation\"}"},{"type":"tool-call","id":"call_skipped","name":"bash","arguments":"{\"command\":\"printf skipped > skipped.txt\",\"description\":\"Write skipped marker\"}"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{message:3}}"},"usage":{"inputTokens":10,"outputTokens":10}},"sourceEventSeqs":[12,13,14,15,16,17,18,19],"surfaceOp":"append"}
{"type":"tool/call","data":{"turn":1,"step":1,"callId":"call_wait","name":"bash","arguments":"{\"command\":\"node -e \\\"const fs=require('node:fs'); fs.writeFileSync('started.tmp', 'started'); fs.renameSync('started.tmp', 'started.txt'); setInterval(() => {}, 1000)\\\"\",\"description\":\"Wait until cancellation\"}"}}
{"type":"tool/result","data":{"turn":1,"step":1,"message":{"source":{"kind":"tool","callId":"call_wait"},"content":[{"type":"tool-result","toolCallId":"call_wait","content":[{"type":"text","text":"Error: tool call aborted"}],"isError":true}],"role":"user","id":"{{message:4}}"},"error":{"name":"AbortError","code":"ABORTED"}},"sourceEventSeqs":[21],"surfaceOp":"append"}
{"type":"tool/call","data":{"turn":1,"step":1,"callId":"call_skipped","name":"bash","arguments":"{\"command\":\"printf skipped > skipped.txt\",\"description\":\"Write skipped marker\"}"}}
{"type":"tool/result","data":{"turn":1,"step":1,"message":{"source":{"kind":"tool","callId":"call_skipped"},"content":[{"type":"tool-result","toolCallId":"call_skipped","content":[{"type":"text","text":"Error: tool call aborted before dispatch"}],"isError":true}],"role":"user","id":"{{message:5}}"},"error":{"name":"AbortError","code":"ABORTED_BEFORE_DISPATCH"}},"sourceEventSeqs":[23],"surfaceOp":"append"}
@@ -1,6 +1,6 @@
{"jsonrpc":"2.0","id":1,"result":{"protocolVersion":1,"agentInfo":{"name":"deepseek-harness-acp","version":"0.0.1"},"agentCapabilities":{"mcpCapabilities":{"http":true},"promptCapabilities":{"image":false,"audio":false,"embeddedContext":false},"sessionCapabilities":{"close":{},"list":{},"resume":{}}},"authMethods":[]}}
{"jsonrpc":"2.0","id":2,"result":{"sessionId":"{{sessionId}}","configOptions":[{"id":"model","name":"Model","category":"model","type":"select","currentValue":"[\"deepseek-official\",\"deepseek-v4-flash\"]","options":[{"group":"deepseek-official","name":"DeepSeek","options":[{"value":"[\"deepseek-official\",\"deepseek-v4-flash\"]","name":"deepseek-v4-flash"},{"value":"[\"deepseek-official\",\"deepseek-v4-pro\"]","name":"deepseek-v4-pro"}]}]}]}}
{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"{{sessionId}}","update":{"sessionUpdate":"tool_call","toolCallId":"call_wait","title":"bash","kind":"other","status":"in_progress","rawInput":{"command":"node -e \"require('node:fs').writeFileSync('started.txt', 'started'); setInterval(() => {}, 1000)\"","description":"Wait until cancellation"}}}}
{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"{{sessionId}}","update":{"sessionUpdate":"tool_call","toolCallId":"call_wait","title":"bash","kind":"other","status":"in_progress","rawInput":{"command":"node -e \"const fs=require('node:fs'); fs.writeFileSync('started.tmp', 'started'); fs.renameSync('started.tmp', 'started.txt'); setInterval(() => {}, 1000)\"","description":"Wait until cancellation"}}}}
{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"{{sessionId}}","update":{"sessionUpdate":"tool_call_update","toolCallId":"call_wait","status":"failed","content":[{"type":"content","content":{"type":"text","text":"Error: tool call aborted"}}]}}}
{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"{{sessionId}}","update":{"sessionUpdate":"tool_call","toolCallId":"call_skipped","title":"bash","kind":"other","status":"in_progress","rawInput":{"command":"printf skipped > skipped.txt","description":"Write skipped marker"}}}}
{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"{{sessionId}}","update":{"sessionUpdate":"tool_call_update","toolCallId":"call_skipped","status":"failed","content":[{"type":"content","content":{"type":"text","text":"Error: tool call aborted before dispatch"}}]}}}
+14 -3
View File
@@ -4,7 +4,7 @@ import { cp, copyFile, mkdir, readFile, readdir, rm, utimes, writeFile } from 'n
import { existsSync } from 'node:fs'
import { spawnSync } from 'node:child_process'
import { homedir } from 'node:os'
import { delimiter, dirname, join } from 'node:path'
import { basename, delimiter, dirname, join } from 'node:path'
import { fileURLToPath } from 'node:url'
import { describe, expect, it } from 'vitest'
import {
@@ -13,6 +13,7 @@ import {
fixtureContext,
formatSystemPromptSnapshot,
formatToolSchemasSnapshot,
materializeProfilePatch,
normalizeSessionSnapshots,
normalizedHeaders,
normalizedSystemPrompts,
@@ -62,7 +63,7 @@ function snapshotMode(value: string | undefined): SnapshotMode {
}
const mode = snapshotMode(process.env.DSH_SNAPSHOT)
const RUNTIME_WORKSPACE_ENTRIES = ['.agents', '.dsh'] as const
const RUNTIME_WORKSPACE_ENTRIES = ['.agents', '.dsh', '.snapshot-patches'] as const
interface JsonObject {
[key: string]: unknown
@@ -507,11 +508,15 @@ describe('headless recorded-session snapshots', () => {
const fixtureFiles = sessionFixtureNames(await readdir(scenario.dir))
const replaying = mode !== 'record'
const compositionPatch = join(composition.dir, replaying ? 'cordis.snapshot.yml' : 'cordis.yml')
const patches = [
const patchSources = [
join(baseComposition.dir, 'cordis.yml'),
...composition === baseComposition && !replaying ? [] : [compositionPatch],
join(baseComposition.dir, 'model.cordis.yml'),
]
const patchRoot = '.snapshot-patches'
const patches = patchSources.map((source, index) => source.endsWith('.snapshot.yml')
? join(patchRoot, `${String(index)}-${basename(source)}`)
: source)
let actualLogs: SessionLog[] = []
let initialWorkspace: WorkspaceSnapshotEntry[] | undefined
@@ -556,6 +561,12 @@ describe('headless recorded-session snapshots', () => {
DSH_TELEMETRY_DISABLED: '1',
},
prepare: async (cwd) => {
await mkdir(join(cwd, patchRoot), { recursive: true })
patchSources.forEach((source, index) => {
if (source.endsWith('.snapshot.yml')) {
materializeProfilePatch(source, cwd, join(cwd, patchRoot), index)
}
})
await seedWorkspace(scenario, cwd)
initialWorkspace = await captureWorkspaceSnapshot(cwd, {
ignoredRootEntries: RUNTIME_WORKSPACE_ENTRIES,
File diff suppressed because one or more lines are too long
@@ -16,10 +16,6 @@
- img
- img
- text: Context injection @deepseek-ai/dsh-system-prompt
- button "Think The user wants me to use the ask_user_question tool to ask them a specific question with the given parameters. Let me do exactly that.":
- img
- img
- text: Think The user wants me to use the ask_user_question tool to ask them a specific question with the given parameters. Let me do exactly that.
- text: Running
- button "Ask question waiting":
- img
+19 -23
View File
@@ -1,40 +1,36 @@
{"type":"session","version":0,"id":"{{session:1}}","createdAt":1787520632977,"cwd":"{{cwd}}","agentPreset":"standard"}
{"type":"session","version":0,"id":"{{session:1}}","createdAt":1787528667010,"cwd":"{{cwd}}","agentPreset":"standard"}
{"type":"permission/preset","data":{"preset":"workspace-write"}}
{"type":"sandbox/mode","data":{"mode":"workspace-write"}}
{"type":"approval/policy","data":{"policy":"ask"}}
{"type":"agent/inbox/spliced","data":{"target":"next-turn","start":0,"inserted":[{"content":[{"type":"text","text":"Use the ask_user_question tool to ask me exactly one question with id \"checkpoint\", question \"Ready to continue?\", header \"Checkpoint\", and options labeled \"Yes\" and \"No\". After I answer, reply with one short sentence acknowledging my answer and stop."}],"source":{"kind":"user","rpcId":"{{rpc:1}}","clientTimeZone":"Asia/Shanghai"},"role":"user","id":"{{message:1}}"}]}}
{"type":"turn/start","data":{"turn":1}}
{"type":"agent/inbox/spliced","data":{"target":"next-turn","start":0,"removedCount":1,"inserted":[]}}
{"type":"agent/inbox/spliced","data":{"target":"next-step","start":0,"inserted":[{"content":[{"type":"text","text":"Interjection: include the word BANANA in your final reply."}],"source":{"kind":"user","rpcId":"{{rpc:2}}","clientTimeZone":"Asia/Shanghai"},"role":"user","id":"{{message:2}}"}]}}
{"type":"step/start","data":{"turn":1,"step":1}}
{"type":"user/message","data":{"content":[{"type":"text","text":"Use the ask_user_question tool to ask me exactly one question with id \"checkpoint\", question \"Ready to continue?\", header \"Checkpoint\", and options labeled \"Yes\" and \"No\". After I answer, reply with one short sentence acknowledging my answer and stop."}],"source":{"kind":"user","rpcId":"{{rpc:1}}","clientTimeZone":"Asia/Shanghai"},"role":"user","id":"{{message:1}}"},"surfaceOp":"append"}
{"type":"user/message","data":{"content":[{"type":"text","text":"Current runtime context. This snapshot supersedes earlier runtime-context snapshots.\n\nCurrent DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable.\n\nApproval policy: ask. Operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."}],"source":{"kind":"plugin","plugin":"@deepseek-ai/dsh-system-prompt","form":"snapshot","sections":[{"name":"sandbox:policy","text":"Current DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable."},{"name":"approval:policy","text":"Approval policy: ask. Operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."}]},"role":"user","id":"{{message:3}}"},"surfaceOp":"append"}
{"type":"session/title","data":{"title":"Use the ask_user_question tool to","messageSeqs":[8],"source":{"kind":"fallback"}}}
{"type":"user/message","data":{"content":[{"type":"text","text":"Current runtime context. This snapshot supersedes earlier runtime-context snapshots.\n\nCurrent DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable.\n\nApproval policy: ask. Operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."}],"source":{"kind":"plugin","plugin":"@deepseek-ai/dsh-system-prompt","form":"snapshot","sections":[{"name":"sandbox:policy","text":"Current DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable."},{"name":"approval:policy","text":"Approval policy: ask. Operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."}]},"role":"user","id":"{{message:2}}"},"surfaceOp":"append"}
{"type":"session/title","data":{"title":"Use the ask_user_question tool to","messageSeqs":[7],"source":{"kind":"fallback"}}}
{"type":"request/header","data":{"header":{"config":{"provider":"deepseek-official","model":"deepseek-v4-flash"},"system":"{{system}}","tools":"{{tools}}"},"reason":"initial"}}
{"type":"request/context","data":{"provider":"deepseek-official","model":"deepseek-v4-flash","contextWindow":128000}}
{"type":"assistant/chunk","data":{"turn":1,"step":1,"chunk":{"type":"block-start","index":0,"blockType":"reasoning"}}}
{"type":"reasoning-chunks","data":{"turn":1,"step":1,"index":0,"dt":[52,52,52,52,52,50,50,52,52,51,51,50,52,53,50,52,52,52,52,51,52,51,50,51,52,52,51,51],"texts":["The"," user"," wants"," me"," to"," use"," the"," ask","_user","_","question"," tool"," to"," ask"," them"," a"," specific"," question"," with"," the"," given"," parameters","."," Let"," me"," do"," exactly"," that","."]}}
{"type":"assistant/chunk","data":{"turn":1,"step":1,"chunk":{"type":"block-start","index":1,"blockType":"tool-call"}}}
{"type":"tool-call-chunks","data":{"turn":1,"step":1,"index":1,"dt":[51,52,51,51,51,52,52,52,50,52,52,50,52,50,52,50,50,51,51,51,52,50,53,50,51,50,53,51,52,51,52,51,49,51,53,52,51,51,52,52,50,52,53,53,51,52,50],"id":"call_00_sAvjivLShvnWVk0sPQPV7661","name":"ask_user_question","args":["","{","\"","questions","\"",": ","[","{\"","id","\":"," \"","check","point","\","," \"","question","\":"," \"","Ready"," to"," continue","?\","," \"","header","\":"," \"","Check","point","\","," \"","options","\":"," [","{\"","label","\":"," \"","Yes","\"},"," {\"","label","\":"," \"","No","\"","}]","}]","}"]}}
{"type":"assistant/chunk","data":{"turn":1,"step":1,"chunk":{"type":"block-end","index":0,"block":{"type":"reasoning","text":"The user wants me to use the ask_user_question tool to ask them a specific question with the given parameters. Let me do exactly that."}}}}
{"type":"assistant/chunk","data":{"turn":1,"step":1,"chunk":{"type":"block-end","index":1,"block":{"type":"tool-call","id":"call_00_sAvjivLShvnWVk0sPQPV7661","name":"ask_user_question","arguments":"{\"questions\": [{\"id\": \"checkpoint\", \"question\": \"Ready to continue?\", \"header\": \"Checkpoint\", \"options\": [{\"label\": \"Yes\"}, {\"label\": \"No\"}]}]}"}}}}
{"type":"assistant/chunk","data":{"turn":1,"step":1,"chunk":{"type":"usage","usage":{"inputTokens":151,"outputTokens":115,"cacheReadTokens":7680,"reasoningTokens":29}}}}
{"type":"agent/inbox/spliced","data":{"target":"next-turn","start":0,"inserted":[{"content":[{"type":"text","text":"Interjection: include the word BANANA in your final reply."}],"source":{"kind":"user","rpcId":"{{rpc:2}}","clientTimeZone":"Asia/Shanghai"},"role":"user","id":"{{message:3}}"}]}}
{"type":"agent/inbox/spliced","data":{"target":"next-turn","start":0,"removedCount":1,"inserted":[],"outcome":"canceled"}}
{"type":"agent/inbox/spliced","data":{"target":"next-step","start":0,"inserted":[{"content":[{"type":"text","text":"Interjection: include the word BANANA in your final reply."}],"source":{"kind":"user","rpcId":"{{rpc:2}}","clientTimeZone":"Asia/Shanghai"},"role":"user","id":"{{message:3}}"}]}}
{"type":"assistant/chunk","data":{"turn":1,"step":1,"chunk":{"type":"block-start","index":0,"blockType":"tool-call"}}}
{"type":"assistant/chunk","data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":0,"id":"steering-question","name":"ask_user_question","argumentsDelta":"{\"questions\":[{\"id\":\"checkpoint\",\"question\":\"Ready to continue?\",\"header\":\"Checkpoint\",\"options\":[{\"label\":\"Yes\"},{\"label\":\"No\"}]}]}"}}}
{"type":"assistant/chunk","data":{"turn":1,"step":1,"chunk":{"type":"block-end","index":0,"block":{"type":"tool-call","id":"steering-question","name":"ask_user_question","arguments":"{\"questions\":[{\"id\":\"checkpoint\",\"question\":\"Ready to continue?\",\"header\":\"Checkpoint\",\"options\":[{\"label\":\"Yes\"},{\"label\":\"No\"}]}]}"}}}}
{"type":"assistant/chunk","data":{"turn":1,"step":1,"chunk":{"type":"usage","usage":{"inputTokens":10,"outputTokens":5}}}}
{"type":"assistant/chunk","data":{"turn":1,"step":1,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}}
{"type":"assistant/message","data":{"turn":1,"step":1,"message":{"role":"assistant","content":[{"type":"reasoning","text":"The user wants me to use the ask_user_question tool to ask them a specific question with the given parameters. Let me do exactly that."},{"type":"tool-call","id":"call_00_sAvjivLShvnWVk0sPQPV7661","name":"ask_user_question","arguments":"{\"questions\": [{\"id\": \"checkpoint\", \"question\": \"Ready to continue?\", \"header\": \"Checkpoint\", \"options\": [{\"label\": \"Yes\"}, {\"label\": \"No\"}]}]}"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{message:4}}"},"usage":{"inputTokens":151,"outputTokens":115,"cacheReadTokens":7680,"reasoningTokens":29}},"sourceEventSeqs":[13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95],"surfaceOp":"append"}
{"type":"tool/call","data":{"turn":1,"step":1,"callId":"call_00_sAvjivLShvnWVk0sPQPV7661","name":"ask_user_question","arguments":"{\"questions\": [{\"id\": \"checkpoint\", \"question\": \"Ready to continue?\", \"header\": \"Checkpoint\", \"options\": [{\"label\": \"Yes\"}, {\"label\": \"No\"}]}]}"}}
{"type":"tool/result","data":{"turn":1,"step":1,"message":{"source":{"kind":"tool","callId":"call_00_sAvjivLShvnWVk0sPQPV7661"},"content":[{"type":"tool-result","toolCallId":"call_00_sAvjivLShvnWVk0sPQPV7661","content":[{"type":"text","text":"{\"answers\":[{\"id\":\"checkpoint\",\"selected\":[\"Yes\"]}]}"}],"isError":false}],"role":"user","id":"{{message:5}}"}},"sourceEventSeqs":[97],"surfaceOp":"append"}
{"type":"assistant/message","data":{"turn":1,"step":1,"message":{"role":"assistant","content":[{"type":"tool-call","id":"steering-question","name":"ask_user_question","arguments":"{\"questions\":[{\"id\":\"checkpoint\",\"question\":\"Ready to continue?\",\"header\":\"Checkpoint\",\"options\":[{\"label\":\"Yes\"},{\"label\":\"No\"}]}]}"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{message:4}}"},"usage":{"inputTokens":10,"outputTokens":5}},"sourceEventSeqs":[15,16,17,18,19],"surfaceOp":"append"}
{"type":"tool/call","data":{"turn":1,"step":1,"callId":"steering-question","name":"ask_user_question","arguments":"{\"questions\":[{\"id\":\"checkpoint\",\"question\":\"Ready to continue?\",\"header\":\"Checkpoint\",\"options\":[{\"label\":\"Yes\"},{\"label\":\"No\"}]}]}"}}
{"type":"tool/result","data":{"turn":1,"step":1,"message":{"source":{"kind":"tool","callId":"steering-question"},"content":[{"type":"tool-result","toolCallId":"steering-question","content":[{"type":"text","text":"{\"answers\":[{\"id\":\"checkpoint\",\"selected\":[\"Yes\"]}]}"}],"isError":false}],"role":"user","id":"{{message:5}}"}},"sourceEventSeqs":[21],"surfaceOp":"append"}
{"type":"step/end","data":{"turn":1,"step":1}}
{"type":"agent/inbox/spliced","data":{"target":"next-step","start":0,"removedCount":1,"inserted":[]}}
{"type":"step/start","data":{"turn":1,"step":2}}
{"type":"user/message","data":{"content":[{"type":"text","text":"Interjection: include the word BANANA in your final reply."}],"source":{"kind":"user","rpcId":"{{rpc:2}}","clientTimeZone":"Asia/Shanghai"},"role":"user","id":"{{message:2}}"},"surfaceOp":"append"}
{"type":"assistant/chunk","data":{"turn":1,"step":2,"chunk":{"type":"block-start","index":0,"blockType":"reasoning"}}}
{"type":"reasoning-chunks","data":{"turn":1,"step":2,"index":0,"dt":[53,53,51,51,53,51,52,52,51,52,50,53,52,51,49,51,52,52,50,52,52,52,51,53,51,51,50,52],"texts":["The"," user"," selected"," \"","Yes","\""," and"," wants"," me"," to"," include"," the"," word"," \"","B","AN","ANA","\""," in"," my"," final"," reply","."," Let"," me"," acknowledge"," their"," answer","."]}}
{"type":"assistant/chunk","data":{"turn":1,"step":2,"chunk":{"type":"block-start","index":1,"blockType":"text"}}}
{"type":"text-chunks","data":{"turn":1,"step":2,"index":1,"dt":[51,53,53,52,51,52,53,51,51,51],"texts":["Great",","," let","'s"," move"," forward","."," B","AN","ANA","!"]}}
{"type":"assistant/chunk","data":{"turn":1,"step":2,"chunk":{"type":"block-end","index":0,"block":{"type":"reasoning","text":"The user selected \"Yes\" and wants me to include the word \"BANANA\" in my final reply. Let me acknowledge their answer."}}}}
{"type":"assistant/chunk","data":{"turn":1,"step":2,"chunk":{"type":"block-end","index":1,"block":{"type":"text","text":"Great, let's move forward. BANANA!"}}}}
{"type":"assistant/chunk","data":{"turn":1,"step":2,"chunk":{"type":"usage","usage":{"inputTokens":172,"outputTokens":41,"cacheReadTokens":7808,"reasoningTokens":29}}}}
{"type":"user/message","data":{"content":[{"type":"text","text":"Interjection: include the word BANANA in your final reply."}],"source":{"kind":"user","rpcId":"{{rpc:2}}","clientTimeZone":"Asia/Shanghai"},"role":"user","id":"{{message:3}}"},"surfaceOp":"append"}
{"type":"assistant/chunk","data":{"turn":1,"step":2,"chunk":{"type":"block-start","index":0,"blockType":"text"}}}
{"type":"assistant/chunk","data":{"turn":1,"step":2,"chunk":{"type":"text-delta","index":0,"text":"Great, let's move forward. BANANA!"}}}
{"type":"assistant/chunk","data":{"turn":1,"step":2,"chunk":{"type":"block-end","index":0,"block":{"type":"text","text":"Great, let's move forward. BANANA!"}}}}
{"type":"assistant/chunk","data":{"turn":1,"step":2,"chunk":{"type":"usage","usage":{"inputTokens":10,"outputTokens":5}}}}
{"type":"assistant/chunk","data":{"turn":1,"step":2,"chunk":{"type":"finish","reason":{"kind":"stop"}}}}
{"type":"assistant/message","data":{"turn":1,"step":2,"message":{"role":"assistant","content":[{"type":"reasoning","text":"The user selected \"Yes\" and wants me to include the word \"BANANA\" in my final reply. Let me acknowledge their answer."},{"type":"text","text":"Great, let's move forward. BANANA!"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{message:6}}"},"usage":{"inputTokens":172,"outputTokens":41,"cacheReadTokens":7808,"reasoningTokens":29}},"sourceEventSeqs":[103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,144,145,146,147,148],"surfaceOp":"append"}
{"type":"assistant/message","data":{"turn":1,"step":2,"message":{"role":"assistant","content":[{"type":"text","text":"Great, let's move forward. BANANA!"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{message:6}}"},"usage":{"inputTokens":10,"outputTokens":5}},"sourceEventSeqs":[27,28,29,30,31],"surfaceOp":"append"}
{"type":"step/end","data":{"turn":1,"step":2}}
{"type":"turn/end","data":{"turn":1,"reason":{"kind":"completed"}}}
+2 -10
View File
@@ -16,10 +16,6 @@
- img
- img
- text: Context injection @deepseek-ai/dsh-system-prompt
- button "Think The user wants me to use the ask_user_question tool to ask them a specific question with the given parameters. Let me do exactly that.":
- img
- img
- text: Think The user wants me to use the ask_user_question tool to ask them a specific question with the given parameters. Let me do exactly that.
- button "Ask question 1/1 answered":
- img
- img
@@ -27,10 +23,6 @@
- text: "Interjection: include the word BANANA in your final reply. {{clock}}"
- button "Copy":
- img
- button "Think The user selected \"Yes\" and wants me to include the word \"BANANA\" in my final reply. Let me acknowledge their answer.":
- img
- img
- text: Think The user selected "Yes" and wants me to include the word "BANANA" in my final reply. Let me acknowledge their answer.
- paragraph: Great, let's move forward. BANANA!
- button "Copy":
- img
@@ -48,6 +40,6 @@
- button "Select model, current DeepSeek-V4-Flash":
- text: DeepSeek-V4-Flash
- img
- button "6% of context used"
- button "0% of context used"
- button "Send message" [disabled]
- text: 1 turns · 2 steps LLM {{duration}} · Tool call {{duration}} TTFT avg {{duration}} · {{throughput}} tok/s Cache hit 98% Input 15.8K tok · Output 156 tok
- text: 1 turns · 2 steps LLM {{duration}} · Tool call {{duration}} TTFT avg {{duration}} · {{throughput}} tok/s Cache hit 0% Input 20 tok · Output 10 tok