Merge remote-tracking branch 'origin/master' into web-e2e-interactions
This commit is contained in:
@@ -0,0 +1,6 @@
|
||||
# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each
|
||||
# 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
|
||||
2026-07-26-code-mode-chat-subcall-rows.md: 7d666f0a9e4b8bdb9bd6f5d0d0984fee0c4b21e2
|
||||
2026-07-26-code-mode-chat-subcall-rows.zh.md: fb9b0c62bb702cfdb7ba3c8ccce73d8e43f29c1b
|
||||
@@ -0,0 +1,32 @@
|
||||
# Agent Note: Code Mode chat rendering — sub-calls as native rows under the parent
|
||||
|
||||
Status: implemented
|
||||
|
||||
English | [中文](2026-07-26-code-mode-chat-subcall-rows.zh.md)
|
||||
|
||||
> Scope: how the web chat view renders a `run_code` turn — the client-side half of the Code Mode UI stack, built on the [host foundation](2026-07-26-code-dispatch-ui-foundation.md) (full-content `tool/code-dispatch`, the required `description` parameter). The [toolview dissolution](../architecture/2026-07-23-toolview-dissolution.md) owns the slot model this rides on.
|
||||
|
||||
## Problem
|
||||
|
||||
With Code Mode enabled, the chat view showed one opaque `run_code` row: raw program text as the summary, sub-calls invisible everywhere. The settled product requirement is the opposite: each sub-call must render *identically* to a native tool call — same row components, same custom registrations, same details panel — while the transcript stays honest about the fact that the model made ONE call.
|
||||
|
||||
## Decision
|
||||
|
||||
**Sub-calls are `ToolResultNode`s indexed off the surface flow, rendered through the same keyed slot as native rows, nested always-visible under their parent.**
|
||||
|
||||
- **Data layer**: `Session.applyEventSideEffects` folds each in-window `tool/code-dispatch` into `ConversationSnapshot.codeDispatches: ReadonlyMap<parentCallId, readonly CodeSubCall[]>`, where `CodeSubCall` IS `ToolResultNode` (the sub-call id as `callId`, the logged args JSON-stringified into `call.argsRaw`, the full logged `content`/`isError`). Live mux frames and history replay build the identical index (`rebuildDerivedFromWindow` clears and re-derives; copy-on-write per-parent arrays keep snapshot references memo-stable). Sub-calls never join `nodes` — the surface flow remains exactly the model-visible turn structure. The event is narrowed structurally at the wire-consumer boundary (dsh-tools' host types cannot enter the client program — the host/client `Context` merges collide), the same posture as every cross-wire payload.
|
||||
- **Render layer**: `ChatView`'s `CallRow` renders the parent, then — for parents present in the index — a `[data-subcalls]` nest of `SubCallRow`s, each dispatching through the SAME `'conversation.chat.toolview'` keyed hole with `entryKey = sub-tool name` and the same `GenericToolCard` fallback. Identity with native rows holds by construction: a keyed registration (e.g. the bash sample) takes over sub-rows exactly as it takes over top-level rows, with zero registration changes. Running parents (`runningCalls`) nest their so-far dispatches the same way, so sub-rows stream in live during the run (PR1 logs each dispatch as it completes).
|
||||
- **`run_code` presentation**: a new `code` row variant (classifier `run_code → code`, `Code` title, `IconCodeOutline16`) summarizes with the model-authored `description` and expands to the program itself (monospace on the markdown code-block fill) rather than the args JSON envelope.
|
||||
- **Details panel**: `materialFor` falls through nodes → runningCalls → the dispatch index, so a selected sub-callId resolves to full args and complete output through the identical rendering path as a native settled call.
|
||||
|
||||
## Alternatives considered
|
||||
|
||||
**Sub-calls flat in the surface flow (fold them into `nodes`).** Rejected: misrepresents the transcript — the model made one call; nesting under the parent preserves the code↔calls association and keeps the fold's model-visible-order invariant untouched.
|
||||
|
||||
**Hidden until the parent row expands.** Rejected by product decision: the sub-calls ARE the story of a Code Mode turn; hiding them re-creates the opacity this feature removes. The parent's expand toggle reveals only the program.
|
||||
|
||||
**A dedicated sub-call row component.** Rejected: the whole point is identity with native rows; a parallel component would drift. The nest wrapper (indent + left edge) is the only sub-call-specific chrome.
|
||||
|
||||
## Consequences
|
||||
|
||||
Custom toolview registrations apply to sub-calls for free — and deliberately: there is no per-registration opt-out short of the component reading its own context, which no current consumer needs. Selection highlighting reaches nested rows through the same `selectedCallId` channel (group membership tests both levels). Trajectory/waterfall still render `run_code` as a single row — their sub-call spans are deferred to the PR that adds dispatch timing (start/end events), without which a waterfall span would be a lie. Fixture turn 64 (`?fixture`) plus the `code-mode-round` browser e2e (recorded real round, keyless replay) pin the full surface; the jsdom suites pin the slot dispatch, error states, details resolution, and index reference stability.
|
||||
@@ -0,0 +1,32 @@
|
||||
# Agent Note:Code Mode 的 chat 渲染——子调用作为父行之下的原生行
|
||||
|
||||
Status: implemented
|
||||
|
||||
[English](2026-07-26-code-mode-chat-subcall-rows.md) | 中文
|
||||
|
||||
> 范围:web chat 视图如何渲染一个 `run_code` 轮次,即 Code Mode UI 堆叠 PR(Pull Request)链的 client 侧一半,构建在[宿主侧基础](2026-07-26-code-dispatch-ui-foundation.md)之上(携带完整内容的 `tool/code-dispatch`、必填的 `description` 参数)。本篇所依托的 slot 模型归 [toolview 溶解](../architecture/2026-07-23-toolview-dissolution.md)所有。
|
||||
|
||||
## 问题
|
||||
|
||||
启用 Code Mode 后,chat 视图过去只显示一条不透明的 `run_code` 行:摘要就是原始程序文本,子调用则处处不可见。已敲定的产品要求恰恰相反:每个子调用都必须与原生工具调用渲染得*完全一致*——同样的行组件、同样的自定义注册、同样的 details 面板——同时 transcript(文本记录)仍须如实反映模型只发起了一次调用这一事实。
|
||||
|
||||
## 决策
|
||||
|
||||
**子调用是 surface 流之外单独索引的 `ToolResultNode`,经由与原生行相同的 keyed slot 渲染,以始终可见的方式嵌套在父行之下。**
|
||||
|
||||
- **数据层**:`Session.applyEventSideEffects` 把窗口内的每条 `tool/code-dispatch` 折入 `ConversationSnapshot.codeDispatches: ReadonlyMap<parentCallId, readonly CodeSubCall[]>`,其中 `CodeSubCall` 本身就是 `ToolResultNode`(子调用 id 充当 `callId`,已记录的参数经 JSON 字符串化写入 `call.argsRaw`,完整记录的 `content`/`isError` 原样携带)。live mux 帧与历史回放构建出同一份索引(`rebuildDerivedFromWindow` 先清空再重新推导;逐父级的写时复制(copy-on-write)数组保持快照引用 memo 稳定)。子调用永不进入 `nodes`——surface 流始终精确等于模型可见的轮次结构。该事件在 wire 消费方边界作结构性收窄(dsh-tools 的 host 类型进不了 client 程序——host/client 两侧的 `Context` 声明合并会冲突),姿态与所有跨 wire 载荷一致。
|
||||
- **渲染层**:`ChatView` 的 `CallRow` 先渲染父行,随后对索引中出现的父级渲染一组 `[data-subcalls]` 嵌套的 `SubCallRow`,每一行都经由同一个 `'conversation.chat.toolview'` keyed 孔位、以 `entryKey = sub-tool name` 分发,并共用同一个 `GenericToolCard` fallback。与原生行的同一性由构造保证:一个 keyed 注册(例如 bash 样例)接管子行与接管顶层行的方式完全相同,注册本身零改动。运行中的父调用(`runningCalls`)也以同样的方式嵌套目前已产生的分发,因此子行在运行期间实时流入(PR1 在每次分发完成时即记录该分发)。
|
||||
- **`run_code` 的呈现**:新增一种 `code` 行变体(分类器映射 `run_code → code`、标题 `Code`、图标 `IconCodeOutline16`),以模型撰写的 `description` 作摘要,展开后显示程序本身(在 markdown 代码块的填充底色上以等宽字体呈现),而非参数的 JSON 信封。
|
||||
- **details 面板**:`materialFor` 按 nodes → runningCalls → 分发索引的顺序逐级回落,因此被选中的子调用 callId 会经由与已完结的原生调用完全相同的渲染路径,解析出完整参数与完整输出。
|
||||
|
||||
## 曾考虑的替代方案
|
||||
|
||||
**把子调用平铺进 surface 流(折入 `nodes`)。** 否决:这会歪曲 transcript——模型只发起了一次调用;嵌套在父行之下既保住代码↔调用的关联,也让 fold 的模型可见顺序不变式原封不动。
|
||||
|
||||
**隐藏子调用,展开父行后才显示。** 由产品决策否决:子调用正是一个 Code Mode 轮次的核心内容;把它们藏起来,等于重新制造出本功能所要消除的那种不透明。父行的展开开关只用于显示程序本身。
|
||||
|
||||
**专用的子调用行组件。** 否决:本功能的全部要义就在于与原生行保持同一性;一个平行组件必然漂移。嵌套包装层(缩进 + 左侧边线)是子调用唯一的专属 chrome。
|
||||
|
||||
## 后果
|
||||
|
||||
自定义 toolview 注册免费适用于子调用——而且是刻意为之:不存在按注册粒度的 opt-out,唯一的出路是组件自行读取自身上下文,而当前没有任何消费方需要这么做。选中高亮经由同一条 `selectedCallId` 通道到达嵌套行(分组归属判断会同时检验两个层级)。trajectory/waterfall 仍把 `run_code` 渲染为单独一行——它们的子调用 span 推迟到增加分发计时(start/end 事件)的那个 PR;缺少计时,waterfall 上的 span 就是在撒谎。fixture(测试前置数据)的轮次 64(`?fixture`),加上 `code-mode-round` 浏览器 e2e(录制的真实 round、无密钥回放),共同锁定整个表面;jsdom 套件则锁定 slot 分发、错误状态、details 解析与索引引用稳定性。
|
||||
@@ -0,0 +1,188 @@
|
||||
// @vitest-environment jsdom
|
||||
// Code Mode fixture snapshot over the BUILT client graph (the workspace-flow
|
||||
// idiom: real bundles via AppWebEntry, keyless FixtureApiClient transport).
|
||||
// Opens the fixture history session and pins the run_code turn's rendering:
|
||||
// the code-variant parent row titled by the model-authored description, its
|
||||
// three always-visible nested sub-rows (bash through the sample registration,
|
||||
// read through GenericToolCard, the failing read wearing the error state),
|
||||
// the expanded program body, and details-panel resolution of a sub-callId.
|
||||
import { readFileSync } from 'node:fs'
|
||||
import { join } from 'node:path'
|
||||
import { act, cleanup, fireEvent, screen, waitFor, within } from '@testing-library/react'
|
||||
import { afterEach, beforeEach, expect, it, vi } from 'vitest'
|
||||
import type { WebBootEntry } from '@deepseek-ai/dsh-client-modules/client'
|
||||
import { AppWebEntry } from '@deepseek-ai/dsh-client-web'
|
||||
|
||||
const PLUGINS: readonly (WebBootEntry & { dir: string })[] = [
|
||||
{ id: '@deepseek-ai/dsh-client-connection', dir: 'connection', url: '/plugins/connection.js', rev: 'fx', inject: [], immediately: true },
|
||||
{ id: '@deepseek-ai/dsh-client-runtime', dir: 'runtime', url: '/plugins/runtime.js', rev: 'fx', inject: ['@deepseek-ai/dsh-client-connection'], immediately: true },
|
||||
{ id: '@deepseek-ai/dsh-client-ui-theme', dir: 'ui-theme', url: '/plugins/ui-theme.js', rev: 'fx', inject: [], immediately: true },
|
||||
{ id: '@deepseek-ai/dsh-client-locale', dir: 'locale', url: '/plugins/locale.js', rev: 'fx', inject: [], immediately: true },
|
||||
{ id: '@deepseek-ai/dsh-client-ui-layout', dir: 'ui-layout', url: '/plugins/ui-layout.js', rev: 'fx', inject: ['@deepseek-ai/dsh-client-runtime'] },
|
||||
{ id: '@deepseek-ai/dsh-client-ui-sidebar', dir: 'ui-sidebar', url: '/plugins/ui-sidebar.js', rev: 'fx', inject: ['@deepseek-ai/dsh-client-ui-layout'] },
|
||||
{ id: '@deepseek-ai/dsh-client-ui-conversation', dir: 'ui-conversation', url: '/plugins/ui-conversation.js', rev: 'fx', inject: ['@deepseek-ai/dsh-client-ui-layout'] },
|
||||
{
|
||||
id: '@deepseek-ai/dsh-client-ui-workspace',
|
||||
dir: 'ui-workspace',
|
||||
url: '/plugins/ui-workspace.js',
|
||||
rev: 'fx',
|
||||
inject: [
|
||||
'@deepseek-ai/dsh-client-runtime',
|
||||
'@deepseek-ai/dsh-client-ui-conversation',
|
||||
'@deepseek-ai/dsh-client-ui-sidebar',
|
||||
],
|
||||
},
|
||||
{ id: '@deepseek-ai/dsh-client-ui-trajectory', dir: 'ui-trajectory', url: '/plugins/ui-trajectory.js', rev: 'fx', inject: ['@deepseek-ai/dsh-client-ui-conversation'] },
|
||||
]
|
||||
|
||||
const bundles = new Map(PLUGINS.map(plugin => [
|
||||
plugin.url,
|
||||
readFileSync(join(process.cwd(), 'packages/client', plugin.dir, 'lib/client.js'), 'utf8'),
|
||||
]))
|
||||
|
||||
interface FixtureWindow extends Window {
|
||||
__DSH_BOOT__?: { rev: string; entries: WebBootEntry[] }
|
||||
__ModuleLoader__?: unknown
|
||||
}
|
||||
|
||||
class ResizeObserverStub {
|
||||
observe(): void {}
|
||||
disconnect(): void {}
|
||||
unobserve(): void {}
|
||||
}
|
||||
|
||||
const win = window as FixtureWindow
|
||||
let unmount: (() => void) | undefined
|
||||
|
||||
beforeEach(() => {
|
||||
localStorage.clear()
|
||||
document.title = 'DeepSeek Harness'
|
||||
vi.stubGlobal('ResizeObserver', ResizeObserverStub)
|
||||
vi.stubGlobal('requestAnimationFrame', (callback: FrameRequestCallback) =>
|
||||
setTimeout(() => { callback(0) }, 0) as unknown as number)
|
||||
vi.stubGlobal('cancelAnimationFrame', (id: number) => { clearTimeout(id) })
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
act(() => { unmount?.() })
|
||||
unmount = undefined
|
||||
cleanup()
|
||||
delete win.__DSH_BOOT__
|
||||
delete win.__ModuleLoader__
|
||||
delete (globalThis as Record<string, unknown>).__fxTiming
|
||||
document.body.innerHTML = ''
|
||||
document.head.querySelectorAll('style[data-plugin]').forEach((style) => { style.remove() })
|
||||
document.title = ''
|
||||
history.replaceState(null, '', '/')
|
||||
vi.unstubAllGlobals()
|
||||
})
|
||||
|
||||
/** Boot the complete built client graph against the populated fixture branch. */
|
||||
function boot(): void {
|
||||
history.replaceState(null, '', '/?fixture')
|
||||
const root = document.createElement('div')
|
||||
root.id = 'root'
|
||||
document.body.appendChild(root)
|
||||
win.__DSH_BOOT__ = { rev: 'fx', entries: PLUGINS.map(({ dir: _dir, ...plugin }) => plugin) }
|
||||
act(() => {
|
||||
const entry = new AppWebEntry(root, {
|
||||
fetchBundle: (url) => {
|
||||
const code = bundles.get(url)
|
||||
return code === undefined ? Promise.reject(new Error(`missing built bundle ${url}`)) : Promise.resolve(code)
|
||||
},
|
||||
executeBundle: (code) => { (0, eval)(code) },
|
||||
})
|
||||
void entry.run()
|
||||
unmount = () => { entry.dispose() }
|
||||
})
|
||||
}
|
||||
|
||||
/** Collapse decorative whitespace while preserving the text a user sees. */
|
||||
function visibleText(element: Element): string {
|
||||
return (element.textContent ?? '').replace(/\s+/g, ' ').trim()
|
||||
}
|
||||
|
||||
/** Open the fixture history session (the alpha log carrying the run_code turn) and scroll to its tail. */
|
||||
async function openFixtureSession(): Promise<void> {
|
||||
const tree = await screen.findByRole('tree', { name: 'Sessions' }, { timeout: 10_000 })
|
||||
const group = within(tree).getByText('4 sessions').closest<HTMLElement>('[role="treeitem"]')
|
||||
if (group === null) throw new Error('fixture Workspace group missing')
|
||||
if (group.getAttribute('aria-expanded') === 'false') {
|
||||
fireEvent.click(within(group).getByText('fixture'))
|
||||
await waitFor(() => {
|
||||
expect(within(tree).getByText('4 sessions').closest('[role="treeitem"]')?.getAttribute('aria-expanded')).toBe('true')
|
||||
})
|
||||
}
|
||||
const session = await within(tree).findByText('Fixture 历史会话')
|
||||
fireEvent.click(session)
|
||||
await waitFor(() => {
|
||||
expect(document.querySelector('[data-variant="code"]')).not.toBeNull()
|
||||
}, { timeout: 10_000 })
|
||||
}
|
||||
|
||||
it('renders the fixture run_code turn: code parent row, nested sub-rows, error state', async () => {
|
||||
boot()
|
||||
await openFixtureSession()
|
||||
|
||||
const codeRoot = document.querySelector('[data-variant="code"]')
|
||||
if (codeRoot === null) throw new Error('code-variant row missing')
|
||||
const nest = codeRoot.closest('[class*="callRow"]')?.querySelector('[data-subcalls]')
|
||||
if (nest === undefined || nest === null) throw new Error('sub-call nest missing under the code row')
|
||||
|
||||
expect({
|
||||
parentRow: visibleText(codeRoot),
|
||||
// The three sub-rows in dispatch order: bash rides the sample plugin's
|
||||
// keyed registration (the same one a native top-level bash row uses),
|
||||
// both reads ride GenericToolCard.
|
||||
bashSample: nest.querySelector('[data-sample="bash-global"]') !== null,
|
||||
subRows: [...nest.querySelectorAll(':scope > *')].map(visibleText),
|
||||
errorSubRow: nest.querySelector('[data-state="error"]') !== null,
|
||||
}).toMatchInlineSnapshot(`
|
||||
{
|
||||
"bashSample": true,
|
||||
"errorSubRow": true,
|
||||
"parentRow": "CodeRead the notes files and summarize",
|
||||
"subRows": [
|
||||
"$List notes",
|
||||
"Readnotes/demo.txt",
|
||||
"Readnotes/missing.txt",
|
||||
],
|
||||
}
|
||||
`)
|
||||
})
|
||||
|
||||
it('expands the code row into the program body and resolves a sub-row through the details panel', async () => {
|
||||
boot()
|
||||
await openFixtureSession()
|
||||
|
||||
// Expand: the leading control reveals the program verbatim.
|
||||
const codeRoot = document.querySelector('[data-variant="code"]')
|
||||
if (codeRoot === null) throw new Error('code-variant row missing')
|
||||
const toggle = codeRoot.querySelector('button[aria-expanded]')
|
||||
if (toggle === null) throw new Error('code row expand control missing')
|
||||
fireEvent.click(toggle)
|
||||
await screen.findByText(/const listing = await tools\.bash/)
|
||||
|
||||
// Sub-row click → details panel resolves the sub-callId with FULL output.
|
||||
const nest = document.querySelector('[data-subcalls]')
|
||||
if (nest === null) throw new Error('sub-call nest missing')
|
||||
const bashRow = nest.querySelector('[data-sample="bash-global"]')
|
||||
if (bashRow === null) throw new Error('bash sample sub-row missing')
|
||||
fireEvent.click(bashRow)
|
||||
const details = await screen.findByText('Input')
|
||||
const panel = details.closest('[class*="root"]')
|
||||
if (panel === null) throw new Error('details panel missing')
|
||||
expect({
|
||||
title: visibleText(within(panel as HTMLElement).getByText('bash')),
|
||||
inputEchoesArgs: visibleText(panel).includes('ls notes'),
|
||||
outputComplete: visibleText(panel).includes('demo.txt new-demo.txt')
|
||||
|| visibleText(panel).includes('demo.txt\nnew-demo.txt')
|
||||
|| (panel.textContent ?? '').includes('demo.txt\nnew-demo.txt'),
|
||||
}).toMatchInlineSnapshot(`
|
||||
{
|
||||
"inputEchoesArgs": true,
|
||||
"outputComplete": true,
|
||||
"title": "bash",
|
||||
}
|
||||
`)
|
||||
})
|
||||
@@ -0,0 +1,144 @@
|
||||
// Web e2e scenario: a Code Mode round trip. The scaffold boots the SAME
|
||||
// shipped tree with the tools row patched to mode: code (the run_code-only
|
||||
// wire), a real chromium sends a prompt engineered to elicit one run_code
|
||||
// program with several sub-calls, and the UI must render the code-variant
|
||||
// parent row with its always-visible nested sub-rows — each sub-row the same
|
||||
// component a native call renders through — plus details-panel resolution for
|
||||
// a clicked sub-row. Drive steps wait only on generic completion
|
||||
// (whenTurnSettled); assertion steps run in replay/refresh only.
|
||||
// Record: DSH_SNAPSHOT=record rewrites session.jsonl, then a keyless
|
||||
// DSH_SNAPSHOT=refresh regenerates ui.expected.md.
|
||||
import { readFile } from 'node:fs/promises'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
import type { Browser, Page } from 'playwright'
|
||||
import { chromium } from 'playwright'
|
||||
import { afterAll, beforeAll, describe, expect, it, onTestFailed } from 'vitest'
|
||||
import type { SessionEvent } from '@deepseek-ai/dsh-session'
|
||||
import {
|
||||
captureStableAria, compareOrRefreshGolden, fixtureUserPrompts,
|
||||
launchWebScaffold, recordFixture, watchConsole, webSnapshotMode, type WebScaffold,
|
||||
} from './scaffold.ts'
|
||||
import { saveFailureShot } from './support.ts'
|
||||
|
||||
const FIXTURE = fileURLToPath(new URL('./snapshots/code-mode-round/session.jsonl', import.meta.url))
|
||||
const UI_EXPECTED = fileURLToPath(new URL('./snapshots/code-mode-round/ui.expected.md', import.meta.url))
|
||||
const MODE = webSnapshotMode()
|
||||
|
||||
// The scenario's one drive prompt: elicits one program with a bash sub-call
|
||||
// and a failing read the program tolerates — the sub-row set the assertions
|
||||
// (and the PR gif) need. Never asserted against model prose.
|
||||
const PROMPT = 'Using ONE run_code program: run bash `echo CODE_ROUND_OK`, then read the file missing.txt '
|
||||
+ 'catching its error in the program. Return an object with both outcomes. Then reply DONE and stop.'
|
||||
|
||||
describe('web e2e: Code Mode round renders nested sub-calls', () => {
|
||||
let scaffold: WebScaffold
|
||||
let browser: Browser
|
||||
let page: Page
|
||||
let tripwire: ReturnType<typeof watchConsole>
|
||||
const sessionEvents: SessionEvent[] = []
|
||||
|
||||
beforeAll(async () => {
|
||||
scaffold = await launchWebScaffold({
|
||||
toolsMode: 'code',
|
||||
...(MODE === 'record' ? {} : { replayFixture: FIXTURE, paceMs: 15 }),
|
||||
})
|
||||
scaffold.ctx.on('session/event', (_session, event: SessionEvent) => { sessionEvents.push(event) })
|
||||
browser = await chromium.launch()
|
||||
page = await browser.newPage({ viewport: { width: 1680, height: 1000 } })
|
||||
tripwire = watchConsole(page)
|
||||
await page.goto(scaffold.baseUrl, { waitUntil: 'load' })
|
||||
await page.waitForSelector('[class*="frame"]', { timeout: 30_000 })
|
||||
}, 120_000)
|
||||
|
||||
afterAll(async () => {
|
||||
await browser?.close()
|
||||
await scaffold?.close()
|
||||
})
|
||||
|
||||
it('drives the recorded prompt to a settled turn (all modes)', async () => {
|
||||
onTestFailed(() => saveFailureShot(page, 'web-e2e-code-mode-drive'))
|
||||
if (MODE !== 'record') {
|
||||
// Drift guard: the committed fixture must carry exactly the drive prompt.
|
||||
expect(fixtureUserPrompts(await readFile(FIXTURE, 'utf8'))).toEqual([PROMPT])
|
||||
}
|
||||
const input = page.locator('textarea').first()
|
||||
await input.waitFor({ timeout: 10_000 })
|
||||
const settled = scaffold.whenTurnSettled()
|
||||
await input.fill(PROMPT)
|
||||
await input.press('Enter')
|
||||
const sessionId = await settled
|
||||
if (MODE === 'record') {
|
||||
await recordFixture(scaffold, sessionId, FIXTURE)
|
||||
}
|
||||
}, 200_000)
|
||||
|
||||
it.skipIf(MODE === 'record')('the durable log carries run_code with full-content sub-dispatches', () => {
|
||||
// Wire discipline: code mode collapsed the call surface to run_code.
|
||||
const calls = sessionEvents.filter(event => event.type === 'tool/call')
|
||||
expect(calls.length).toBeGreaterThanOrEqual(1)
|
||||
expect(new Set(calls.map(call => (call.data as { name: string }).name))).toEqual(new Set(['run_code']))
|
||||
// Sub-dispatches logged with the complete tool/result vocabulary.
|
||||
const dispatches = sessionEvents.filter(event => (event.type as string) === 'tool/code-dispatch')
|
||||
expect(dispatches.length).toBeGreaterThanOrEqual(2)
|
||||
for (const dispatch of dispatches) {
|
||||
const data = dispatch.data as unknown as {
|
||||
parentCallId: string
|
||||
subCallId: string
|
||||
name: string
|
||||
isError: boolean
|
||||
content: { type: string }[]
|
||||
}
|
||||
expect(data.subCallId.startsWith(`${data.parentCallId}:code:`)).toBe(true)
|
||||
expect(Array.isArray(data.content)).toBe(true)
|
||||
expect(typeof data.isError).toBe('boolean')
|
||||
}
|
||||
const bash = dispatches.find(dispatch => (dispatch.data as { name: string }).name === 'bash')
|
||||
expect(bash).toBeDefined()
|
||||
const bashContent = (bash!.data as { content: { type: string; text?: string }[] }).content
|
||||
expect(bashContent.filter(block => block.type === 'text').map(block => block.text).join('')).toContain('CODE_ROUND_OK')
|
||||
})
|
||||
|
||||
it.skipIf(MODE === 'record')('renders the code parent row with always-visible nested sub-rows', async () => {
|
||||
onTestFailed(() => saveFailureShot(page, 'web-e2e-code-mode-rows'))
|
||||
await expect.poll(() => page.getByText('DONE', { exact: true }).count(), { timeout: 15_000 }).toBeGreaterThanOrEqual(1)
|
||||
// The parent run_code row wears the code variant with the model-authored
|
||||
// description as its summary (the PR1 presentCall contract).
|
||||
const codeRow = page.locator('[data-variant="code"]').first()
|
||||
await codeRow.waitFor({ timeout: 10_000 })
|
||||
// Nested rows are visible WITHOUT any expand interaction, inside the
|
||||
// sub-call nest, each rendered by the same components as native rows:
|
||||
// the bash sub-call landed in the bash sample registration.
|
||||
const nest = page.locator('[data-subcalls]').first()
|
||||
await nest.waitFor({ timeout: 10_000 })
|
||||
expect(await nest.locator('[data-sample="bash-global"]').count()).toBeGreaterThanOrEqual(1)
|
||||
// The failing read sub-call wears the same error state a native failed
|
||||
// row wears (the recorded program tolerates a read of missing.txt).
|
||||
expect(await nest.locator('[data-state="error"]').count()).toBeGreaterThanOrEqual(1)
|
||||
}, 60_000)
|
||||
|
||||
it.skipIf(MODE === 'record')('a sub-row click opens the details panel on the sub-call material', async () => {
|
||||
onTestFailed(() => saveFailureShot(page, 'web-e2e-code-mode-details'))
|
||||
const nest = page.locator('[data-subcalls]').first()
|
||||
await nest.locator('[data-sample="bash-global"]').first().click()
|
||||
// The details column opens (width > 0) and shows the sub-call's complete
|
||||
// output — the full-content log contract, no truncation marker anywhere.
|
||||
await page.waitForFunction(() => {
|
||||
const frame = document.querySelector('[class*="frame"]')
|
||||
if (frame === null) return false
|
||||
return Number(getComputedStyle(frame).gridTemplateColumns.split(' ').pop()!.replace('px', '')) > 0
|
||||
}, undefined, { timeout: 10_000 })
|
||||
await expect.poll(() => page.getByText('CODE_ROUND_OK', { exact: false }).count(), { timeout: 5_000 })
|
||||
.toBeGreaterThanOrEqual(1)
|
||||
})
|
||||
|
||||
it.skipIf(MODE === 'record')('matches the conversation aria golden with stable anchors', async () => {
|
||||
onTestFailed(() => saveFailureShot(page, 'web-e2e-code-mode-aria'))
|
||||
const snapshot = await captureStableAria(page, '[class*="centerCol"]', scaffold.workspaceCwd)
|
||||
await compareOrRefreshGolden(UI_EXPECTED, snapshot, MODE)
|
||||
})
|
||||
|
||||
it.skipIf(MODE === 'record')('stayed clean: no page errors, no reconnect churn', () => {
|
||||
expect(tripwire.pageErrors).toEqual([])
|
||||
expect(tripwire.warnings).toEqual([])
|
||||
})
|
||||
})
|
||||
@@ -109,6 +109,13 @@ export interface LaunchOptions {
|
||||
replayOverride?: string
|
||||
/** Per-chunk replay pacing (ms) so the browser observes genuinely incremental SSE; replay/refresh only. */
|
||||
paceMs?: number
|
||||
/**
|
||||
* Tool presentation mode patched onto the shipped `tools` row (`code`
|
||||
* collapses the wire to run_code + the SDK prompt section). Omit for the
|
||||
* yml default. The code runtime row is always in the tree, so no extra
|
||||
* insertion is needed.
|
||||
*/
|
||||
toolsMode?: 'native' | 'code' | 'both'
|
||||
}
|
||||
|
||||
/** Dispose the booted tree and remove both owned temp roots, reporting every independent cleanup failure. */
|
||||
@@ -160,6 +167,7 @@ export async function launchWebScaffold(options: LaunchOptions = {}): Promise<We
|
||||
{ id: 'workspace-context', disabled: true },
|
||||
{ id: 'session-title-llm', disabled: true },
|
||||
{ id: 'webserver', config: { host: '127.0.0.1', port: 0, distIndex: DIST_INDEX } },
|
||||
...options.toolsMode === undefined ? [] : [{ id: 'tools', config: { mode: options.toolsMode } }],
|
||||
...mode === 'record' ? [] : [{ id: 'llm-deepseek', disabled: true }],
|
||||
]
|
||||
|
||||
|
||||
@@ -0,0 +1,293 @@
|
||||
{"type":"session","version":0,"id":"{{sessionId}}","createdAt":1785008259915,"cwd":"{{cwd}}/workspace"}
|
||||
{"type":"turn/start","seq":0,"time":1785008259926,"data":{"turn":1,"trigger":{"kind":"message","source":{"kind":"user","rpcId":"{{rpcId}}"}}}}
|
||||
{"type":"user/message","seq":1,"time":1785008259927,"data":{"content":[{"type":"text","text":"Using ONE run_code program: run bash `echo CODE_ROUND_OK`, then read the file missing.txt catching its error in the program. Return an object with both outcomes. Then reply DONE and stop."}],"source":{"kind":"user","rpcId":"{{rpcId}}"}},"surfaceOp":"append"}
|
||||
{"type":"session/title","seq":2,"time":1785008259933,"data":{"title":"Using ONE run_code program: run","messageSeqs":[1],"source":{"kind":"fallback"}}}
|
||||
{"type":"step/start","seq":3,"time":1785008259984,"data":{"turn":1,"step":1}}
|
||||
{"type":"request/header","seq":4,"time":1785008259985,"data":{"header":{"config":{"provider":"deepseek","model":"deepseek-v4-flash"},"system":"{{system}}","tools":"{{tools}}","messagePrefix":["{{messagePrefix}}"]},"reason":"initial"}}
|
||||
{"type":"assistant/chunk","seq":5,"time":1785008260650,"data":{"turn":1,"step":1,"chunk":{"type":"block-start","index":0,"blockType":"reasoning"}}}
|
||||
{"type":"assistant/chunk","seq":6,"time":1785008260651,"data":{"turn":1,"step":1,"chunk":{"type":"reasoning-delta","index":0,"text":"The"}}}
|
||||
{"type":"assistant/chunk","seq":7,"time":1785008260748,"data":{"turn":1,"step":1,"chunk":{"type":"reasoning-delta","index":0,"text":" user"}}}
|
||||
{"type":"assistant/chunk","seq":8,"time":1785008260773,"data":{"turn":1,"step":1,"chunk":{"type":"reasoning-delta","index":0,"text":" wants"}}}
|
||||
{"type":"assistant/chunk","seq":9,"time":1785008260773,"data":{"turn":1,"step":1,"chunk":{"type":"reasoning-delta","index":0,"text":" me"}}}
|
||||
{"type":"assistant/chunk","seq":10,"time":1785008260773,"data":{"turn":1,"step":1,"chunk":{"type":"reasoning-delta","index":0,"text":" to"}}}
|
||||
{"type":"assistant/chunk","seq":11,"time":1785008260773,"data":{"turn":1,"step":1,"chunk":{"type":"reasoning-delta","index":0,"text":" write"}}}
|
||||
{"type":"assistant/chunk","seq":12,"time":1785008260798,"data":{"turn":1,"step":1,"chunk":{"type":"reasoning-delta","index":0,"text":" a"}}}
|
||||
{"type":"assistant/chunk","seq":13,"time":1785008260799,"data":{"turn":1,"step":1,"chunk":{"type":"reasoning-delta","index":0,"text":" single"}}}
|
||||
{"type":"assistant/chunk","seq":14,"time":1785008260799,"data":{"turn":1,"step":1,"chunk":{"type":"reasoning-delta","index":0,"text":" run"}}}
|
||||
{"type":"assistant/chunk","seq":15,"time":1785008260799,"data":{"turn":1,"step":1,"chunk":{"type":"reasoning-delta","index":0,"text":"_code"}}}
|
||||
{"type":"assistant/chunk","seq":16,"time":1785008260799,"data":{"turn":1,"step":1,"chunk":{"type":"reasoning-delta","index":0,"text":" program"}}}
|
||||
{"type":"assistant/chunk","seq":17,"time":1785008260823,"data":{"turn":1,"step":1,"chunk":{"type":"reasoning-delta","index":0,"text":" that"}}}
|
||||
{"type":"assistant/chunk","seq":18,"time":1785008260824,"data":{"turn":1,"step":1,"chunk":{"type":"reasoning-delta","index":0,"text":":\n"}}}
|
||||
{"type":"assistant/chunk","seq":19,"time":1785008260824,"data":{"turn":1,"step":1,"chunk":{"type":"reasoning-delta","index":0,"text":"1"}}}
|
||||
{"type":"assistant/chunk","seq":20,"time":1785008260824,"data":{"turn":1,"step":1,"chunk":{"type":"reasoning-delta","index":0,"text":"."}}}
|
||||
{"type":"assistant/chunk","seq":21,"time":1785008260824,"data":{"turn":1,"step":1,"chunk":{"type":"reasoning-delta","index":0,"text":" Runs"}}}
|
||||
{"type":"assistant/chunk","seq":22,"time":1785008260824,"data":{"turn":1,"step":1,"chunk":{"type":"reasoning-delta","index":0,"text":" bash"}}}
|
||||
{"type":"assistant/chunk","seq":23,"time":1785008260848,"data":{"turn":1,"step":1,"chunk":{"type":"reasoning-delta","index":0,"text":" to"}}}
|
||||
{"type":"assistant/chunk","seq":24,"time":1785008260874,"data":{"turn":1,"step":1,"chunk":{"type":"reasoning-delta","index":0,"text":" echo"}}}
|
||||
{"type":"assistant/chunk","seq":25,"time":1785008260874,"data":{"turn":1,"step":1,"chunk":{"type":"reasoning-delta","index":0,"text":" \""}}}
|
||||
{"type":"assistant/chunk","seq":26,"time":1785008260874,"data":{"turn":1,"step":1,"chunk":{"type":"reasoning-delta","index":0,"text":"CODE"}}}
|
||||
{"type":"assistant/chunk","seq":27,"time":1785008260875,"data":{"turn":1,"step":1,"chunk":{"type":"reasoning-delta","index":0,"text":"_RO"}}}
|
||||
{"type":"assistant/chunk","seq":28,"time":1785008260900,"data":{"turn":1,"step":1,"chunk":{"type":"reasoning-delta","index":0,"text":"UND"}}}
|
||||
{"type":"assistant/chunk","seq":29,"time":1785008260900,"data":{"turn":1,"step":1,"chunk":{"type":"reasoning-delta","index":0,"text":"_OK"}}}
|
||||
{"type":"assistant/chunk","seq":30,"time":1785008260900,"data":{"turn":1,"step":1,"chunk":{"type":"reasoning-delta","index":0,"text":"\"\n"}}}
|
||||
{"type":"assistant/chunk","seq":31,"time":1785008260900,"data":{"turn":1,"step":1,"chunk":{"type":"reasoning-delta","index":0,"text":"2"}}}
|
||||
{"type":"assistant/chunk","seq":32,"time":1785008260900,"data":{"turn":1,"step":1,"chunk":{"type":"reasoning-delta","index":0,"text":"."}}}
|
||||
{"type":"assistant/chunk","seq":33,"time":1785008260901,"data":{"turn":1,"step":1,"chunk":{"type":"reasoning-delta","index":0,"text":" Reads"}}}
|
||||
{"type":"assistant/chunk","seq":34,"time":1785008260925,"data":{"turn":1,"step":1,"chunk":{"type":"reasoning-delta","index":0,"text":" the"}}}
|
||||
{"type":"assistant/chunk","seq":35,"time":1785008260951,"data":{"turn":1,"step":1,"chunk":{"type":"reasoning-delta","index":0,"text":" file"}}}
|
||||
{"type":"assistant/chunk","seq":36,"time":1785008260951,"data":{"turn":1,"step":1,"chunk":{"type":"reasoning-delta","index":0,"text":" missing"}}}
|
||||
{"type":"assistant/chunk","seq":37,"time":1785008260976,"data":{"turn":1,"step":1,"chunk":{"type":"reasoning-delta","index":0,"text":".txt"}}}
|
||||
{"type":"assistant/chunk","seq":38,"time":1785008260976,"data":{"turn":1,"step":1,"chunk":{"type":"reasoning-delta","index":0,"text":","}}}
|
||||
{"type":"assistant/chunk","seq":39,"time":1785008260976,"data":{"turn":1,"step":1,"chunk":{"type":"reasoning-delta","index":0,"text":" catching"}}}
|
||||
{"type":"assistant/chunk","seq":40,"time":1785008260977,"data":{"turn":1,"step":1,"chunk":{"type":"reasoning-delta","index":0,"text":" its"}}}
|
||||
{"type":"assistant/chunk","seq":41,"time":1785008261001,"data":{"turn":1,"step":1,"chunk":{"type":"reasoning-delta","index":0,"text":" error"}}}
|
||||
{"type":"assistant/chunk","seq":42,"time":1785008261002,"data":{"turn":1,"step":1,"chunk":{"type":"reasoning-delta","index":0,"text":"\n"}}}
|
||||
{"type":"assistant/chunk","seq":43,"time":1785008261002,"data":{"turn":1,"step":1,"chunk":{"type":"reasoning-delta","index":0,"text":"3"}}}
|
||||
{"type":"assistant/chunk","seq":44,"time":1785008261002,"data":{"turn":1,"step":1,"chunk":{"type":"reasoning-delta","index":0,"text":"."}}}
|
||||
{"type":"assistant/chunk","seq":45,"time":1785008261002,"data":{"turn":1,"step":1,"chunk":{"type":"reasoning-delta","index":0,"text":" Returns"}}}
|
||||
{"type":"assistant/chunk","seq":46,"time":1785008261027,"data":{"turn":1,"step":1,"chunk":{"type":"reasoning-delta","index":0,"text":" an"}}}
|
||||
{"type":"assistant/chunk","seq":47,"time":1785008261028,"data":{"turn":1,"step":1,"chunk":{"type":"reasoning-delta","index":0,"text":" object"}}}
|
||||
{"type":"assistant/chunk","seq":48,"time":1785008261028,"data":{"turn":1,"step":1,"chunk":{"type":"reasoning-delta","index":0,"text":" with"}}}
|
||||
{"type":"assistant/chunk","seq":49,"time":1785008261028,"data":{"turn":1,"step":1,"chunk":{"type":"reasoning-delta","index":0,"text":" both"}}}
|
||||
{"type":"assistant/chunk","seq":50,"time":1785008261028,"data":{"turn":1,"step":1,"chunk":{"type":"reasoning-delta","index":0,"text":" outcomes"}}}
|
||||
{"type":"assistant/chunk","seq":51,"time":1785008261053,"data":{"turn":1,"step":1,"chunk":{"type":"reasoning-delta","index":0,"text":"\n"}}}
|
||||
{"type":"assistant/chunk","seq":52,"time":1785008261053,"data":{"turn":1,"step":1,"chunk":{"type":"reasoning-delta","index":0,"text":"4"}}}
|
||||
{"type":"assistant/chunk","seq":53,"time":1785008261053,"data":{"turn":1,"step":1,"chunk":{"type":"reasoning-delta","index":0,"text":"."}}}
|
||||
{"type":"assistant/chunk","seq":54,"time":1785008261054,"data":{"turn":1,"step":1,"chunk":{"type":"reasoning-delta","index":0,"text":" Then"}}}
|
||||
{"type":"assistant/chunk","seq":55,"time":1785008261078,"data":{"turn":1,"step":1,"chunk":{"type":"reasoning-delta","index":0,"text":" replies"}}}
|
||||
{"type":"assistant/chunk","seq":56,"time":1785008261079,"data":{"turn":1,"step":1,"chunk":{"type":"reasoning-delta","index":0,"text":" D"}}}
|
||||
{"type":"assistant/chunk","seq":57,"time":1785008261104,"data":{"turn":1,"step":1,"chunk":{"type":"reasoning-delta","index":0,"text":"ONE"}}}
|
||||
{"type":"assistant/chunk","seq":58,"time":1785008261129,"data":{"turn":1,"step":1,"chunk":{"type":"reasoning-delta","index":0,"text":"\n\n"}}}
|
||||
{"type":"assistant/chunk","seq":59,"time":1785008261130,"data":{"turn":1,"step":1,"chunk":{"type":"reasoning-delta","index":0,"text":"Let"}}}
|
||||
{"type":"assistant/chunk","seq":60,"time":1785008261130,"data":{"turn":1,"step":1,"chunk":{"type":"reasoning-delta","index":0,"text":" me"}}}
|
||||
{"type":"assistant/chunk","seq":61,"time":1785008261130,"data":{"turn":1,"step":1,"chunk":{"type":"reasoning-delta","index":0,"text":" write"}}}
|
||||
{"type":"assistant/chunk","seq":62,"time":1785008261154,"data":{"turn":1,"step":1,"chunk":{"type":"reasoning-delta","index":0,"text":" this"}}}
|
||||
{"type":"assistant/chunk","seq":63,"time":1785008261181,"data":{"turn":1,"step":1,"chunk":{"type":"reasoning-delta","index":0,"text":" program"}}}
|
||||
{"type":"assistant/chunk","seq":64,"time":1785008261181,"data":{"turn":1,"step":1,"chunk":{"type":"reasoning-delta","index":0,"text":"."}}}
|
||||
{"type":"assistant/chunk","seq":65,"time":1785008261257,"data":{"turn":1,"step":1,"chunk":{"type":"block-start","index":1,"blockType":"tool-call"}}}
|
||||
{"type":"assistant/chunk","seq":66,"time":1785008261257,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":1,"id":"call_00_vDInl6gU7H4iAs1w3ToB5854","name":"run_code","argumentsDelta":""}}}
|
||||
{"type":"assistant/chunk","seq":67,"time":1785008261257,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":1,"id":"call_00_vDInl6gU7H4iAs1w3ToB5854","name":"run_code","argumentsDelta":"{"}}}
|
||||
{"type":"assistant/chunk","seq":68,"time":1785008261257,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":1,"id":"call_00_vDInl6gU7H4iAs1w3ToB5854","name":"run_code","argumentsDelta":"\""}}}
|
||||
{"type":"assistant/chunk","seq":69,"time":1785008261282,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":1,"id":"call_00_vDInl6gU7H4iAs1w3ToB5854","name":"run_code","argumentsDelta":"description"}}}
|
||||
{"type":"assistant/chunk","seq":70,"time":1785008261282,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":1,"id":"call_00_vDInl6gU7H4iAs1w3ToB5854","name":"run_code","argumentsDelta":"\""}}}
|
||||
{"type":"assistant/chunk","seq":71,"time":1785008261282,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":1,"id":"call_00_vDInl6gU7H4iAs1w3ToB5854","name":"run_code","argumentsDelta":": "}}}
|
||||
{"type":"assistant/chunk","seq":72,"time":1785008261282,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":1,"id":"call_00_vDInl6gU7H4iAs1w3ToB5854","name":"run_code","argumentsDelta":"\""}}}
|
||||
{"type":"assistant/chunk","seq":73,"time":1785008261343,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":1,"id":"call_00_vDInl6gU7H4iAs1w3ToB5854","name":"run_code","argumentsDelta":"Run"}}}
|
||||
{"type":"assistant/chunk","seq":74,"time":1785008261344,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":1,"id":"call_00_vDInl6gU7H4iAs1w3ToB5854","name":"run_code","argumentsDelta":" bash"}}}
|
||||
{"type":"assistant/chunk","seq":75,"time":1785008261344,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":1,"id":"call_00_vDInl6gU7H4iAs1w3ToB5854","name":"run_code","argumentsDelta":" echo"}}}
|
||||
{"type":"assistant/chunk","seq":76,"time":1785008261357,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":1,"id":"call_00_vDInl6gU7H4iAs1w3ToB5854","name":"run_code","argumentsDelta":" and"}}}
|
||||
{"type":"assistant/chunk","seq":77,"time":1785008261357,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":1,"id":"call_00_vDInl6gU7H4iAs1w3ToB5854","name":"run_code","argumentsDelta":" read"}}}
|
||||
{"type":"assistant/chunk","seq":78,"time":1785008261357,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":1,"id":"call_00_vDInl6gU7H4iAs1w3ToB5854","name":"run_code","argumentsDelta":" missing"}}}
|
||||
{"type":"assistant/chunk","seq":79,"time":1785008261383,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":1,"id":"call_00_vDInl6gU7H4iAs1w3ToB5854","name":"run_code","argumentsDelta":".txt"}}}
|
||||
{"type":"assistant/chunk","seq":80,"time":1785008261384,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":1,"id":"call_00_vDInl6gU7H4iAs1w3ToB5854","name":"run_code","argumentsDelta":" with"}}}
|
||||
{"type":"assistant/chunk","seq":81,"time":1785008261384,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":1,"id":"call_00_vDInl6gU7H4iAs1w3ToB5854","name":"run_code","argumentsDelta":" error"}}}
|
||||
{"type":"assistant/chunk","seq":82,"time":1785008261409,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":1,"id":"call_00_vDInl6gU7H4iAs1w3ToB5854","name":"run_code","argumentsDelta":" handling"}}}
|
||||
{"type":"assistant/chunk","seq":83,"time":1785008261434,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":1,"id":"call_00_vDInl6gU7H4iAs1w3ToB5854","name":"run_code","argumentsDelta":"\""}}}
|
||||
{"type":"assistant/chunk","seq":84,"time":1785008261460,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":1,"id":"call_00_vDInl6gU7H4iAs1w3ToB5854","name":"run_code","argumentsDelta":", "}}}
|
||||
{"type":"assistant/chunk","seq":85,"time":1785008261461,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":1,"id":"call_00_vDInl6gU7H4iAs1w3ToB5854","name":"run_code","argumentsDelta":"\""}}}
|
||||
{"type":"assistant/chunk","seq":86,"time":1785008261461,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":1,"id":"call_00_vDInl6gU7H4iAs1w3ToB5854","name":"run_code","argumentsDelta":"code"}}}
|
||||
{"type":"assistant/chunk","seq":87,"time":1785008261461,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":1,"id":"call_00_vDInl6gU7H4iAs1w3ToB5854","name":"run_code","argumentsDelta":"\""}}}
|
||||
{"type":"assistant/chunk","seq":88,"time":1785008261461,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":1,"id":"call_00_vDInl6gU7H4iAs1w3ToB5854","name":"run_code","argumentsDelta":": "}}}
|
||||
{"type":"assistant/chunk","seq":89,"time":1785008261486,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":1,"id":"call_00_vDInl6gU7H4iAs1w3ToB5854","name":"run_code","argumentsDelta":"\""}}}
|
||||
{"type":"assistant/chunk","seq":90,"time":1785008261486,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":1,"id":"call_00_vDInl6gU7H4iAs1w3ToB5854","name":"run_code","argumentsDelta":"\\n"}}}
|
||||
{"type":"assistant/chunk","seq":91,"time":1785008261486,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":1,"id":"call_00_vDInl6gU7H4iAs1w3ToB5854","name":"run_code","argumentsDelta":"const"}}}
|
||||
{"type":"assistant/chunk","seq":92,"time":1785008261486,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":1,"id":"call_00_vDInl6gU7H4iAs1w3ToB5854","name":"run_code","argumentsDelta":" bash"}}}
|
||||
{"type":"assistant/chunk","seq":93,"time":1785008261486,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":1,"id":"call_00_vDInl6gU7H4iAs1w3ToB5854","name":"run_code","argumentsDelta":"Result"}}}
|
||||
{"type":"assistant/chunk","seq":94,"time":1785008261511,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":1,"id":"call_00_vDInl6gU7H4iAs1w3ToB5854","name":"run_code","argumentsDelta":" ="}}}
|
||||
{"type":"assistant/chunk","seq":95,"time":1785008261511,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":1,"id":"call_00_vDInl6gU7H4iAs1w3ToB5854","name":"run_code","argumentsDelta":" await"}}}
|
||||
{"type":"assistant/chunk","seq":96,"time":1785008261511,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":1,"id":"call_00_vDInl6gU7H4iAs1w3ToB5854","name":"run_code","argumentsDelta":" tools"}}}
|
||||
{"type":"assistant/chunk","seq":97,"time":1785008261511,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":1,"id":"call_00_vDInl6gU7H4iAs1w3ToB5854","name":"run_code","argumentsDelta":".b"}}}
|
||||
{"type":"assistant/chunk","seq":98,"time":1785008261539,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":1,"id":"call_00_vDInl6gU7H4iAs1w3ToB5854","name":"run_code","argumentsDelta":"ash"}}}
|
||||
{"type":"assistant/chunk","seq":99,"time":1785008261539,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":1,"id":"call_00_vDInl6gU7H4iAs1w3ToB5854","name":"run_code","argumentsDelta":"({\\n"}}}
|
||||
{"type":"assistant/chunk","seq":100,"time":1785008261565,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":1,"id":"call_00_vDInl6gU7H4iAs1w3ToB5854","name":"run_code","argumentsDelta":" "}}}
|
||||
{"type":"assistant/chunk","seq":101,"time":1785008261565,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":1,"id":"call_00_vDInl6gU7H4iAs1w3ToB5854","name":"run_code","argumentsDelta":" command"}}}
|
||||
{"type":"assistant/chunk","seq":102,"time":1785008261565,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":1,"id":"call_00_vDInl6gU7H4iAs1w3ToB5854","name":"run_code","argumentsDelta":":"}}}
|
||||
{"type":"assistant/chunk","seq":103,"time":1785008261566,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":1,"id":"call_00_vDInl6gU7H4iAs1w3ToB5854","name":"run_code","argumentsDelta":" \\\""}}}
|
||||
{"type":"assistant/chunk","seq":104,"time":1785008261566,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":1,"id":"call_00_vDInl6gU7H4iAs1w3ToB5854","name":"run_code","argumentsDelta":"echo"}}}
|
||||
{"type":"assistant/chunk","seq":105,"time":1785008261566,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":1,"id":"call_00_vDInl6gU7H4iAs1w3ToB5854","name":"run_code","argumentsDelta":" CODE"}}}
|
||||
{"type":"assistant/chunk","seq":106,"time":1785008261591,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":1,"id":"call_00_vDInl6gU7H4iAs1w3ToB5854","name":"run_code","argumentsDelta":"_RO"}}}
|
||||
{"type":"assistant/chunk","seq":107,"time":1785008261592,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":1,"id":"call_00_vDInl6gU7H4iAs1w3ToB5854","name":"run_code","argumentsDelta":"UND"}}}
|
||||
{"type":"assistant/chunk","seq":108,"time":1785008261592,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":1,"id":"call_00_vDInl6gU7H4iAs1w3ToB5854","name":"run_code","argumentsDelta":"_OK"}}}
|
||||
{"type":"assistant/chunk","seq":109,"time":1785008261592,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":1,"id":"call_00_vDInl6gU7H4iAs1w3ToB5854","name":"run_code","argumentsDelta":"\\\",\\n"}}}
|
||||
{"type":"assistant/chunk","seq":110,"time":1785008261592,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":1,"id":"call_00_vDInl6gU7H4iAs1w3ToB5854","name":"run_code","argumentsDelta":" "}}}
|
||||
{"type":"assistant/chunk","seq":111,"time":1785008261592,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":1,"id":"call_00_vDInl6gU7H4iAs1w3ToB5854","name":"run_code","argumentsDelta":" description"}}}
|
||||
{"type":"assistant/chunk","seq":112,"time":1785008261624,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":1,"id":"call_00_vDInl6gU7H4iAs1w3ToB5854","name":"run_code","argumentsDelta":":"}}}
|
||||
{"type":"assistant/chunk","seq":113,"time":1785008261624,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":1,"id":"call_00_vDInl6gU7H4iAs1w3ToB5854","name":"run_code","argumentsDelta":" \\\""}}}
|
||||
{"type":"assistant/chunk","seq":114,"time":1785008261624,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":1,"id":"call_00_vDInl6gU7H4iAs1w3ToB5854","name":"run_code","argumentsDelta":"E"}}}
|
||||
{"type":"assistant/chunk","seq":115,"time":1785008261648,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":1,"id":"call_00_vDInl6gU7H4iAs1w3ToB5854","name":"run_code","argumentsDelta":"cho"}}}
|
||||
{"type":"assistant/chunk","seq":116,"time":1785008261648,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":1,"id":"call_00_vDInl6gU7H4iAs1w3ToB5854","name":"run_code","argumentsDelta":" CODE"}}}
|
||||
{"type":"assistant/chunk","seq":117,"time":1785008261648,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":1,"id":"call_00_vDInl6gU7H4iAs1w3ToB5854","name":"run_code","argumentsDelta":"_RO"}}}
|
||||
{"type":"assistant/chunk","seq":118,"time":1785008261648,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":1,"id":"call_00_vDInl6gU7H4iAs1w3ToB5854","name":"run_code","argumentsDelta":"UND"}}}
|
||||
{"type":"assistant/chunk","seq":119,"time":1785008261648,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":1,"id":"call_00_vDInl6gU7H4iAs1w3ToB5854","name":"run_code","argumentsDelta":"_OK"}}}
|
||||
{"type":"assistant/chunk","seq":120,"time":1785008261649,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":1,"id":"call_00_vDInl6gU7H4iAs1w3ToB5854","name":"run_code","argumentsDelta":"\\\"\\n"}}}
|
||||
{"type":"assistant/chunk","seq":121,"time":1785008261675,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":1,"id":"call_00_vDInl6gU7H4iAs1w3ToB5854","name":"run_code","argumentsDelta":"});\\n\\n"}}}
|
||||
{"type":"assistant/chunk","seq":122,"time":1785008261675,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":1,"id":"call_00_vDInl6gU7H4iAs1w3ToB5854","name":"run_code","argumentsDelta":"let"}}}
|
||||
{"type":"assistant/chunk","seq":123,"time":1785008261692,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":1,"id":"call_00_vDInl6gU7H4iAs1w3ToB5854","name":"run_code","argumentsDelta":" read"}}}
|
||||
{"type":"assistant/chunk","seq":124,"time":1785008261693,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":1,"id":"call_00_vDInl6gU7H4iAs1w3ToB5854","name":"run_code","argumentsDelta":"Result"}}}
|
||||
{"type":"assistant/chunk","seq":125,"time":1785008261693,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":1,"id":"call_00_vDInl6gU7H4iAs1w3ToB5854","name":"run_code","argumentsDelta":";\\n"}}}
|
||||
{"type":"assistant/chunk","seq":126,"time":1785008261719,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":1,"id":"call_00_vDInl6gU7H4iAs1w3ToB5854","name":"run_code","argumentsDelta":"try"}}}
|
||||
{"type":"assistant/chunk","seq":127,"time":1785008261719,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":1,"id":"call_00_vDInl6gU7H4iAs1w3ToB5854","name":"run_code","argumentsDelta":" {\\n"}}}
|
||||
{"type":"assistant/chunk","seq":128,"time":1785008261720,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":1,"id":"call_00_vDInl6gU7H4iAs1w3ToB5854","name":"run_code","argumentsDelta":" "}}}
|
||||
{"type":"assistant/chunk","seq":129,"time":1785008261720,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":1,"id":"call_00_vDInl6gU7H4iAs1w3ToB5854","name":"run_code","argumentsDelta":" read"}}}
|
||||
{"type":"assistant/chunk","seq":130,"time":1785008261720,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":1,"id":"call_00_vDInl6gU7H4iAs1w3ToB5854","name":"run_code","argumentsDelta":"Result"}}}
|
||||
{"type":"assistant/chunk","seq":131,"time":1785008261720,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":1,"id":"call_00_vDInl6gU7H4iAs1w3ToB5854","name":"run_code","argumentsDelta":" ="}}}
|
||||
{"type":"assistant/chunk","seq":132,"time":1785008261743,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":1,"id":"call_00_vDInl6gU7H4iAs1w3ToB5854","name":"run_code","argumentsDelta":" await"}}}
|
||||
{"type":"assistant/chunk","seq":133,"time":1785008261744,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":1,"id":"call_00_vDInl6gU7H4iAs1w3ToB5854","name":"run_code","argumentsDelta":" tools"}}}
|
||||
{"type":"assistant/chunk","seq":134,"time":1785008261744,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":1,"id":"call_00_vDInl6gU7H4iAs1w3ToB5854","name":"run_code","argumentsDelta":".read"}}}
|
||||
{"type":"assistant/chunk","seq":135,"time":1785008261744,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":1,"id":"call_00_vDInl6gU7H4iAs1w3ToB5854","name":"run_code","argumentsDelta":"({\\n"}}}
|
||||
{"type":"assistant/chunk","seq":136,"time":1785008261768,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":1,"id":"call_00_vDInl6gU7H4iAs1w3ToB5854","name":"run_code","argumentsDelta":" "}}}
|
||||
{"type":"assistant/chunk","seq":137,"time":1785008261769,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":1,"id":"call_00_vDInl6gU7H4iAs1w3ToB5854","name":"run_code","argumentsDelta":" file"}}}
|
||||
{"type":"assistant/chunk","seq":138,"time":1785008261769,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":1,"id":"call_00_vDInl6gU7H4iAs1w3ToB5854","name":"run_code","argumentsDelta":"_path"}}}
|
||||
{"type":"assistant/chunk","seq":139,"time":1785008261769,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":1,"id":"call_00_vDInl6gU7H4iAs1w3ToB5854","name":"run_code","argumentsDelta":":"}}}
|
||||
{"type":"assistant/chunk","seq":140,"time":1785008261769,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":1,"id":"call_00_vDInl6gU7H4iAs1w3ToB5854","name":"run_code","argumentsDelta":" \\\""}}}
|
||||
{"type":"assistant/chunk","seq":141,"time":1785008261769,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":1,"id":"call_00_vDInl6gU7H4iAs1w3ToB5854","name":"run_code","argumentsDelta":"missing"}}}
|
||||
{"type":"assistant/chunk","seq":142,"time":1785008261794,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":1,"id":"call_00_vDInl6gU7H4iAs1w3ToB5854","name":"run_code","argumentsDelta":".txt"}}}
|
||||
{"type":"assistant/chunk","seq":143,"time":1785008261794,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":1,"id":"call_00_vDInl6gU7H4iAs1w3ToB5854","name":"run_code","argumentsDelta":"\\\"\\n"}}}
|
||||
{"type":"assistant/chunk","seq":144,"time":1785008261794,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":1,"id":"call_00_vDInl6gU7H4iAs1w3ToB5854","name":"run_code","argumentsDelta":" "}}}
|
||||
{"type":"assistant/chunk","seq":145,"time":1785008261794,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":1,"id":"call_00_vDInl6gU7H4iAs1w3ToB5854","name":"run_code","argumentsDelta":" });\\n"}}}
|
||||
{"type":"assistant/chunk","seq":146,"time":1785008261794,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":1,"id":"call_00_vDInl6gU7H4iAs1w3ToB5854","name":"run_code","argumentsDelta":"}"}}}
|
||||
{"type":"assistant/chunk","seq":147,"time":1785008261794,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":1,"id":"call_00_vDInl6gU7H4iAs1w3ToB5854","name":"run_code","argumentsDelta":" catch"}}}
|
||||
{"type":"assistant/chunk","seq":148,"time":1785008261819,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":1,"id":"call_00_vDInl6gU7H4iAs1w3ToB5854","name":"run_code","argumentsDelta":" ("}}}
|
||||
{"type":"assistant/chunk","seq":149,"time":1785008261819,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":1,"id":"call_00_vDInl6gU7H4iAs1w3ToB5854","name":"run_code","argumentsDelta":"e"}}}
|
||||
{"type":"assistant/chunk","seq":150,"time":1785008261819,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":1,"id":"call_00_vDInl6gU7H4iAs1w3ToB5854","name":"run_code","argumentsDelta":")"}}}
|
||||
{"type":"assistant/chunk","seq":151,"time":1785008261820,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":1,"id":"call_00_vDInl6gU7H4iAs1w3ToB5854","name":"run_code","argumentsDelta":" {\\n"}}}
|
||||
{"type":"assistant/chunk","seq":152,"time":1785008261820,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":1,"id":"call_00_vDInl6gU7H4iAs1w3ToB5854","name":"run_code","argumentsDelta":" "}}}
|
||||
{"type":"assistant/chunk","seq":153,"time":1785008261820,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":1,"id":"call_00_vDInl6gU7H4iAs1w3ToB5854","name":"run_code","argumentsDelta":" read"}}}
|
||||
{"type":"assistant/chunk","seq":154,"time":1785008261844,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":1,"id":"call_00_vDInl6gU7H4iAs1w3ToB5854","name":"run_code","argumentsDelta":"Result"}}}
|
||||
{"type":"assistant/chunk","seq":155,"time":1785008261845,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":1,"id":"call_00_vDInl6gU7H4iAs1w3ToB5854","name":"run_code","argumentsDelta":" ="}}}
|
||||
{"type":"assistant/chunk","seq":156,"time":1785008261845,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":1,"id":"call_00_vDInl6gU7H4iAs1w3ToB5854","name":"run_code","argumentsDelta":" {\\n"}}}
|
||||
{"type":"assistant/chunk","seq":157,"time":1785008261845,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":1,"id":"call_00_vDInl6gU7H4iAs1w3ToB5854","name":"run_code","argumentsDelta":" "}}}
|
||||
{"type":"assistant/chunk","seq":158,"time":1785008261845,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":1,"id":"call_00_vDInl6gU7H4iAs1w3ToB5854","name":"run_code","argumentsDelta":" error"}}}
|
||||
{"type":"assistant/chunk","seq":159,"time":1785008261879,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":1,"id":"call_00_vDInl6gU7H4iAs1w3ToB5854","name":"run_code","argumentsDelta":":"}}}
|
||||
{"type":"assistant/chunk","seq":160,"time":1785008261879,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":1,"id":"call_00_vDInl6gU7H4iAs1w3ToB5854","name":"run_code","argumentsDelta":" true"}}}
|
||||
{"type":"assistant/chunk","seq":161,"time":1785008261895,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":1,"id":"call_00_vDInl6gU7H4iAs1w3ToB5854","name":"run_code","argumentsDelta":",\\n"}}}
|
||||
{"type":"assistant/chunk","seq":162,"time":1785008261895,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":1,"id":"call_00_vDInl6gU7H4iAs1w3ToB5854","name":"run_code","argumentsDelta":" "}}}
|
||||
{"type":"assistant/chunk","seq":163,"time":1785008261895,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":1,"id":"call_00_vDInl6gU7H4iAs1w3ToB5854","name":"run_code","argumentsDelta":" tool"}}}
|
||||
{"type":"assistant/chunk","seq":164,"time":1785008261895,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":1,"id":"call_00_vDInl6gU7H4iAs1w3ToB5854","name":"run_code","argumentsDelta":"Name"}}}
|
||||
{"type":"assistant/chunk","seq":165,"time":1785008261895,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":1,"id":"call_00_vDInl6gU7H4iAs1w3ToB5854","name":"run_code","argumentsDelta":":"}}}
|
||||
{"type":"assistant/chunk","seq":166,"time":1785008261896,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":1,"id":"call_00_vDInl6gU7H4iAs1w3ToB5854","name":"run_code","argumentsDelta":" e"}}}
|
||||
{"type":"assistant/chunk","seq":167,"time":1785008261921,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":1,"id":"call_00_vDInl6gU7H4iAs1w3ToB5854","name":"run_code","argumentsDelta":".t"}}}
|
||||
{"type":"assistant/chunk","seq":168,"time":1785008261921,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":1,"id":"call_00_vDInl6gU7H4iAs1w3ToB5854","name":"run_code","argumentsDelta":"ool"}}}
|
||||
{"type":"assistant/chunk","seq":169,"time":1785008261921,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":1,"id":"call_00_vDInl6gU7H4iAs1w3ToB5854","name":"run_code","argumentsDelta":"Name"}}}
|
||||
{"type":"assistant/chunk","seq":170,"time":1785008261921,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":1,"id":"call_00_vDInl6gU7H4iAs1w3ToB5854","name":"run_code","argumentsDelta":",\\n"}}}
|
||||
{"type":"assistant/chunk","seq":171,"time":1785008261921,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":1,"id":"call_00_vDInl6gU7H4iAs1w3ToB5854","name":"run_code","argumentsDelta":" "}}}
|
||||
{"type":"assistant/chunk","seq":172,"time":1785008261921,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":1,"id":"call_00_vDInl6gU7H4iAs1w3ToB5854","name":"run_code","argumentsDelta":" message"}}}
|
||||
{"type":"assistant/chunk","seq":173,"time":1785008261946,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":1,"id":"call_00_vDInl6gU7H4iAs1w3ToB5854","name":"run_code","argumentsDelta":":"}}}
|
||||
{"type":"assistant/chunk","seq":174,"time":1785008261947,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":1,"id":"call_00_vDInl6gU7H4iAs1w3ToB5854","name":"run_code","argumentsDelta":" e"}}}
|
||||
{"type":"assistant/chunk","seq":175,"time":1785008261947,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":1,"id":"call_00_vDInl6gU7H4iAs1w3ToB5854","name":"run_code","argumentsDelta":".message"}}}
|
||||
{"type":"assistant/chunk","seq":176,"time":1785008261947,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":1,"id":"call_00_vDInl6gU7H4iAs1w3ToB5854","name":"run_code","argumentsDelta":"\\n"}}}
|
||||
{"type":"assistant/chunk","seq":177,"time":1785008261947,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":1,"id":"call_00_vDInl6gU7H4iAs1w3ToB5854","name":"run_code","argumentsDelta":" "}}}
|
||||
{"type":"assistant/chunk","seq":178,"time":1785008261947,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":1,"id":"call_00_vDInl6gU7H4iAs1w3ToB5854","name":"run_code","argumentsDelta":" };\\n"}}}
|
||||
{"type":"assistant/chunk","seq":179,"time":1785008261972,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":1,"id":"call_00_vDInl6gU7H4iAs1w3ToB5854","name":"run_code","argumentsDelta":"}\\n\\n"}}}
|
||||
{"type":"assistant/chunk","seq":180,"time":1785008261972,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":1,"id":"call_00_vDInl6gU7H4iAs1w3ToB5854","name":"run_code","argumentsDelta":"return"}}}
|
||||
{"type":"assistant/chunk","seq":181,"time":1785008261972,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":1,"id":"call_00_vDInl6gU7H4iAs1w3ToB5854","name":"run_code","argumentsDelta":" {\\n"}}}
|
||||
{"type":"assistant/chunk","seq":182,"time":1785008261972,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":1,"id":"call_00_vDInl6gU7H4iAs1w3ToB5854","name":"run_code","argumentsDelta":" "}}}
|
||||
{"type":"assistant/chunk","seq":183,"time":1785008261972,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":1,"id":"call_00_vDInl6gU7H4iAs1w3ToB5854","name":"run_code","argumentsDelta":" bash"}}}
|
||||
{"type":"assistant/chunk","seq":184,"time":1785008261997,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":1,"id":"call_00_vDInl6gU7H4iAs1w3ToB5854","name":"run_code","argumentsDelta":":"}}}
|
||||
{"type":"assistant/chunk","seq":185,"time":1785008261997,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":1,"id":"call_00_vDInl6gU7H4iAs1w3ToB5854","name":"run_code","argumentsDelta":" {\\n"}}}
|
||||
{"type":"assistant/chunk","seq":186,"time":1785008262023,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":1,"id":"call_00_vDInl6gU7H4iAs1w3ToB5854","name":"run_code","argumentsDelta":" "}}}
|
||||
{"type":"assistant/chunk","seq":187,"time":1785008262023,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":1,"id":"call_00_vDInl6gU7H4iAs1w3ToB5854","name":"run_code","argumentsDelta":" exit"}}}
|
||||
{"type":"assistant/chunk","seq":188,"time":1785008262048,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":1,"id":"call_00_vDInl6gU7H4iAs1w3ToB5854","name":"run_code","argumentsDelta":"Code"}}}
|
||||
{"type":"assistant/chunk","seq":189,"time":1785008262049,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":1,"id":"call_00_vDInl6gU7H4iAs1w3ToB5854","name":"run_code","argumentsDelta":":"}}}
|
||||
{"type":"assistant/chunk","seq":190,"time":1785008262049,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":1,"id":"call_00_vDInl6gU7H4iAs1w3ToB5854","name":"run_code","argumentsDelta":" bash"}}}
|
||||
{"type":"assistant/chunk","seq":191,"time":1785008262049,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":1,"id":"call_00_vDInl6gU7H4iAs1w3ToB5854","name":"run_code","argumentsDelta":"Result"}}}
|
||||
{"type":"assistant/chunk","seq":192,"time":1785008262049,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":1,"id":"call_00_vDInl6gU7H4iAs1w3ToB5854","name":"run_code","argumentsDelta":".exit"}}}
|
||||
{"type":"assistant/chunk","seq":193,"time":1785008262049,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":1,"id":"call_00_vDInl6gU7H4iAs1w3ToB5854","name":"run_code","argumentsDelta":"Code"}}}
|
||||
{"type":"assistant/chunk","seq":194,"time":1785008262073,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":1,"id":"call_00_vDInl6gU7H4iAs1w3ToB5854","name":"run_code","argumentsDelta":",\\n"}}}
|
||||
{"type":"assistant/chunk","seq":195,"time":1785008262074,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":1,"id":"call_00_vDInl6gU7H4iAs1w3ToB5854","name":"run_code","argumentsDelta":" "}}}
|
||||
{"type":"assistant/chunk","seq":196,"time":1785008262074,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":1,"id":"call_00_vDInl6gU7H4iAs1w3ToB5854","name":"run_code","argumentsDelta":" stdout"}}}
|
||||
{"type":"assistant/chunk","seq":197,"time":1785008262074,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":1,"id":"call_00_vDInl6gU7H4iAs1w3ToB5854","name":"run_code","argumentsDelta":":"}}}
|
||||
{"type":"assistant/chunk","seq":198,"time":1785008262074,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":1,"id":"call_00_vDInl6gU7H4iAs1w3ToB5854","name":"run_code","argumentsDelta":" bash"}}}
|
||||
{"type":"assistant/chunk","seq":199,"time":1785008262074,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":1,"id":"call_00_vDInl6gU7H4iAs1w3ToB5854","name":"run_code","argumentsDelta":"Result"}}}
|
||||
{"type":"assistant/chunk","seq":200,"time":1785008262099,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":1,"id":"call_00_vDInl6gU7H4iAs1w3ToB5854","name":"run_code","argumentsDelta":".stdout"}}}
|
||||
{"type":"assistant/chunk","seq":201,"time":1785008262099,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":1,"id":"call_00_vDInl6gU7H4iAs1w3ToB5854","name":"run_code","argumentsDelta":".text"}}}
|
||||
{"type":"assistant/chunk","seq":202,"time":1785008262125,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":1,"id":"call_00_vDInl6gU7H4iAs1w3ToB5854","name":"run_code","argumentsDelta":"\\n"}}}
|
||||
{"type":"assistant/chunk","seq":203,"time":1785008262125,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":1,"id":"call_00_vDInl6gU7H4iAs1w3ToB5854","name":"run_code","argumentsDelta":" "}}}
|
||||
{"type":"assistant/chunk","seq":204,"time":1785008262125,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":1,"id":"call_00_vDInl6gU7H4iAs1w3ToB5854","name":"run_code","argumentsDelta":" },\\n"}}}
|
||||
{"type":"assistant/chunk","seq":205,"time":1785008262125,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":1,"id":"call_00_vDInl6gU7H4iAs1w3ToB5854","name":"run_code","argumentsDelta":" "}}}
|
||||
{"type":"assistant/chunk","seq":206,"time":1785008262125,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":1,"id":"call_00_vDInl6gU7H4iAs1w3ToB5854","name":"run_code","argumentsDelta":" read"}}}
|
||||
{"type":"assistant/chunk","seq":207,"time":1785008262126,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":1,"id":"call_00_vDInl6gU7H4iAs1w3ToB5854","name":"run_code","argumentsDelta":"Missing"}}}
|
||||
{"type":"assistant/chunk","seq":208,"time":1785008262150,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":1,"id":"call_00_vDInl6gU7H4iAs1w3ToB5854","name":"run_code","argumentsDelta":"File"}}}
|
||||
{"type":"assistant/chunk","seq":209,"time":1785008262150,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":1,"id":"call_00_vDInl6gU7H4iAs1w3ToB5854","name":"run_code","argumentsDelta":":"}}}
|
||||
{"type":"assistant/chunk","seq":210,"time":1785008262150,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":1,"id":"call_00_vDInl6gU7H4iAs1w3ToB5854","name":"run_code","argumentsDelta":" read"}}}
|
||||
{"type":"assistant/chunk","seq":211,"time":1785008262150,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":1,"id":"call_00_vDInl6gU7H4iAs1w3ToB5854","name":"run_code","argumentsDelta":"Result"}}}
|
||||
{"type":"assistant/chunk","seq":212,"time":1785008262150,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":1,"id":"call_00_vDInl6gU7H4iAs1w3ToB5854","name":"run_code","argumentsDelta":"\\n"}}}
|
||||
{"type":"assistant/chunk","seq":213,"time":1785008262176,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":1,"id":"call_00_vDInl6gU7H4iAs1w3ToB5854","name":"run_code","argumentsDelta":"};\\n"}}}
|
||||
{"type":"assistant/chunk","seq":214,"time":1785008262176,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":1,"id":"call_00_vDInl6gU7H4iAs1w3ToB5854","name":"run_code","argumentsDelta":"\""}}}
|
||||
{"type":"assistant/chunk","seq":215,"time":1785008262201,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":1,"id":"call_00_vDInl6gU7H4iAs1w3ToB5854","name":"run_code","argumentsDelta":"}"}}}
|
||||
{"type":"assistant/chunk","seq":216,"time":1785008262229,"data":{"turn":1,"step":1,"chunk":{"type":"block-end","index":0,"block":{"type":"reasoning","text":"The user wants me to write a single run_code program that:\n1. Runs bash to echo \"CODE_ROUND_OK\"\n2. Reads the file missing.txt, catching its error\n3. Returns an object with both outcomes\n4. Then replies DONE\n\nLet me write this program."}}}}
|
||||
{"type":"assistant/chunk","seq":217,"time":1785008262230,"data":{"turn":1,"step":1,"chunk":{"type":"block-end","index":1,"block":{"type":"tool-call","id":"call_00_vDInl6gU7H4iAs1w3ToB5854","name":"run_code","arguments":"{\"description\": \"Run bash echo and read missing.txt with error handling\", \"code\": \"\\nconst bashResult = await tools.bash({\\n command: \\\"echo CODE_ROUND_OK\\\",\\n description: \\\"Echo CODE_ROUND_OK\\\"\\n});\\n\\nlet readResult;\\ntry {\\n readResult = await tools.read({\\n file_path: \\\"missing.txt\\\"\\n });\\n} catch (e) {\\n readResult = {\\n error: true,\\n toolName: e.toolName,\\n message: e.message\\n };\\n}\\n\\nreturn {\\n bash: {\\n exitCode: bashResult.exitCode,\\n stdout: bashResult.stdout.text\\n },\\n readMissingFile: readResult\\n};\\n\"}"}}}}
|
||||
{"type":"assistant/chunk","seq":218,"time":1785008262230,"data":{"turn":1,"step":1,"chunk":{"type":"usage","usage":{"inputTokens":8442,"outputTokens":250,"cacheReadTokens":0,"reasoningTokens":59}}}}
|
||||
{"type":"assistant/chunk","seq":219,"time":1785008262230,"data":{"turn":1,"step":1,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}}
|
||||
{"type":"assistant/message","seq":220,"time":1785008262234,"data":{"turn":1,"step":1,"content":[{"type":"reasoning","text":"The user wants me to write a single run_code program that:\n1. Runs bash to echo \"CODE_ROUND_OK\"\n2. Reads the file missing.txt, catching its error\n3. Returns an object with both outcomes\n4. Then replies DONE\n\nLet me write this program."},{"type":"tool-call","id":"call_00_vDInl6gU7H4iAs1w3ToB5854","name":"run_code","arguments":"{\"description\": \"Run bash echo and read missing.txt with error handling\", \"code\": \"\\nconst bashResult = await tools.bash({\\n command: \\\"echo CODE_ROUND_OK\\\",\\n description: \\\"Echo CODE_ROUND_OK\\\"\\n});\\n\\nlet readResult;\\ntry {\\n readResult = await tools.read({\\n file_path: \\\"missing.txt\\\"\\n });\\n} catch (e) {\\n readResult = {\\n error: true,\\n toolName: e.toolName,\\n message: e.message\\n };\\n}\\n\\nreturn {\\n bash: {\\n exitCode: bashResult.exitCode,\\n stdout: bashResult.stdout.text\\n },\\n readMissingFile: readResult\\n};\\n\"}"}],"provenance":{"provider":"deepseek","model":"deepseek-v4-flash"},"usage":{"inputTokens":8442,"outputTokens":250,"cacheReadTokens":0,"reasoningTokens":59}},"sourceEventSeqs":[5,6,7,8,9,10,11,12,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,96,97,98,99,100,101,102,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,149,150,151,152,153,154,155,156,157,158,159,160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219],"surfaceOp":"append"}
|
||||
{"type":"tool/call","seq":221,"time":1785008262234,"data":{"turn":1,"step":1,"callId":"call_00_vDInl6gU7H4iAs1w3ToB5854","name":"run_code","arguments":"{\"description\": \"Run bash echo and read missing.txt with error handling\", \"code\": \"\\nconst bashResult = await tools.bash({\\n command: \\\"echo CODE_ROUND_OK\\\",\\n description: \\\"Echo CODE_ROUND_OK\\\"\\n});\\n\\nlet readResult;\\ntry {\\n readResult = await tools.read({\\n file_path: \\\"missing.txt\\\"\\n });\\n} catch (e) {\\n readResult = {\\n error: true,\\n toolName: e.toolName,\\n message: e.message\\n };\\n}\\n\\nreturn {\\n bash: {\\n exitCode: bashResult.exitCode,\\n stdout: bashResult.stdout.text\\n },\\n readMissingFile: readResult\\n};\\n\"}"}}
|
||||
{"type":"tool/code-dispatch","seq":222,"time":1785008262308,"data":{"parentCallId":"call_00_vDInl6gU7H4iAs1w3ToB5854","subCallId":"call_00_vDInl6gU7H4iAs1w3ToB5854:code:1","name":"bash","arguments":{"command":"echo CODE_ROUND_OK","description":"Echo CODE_ROUND_OK"},"isError":false,"content":[{"type":"text","text":"CODE_ROUND_OK\n"}]}}
|
||||
{"type":"tool/code-dispatch","seq":223,"time":1785008262310,"data":{"parentCallId":"call_00_vDInl6gU7H4iAs1w3ToB5854","subCallId":"call_00_vDInl6gU7H4iAs1w3ToB5854:code:2","name":"read","arguments":{"file_path":"missing.txt"},"isError":true,"content":[{"type":"text","text":"Error: cannot read \"{{cwd}}/workspace/missing.txt\": not found"}]}}
|
||||
{"type":"tool/result","seq":224,"time":1785008262313,"data":{"turn":1,"step":1,"callId":"call_00_vDInl6gU7H4iAs1w3ToB5854","content":[{"type":"text","text":"{\n \"bash\": {\n \"exitCode\": 0,\n \"stdout\": \"CODE_ROUND_OK\\n\"\n },\n \"readMissingFile\": {\n \"error\": true,\n \"toolName\": \"read\",\n \"message\": \"cannot read \\\"{{cwd}}/workspace/missing.txt\\\": not found\"\n }\n}"}],"isError":false},"sourceEventSeqs":[221],"surfaceOp":"append"}
|
||||
{"type":"step/end","seq":225,"time":1785008262316,"data":{"turn":1,"step":1}}
|
||||
{"type":"step/start","seq":226,"time":1785008262319,"data":{"turn":1,"step":2}}
|
||||
{"type":"assistant/chunk","seq":227,"time":1785008262746,"data":{"turn":1,"step":2,"chunk":{"type":"block-start","index":0,"blockType":"reasoning"}}}
|
||||
{"type":"assistant/chunk","seq":228,"time":1785008262747,"data":{"turn":1,"step":2,"chunk":{"type":"reasoning-delta","index":0,"text":"The"}}}
|
||||
{"type":"assistant/chunk","seq":229,"time":1785008262871,"data":{"turn":1,"step":2,"chunk":{"type":"reasoning-delta","index":0,"text":" program"}}}
|
||||
{"type":"assistant/chunk","seq":230,"time":1785008262919,"data":{"turn":1,"step":2,"chunk":{"type":"reasoning-delta","index":0,"text":" ran"}}}
|
||||
{"type":"assistant/chunk","seq":231,"time":1785008262920,"data":{"turn":1,"step":2,"chunk":{"type":"reasoning-delta","index":0,"text":" successfully"}}}
|
||||
{"type":"assistant/chunk","seq":232,"time":1785008262920,"data":{"turn":1,"step":2,"chunk":{"type":"reasoning-delta","index":0,"text":"."}}}
|
||||
{"type":"assistant/chunk","seq":233,"time":1785008262920,"data":{"turn":1,"step":2,"chunk":{"type":"reasoning-delta","index":0,"text":" Both"}}}
|
||||
{"type":"assistant/chunk","seq":234,"time":1785008262921,"data":{"turn":1,"step":2,"chunk":{"type":"reasoning-delta","index":0,"text":" outcomes"}}}
|
||||
{"type":"assistant/chunk","seq":235,"time":1785008262921,"data":{"turn":1,"step":2,"chunk":{"type":"reasoning-delta","index":0,"text":" are"}}}
|
||||
{"type":"assistant/chunk","seq":236,"time":1785008262940,"data":{"turn":1,"step":2,"chunk":{"type":"reasoning-delta","index":0,"text":" captured"}}}
|
||||
{"type":"assistant/chunk","seq":237,"time":1785008262968,"data":{"turn":1,"step":2,"chunk":{"type":"reasoning-delta","index":0,"text":":\n"}}}
|
||||
{"type":"assistant/chunk","seq":238,"time":1785008262968,"data":{"turn":1,"step":2,"chunk":{"type":"reasoning-delta","index":0,"text":"1"}}}
|
||||
{"type":"assistant/chunk","seq":239,"time":1785008262968,"data":{"turn":1,"step":2,"chunk":{"type":"reasoning-delta","index":0,"text":"."}}}
|
||||
{"type":"assistant/chunk","seq":240,"time":1785008262969,"data":{"turn":1,"step":2,"chunk":{"type":"reasoning-delta","index":0,"text":" bash"}}}
|
||||
{"type":"assistant/chunk","seq":241,"time":1785008262998,"data":{"turn":1,"step":2,"chunk":{"type":"reasoning-delta","index":0,"text":" echo"}}}
|
||||
{"type":"assistant/chunk","seq":242,"time":1785008263018,"data":{"turn":1,"step":2,"chunk":{"type":"reasoning-delta","index":0,"text":"'d"}}}
|
||||
{"type":"assistant/chunk","seq":243,"time":1785008263074,"data":{"turn":1,"step":2,"chunk":{"type":"reasoning-delta","index":0,"text":" \""}}}
|
||||
{"type":"assistant/chunk","seq":244,"time":1785008263077,"data":{"turn":1,"step":2,"chunk":{"type":"reasoning-delta","index":0,"text":"CODE"}}}
|
||||
{"type":"assistant/chunk","seq":245,"time":1785008263077,"data":{"turn":1,"step":2,"chunk":{"type":"reasoning-delta","index":0,"text":"_RO"}}}
|
||||
{"type":"assistant/chunk","seq":246,"time":1785008263077,"data":{"turn":1,"step":2,"chunk":{"type":"reasoning-delta","index":0,"text":"UND"}}}
|
||||
{"type":"assistant/chunk","seq":247,"time":1785008263077,"data":{"turn":1,"step":2,"chunk":{"type":"reasoning-delta","index":0,"text":"_OK"}}}
|
||||
{"type":"assistant/chunk","seq":248,"time":1785008263077,"data":{"turn":1,"step":2,"chunk":{"type":"reasoning-delta","index":0,"text":"\""}}}
|
||||
{"type":"assistant/chunk","seq":249,"time":1785008263077,"data":{"turn":1,"step":2,"chunk":{"type":"reasoning-delta","index":0,"text":" with"}}}
|
||||
{"type":"assistant/chunk","seq":250,"time":1785008263093,"data":{"turn":1,"step":2,"chunk":{"type":"reasoning-delta","index":0,"text":" exit"}}}
|
||||
{"type":"assistant/chunk","seq":251,"time":1785008263094,"data":{"turn":1,"step":2,"chunk":{"type":"reasoning-delta","index":0,"text":" code"}}}
|
||||
{"type":"assistant/chunk","seq":252,"time":1785008263094,"data":{"turn":1,"step":2,"chunk":{"type":"reasoning-delta","index":0,"text":" "}}}
|
||||
{"type":"assistant/chunk","seq":253,"time":1785008263094,"data":{"turn":1,"step":2,"chunk":{"type":"reasoning-delta","index":0,"text":"0"}}}
|
||||
{"type":"assistant/chunk","seq":254,"time":1785008263094,"data":{"turn":1,"step":2,"chunk":{"type":"reasoning-delta","index":0,"text":"\n"}}}
|
||||
{"type":"assistant/chunk","seq":255,"time":1785008263094,"data":{"turn":1,"step":2,"chunk":{"type":"reasoning-delta","index":0,"text":"2"}}}
|
||||
{"type":"assistant/chunk","seq":256,"time":1785008263120,"data":{"turn":1,"step":2,"chunk":{"type":"reasoning-delta","index":0,"text":"."}}}
|
||||
{"type":"assistant/chunk","seq":257,"time":1785008263120,"data":{"turn":1,"step":2,"chunk":{"type":"reasoning-delta","index":0,"text":" Reading"}}}
|
||||
{"type":"assistant/chunk","seq":258,"time":1785008263146,"data":{"turn":1,"step":2,"chunk":{"type":"reasoning-delta","index":0,"text":" missing"}}}
|
||||
{"type":"assistant/chunk","seq":259,"time":1785008263147,"data":{"turn":1,"step":2,"chunk":{"type":"reasoning-delta","index":0,"text":".txt"}}}
|
||||
{"type":"assistant/chunk","seq":260,"time":1785008263147,"data":{"turn":1,"step":2,"chunk":{"type":"reasoning-delta","index":0,"text":" threw"}}}
|
||||
{"type":"assistant/chunk","seq":261,"time":1785008263170,"data":{"turn":1,"step":2,"chunk":{"type":"reasoning-delta","index":0,"text":" a"}}}
|
||||
{"type":"assistant/chunk","seq":262,"time":1785008263171,"data":{"turn":1,"step":2,"chunk":{"type":"reasoning-delta","index":0,"text":" Tool"}}}
|
||||
{"type":"assistant/chunk","seq":263,"time":1785008263171,"data":{"turn":1,"step":2,"chunk":{"type":"reasoning-delta","index":0,"text":"Call"}}}
|
||||
{"type":"assistant/chunk","seq":264,"time":1785008263171,"data":{"turn":1,"step":2,"chunk":{"type":"reasoning-delta","index":0,"text":"Error"}}}
|
||||
{"type":"assistant/chunk","seq":265,"time":1785008263195,"data":{"turn":1,"step":2,"chunk":{"type":"reasoning-delta","index":0,"text":" which"}}}
|
||||
{"type":"assistant/chunk","seq":266,"time":1785008263195,"data":{"turn":1,"step":2,"chunk":{"type":"reasoning-delta","index":0,"text":" I"}}}
|
||||
{"type":"assistant/chunk","seq":267,"time":1785008263220,"data":{"turn":1,"step":2,"chunk":{"type":"reasoning-delta","index":0,"text":" caught"}}}
|
||||
{"type":"assistant/chunk","seq":268,"time":1785008263221,"data":{"turn":1,"step":2,"chunk":{"type":"reasoning-delta","index":0,"text":","}}}
|
||||
{"type":"assistant/chunk","seq":269,"time":1785008263221,"data":{"turn":1,"step":2,"chunk":{"type":"reasoning-delta","index":0,"text":" reporting"}}}
|
||||
{"type":"assistant/chunk","seq":270,"time":1785008263246,"data":{"turn":1,"step":2,"chunk":{"type":"reasoning-delta","index":0,"text":" the"}}}
|
||||
{"type":"assistant/chunk","seq":271,"time":1785008263246,"data":{"turn":1,"step":2,"chunk":{"type":"reasoning-delta","index":0,"text":" error"}}}
|
||||
{"type":"assistant/chunk","seq":272,"time":1785008263271,"data":{"turn":1,"step":2,"chunk":{"type":"reasoning-delta","index":0,"text":"\n\n"}}}
|
||||
{"type":"assistant/chunk","seq":273,"time":1785008263272,"data":{"turn":1,"step":2,"chunk":{"type":"reasoning-delta","index":0,"text":"Now"}}}
|
||||
{"type":"assistant/chunk","seq":274,"time":1785008263297,"data":{"turn":1,"step":2,"chunk":{"type":"reasoning-delta","index":0,"text":" I"}}}
|
||||
{"type":"assistant/chunk","seq":275,"time":1785008263297,"data":{"turn":1,"step":2,"chunk":{"type":"reasoning-delta","index":0,"text":" just"}}}
|
||||
{"type":"assistant/chunk","seq":276,"time":1785008263322,"data":{"turn":1,"step":2,"chunk":{"type":"reasoning-delta","index":0,"text":" need"}}}
|
||||
{"type":"assistant/chunk","seq":277,"time":1785008263323,"data":{"turn":1,"step":2,"chunk":{"type":"reasoning-delta","index":0,"text":" to"}}}
|
||||
{"type":"assistant/chunk","seq":278,"time":1785008263323,"data":{"turn":1,"step":2,"chunk":{"type":"reasoning-delta","index":0,"text":" reply"}}}
|
||||
{"type":"assistant/chunk","seq":279,"time":1785008263323,"data":{"turn":1,"step":2,"chunk":{"type":"reasoning-delta","index":0,"text":" D"}}}
|
||||
{"type":"assistant/chunk","seq":280,"time":1785008263323,"data":{"turn":1,"step":2,"chunk":{"type":"reasoning-delta","index":0,"text":"ONE"}}}
|
||||
{"type":"assistant/chunk","seq":281,"time":1785008263349,"data":{"turn":1,"step":2,"chunk":{"type":"reasoning-delta","index":0,"text":"."}}}
|
||||
{"type":"assistant/chunk","seq":282,"time":1785008263349,"data":{"turn":1,"step":2,"chunk":{"type":"block-start","index":1,"blockType":"text"}}}
|
||||
{"type":"assistant/chunk","seq":283,"time":1785008263349,"data":{"turn":1,"step":2,"chunk":{"type":"text-delta","index":1,"text":"D"}}}
|
||||
{"type":"assistant/chunk","seq":284,"time":1785008263349,"data":{"turn":1,"step":2,"chunk":{"type":"text-delta","index":1,"text":"ONE"}}}
|
||||
{"type":"assistant/chunk","seq":285,"time":1785008263350,"data":{"turn":1,"step":2,"chunk":{"type":"block-end","index":0,"block":{"type":"reasoning","text":"The program ran successfully. Both outcomes are captured:\n1. bash echo'd \"CODE_ROUND_OK\" with exit code 0\n2. Reading missing.txt threw a ToolCallError which I caught, reporting the error\n\nNow I just need to reply DONE."}}}}
|
||||
{"type":"assistant/chunk","seq":286,"time":1785008263350,"data":{"turn":1,"step":2,"chunk":{"type":"block-end","index":1,"block":{"type":"text","text":"DONE"}}}}
|
||||
{"type":"assistant/chunk","seq":287,"time":1785008263350,"data":{"turn":1,"step":2,"chunk":{"type":"usage","usage":{"inputTokens":211,"outputTokens":57,"cacheReadTokens":8576,"reasoningTokens":54}}}}
|
||||
{"type":"assistant/chunk","seq":288,"time":1785008263350,"data":{"turn":1,"step":2,"chunk":{"type":"finish","reason":{"kind":"stop"}}}}
|
||||
{"type":"assistant/message","seq":289,"time":1785008263351,"data":{"turn":1,"step":2,"content":[{"type":"reasoning","text":"The program ran successfully. Both outcomes are captured:\n1. bash echo'd \"CODE_ROUND_OK\" with exit code 0\n2. Reading missing.txt threw a ToolCallError which I caught, reporting the error\n\nNow I just need to reply DONE."},{"type":"text","text":"DONE"}],"provenance":{"provider":"deepseek","model":"deepseek-v4-flash"},"usage":{"inputTokens":211,"outputTokens":57,"cacheReadTokens":8576,"reasoningTokens":54}},"sourceEventSeqs":[227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,248,249,250,251,252,253,254,255,256,257,258,259,260,261,262,263,264,265,266,267,268,269,270,271,272,273,274,275,276,277,278,279,280,281,282,283,284,285,286,287,288],"surfaceOp":"append"}
|
||||
{"type":"step/end","seq":290,"time":1785008263351,"data":{"turn":1,"step":2}}
|
||||
{"type":"turn/end","seq":291,"time":1785008263352,"data":{"turn":1,"reason":{"kind":"completed"}}}
|
||||
@@ -0,0 +1,35 @@
|
||||
- banner:
|
||||
- navigation "Session hierarchy":
|
||||
- 'button "Using ONE run_code program: run" [disabled]'
|
||||
- text: · 1 turns
|
||||
- tablist:
|
||||
- tab "Chat" [selected]
|
||||
- tab "Trajectory"
|
||||
- tab "Waterfall"
|
||||
- text: "Using ONE run_code program: run bash `echo CODE_ROUND_OK`, then read the file missing.txt catching its error in the program. Return an object with both outcomes. Then reply DONE and stop."
|
||||
- button "Think The user wants me to write a single run_code program that:":
|
||||
- img
|
||||
- text: "Think The user wants me to write a single run_code program that:"
|
||||
- button:
|
||||
- img
|
||||
- text: Code Run bash echo and read missing.txt with error handling Echo CODE_ROUND_OK
|
||||
- button
|
||||
- text: Read missing.txt
|
||||
- button "Think The program ran successfully. Both outcomes are captured:":
|
||||
- img
|
||||
- text: "Think The program ran successfully. Both outcomes are captured:"
|
||||
- paragraph: DONE
|
||||
- text: cache hit 50% · 17,536 tokens · 1 turns · 2 steps
|
||||
- textbox "Message the agent"
|
||||
- button "Add attachment":
|
||||
- img
|
||||
- combobox "Plan mode":
|
||||
- option "Plan" [selected]
|
||||
- option "Agent"
|
||||
- combobox "Access mode":
|
||||
- option "Read-only" [selected]
|
||||
- option "Read-write"
|
||||
- combobox "Model":
|
||||
- option "DeepSeek-V4-Pro High" [selected]
|
||||
- option "DeepSeek-V4-Pro"
|
||||
- button "Send message" [disabled]
|
||||
@@ -31,7 +31,8 @@
|
||||
"tests/settings-chrome.e2e.ts",
|
||||
"tests/workspace-management.e2e.ts",
|
||||
"tests/replay-round-trip.e2e.ts",
|
||||
"tests/seeded-history.e2e.ts"
|
||||
"tests/seeded-history.e2e.ts",
|
||||
"tests/code-mode-round.e2e.ts"
|
||||
],
|
||||
"references": [
|
||||
{
|
||||
|
||||
@@ -124,6 +124,57 @@ function buildAlphaLog(): SessionEvent[] {
|
||||
toolTurn(61, 'fx-write', '{"path":"notes/demo.txt","content":"hello fixture\\n"}', 'wrote notes/demo.txt')
|
||||
toolTurn(62, 'edit', '{"file_path":"notes/demo.txt","old_string":"hello","new_string":"hello fixture"}', '已编辑')
|
||||
toolTurn(63, 'write', '{"file_path":"notes/new-demo.txt","content":"hello fixture\\n"}', '已写入')
|
||||
// Turn 64: one run_code turn with three logged sub-dispatches — the Code
|
||||
// Mode acceptance surface (parent code row + nested native-identical rows,
|
||||
// including an isError sub-call and a bash sub-call that must hit the same
|
||||
// keyed registration a top-level bash row uses).
|
||||
{
|
||||
const turn = 64
|
||||
const callId = `fx-call-${turn}`
|
||||
const program = 'const listing = await tools.bash({ command: "ls notes", description: "List notes" })\n'
|
||||
+ 'const demo = await tools.read({ path: "notes/demo.txt" })\n'
|
||||
+ 'await tools.read({ path: "notes/missing.txt" }).catch(() => "tolerated")\n'
|
||||
+ 'return { listing, demo }'
|
||||
const args = JSON.stringify({ code: program, description: 'Read the notes files and summarize' })
|
||||
push({ type: 'turn/start', data: { turn, trigger: { kind: 'message', source: { kind: 'user' } } } })
|
||||
push({ type: 'user/message', surfaceOp: 'append', data: { content: text(`问题 ${turn}:run_code 样本。`), source: { kind: 'user' } } })
|
||||
push({ type: 'step/start', data: { turn, step: 0 } })
|
||||
push({
|
||||
type: 'assistant/message', surfaceOp: 'append',
|
||||
data: { turn, step: 0, content: [{ type: 'tool-call', id: callId, name: 'run_code', arguments: args } as ContentBlock], provenance: { provider: 'fixture', model: 'fx-1' } },
|
||||
})
|
||||
push({ type: 'tool/call', data: { turn, step: 0, callId, name: 'run_code', arguments: args } })
|
||||
push({
|
||||
type: 'tool/code-dispatch',
|
||||
data: {
|
||||
parentCallId: callId, subCallId: `${callId}:code:1`, name: 'bash',
|
||||
arguments: { command: 'ls notes', description: 'List notes' },
|
||||
isError: false, content: [{ type: 'text', text: 'demo.txt\nnew-demo.txt' }],
|
||||
},
|
||||
})
|
||||
push({
|
||||
type: 'tool/code-dispatch',
|
||||
data: {
|
||||
parentCallId: callId, subCallId: `${callId}:code:2`, name: 'read',
|
||||
arguments: { path: 'notes/demo.txt' },
|
||||
isError: false, content: [{ type: 'text', text: 'hello fixture\n' }],
|
||||
},
|
||||
})
|
||||
push({
|
||||
type: 'tool/code-dispatch',
|
||||
data: {
|
||||
parentCallId: callId, subCallId: `${callId}:code:3`, name: 'read',
|
||||
arguments: { path: 'notes/missing.txt' },
|
||||
isError: true, content: [{ type: 'text', text: 'Error: ENOENT: notes/missing.txt not found' }],
|
||||
},
|
||||
})
|
||||
push({
|
||||
type: 'tool/result', surfaceOp: 'append',
|
||||
data: { turn, step: 0, callId, content: text('{"listing":"demo.txt\\nnew-demo.txt","demo":"hello fixture\\n"}'), isError: false },
|
||||
})
|
||||
push({ type: 'step/end', data: { turn, step: 0 } })
|
||||
push({ type: 'turn/end', data: { turn, reason: { kind: 'completed' } } })
|
||||
}
|
||||
return events as unknown as SessionEvent[]
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
README.md: 5d44f666a12e747eb79535c48d87fcaff381d770
|
||||
README.zh.md: 16577c356ba79066c7c56ae07a266f4ec85fa2e9
|
||||
README.md: f37216a88c78a93a561c919bc23f5e728ba666c6
|
||||
README.zh.md: 8ea65a0ce2aaa89f6a9c4d5a3c5215a46dbc27a7
|
||||
@@ -14,6 +14,10 @@ SlotsService gives the renderer separate bare observables for `useSessions` and
|
||||
|
||||
`SessionsService.create` accepts an optional caller-preallocated SessionId. It throws `SessionCreateError` on failure: `requestedSessionId` remains available after transport uncertainty, while `publishedSessionId` is set when `workspace-attach-failed` proves the Host published a real Session before attachment failed. For the New Session flow, the frontend Session object owns its retained prompt and advances it through attachment and send; a partially published Session keeps the same object and prompt while it appears as Ungrouped.
|
||||
|
||||
## Code Mode sub-dispatch index
|
||||
|
||||
`ConversationSnapshot.codeDispatches` groups a `run_code` call's sub-dispatches under their parent callId, in dispatch order, as settled `ToolResultNode` entries (the native result shape): each `tool/code-dispatch` event appends one. The event carries only the settle timestamp, so `callTime` is `null` (start unknown) — no duration claim is possible from this index yet. Live mux frames and history replay build the identical index; sub-calls never join the surface `nodes` flow; per-parent array and map references are memo-stable across unrelated snapshot swaps.
|
||||
|
||||
## Session title projection
|
||||
|
||||
`SessionManager` retains the latest validated `session/title` control snapshot independently of list and session-instance arrival. Newer event seqs replace older snapshots, title timestamps contribute to list recency, and a subscription baseline discards any retained title beyond its `lastSeq` before the optional folded title arrives. Explicit session removal also clears the retained title. The client-facing `SessionSummary.title` is therefore only the actual durable title; `displayTitle` is always present and falls back through the cwd basename and session id. A cold persisted session keeps that fallback until opening or resuming it causes the host to fold and project its log-backed title.
|
||||
|
||||
@@ -14,6 +14,10 @@ SlotsService 分别为 renderer 提供 `useSessions` 与 `useWorkspaces` 的裸
|
||||
|
||||
`SessionsService.create` 接受可选的、由调用方预先分配的 SessionId。失败时抛出 `SessionCreateError`:传输状态不确定后仍可取得 `requestedSessionId`;如果 Host 在附加失败前已经发布真实 Session,则会设置 `publishedSessionId`,此时 `workspace-attach-failed` 提供了证明。在 New Session 流程中,前端 Session 对象拥有其保留的提示词,并推动提示词完成附加与发送;部分发布的 Session 会保留同一对象和提示词,同时显示为 Ungrouped。
|
||||
|
||||
## Code Mode 子调用索引
|
||||
|
||||
`ConversationSnapshot.codeDispatches` 按父调用的 callId 和分发顺序,将一个 `run_code` 调用的子调用组织为已完结的 `ToolResultNode` 条目(即原生结果形状):每条 `tool/code-dispatch` 事件追加一个。该事件只携带完结时间戳,因此 `callTime` 为 `null`(起始时间未知);此索引目前无法据此作出任何耗时声明。live mux 帧与历史回放构建相同的索引;子调用永不进入 surface `nodes` 流;无关快照交换不会改变每个父调用对应的数组引用和映射引用,两者均保持 memo 稳定。
|
||||
|
||||
## Session 标题投影
|
||||
|
||||
`SessionManager` 独立于列表和 Session 实例到达情况,保留最近一次通过验证的 `session/title` 控制快照。seq 更新的事件会替换旧快照,标题时间戳计入列表新近程度;订阅基线会先丢弃 seq 超过其 `lastSeq` 的任何已保留标题,再接收可选的折叠标题。显式移除 Session 也会清除已保留标题。因此,面向客户端的 `SessionSummary.title` 只包含真实的持久标题;`displayTitle` 始终存在,并依次回退到 cwd basename 和 Session id。冷启动的持久会话会保持该回退值,直到打开或恢复会话,促使主机折叠并投影日志支持的标题。
|
||||
|
||||
@@ -24,7 +24,7 @@ export type {
|
||||
EngineStoreHandle, EngineStoreInstance, ObservableSnapshot, SnapshotStore,
|
||||
} from './contract/store.ts'
|
||||
export type {
|
||||
AssistantBlock, AssistantMessageNode, ComposerPhase, ContextMessageNode, ConversationNode,
|
||||
AssistantBlock, AssistantMessageNode, CodeSubCall, ComposerPhase, ContextMessageNode, ConversationNode,
|
||||
ConversationSnapshot, PendingPrompt, RunningToolCall, SessionIntentSnapshot, SessionIntentTarget,
|
||||
SteeringMessageNode, ToolResultNode, UnknownSurfaceNode, UserMessageNode,
|
||||
} from './sessions/conversation.ts'
|
||||
|
||||
@@ -127,6 +127,17 @@ export type ConversationNode =
|
||||
| ToolResultNode
|
||||
| UnknownSurfaceNode
|
||||
|
||||
/**
|
||||
* One `run_code` sub-dispatch materialized as a {@link ToolResultNode} so every
|
||||
* consumer (tool rows, details panel) renders it through the exact components
|
||||
* that render a native settled call. Never part of the surface `nodes` flow —
|
||||
* sub-calls live under their parent via {@link ConversationSnapshot.codeDispatches}.
|
||||
* `callId` is the deterministic sub-call id (`<parent>:code:<n>`); `call`
|
||||
* carries the sub-tool name and its JSON-stringified logged arguments;
|
||||
* `content`/`isError` are the sub-call's complete logged outcome.
|
||||
*/
|
||||
export type CodeSubCall = ToolResultNode
|
||||
|
||||
/** In-flight tool card material: tool/call seen, tool/result not yet. */
|
||||
export interface RunningToolCall {
|
||||
callId: string
|
||||
@@ -212,6 +223,13 @@ export interface ConversationSnapshot {
|
||||
foldDegraded: boolean
|
||||
partial: PartialAssistant | null
|
||||
runningCalls: readonly RunningToolCall[]
|
||||
/**
|
||||
* `run_code` sub-dispatches grouped under their parent callId, in dispatch
|
||||
* order. Populated from in-window `tool/code-dispatch` events (live and
|
||||
* replay identically); the per-parent array reference is stable across
|
||||
* unrelated snapshot swaps (memo premise, same regime as `nodes`).
|
||||
*/
|
||||
codeDispatches: ReadonlyMap<string, readonly CodeSubCall[]>
|
||||
pending: readonly PendingInteraction[]
|
||||
running: boolean
|
||||
/** Input-area shape (see {@link ComposerPhase}); derived here, switched on by consumers. */
|
||||
|
||||
@@ -11,7 +11,7 @@ import type {
|
||||
import { transportError } from '@deepseek-ai/dsh-host-apiproxy/api'
|
||||
import type { ObservableSnapshot } from '../contract/store.ts'
|
||||
import type {
|
||||
ComposerPhase, ConversationNode, ConversationSnapshot, OpenState, PendingPrompt,
|
||||
CodeSubCall, ComposerPhase, ConversationNode, ConversationSnapshot, OpenState, PendingPrompt,
|
||||
PromptError, RunningToolCall, SessionIntentSnapshot, SessionIntentTarget,
|
||||
} from './conversation.ts'
|
||||
import type { PendingInteraction } from './pending.ts'
|
||||
@@ -66,6 +66,11 @@ export class Session implements ObservableSnapshot<ConversationSnapshot> {
|
||||
private pendingCache: { rev: number; value: PendingInteraction[] } | null = null
|
||||
private frozenRev = 0
|
||||
private nodesCache: { folded: readonly ConversationNode[]; frozenRev: number; value: readonly ConversationNode[] } | null = null
|
||||
/** `run_code` sub-dispatches by parent callId (window-derived, like openCalls). Appends
|
||||
* copy-on-write the per-parent array so published snapshot references never mutate. */
|
||||
private codeDispatches = new Map<string, readonly CodeSubCall[]>()
|
||||
private dispatchesRev = 0
|
||||
private dispatchesCache: { rev: number; value: ReadonlyMap<string, readonly CodeSubCall[]> } | null = null
|
||||
private running = false
|
||||
/**
|
||||
* Sticky send marker, private input of the composerPhase derivation: set
|
||||
@@ -611,6 +616,39 @@ export class Session implements ObservableSnapshot<ConversationSnapshot> {
|
||||
/** Per-event side effects (right column of the §A.9 dispatch table):
|
||||
* chunk accumulation / partial clear on finalize / openCalls add-remove. */
|
||||
private applyEventSideEffects(event: SessionEvent, view?: ToolEventView): void {
|
||||
// `tool/code-dispatch` is declared by the host-side dsh-tools plugin whose
|
||||
// types cannot enter the client program (its host Context merges collide
|
||||
// with the client's), so this wire consumer narrows it structurally —
|
||||
// the same posture as every other cross-wire event payload.
|
||||
if ((event.type as string) === 'tool/code-dispatch') {
|
||||
// A sub-dispatch becomes a ToolResultNode so rows and the details
|
||||
// panel reuse the native rendering path verbatim; it indexes under its
|
||||
// parent run_code callId and never joins the surface flow.
|
||||
const data = event.data as unknown as {
|
||||
parentCallId: string
|
||||
subCallId: string
|
||||
name: string
|
||||
arguments: unknown
|
||||
isError: boolean
|
||||
content: ContentBlock[]
|
||||
}
|
||||
const parent = data.parentCallId
|
||||
const siblings = this.codeDispatches.get(parent) ?? []
|
||||
const sub: CodeSubCall = {
|
||||
kind: 'tool-result', seq: event.seq, time: event.time,
|
||||
callId: data.subCallId,
|
||||
call: { name: data.name, argsRaw: JSON.stringify(data.arguments) },
|
||||
// The settle event is the only timestamp this event carries; the
|
||||
// start time is unknown (null per the ToolResultNode contract), so
|
||||
// duration-aware consumers never see a fabricated zero-duration call.
|
||||
callTime: null,
|
||||
content: data.content, isError: data.isError,
|
||||
callView: null, resultView: null,
|
||||
}
|
||||
this.codeDispatches.set(parent, [...siblings, sub])
|
||||
this.dispatchesRev++
|
||||
return
|
||||
}
|
||||
switch (event.type) {
|
||||
case 'assistant/chunk': {
|
||||
const { turn, step, chunk } = event.data
|
||||
@@ -690,6 +728,8 @@ export class Session implements ObservableSnapshot<ConversationSnapshot> {
|
||||
this.callsRev++
|
||||
this.frozenNodes = []
|
||||
this.frozenRev++
|
||||
this.codeDispatches = new Map()
|
||||
this.dispatchesRev++
|
||||
for (let i = 0; i < this.events.length; i++) {
|
||||
const event = this.events[i]
|
||||
/* v8 ignore next -- dense-array guard: i stays within events.length, so the undefined arm needs a sparse array no caller builds. */
|
||||
@@ -722,6 +762,9 @@ export class Session implements ObservableSnapshot<ConversationSnapshot> {
|
||||
if (this.pendingCache === null || this.pendingCache.rev !== this.pendingRev) {
|
||||
this.pendingCache = { rev: this.pendingRev, value: [...this.pending.values()] }
|
||||
}
|
||||
if (this.dispatchesCache === null || this.dispatchesCache.rev !== this.dispatchesRev) {
|
||||
this.dispatchesCache = { rev: this.dispatchesRev, value: new Map(this.codeDispatches) }
|
||||
}
|
||||
const partial = this.partial?.toPartial() ?? null
|
||||
return {
|
||||
sessionId: this.sessionId,
|
||||
@@ -730,6 +773,7 @@ export class Session implements ObservableSnapshot<ConversationSnapshot> {
|
||||
partial,
|
||||
runningCalls: this.callsCache.value,
|
||||
pending: this.pendingCache.value,
|
||||
codeDispatches: this.dispatchesCache.value,
|
||||
running: this.running,
|
||||
composerPhase: derivePhase(
|
||||
nodes.length > 0 || partial !== null || this.running || this.pendingCache.value.length > 0,
|
||||
|
||||
@@ -26,6 +26,11 @@ export const ev = {
|
||||
at(seq, { type: 'tool/call', data: { turn, step, callId, name, arguments: args } }),
|
||||
toolResult: (seq: number, turn: number, callId: string, body: string, step = 0): SessionEvent =>
|
||||
at(seq, { type: 'tool/result', surfaceOp: 'append', data: { turn, step, callId, content: text(body), isError: false } }),
|
||||
codeDispatch: (seq: number, parentCallId: string, n: number, name: string, args: unknown, body: string, isError = false): SessionEvent =>
|
||||
at(seq, {
|
||||
type: 'tool/code-dispatch',
|
||||
data: { parentCallId, subCallId: `${parentCallId}:code:${n}`, name, arguments: args, isError, content: text(body) },
|
||||
}),
|
||||
stepEnd: (seq: number, turn: number, step = 0): SessionEvent =>
|
||||
at(seq, { type: 'step/end', data: { turn, step } }),
|
||||
turnEnd: (seq: number, turn: number, reason: 'completed' | 'cancelled' = 'completed'): SessionEvent =>
|
||||
|
||||
@@ -644,6 +644,66 @@ describe('resync', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('run_code sub-dispatch indexing', () => {
|
||||
it('indexes live tool/code-dispatch events under their parent as native-shaped result nodes', async () => {
|
||||
const { api, session } = makeSession()
|
||||
api.onHistory = () => histResponse(plainTurn(0, 0, '问', '答'))
|
||||
await session.open()
|
||||
const feed = (event: SessionEvent) => { session.handleMuxEnvelope('r' as never, { type: 'session/event', sessionId: SID, event }) }
|
||||
feed(ev.turnStart(6, 1))
|
||||
feed(ev.toolCall(7, 1, 'p1', 'run_code', '{"code":"return 1","description":"跑一个程序"}'))
|
||||
feed(ev.codeDispatch(8, 'p1', 1, 'bash', { command: 'ls', description: '列目录' }, 'demo.txt'))
|
||||
feed(ev.codeDispatch(9, 'p1', 2, 'read', { path: 'a.txt' }, 'Error: ENOENT', true))
|
||||
const subs = session.getSnapshot().codeDispatches.get('p1')
|
||||
expect(subs).toHaveLength(2)
|
||||
expect(subs?.[0]).toMatchObject({
|
||||
kind: 'tool-result', callId: 'p1:code:1',
|
||||
call: { name: 'bash', argsRaw: '{"command":"ls","description":"列目录"}' },
|
||||
// The settle event carries no start time: callTime stays null (never a
|
||||
// fabricated zero-duration).
|
||||
callTime: null,
|
||||
isError: false, content: [{ type: 'text', text: 'demo.txt' }],
|
||||
})
|
||||
expect(subs?.[1]).toMatchObject({ callId: 'p1:code:2', isError: true })
|
||||
// Sub-dispatches never join the surface flow.
|
||||
expect(session.getSnapshot().nodes.some(n => n.kind === 'tool-result' && n.callId.includes(':code:'))).toBe(false)
|
||||
})
|
||||
|
||||
it('rebuilds the same index from a history window (replay parity)', async () => {
|
||||
const { api, session } = makeSession()
|
||||
api.onHistory = () => histResponse([
|
||||
...plainTurn(0, 0, '问', '答'),
|
||||
ev.turnStart(6, 1),
|
||||
ev.toolCall(7, 1, 'p1', 'run_code', '{"code":"return 1","description":"跑一个程序"}'),
|
||||
ev.codeDispatch(8, 'p1', 1, 'bash', { command: 'ls' }, 'demo.txt'),
|
||||
ev.toolResult(9, 1, 'p1', '{"done":true}'),
|
||||
ev.turnEnd(10, 1),
|
||||
])
|
||||
await session.open()
|
||||
const subs = session.getSnapshot().codeDispatches.get('p1')
|
||||
expect(subs).toHaveLength(1)
|
||||
expect(subs?.[0]).toMatchObject({ callId: 'p1:code:1', call: { name: 'bash' } })
|
||||
})
|
||||
|
||||
it('keeps the dispatch map reference across unrelated changes and swaps it on a new dispatch', async () => {
|
||||
const { api, session } = makeSession()
|
||||
api.onHistory = () => histResponse(plainTurn(0, 0, '稳', '定'))
|
||||
await session.open()
|
||||
const feed = (event: SessionEvent) => { session.handleMuxEnvelope('r' as never, { type: 'session/event', sessionId: SID, event }) }
|
||||
feed(ev.turnStart(6, 1))
|
||||
feed(ev.toolCall(7, 1, 'p1', 'run_code', '{"code":"1","description":"d"}'))
|
||||
feed(ev.codeDispatch(8, 'p1', 1, 'bash', { command: 'ls' }, 'x'))
|
||||
const before = session.getSnapshot()
|
||||
feed(ev.chunkStart(9, 1))
|
||||
feed(ev.chunkText(10, 1, '流式'))
|
||||
const after = session.getSnapshot()
|
||||
expect(after.codeDispatches).toBe(before.codeDispatches)
|
||||
feed(ev.codeDispatch(11, 'p1', 2, 'read', { path: 'a' }, 'y'))
|
||||
expect(session.getSnapshot().codeDispatches).not.toBe(after.codeDispatches)
|
||||
expect(session.getSnapshot().codeDispatches.get('p1')).toHaveLength(2)
|
||||
})
|
||||
})
|
||||
|
||||
describe('reference stability (the memo contract)', () => {
|
||||
it('keeps unchanged node references across an append and swaps the snapshot object', async () => {
|
||||
const { api, session } = makeSession()
|
||||
|
||||
@@ -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
|
||||
README.md: f5e2603e8ad588740bea3be4f1a195d658721f0c
|
||||
README.zh.md: 900d3d1608da3c86078dc56ec819b1f851e63de7
|
||||
README.md: b9ec555f158722ea1f41e01c4b3f7131d3fe3467
|
||||
README.zh.md: b1e3c1f4331148ebf1c58b4bcd4869270bb44311
|
||||
@@ -8,7 +8,7 @@ The no-session hero renders the frontend Session Intent from the Session list pr
|
||||
|
||||
The view ring IS a slot: the conversation registration declares the `'conversation.view'` list slot (session scope) in its `children` table, ConversationRoot renders the active entry through its renderSlot share (`only: <active id>`), and view tabs project from the ring ledger's registration options (`id`/`order`/`label`). The chat view is this package's own ring entry; other plugins (ui-trajectory) contribute tabs through plain `ctx.slots.register` — the former package-local view registry (`registerView`/`ViewEntry`/`ConversationViewMap` and the chrome attachment table) is retired, with per-view chrome dissolved into the view components themselves.
|
||||
|
||||
Generic tool rows classify the built-in bash, read, search, write, and edit names into dedicated visual variants. The filesystem variants render the edit icon and `Write · <path>` or `Edit · <path>` summary while retaining the shared row-to-details interaction.
|
||||
Generic tool rows classify the built-in bash, read, search, write, edit, and run_code names into dedicated visual variants. The filesystem variants render the edit icon and `Write · <path>` or `Edit · <path>` summary while retaining the shared row-to-details interaction. The code variant summarizes with the model-authored `description` and expands to the program itself; its logged sub-dispatches render as always-visible nested rows through the SAME keyed toolview hole (custom registrations and the GenericToolCard fallback apply to sub-rows unchanged), and the details panel resolves a selected sub-call id to its full logged args and complete output.
|
||||
|
||||
Tool rows are slots too — the standalone tool ring (`ToolViewRegistry`/`ctx.toolviews`/outlet) is retired. The chat entry declares the keyed `'conversation.chat.toolview'` hole (session scope; the key space is runtime-open); its render site dispatches per row via `entryKey: toolName` with `GenericToolCard` as the call-site `fallback`. The owner payload is the uniform `ToolRowOwnerProps` (`callId`/`toolName`/`block`/`openDetails`) and `ToolRowProps` pre-composes it with the session standard kit. A registrant is a plain plugin: `ctx.slots.register({ name: 'conversation.chat.toolview', key: '<tool>', inject? }, Row)` with `inject: ['slots', 'conversation']` as the load-order seam (apply mounts ConversationService after the chat registration, so the service being present guarantees the slot is declared); session differentiation happens inside the component (`useSessions` reading `parentId` — the bash sample is the third-party-posture exemplar). Trajectory/waterfall toolview slots share this shape and land with their own render sites (RendersCheck rejects a declaration nobody renders).
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
视图环本身就是 slot:会话注册声明 `'conversation.view'` 列表 slot(Session scope),并将其列在 `children` 表中;ConversationRoot 通过 renderSlot share 渲染活跃配置项(`only: <active id>`);视图标签页从环账本的注册选项(`id`/`order`/`label`)投影而来。聊天视图是该包自身的环配置项;其他插件(ui-trajectory)通过普通的 `ctx.slots.register` 贡献标签页。先前包内的视图注册表(`registerView`/`ViewEntry`/`ConversationViewMap` 及 chrome 附加表)已退役,逐视图 chrome 则被拆入视图组件自身。
|
||||
|
||||
通用工具行把内置的 bash、read、search、write 和 edit 名称归入专用视觉变体。文件系统变体会渲染 edit 图标和 `Write · <path>` 或 `Edit · <path>` 摘要,同时保留共享的行到详情交互。
|
||||
通用工具行把内置的 bash、read、search、write、edit 和 run_code 名称归入专用视觉变体。文件系统变体会渲染 edit 图标和 `Write · <path>` 或 `Edit · <path>` 摘要,同时保留共享的行到详情交互。code 变体以模型撰写的 `description` 作摘要,展开后显示程序本身;其已记录的子调用经由同一个键控 toolview 空位渲染为始终可见的嵌套行(自定义注册和 GenericToolCard fallback 原样适用于子行),details 面板则会根据选中的子调用 id 解析出其完整记录的参数与完整输出。
|
||||
|
||||
工具行同样是 slot:独立工具环(`ToolViewRegistry`/`ctx.toolviews`/outlet)已经退役。聊天配置项声明键控的 `'conversation.chat.toolview'` 空位(Session scope;key 空间在运行时开放);其渲染点逐行通过 `entryKey: toolName` 分发,并以 `GenericToolCard` 作为调用点 `fallback`。owner 载荷是统一的 `ToolRowOwnerProps`(`callId`/`toolName`/`block`/`openDetails`),`ToolRowProps` 则预先将其与 Session 标准工具包组合。注册方只是普通插件:`ctx.slots.register({ name: 'conversation.chat.toolview', key: '<tool>', inject? }, Row)`,以 `inject: ['slots', 'conversation']` 作为加载顺序 seam(apply 在聊天注册后挂载 ConversationService,因此服务存在即可保证 slot 已声明);Session 区分在组件内部完成(`useSessions` 读取 `parentId`,bash 示例是第三方姿态的范例)。Trajectory/waterfall 工具视图 slot 共享此形状,并随各自的渲染点落地(RendersCheck 会拒绝没有任何渲染方的声明)。
|
||||
|
||||
|
||||
@@ -45,6 +45,18 @@
|
||||
outline-offset: 1px;
|
||||
}
|
||||
|
||||
/* run_code sub-dispatch rows: indented under the parent row, left-edged so
|
||||
the code turn reads as one unit; each nested row is itself a .callRow
|
||||
(same components, same selection outline as top-level rows). */
|
||||
.subCalls {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
margin: 4px 0 2px 22px;
|
||||
padding-left: 8px;
|
||||
border-left: 1px solid var(--dsw-alias-border-l2);
|
||||
}
|
||||
|
||||
.hint {
|
||||
color: var(--dsw-alias-label-tertiary);
|
||||
font-size: 12px;
|
||||
|
||||
@@ -45,10 +45,35 @@ type RenderToolRow = ChatViewSlotProps['renderSlot']
|
||||
* chat view narrows once to the runtime snapshot the binding actually feeds. */
|
||||
type UseConversation = SnapshotSelectorHook<ConversationSnapshot>
|
||||
|
||||
/** One `run_code` sub-dispatch row: the identical keyed-slot dispatch as a
|
||||
* top-level call (same registrations, same fallback), nested by the parent. */
|
||||
const SubCallRow = memo(function SubCallRow({ renderSlot, node, onOpenDetails, selected }: {
|
||||
renderSlot: RenderToolRow
|
||||
node: ToolResultNode
|
||||
onOpenDetails: OpenDetails
|
||||
selected: boolean
|
||||
}) {
|
||||
const toolName = node.call?.name ?? ''
|
||||
const owner = useMemo(() => ({
|
||||
callId: node.callId, toolName, block: node,
|
||||
openDetails: () => { onOpenDetails({ turnSeq: node.seq, callId: node.callId, toolName }) },
|
||||
}), [node, toolName, onOpenDetails])
|
||||
return (
|
||||
<div className={css.callRow} data-selected={selected || undefined}>
|
||||
{renderSlot('conversation.chat.toolview', owner, {
|
||||
entryKey: toolName,
|
||||
fallback: <GenericToolCard {...owner} />,
|
||||
})}
|
||||
</div>
|
||||
)
|
||||
})
|
||||
|
||||
/** One tool call row (result or running): dispatches through the keyed
|
||||
* toolview slot with the owner payload; unregistered tools fall back to
|
||||
* GenericToolCard at this render site. */
|
||||
const CallRow = memo(function CallRow({ renderSlot, callId, toolName, block, seq, onOpenDetails, selected }: {
|
||||
* GenericToolCard at this render site. A `run_code` call additionally
|
||||
* renders its logged sub-dispatches as always-visible indented rows —
|
||||
* each one the same keyed-slot dispatch as a native top-level call. */
|
||||
const CallRow = memo(function CallRow({ renderSlot, callId, toolName, block, seq, onOpenDetails, selected, subCalls, selectedCallId }: {
|
||||
renderSlot: RenderToolRow
|
||||
callId: string
|
||||
toolName: string
|
||||
@@ -57,6 +82,10 @@ const CallRow = memo(function CallRow({ renderSlot, callId, toolName, block, seq
|
||||
seq: number
|
||||
onOpenDetails: OpenDetails
|
||||
selected: boolean
|
||||
/** `run_code` sub-dispatches in dispatch order (reference-stable per parent); undefined for ordinary calls. */
|
||||
subCalls?: readonly ToolResultNode[] | undefined
|
||||
/** The store's selected callId, matched against sub-rows (undefined when no sub-row here is selected). */
|
||||
selectedCallId?: string | undefined
|
||||
}) {
|
||||
const owner = useMemo(() => ({
|
||||
callId, toolName, block,
|
||||
@@ -68,17 +97,32 @@ const CallRow = memo(function CallRow({ renderSlot, callId, toolName, block, seq
|
||||
entryKey: toolName,
|
||||
fallback: <GenericToolCard {...owner} />,
|
||||
})}
|
||||
{subCalls !== undefined && subCalls.length > 0 && (
|
||||
<div className={css.subCalls} data-subcalls>
|
||||
{subCalls.map((node) => (
|
||||
<SubCallRow
|
||||
key={node.callId}
|
||||
renderSlot={renderSlot}
|
||||
node={node}
|
||||
onOpenDetails={onOpenDetails}
|
||||
selected={node.callId === selectedCallId}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
})
|
||||
|
||||
/** Consecutive tool results as one step-run group (figma VERTICAL gap10). */
|
||||
const ToolGroup = memo(function ToolGroup({ renderSlot, results, onOpenDetails, selectedCallId }: {
|
||||
const ToolGroup = memo(function ToolGroup({ renderSlot, results, onOpenDetails, selectedCallId, codeDispatches }: {
|
||||
renderSlot: RenderToolRow
|
||||
results: readonly ToolResultNode[]
|
||||
onOpenDetails: OpenDetails
|
||||
/** Only set when the selected call lives in THIS group (memo economy). */
|
||||
/** Only set when the selected call lives in THIS group, top-level or nested (memo economy). */
|
||||
selectedCallId: string | undefined
|
||||
/** Sub-dispatch index off the snapshot (map reference is chunk-storm stable). */
|
||||
codeDispatches: ReadonlyMap<string, readonly ToolResultNode[]>
|
||||
}) {
|
||||
return (
|
||||
<div className={css.toolGroup}>
|
||||
@@ -92,6 +136,8 @@ const ToolGroup = memo(function ToolGroup({ renderSlot, results, onOpenDetails,
|
||||
seq={node.seq}
|
||||
onOpenDetails={onOpenDetails}
|
||||
selected={node.callId === selectedCallId}
|
||||
subCalls={codeDispatches.get(node.callId)}
|
||||
selectedCallId={selectedCallId}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
@@ -116,6 +162,7 @@ function StreamingTail({ useSession, onGrow }: {
|
||||
export function ChatView({ useSession, useStore, renderSlot, openDetails, loadOlder }: ChatViewSlotProps) {
|
||||
const nodes = useSession((s) => s.nodes)
|
||||
const runningCalls = useSession((s) => s.runningCalls)
|
||||
const codeDispatches = useSession((s) => s.codeDispatches)
|
||||
const pending = useSession((s) => s.pending)
|
||||
const openState = useSession((s) => s.openState)
|
||||
const openErrorMessage = useSession((s) => s.openError === null ? null : `${s.openError.message}(${s.openError.code})`)
|
||||
@@ -203,7 +250,8 @@ export function ChatView({ useSession, useStore, renderSlot, openDetails, loadOl
|
||||
const renderItem = (item: ChatFlowItem): ReactNode => {
|
||||
if (item.kind === 'tool-group') {
|
||||
const inGroup = selectedCallId !== undefined
|
||||
&& item.results.some((r) => r.callId === selectedCallId)
|
||||
&& item.results.some((r) => r.callId === selectedCallId
|
||||
|| codeDispatches.get(r.callId)?.some((sub) => sub.callId === selectedCallId) === true)
|
||||
return (
|
||||
<ToolGroup
|
||||
key={item.key}
|
||||
@@ -211,6 +259,7 @@ export function ChatView({ useSession, useStore, renderSlot, openDetails, loadOl
|
||||
results={item.results}
|
||||
onOpenDetails={openDetails}
|
||||
selectedCallId={inGroup ? selectedCallId : undefined}
|
||||
codeDispatches={codeDispatches}
|
||||
/>
|
||||
)
|
||||
}
|
||||
@@ -250,6 +299,8 @@ export function ChatView({ useSession, useStore, renderSlot, openDetails, loadOl
|
||||
seq={call.turn}
|
||||
onOpenDetails={openDetails}
|
||||
selected={call.callId === selectedCallId}
|
||||
subCalls={codeDispatches.get(call.callId)}
|
||||
selectedCallId={selectedCallId}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
import type { ReactNode } from 'react'
|
||||
import {
|
||||
IconApiOutline14, IconBrowseOutline16, IconEditOutline16, IconSearchOutline16, IconThinkOutline14,
|
||||
IconApiOutline14, IconBrowseOutline16, IconCodeOutline16, IconEditOutline16, IconSearchOutline16, IconThinkOutline14,
|
||||
} from '@deepseek-ai/dsh-client-ui-primitives'
|
||||
import type { ToolRowOwnerProps } from '../contract/slots.ts'
|
||||
import { toolRowModel, type ToolRowVariant } from '../contract/tool-call-model.ts'
|
||||
@@ -21,6 +21,7 @@ const VARIANT_ICONS: Record<ToolRowVariant, ReactNode> = {
|
||||
bash: <IconApiOutline14 size={16} />,
|
||||
write: <IconEditOutline16 />,
|
||||
edit: <IconEditOutline16 />,
|
||||
code: <IconCodeOutline16 />,
|
||||
others: <IconSparkle16 />,
|
||||
}
|
||||
|
||||
|
||||
@@ -86,3 +86,15 @@ button.leading {
|
||||
word-break: break-word;
|
||||
color: var(--dsw-alias-label-tertiary);
|
||||
}
|
||||
|
||||
/* The code variant's expanded body is the run_code program: monospace on the
|
||||
markdown code-block fill so the program reads as code, not prose. */
|
||||
.root[data-variant='code'] .body {
|
||||
font-family: var(--ds-font-family-code);
|
||||
font-size: 13px;
|
||||
line-height: 20px;
|
||||
padding: 6px 8px;
|
||||
margin-left: 22px;
|
||||
border-radius: 6px;
|
||||
background: var(--dsw-alias-markdown-code-block);
|
||||
}
|
||||
@@ -13,8 +13,8 @@ export type { ToolCallBlock } from '@deepseek-ai/dsh-client-runtime/client'
|
||||
/** The frozen slice the chat view hands to toolview components as `block`
|
||||
* (both members are cache-stable references off ConversationSnapshot). */
|
||||
|
||||
/** The seven row variants (think is fed by reasoning blocks, not tool calls). */
|
||||
export type ToolRowVariant = 'think' | 'search' | 'read' | 'bash' | 'write' | 'edit' | 'others'
|
||||
/** The eight row variants (think is fed by reasoning blocks, not tool calls). */
|
||||
export type ToolRowVariant = 'think' | 'search' | 'read' | 'bash' | 'write' | 'edit' | 'code' | 'others'
|
||||
|
||||
/** Row state semantic; colors self-supplied via StateDot (design gives none). */
|
||||
export type ToolRowState = 'running' | 'ok' | 'error' | 'stopped'
|
||||
@@ -22,7 +22,7 @@ export type ToolRowState = 'running' | 'ok' | 'error' | 'stopped'
|
||||
/** Figma row titles per variant (design literals, not translatable copy). */
|
||||
export const VARIANT_TITLES: Record<ToolRowVariant, string> = {
|
||||
think: 'Think', search: 'Search', read: 'Read', bash: 'Bash',
|
||||
write: 'Write', edit: 'Edit', others: 'Tool call',
|
||||
write: 'Write', edit: 'Edit', code: 'Code', others: 'Tool call',
|
||||
}
|
||||
|
||||
/** Known tool name -> variant. */
|
||||
@@ -35,6 +35,7 @@ const TOOL_VARIANTS: Record<string, ToolRowVariant> = {
|
||||
glob: 'search',
|
||||
write: 'write',
|
||||
edit: 'edit',
|
||||
run_code: 'code',
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -86,6 +87,7 @@ const SUMMARY_KEYS: Record<ToolRowVariant, readonly string[]> = {
|
||||
think: [],
|
||||
write: ['path', 'file_path'],
|
||||
edit: ['path', 'file_path'],
|
||||
code: ['description'],
|
||||
others: [],
|
||||
}
|
||||
|
||||
@@ -101,10 +103,17 @@ function deriveSummary(variant: ToolRowVariant, argsRaw: string): string {
|
||||
return firstLine(argsRaw)
|
||||
}
|
||||
|
||||
function deriveBody(argsRaw: string): string | null {
|
||||
function deriveBody(variant: ToolRowVariant, argsRaw: string): string | null {
|
||||
if (argsRaw === '') return null
|
||||
const parsed = parseArgs(argsRaw)
|
||||
return parsed === undefined ? argsRaw : JSON.stringify(parsed, null, 2)
|
||||
if (parsed === undefined) return argsRaw
|
||||
// The code row's expanded body IS the program (monospace via the row's
|
||||
// variant styling), not the args JSON envelope around it.
|
||||
if (variant === 'code' && typeof parsed === 'object' && parsed !== null) {
|
||||
const code = (parsed as Record<string, unknown>).code
|
||||
if (typeof code === 'string' && code !== '') return code
|
||||
}
|
||||
return JSON.stringify(parsed, null, 2)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -128,7 +137,7 @@ export function toolRowModel(toolName: string, block: ToolCallBlock): ToolRowMod
|
||||
variant,
|
||||
title: VARIANT_TITLES[variant],
|
||||
summary,
|
||||
body: deriveBody(argsRaw),
|
||||
body: deriveBody(variant, argsRaw),
|
||||
state,
|
||||
}
|
||||
}
|
||||
@@ -31,6 +31,15 @@ function materialFor(s: ConversationSnapshot, callId: string): CallMaterial | nu
|
||||
if (open !== undefined) {
|
||||
return { name: open.name, argsRaw: open.argsRaw, result: null, running: true }
|
||||
}
|
||||
// run_code sub-dispatches: already ToolResultNode-shaped, so a selected
|
||||
// sub-row resolves through the same material as a native settled call.
|
||||
for (const subs of s.codeDispatches.values()) {
|
||||
for (const sub of subs) {
|
||||
if (sub.callId === callId) {
|
||||
return { name: sub.call?.name ?? callId, argsRaw: sub.call?.argsRaw ?? null, result: sub, running: false }
|
||||
}
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,214 @@
|
||||
// @vitest-environment jsdom
|
||||
// Code Mode sub-call acceptance on the REAL machinery stack (same bench as
|
||||
// chat-toolview-slot.spec): a run_code result renders the 'code' variant row
|
||||
// (description summary, program body), its logged sub-dispatches render as
|
||||
// always-visible nested rows through the SAME keyed toolview hole — the bash
|
||||
// sub-call lands in the bash sample plugin's registration exactly like a
|
||||
// top-level bash row, unregistered sub-tools fall back to GenericToolCard —
|
||||
// and a sub-row click opens details for the sub-callId. Running parents
|
||||
// (runningCalls) nest their so-far dispatches the same way.
|
||||
|
||||
import { Context } from 'cordis'
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import { cleanup, fireEvent, render } from '@testing-library/react'
|
||||
import { createSnapshotStore, SlotsService } from '@deepseek-ai/dsh-client-runtime/client'
|
||||
import type {
|
||||
CodeSubCall, ConversationSnapshot, RunningToolCall, SessionId, SessionListState,
|
||||
ToolResultNode, WorkspaceListState,
|
||||
} from '@deepseek-ai/dsh-client-runtime/client'
|
||||
import { createSlotRenderer } from '@deepseek-ai/dsh-client-web-react'
|
||||
import type { PropsRenderSlots } from '@deepseek-ai/dsh-client-ui-slots'
|
||||
import { apply, inject } from '@deepseek-ai/dsh-client-ui-conversation/client'
|
||||
|
||||
const SID = 's1' as SessionId
|
||||
|
||||
afterEach(cleanup)
|
||||
beforeEach(() => {
|
||||
localStorage.clear()
|
||||
})
|
||||
|
||||
const PROGRAM = 'const listing = await tools.bash({ command: "ls notes", description: "List notes" })\nreturn listing'
|
||||
const RUN_CODE_ARGS = JSON.stringify({ code: PROGRAM, description: 'List the notes directory' })
|
||||
|
||||
const codeResult = (seq: number, callId: string): ToolResultNode => ({
|
||||
kind: 'tool-result', seq, time: seq * 1_000, callId,
|
||||
call: { name: 'run_code', argsRaw: RUN_CODE_ARGS },
|
||||
callTime: seq * 1_000 - 500,
|
||||
content: [{ type: 'text', text: 'demo.txt' }], isError: false, callView: null, resultView: null,
|
||||
})
|
||||
|
||||
const runningCode = (callId: string): RunningToolCall => ({
|
||||
callId, name: 'run_code', argsRaw: RUN_CODE_ARGS, turn: 9, step: 0, time: 9_000, callView: null,
|
||||
})
|
||||
|
||||
const subCall = (seq: number, parent: string, n: number, name: string, args: object, resultText: string, isError = false): CodeSubCall => ({
|
||||
kind: 'tool-result', seq, time: seq * 1_000,
|
||||
callId: `${parent}:code:${n}`,
|
||||
call: { name, argsRaw: JSON.stringify(args) },
|
||||
callTime: seq * 1_000,
|
||||
content: [{ type: 'text', text: resultText }], isError, callView: null, resultView: null,
|
||||
})
|
||||
|
||||
function snapshotWith(
|
||||
nodes: ToolResultNode[],
|
||||
codeDispatches: ReadonlyMap<string, readonly CodeSubCall[]>,
|
||||
runningCalls: RunningToolCall[] = [],
|
||||
): ConversationSnapshot {
|
||||
return {
|
||||
sessionId: SID, nodes, foldDegraded: false, partial: null, runningCalls, codeDispatches,
|
||||
pending: [], running: runningCalls.length > 0, composerPhase: 'active', removed: false,
|
||||
openState: 'open', openError: null,
|
||||
hasMore: false, loadingOlder: false, promptError: null, intent: null, pendingPrompt: null, lastAgentError: null,
|
||||
} as ConversationSnapshot
|
||||
}
|
||||
|
||||
/** Test-owned AppFrame role: declares the layout-owned children and renders the conversation area under the framework session provider. */
|
||||
type AppRootProps = PropsRenderSlots<'conversation' | 'details' | 'conversation.empty'>
|
||||
function AppRoot({ renderSlot, SessionProvider }: AppRootProps) {
|
||||
return <SessionProvider>{() => renderSlot('conversation', {})}</SessionProvider>
|
||||
}
|
||||
|
||||
/** Same real-stack bench as the toolview-slot spec: SlotsService + renderer + this package's apply; fakes only at service seams. */
|
||||
async function bench(snapshot: ConversationSnapshot) {
|
||||
const ctx = new Context()
|
||||
const slotsFiber = ctx.plugin(SlotsService)
|
||||
await slotsFiber.await()
|
||||
const slots = ctx.get('slots') as SlotsService
|
||||
|
||||
const session = createSnapshotStore<ConversationSnapshot>(snapshot)
|
||||
const list = createSnapshotStore<SessionListState>({
|
||||
ids: [SID],
|
||||
byId: { [SID]: { id: SID, title: 'S', displayTitle: 'S', running: false, updatedAt: 1 } },
|
||||
current: SID,
|
||||
intent: undefined,
|
||||
phase: 'ready',
|
||||
})
|
||||
const cell = { sessionId: SID, session }
|
||||
const scoped = { send: vi.fn(async () => {}), cancel: vi.fn(async () => {}) }
|
||||
const layout = { openDetails: vi.fn(), closeDetails: vi.fn() }
|
||||
ctx.provide('sessions', {
|
||||
list,
|
||||
binding: (id: SessionId) => ({ sessionId: id, session: { loadOlder: vi.fn() } }),
|
||||
scope: () => ({ get: () => scoped }),
|
||||
cell: (id: string) => (id === SID ? cell : undefined),
|
||||
create: vi.fn(),
|
||||
open: vi.fn(),
|
||||
updateIntent: vi.fn(),
|
||||
})
|
||||
ctx.provide('workspaces', {
|
||||
list: createSnapshotStore<WorkspaceListState>({
|
||||
items: [], intent: undefined, state: 'idle', phase: 'ready', error: null,
|
||||
baselinesReady: true, recentWorkspaceId: undefined,
|
||||
}),
|
||||
startSession: vi.fn(),
|
||||
sendSession: vi.fn(),
|
||||
})
|
||||
ctx.provide('layout', layout)
|
||||
ctx.provide('i18n', { bind: () => (key: string) => key })
|
||||
|
||||
slots.install(createSlotRenderer())
|
||||
slots.register({
|
||||
name: 'root',
|
||||
children: {
|
||||
'conversation': { kind: 'single', scope: 'session' },
|
||||
'details': { kind: 'single', scope: 'session' },
|
||||
'conversation.empty': { kind: 'single', scope: 'root' },
|
||||
},
|
||||
}, AppRoot)
|
||||
|
||||
const fiber = ctx.plugin({ inject: [...inject], apply })
|
||||
await fiber.await()
|
||||
return { ctx, slots, fiber, session, layout }
|
||||
}
|
||||
|
||||
function mountApp(slots: SlotsService) {
|
||||
return render(<>{slots.renderSlot('root', {})}</>)
|
||||
}
|
||||
|
||||
describe('run_code sub-calls through the real chat machinery', () => {
|
||||
it('renders the code-variant parent row with the description summary and nested sub-rows', async () => {
|
||||
const parent = 'call-64'
|
||||
const dispatches = new Map([[parent, [
|
||||
subCall(11, parent, 1, 'bash', { command: 'ls notes', description: 'List notes' }, 'demo.txt'),
|
||||
subCall(12, parent, 2, 'mystery', { n: 1 }, 'ok'),
|
||||
]]])
|
||||
const b = await bench(snapshotWith([codeResult(10, parent)], dispatches))
|
||||
const view = mountApp(b.slots)
|
||||
|
||||
// Parent row: the code variant with the model-authored description.
|
||||
const codeRoot = view.container.querySelector('[data-variant="code"]')
|
||||
expect(codeRoot).not.toBeNull()
|
||||
expect(view.getByText('Code')).toBeTruthy()
|
||||
expect(view.getByText('List the notes directory')).toBeTruthy()
|
||||
|
||||
// Nested rows are ALWAYS visible (no parent expand needed): the bash
|
||||
// sub-call landed in the bash sample plugin's keyed registration — the
|
||||
// exact component a native top-level bash row uses — and the unregistered
|
||||
// sub-tool fell back to GenericToolCard at the same render site.
|
||||
const nest = view.container.querySelector('[data-subcalls]')
|
||||
expect(nest).not.toBeNull()
|
||||
expect(nest!.querySelector('[data-sample="bash-global"]')).not.toBeNull()
|
||||
expect(view.getByText('List notes')).toBeTruthy()
|
||||
expect(view.getByText('Tool call')).toBeTruthy()
|
||||
})
|
||||
|
||||
it('expanding the code row reveals the program body verbatim', async () => {
|
||||
const parent = 'call-64'
|
||||
const b = await bench(snapshotWith([codeResult(10, parent)], new Map()))
|
||||
const view = mountApp(b.slots)
|
||||
// The code row is expandable via its leading control (body = the program).
|
||||
const toggle = view.container.querySelector('[data-variant="code"] button[aria-expanded]')
|
||||
expect(toggle).not.toBeNull()
|
||||
fireEvent.click(toggle!)
|
||||
expect(view.getByText(/const listing = await tools\.bash/)).toBeTruthy()
|
||||
})
|
||||
|
||||
it('an isError sub-call renders the error state dot exactly like a failed native row', async () => {
|
||||
const parent = 'call-64'
|
||||
const dispatches = new Map([[parent, [
|
||||
subCall(11, parent, 1, 'mystery', { n: 1 }, 'Error: boom', true),
|
||||
]]])
|
||||
const b = await bench(snapshotWith([codeResult(10, parent)], dispatches))
|
||||
const view = mountApp(b.slots)
|
||||
const nested = view.container.querySelector('[data-subcalls] [data-variant][data-state="error"]')
|
||||
expect(nested).not.toBeNull()
|
||||
})
|
||||
|
||||
it('a sub-row click opens details for the sub-callId', async () => {
|
||||
const parent = 'call-64'
|
||||
const dispatches = new Map([[parent, [
|
||||
subCall(11, parent, 1, 'bash', { command: 'ls notes', description: 'List notes' }, 'demo.txt'),
|
||||
]]])
|
||||
const b = await bench(snapshotWith([codeResult(10, parent)], dispatches))
|
||||
const view = mountApp(b.slots)
|
||||
view.getByText('List notes').click()
|
||||
expect(b.layout.openDetails).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
it('a RUNNING run_code call nests its so-far dispatches under the spinner row', async () => {
|
||||
const parent = 'call-live'
|
||||
const dispatches = new Map([[parent, [
|
||||
subCall(21, parent, 1, 'bash', { command: 'ls notes', description: 'List notes' }, 'demo.txt'),
|
||||
]]])
|
||||
const b = await bench(snapshotWith([], dispatches, [runningCode(parent)]))
|
||||
const view = mountApp(b.slots)
|
||||
const running = view.container.querySelector('[data-variant="code"][data-state="running"]')
|
||||
expect(running).not.toBeNull()
|
||||
const nest = view.container.querySelector('[data-subcalls]')
|
||||
expect(nest).not.toBeNull()
|
||||
expect(nest!.querySelector('[data-sample="bash-global"]')).not.toBeNull()
|
||||
})
|
||||
|
||||
it('an ordinary tool row renders no sub-call nest', async () => {
|
||||
const parent = 'call-64'
|
||||
const plain: ToolResultNode = {
|
||||
kind: 'tool-result', seq: 10, time: 10_000, callId: parent,
|
||||
call: { name: 'mystery', argsRaw: '{"n":1}' },
|
||||
callTime: 9_500,
|
||||
content: [], isError: false, callView: null, resultView: null,
|
||||
}
|
||||
const b = await bench(snapshotWith([plain], new Map()))
|
||||
const view = mountApp(b.slots)
|
||||
expect(view.container.querySelector('[data-subcalls]')).toBeNull()
|
||||
})
|
||||
})
|
||||
@@ -26,7 +26,7 @@ const assistant = (seq: number, turn: number, usage?: unknown): AssistantMessage
|
||||
|
||||
function snapshotBase(): ConversationSnapshot {
|
||||
return {
|
||||
sessionId: SID, nodes: [], foldDegraded: false, partial: null, runningCalls: [],
|
||||
sessionId: SID, nodes: [], foldDegraded: false, partial: null, runningCalls: [], codeDispatches: new Map(),
|
||||
pending: [], running: false, composerPhase: 'active', removed: false, openState: 'open', openError: null,
|
||||
hasMore: false, loadingOlder: false, promptError: null, intent: null, pendingPrompt: null, lastAgentError: null,
|
||||
}
|
||||
|
||||
@@ -39,7 +39,7 @@ const toolResult = (seq: number, callId: string, name: string, args = '{"command
|
||||
|
||||
function snapshotWith(nodes: ToolResultNode[]): ConversationSnapshot {
|
||||
return {
|
||||
sessionId: SID, nodes, foldDegraded: false, partial: null, runningCalls: [],
|
||||
sessionId: SID, nodes, foldDegraded: false, partial: null, runningCalls: [], codeDispatches: new Map(),
|
||||
pending: [], running: false, composerPhase: 'active', removed: false, openState: 'open', openError: null,
|
||||
hasMore: false, loadingOlder: false, promptError: null, intent: null, pendingPrompt: null, lastAgentError: null,
|
||||
} as ConversationSnapshot
|
||||
|
||||
@@ -28,7 +28,7 @@ const SID = 's1' as SessionId
|
||||
|
||||
function snapshotBase(): ConversationSnapshot {
|
||||
return {
|
||||
sessionId: SID, nodes: [], foldDegraded: false, partial: null, runningCalls: [],
|
||||
sessionId: SID, nodes: [], foldDegraded: false, partial: null, runningCalls: [], codeDispatches: new Map(),
|
||||
pending: [], running: false, composerPhase: 'active', removed: false, openState: 'open', openError: null,
|
||||
hasMore: false, loadingOlder: false, promptError: null, intent: null, pendingPrompt: null, lastAgentError: null,
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ const SID = 's1' as SessionId
|
||||
|
||||
function snapshotBase(): ConversationSnapshot {
|
||||
return {
|
||||
sessionId: SID, nodes: [], foldDegraded: false, partial: null, runningCalls: [],
|
||||
sessionId: SID, nodes: [], foldDegraded: false, partial: null, runningCalls: [], codeDispatches: new Map(),
|
||||
pending: [], running: false, composerPhase: 'active', removed: false, openState: 'open', openError: null,
|
||||
hasMore: false, loadingOlder: false, promptError: null, intent: null, pendingPrompt: null, lastAgentError: null,
|
||||
} as ConversationSnapshot
|
||||
@@ -84,4 +84,40 @@ describe('render branch tails', () => {
|
||||
expect(view.getByText('详情')).toBeTruthy()
|
||||
expect(view.getByText('该调用不在当前窗口内')).toBeTruthy()
|
||||
})
|
||||
|
||||
it('DetailsPanel resolves a run_code sub-callId to its full logged args and output', () => {
|
||||
localStorage.clear()
|
||||
const snap = snapshotBase()
|
||||
const longText = 'x'.repeat(1_000)
|
||||
snap.codeDispatches = new Map([['p1', [{
|
||||
kind: 'tool-result', seq: 8, time: 8_000, callId: 'p1:code:1',
|
||||
call: { name: 'read', argsRaw: '{"path":"notes/demo.txt"}' },
|
||||
callTime: 8_000,
|
||||
content: [{ type: 'text', text: longText }], isError: false, callView: null, resultView: null,
|
||||
}]]])
|
||||
const chat = createChatStore().create()
|
||||
chat.actions.select({ turnSeq: 8, callId: 'p1:code:1', toolName: 'read' } satisfies SelectionTarget)
|
||||
const emptyList = createSnapshotStore<SessionListState>(
|
||||
{ ids: [], byId: {}, current: undefined, intent: undefined, phase: 'ready' })
|
||||
const emptyWorkspaces = createSnapshotStore<WorkspaceListState>({
|
||||
items: [], intent: undefined, state: 'idle', phase: 'ready', error: null,
|
||||
baselinesReady: true, recentWorkspaceId: undefined,
|
||||
})
|
||||
const view = render(
|
||||
<DetailsPanel
|
||||
sessionId={SID}
|
||||
useSession={bindSnapshotSelector({ getSnapshot: () => snap, subscribe: () => () => {} }) as unknown as UseSession<ConversationSnapshot>}
|
||||
useSessions={bindSnapshotSelector(emptyList)}
|
||||
useWorkspaces={bindSnapshotSelector(emptyWorkspaces)}
|
||||
useStore={bindSnapshotSelector(chat)}
|
||||
actions={chat.actions}
|
||||
closeDetails={vi.fn()}
|
||||
/>,
|
||||
)
|
||||
// Sub-call material: the sub-tool name titles the panel, args pretty-print,
|
||||
// and the COMPLETE logged output renders (no truncation anywhere).
|
||||
expect(view.getByText('read')).toBeTruthy()
|
||||
expect(view.getByText(/notes\/demo\.txt/)).toBeTruthy()
|
||||
expect(view.getByText(longText)).toBeTruthy()
|
||||
})
|
||||
})
|
||||
@@ -119,7 +119,7 @@ function conversationSnapshot(
|
||||
pendingPrompt: ConversationSnapshot['pendingPrompt'] = null,
|
||||
): ConversationSnapshot {
|
||||
return {
|
||||
sessionId: SID, nodes: [], foldDegraded: false, partial: null, runningCalls: [],
|
||||
sessionId: SID, nodes: [], foldDegraded: false, partial: null, runningCalls: [], codeDispatches: new Map(),
|
||||
pending: [], running: false, composerPhase, removed: false, openState: 'open', openError: null,
|
||||
hasMore: false, loadingOlder: false, promptError: null, intent: null, pendingPrompt, lastAgentError: null,
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
"apps/web/tests/workspace-management.e2e.ts",
|
||||
"apps/web/tests/replay-round-trip.e2e.ts",
|
||||
"apps/web/tests/seeded-history.e2e.ts",
|
||||
"apps/web/tests/code-mode-round.e2e.ts",
|
||||
"apps/cli/tests/**/*.ts",
|
||||
"examples/*/src/**/*.ts",
|
||||
"examples/*/start.ts",
|
||||
|
||||
Reference in New Issue
Block a user