Merge remote-tracking branch 'origin/master' into web-e2e-lane
# Conflicts: # docs/config-catalog.md # docs/testing.i18n.yaml # docs/testing.md # docs/testing.zh.md # packages/host/runtime/src/start.ts # packages/support/acp-snapshot/README.md
This commit is contained in:
224
apps/cli/cordis.yml
Normal file
224
apps/cli/cordis.yml
Normal file
@@ -0,0 +1,224 @@
|
||||
# dsh web — the full web-shape composition: host runtime (layer 1), the
|
||||
# transport/service layer (layer 2), and the browser plugin roster (dshClient
|
||||
# rows the modules node half scans into window.__DSH_BOOT__). Row order
|
||||
# carries no load semantics (activation is service-availability driven); the
|
||||
# grouping below is for readers. `--dev` appends the dsh-client-hmr row in
|
||||
# code (AppCLIEntry) — prod and dev differ by exactly that one row.
|
||||
# AppCLIEntry patches this tree before boot: profile json + CLI flags +
|
||||
# distIndex land as config patches over the rows below (yaml = engineering
|
||||
# defaults, json = user config, user wins per field).
|
||||
|
||||
# ── layer 1: runtime ────────────────────────────────────────────────────────
|
||||
|
||||
- id: timer
|
||||
name: '@cordisjs/plugin-timer'
|
||||
|
||||
- id: llm
|
||||
name: '@deepseek-ai/dsh-llm'
|
||||
|
||||
- id: session
|
||||
name: '@deepseek-ai/dsh-session'
|
||||
|
||||
- id: session-title
|
||||
name: '@deepseek-ai/dsh-session-title'
|
||||
config:
|
||||
fallbackMaxWords: 5
|
||||
fallbackMaxBytes: 40
|
||||
maxTitleBytes: 80
|
||||
|
||||
# Model-made titles on the first-message cadence (the web sidebar renders
|
||||
# session/title). Same values as the TUI composition.
|
||||
- id: session-title-llm
|
||||
name: '@deepseek-ai/dsh-session-title-first-message-llm'
|
||||
config:
|
||||
targetWords: 5
|
||||
targetCjkCharacters: 10
|
||||
maxInputBytes: 4096
|
||||
maxOutputTokens: 64
|
||||
timeoutMs: 60000
|
||||
|
||||
- id: system-prompt
|
||||
name: '@deepseek-ai/dsh-system-prompt'
|
||||
config:
|
||||
persona: ''
|
||||
|
||||
- id: tools
|
||||
name: '@deepseek-ai/dsh-tools'
|
||||
|
||||
- id: user-interaction
|
||||
name: '@deepseek-ai/dsh-user-interaction'
|
||||
|
||||
- id: agent
|
||||
name: '@deepseek-ai/dsh-agent'
|
||||
|
||||
- id: tasks
|
||||
name: '@deepseek-ai/dsh-tasks'
|
||||
|
||||
- id: agent-loop
|
||||
name: '@deepseek-ai/dsh-agent-loop'
|
||||
config:
|
||||
agents: []
|
||||
|
||||
# The native DeepSeek adapter; reads the key/base-url the boot's layered
|
||||
# .env loading (cwd then $DSH_HOME) left in the environment.
|
||||
- id: llm-deepseek
|
||||
name: '@deepseek-ai/dsh-llm-deepseek'
|
||||
config:
|
||||
apiKey: !!js process.env.DEEPSEEK_API_KEY
|
||||
baseURL: !!js process.env.DEEPSEEK_BASE_URL
|
||||
|
||||
- id: session-persistence-jsonl
|
||||
name: '@deepseek-ai/dsh-session-persistence-jsonl'
|
||||
config:
|
||||
root: './.sessions'
|
||||
|
||||
- id: bash-local
|
||||
name: '@deepseek-ai/dsh-bash-local'
|
||||
|
||||
- id: tool-bash
|
||||
name: '@deepseek-ai/dsh-tool-bash'
|
||||
|
||||
- id: tool-todo
|
||||
name: '@deepseek-ai/dsh-tool-todo'
|
||||
|
||||
- id: tool-tasks
|
||||
name: '@deepseek-ai/dsh-tool-tasks'
|
||||
|
||||
# fs cwd stays the package default (process.cwd()) — the same value the
|
||||
# gateway injects into session.cwd, so paths and sessions agree.
|
||||
- id: fs-local
|
||||
name: '@deepseek-ai/dsh-fs-local'
|
||||
|
||||
- id: fs-policy
|
||||
name: '@deepseek-ai/dsh-fs-policy'
|
||||
|
||||
- id: tool-fs
|
||||
name: '@deepseek-ai/dsh-tool-fs'
|
||||
|
||||
- id: tool-fs-search
|
||||
name: '@deepseek-ai/dsh-tool-fs-search'
|
||||
|
||||
- id: workspace-context
|
||||
name: '@deepseek-ai/dsh-workspace-context'
|
||||
config:
|
||||
maxBytes: 65536
|
||||
|
||||
- id: skill
|
||||
name: '@deepseek-ai/dsh-skill'
|
||||
|
||||
- id: skill-local
|
||||
name: '@deepseek-ai/dsh-skill-local'
|
||||
|
||||
- id: tool-skill
|
||||
name: '@deepseek-ai/dsh-tool-skill'
|
||||
|
||||
# token-meter rejects unknown config keys — keep this row bare.
|
||||
- id: token-meter
|
||||
name: '@deepseek-ai/dsh-token-meter'
|
||||
|
||||
- id: compact-basic
|
||||
name: '@deepseek-ai/dsh-compact-basic'
|
||||
|
||||
- id: subagent
|
||||
name: '@deepseek-ai/dsh-subagent'
|
||||
|
||||
- id: subagent-spawn
|
||||
name: '@deepseek-ai/dsh-subagent-spawn'
|
||||
config:
|
||||
providerName: spawn
|
||||
|
||||
- id: subagent-fork
|
||||
name: '@deepseek-ai/dsh-subagent-fork'
|
||||
config:
|
||||
providerName: fork
|
||||
|
||||
- id: tool-subagent
|
||||
name: '@deepseek-ai/dsh-tool-subagent'
|
||||
config:
|
||||
provider: spawn
|
||||
toolName: subagent
|
||||
|
||||
- id: tool-subagent-fork
|
||||
name: '@deepseek-ai/dsh-tool-subagent'
|
||||
config:
|
||||
provider: fork
|
||||
toolName: subagent_fork
|
||||
|
||||
- id: workflow-workerthread
|
||||
name: '@deepseek-ai/dsh-workflow-workerthread'
|
||||
config:
|
||||
provider: spawn
|
||||
|
||||
- id: tool-workflow
|
||||
name: '@deepseek-ai/dsh-tool-workflow'
|
||||
|
||||
- id: timeout-policy
|
||||
name: '@deepseek-ai/dsh-timeout-policy'
|
||||
|
||||
- id: spill-local
|
||||
name: '@deepseek-ai/dsh-spill-local'
|
||||
|
||||
# Omitting maxInlineBytes makes the whole policy a silent no-op — always
|
||||
# state it explicitly.
|
||||
- id: spill-policy
|
||||
name: '@deepseek-ai/dsh-spill-policy'
|
||||
config:
|
||||
maxInlineBytes: 50000
|
||||
|
||||
# The API gateway: the transport-agnostic dispatch face every client shape
|
||||
# shares. provider/model are the host default routing — the profile json's
|
||||
# mapping target (user config overrides these engineering defaults).
|
||||
- id: api-gateway
|
||||
name: '@deepseek-ai/dsh-host-apiproxy'
|
||||
config:
|
||||
provider: deepseek
|
||||
model: deepseek-v4-flash
|
||||
|
||||
# ── layer 2: transport/service ──────────────────────────────────────────────
|
||||
|
||||
# Plain route-registration carrier. distIndex is an assembly fact, not user
|
||||
# config — AppCLIEntry resolves the frontend dist and patches it in; host and
|
||||
# port arrive as CLI-flag patches over these defaults.
|
||||
- id: webserver
|
||||
name: '@deepseek-ai/dsh-host-webserver'
|
||||
config:
|
||||
host: 127.0.0.1
|
||||
port: 3080
|
||||
|
||||
# ── browser plugin roster (dshClient rows; node halves are layer-2 hosts) ──
|
||||
|
||||
# Dual-face: node half scans this very tree for dshClient rows, composes
|
||||
# window.__DSH_BOOT__, serves /plugins/<id>/client.js; browser half is the
|
||||
# module table the shell kernel constructs before cordis exists (§4.7 —
|
||||
# adopted as a plugin entry by the kernel, never fetched).
|
||||
- id: modules
|
||||
name: '@deepseek-ai/dsh-client-modules'
|
||||
|
||||
# Owns both ends of the web transport: node half binds the gateway to the
|
||||
# webserver under /api; browser half is the fetch/SSE client.
|
||||
- id: connection
|
||||
name: '@deepseek-ai/dsh-client-connection'
|
||||
|
||||
- id: client-runtime
|
||||
name: '@deepseek-ai/dsh-client-runtime'
|
||||
|
||||
- id: ui-theme
|
||||
name: '@deepseek-ai/dsh-client-ui-theme'
|
||||
|
||||
- id: i18n
|
||||
name: '@deepseek-ai/dsh-client-i18n'
|
||||
|
||||
- id: ui-layout
|
||||
name: '@deepseek-ai/dsh-client-ui-layout'
|
||||
|
||||
- id: ui-sidebar
|
||||
name: '@deepseek-ai/dsh-client-ui-sidebar'
|
||||
|
||||
- id: ui-conversation
|
||||
name: '@deepseek-ai/dsh-client-ui-conversation'
|
||||
|
||||
- id: ui-question
|
||||
name: '@deepseek-ai/dsh-client-ui-question'
|
||||
|
||||
- id: ui-trajectory
|
||||
name: '@deepseek-ai/dsh-client-ui-trajectory'
|
||||
@@ -9,14 +9,22 @@
|
||||
},
|
||||
"files": [
|
||||
"lib/bin.js",
|
||||
"cordis.yml",
|
||||
"src"
|
||||
],
|
||||
"license": "BSD-3-Clause",
|
||||
"dependencies": {
|
||||
"@cordisjs/plugin-include": "workspace:*",
|
||||
"@cordisjs/plugin-loader": "workspace:*",
|
||||
"@cordisjs/plugin-timer": "workspace:*",
|
||||
"@deepseek-ai/dsh-agent": "workspace:^",
|
||||
"@deepseek-ai/dsh-agent-loop": "workspace:^",
|
||||
"@deepseek-ai/dsh-app-boot": "workspace:^",
|
||||
"@deepseek-ai/dsh-bash-local": "workspace:^",
|
||||
"@deepseek-ai/dsh-client-connection": "workspace:^",
|
||||
"@deepseek-ai/dsh-client-hmr": "workspace:^",
|
||||
"@deepseek-ai/dsh-client-i18n": "workspace:^",
|
||||
"@deepseek-ai/dsh-client-modules": "workspace:^",
|
||||
"@deepseek-ai/dsh-client-runtime": "workspace:^",
|
||||
"@deepseek-ai/dsh-client-ui-conversation": "workspace:^",
|
||||
"@deepseek-ai/dsh-client-ui-layout": "workspace:^",
|
||||
@@ -24,13 +32,48 @@
|
||||
"@deepseek-ai/dsh-client-ui-sidebar": "workspace:^",
|
||||
"@deepseek-ai/dsh-client-ui-theme": "workspace:^",
|
||||
"@deepseek-ai/dsh-client-ui-trajectory": "workspace:^",
|
||||
"@deepseek-ai/dsh-compact-basic": "workspace:^",
|
||||
"@deepseek-ai/dsh-frontend": "workspace:^",
|
||||
"@deepseek-ai/dsh-fs-local": "workspace:^",
|
||||
"@deepseek-ai/dsh-fs-policy": "workspace:^",
|
||||
"@deepseek-ai/dsh-host-apiproxy": "workspace:^",
|
||||
"@deepseek-ai/dsh-host-runtime": "workspace:^",
|
||||
"@deepseek-ai/dsh-host-webserver": "workspace:^",
|
||||
"@deepseek-ai/dsh-llm": "workspace:^",
|
||||
"@deepseek-ai/dsh-llm-deepseek": "workspace:^",
|
||||
"@deepseek-ai/dsh-paths": "workspace:^",
|
||||
"@deepseek-ai/dsh-session": "workspace:^",
|
||||
"@deepseek-ai/dsh-session-persistence-jsonl": "workspace:^",
|
||||
"@deepseek-ai/dsh-session-title": "workspace:^",
|
||||
"@deepseek-ai/dsh-session-title-first-message-llm": "workspace:^",
|
||||
"@deepseek-ai/dsh-skill": "workspace:^",
|
||||
"@deepseek-ai/dsh-skill-local": "workspace:^",
|
||||
"@deepseek-ai/dsh-spill-local": "workspace:^",
|
||||
"@deepseek-ai/dsh-spill-policy": "workspace:^",
|
||||
"@deepseek-ai/dsh-subagent": "workspace:^",
|
||||
"@deepseek-ai/dsh-subagent-fork": "workspace:^",
|
||||
"@deepseek-ai/dsh-subagent-spawn": "workspace:^",
|
||||
"@deepseek-ai/dsh-system-prompt": "workspace:^",
|
||||
"@deepseek-ai/dsh-tasks": "workspace:^",
|
||||
"@deepseek-ai/dsh-timeout-policy": "workspace:^",
|
||||
"@deepseek-ai/dsh-token-meter": "workspace:^",
|
||||
"@deepseek-ai/dsh-tool-bash": "workspace:^",
|
||||
"@deepseek-ai/dsh-tool-fs": "workspace:^",
|
||||
"@deepseek-ai/dsh-tool-fs-search": "workspace:^",
|
||||
"@deepseek-ai/dsh-tool-skill": "workspace:^",
|
||||
"@deepseek-ai/dsh-tool-subagent": "workspace:^",
|
||||
"@deepseek-ai/dsh-tool-tasks": "workspace:^",
|
||||
"@deepseek-ai/dsh-tool-todo": "workspace:^",
|
||||
"@deepseek-ai/dsh-tool-workflow": "workspace:^",
|
||||
"@deepseek-ai/dsh-tools": "workspace:^",
|
||||
"@deepseek-ai/dsh-tui": "workspace:^",
|
||||
"cordis": "^4.0.0-rc.7"
|
||||
"@deepseek-ai/dsh-user-interaction": "workspace:^",
|
||||
"@deepseek-ai/dsh-workflow-workerthread": "workspace:^",
|
||||
"@deepseek-ai/dsh-workspace-context": "workspace:^",
|
||||
"cordis": "^4.0.0-rc.7",
|
||||
"js-yaml": "^4.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/js-yaml": "^4.0.9"
|
||||
}
|
||||
}
|
||||
|
||||
231
apps/cli/src/app-cli-entry.ts
Normal file
231
apps/cli/src/app-cli-entry.ts
Normal file
@@ -0,0 +1,231 @@
|
||||
/**
|
||||
* AppCLIEntry — the pre-cordis boot glue every dsh surface shape shares
|
||||
* (config-tree boot wired for `dsh web` this round; TUI/headless migrate
|
||||
* later). Everything here is what must exist before the Loader runs: layered
|
||||
* env, the patch composition over the shipped cordis.yml (profile json + CLI
|
||||
* flags + the resolved frontend dist), and the fail-loud triple after the
|
||||
* tree settles.
|
||||
*/
|
||||
|
||||
import { readFileSync } from 'node:fs'
|
||||
import { createRequire } from 'node:module'
|
||||
import { join, resolve } from 'node:path'
|
||||
import { pathToFileURL } from 'node:url'
|
||||
import { Context } from 'cordis'
|
||||
import type { FiberState } from 'cordis'
|
||||
import Loader from '@cordisjs/plugin-loader'
|
||||
import Include, { type PatchOptions } from '@cordisjs/plugin-include'
|
||||
import yaml from 'js-yaml'
|
||||
import { assertEntriesLoaded, installFailLoud, loadEnv } from '@deepseek-ai/dsh-app-boot'
|
||||
import { resolveDshHome } from '@deepseek-ai/dsh-paths'
|
||||
// Empty type import carries the httpServer Context merge for the port read below.
|
||||
import type {} from '@deepseek-ai/dsh-host-webserver'
|
||||
|
||||
/** Profile file under the invoking directory (read-only this round; never created — see the design's profile ruling). */
|
||||
const PROFILE_DIR = '.dsh-tmp-profile'
|
||||
const PROFILE_FILE = 'config.json'
|
||||
|
||||
/** One profile-json key mapped onto a yml row's config field. */
|
||||
interface ProfileMapping {
|
||||
jsonPath: string
|
||||
entryId: string
|
||||
configKey: string
|
||||
}
|
||||
|
||||
/**
|
||||
* The static profile→row mapping table. json is user config and wins over the
|
||||
* yml engineering default per field; a json key absent from this table fails
|
||||
* loud (a typo silently ignored would read as "setting has no effect").
|
||||
* Developers extend deployments by adding rows here.
|
||||
*/
|
||||
const PROFILE_MAPPINGS: ProfileMapping[] = [
|
||||
{ jsonPath: 'provider', entryId: 'api-gateway', configKey: 'provider' },
|
||||
{ jsonPath: 'model', entryId: 'api-gateway', configKey: 'model' },
|
||||
{ jsonPath: 'persistenceRoot', entryId: 'session-persistence-jsonl', configKey: 'root' },
|
||||
]
|
||||
|
||||
// The include's YAML dialect: `!!js` scalars become expression nodes the
|
||||
// Loader evaluates at entry activation. The bypass parse below must accept
|
||||
// them (and passing one through a patch unchanged is legal).
|
||||
const jsExprType = new yaml.Type('tag:yaml.org,2002:js', {
|
||||
kind: 'scalar',
|
||||
resolve: data => typeof data === 'string',
|
||||
construct: data => ({ __jsExpr: String(data) }),
|
||||
})
|
||||
const includeYamlSchema = yaml.JSON_SCHEMA.extend(jsExprType)
|
||||
|
||||
/**
|
||||
* Value mirror of cordis's `FiberState` const enum members the sweep needs
|
||||
* (a const enum has no runtime object to import; same rationale as the
|
||||
* client-side mirror in dsh-client-web).
|
||||
*/
|
||||
const FIBER_ACTIVE = 2 as FiberState.ACTIVE
|
||||
const FIBER_PENDING = 0 as FiberState.PENDING
|
||||
|
||||
/** Constructor facts for one `dsh web` invocation (argv already parsed by web.ts). */
|
||||
export interface AppCLIEntryOptions {
|
||||
/** Absolute path of the shipped cordis.yml. */
|
||||
configPath: string
|
||||
/** Whether to append the HMR row (the whole prod/dev difference). */
|
||||
dev: boolean
|
||||
/** --host when explicitly passed; undefined keeps the yml engineering default. */
|
||||
host?: string
|
||||
/** --port when explicitly passed; undefined keeps the yml engineering default. */
|
||||
port?: number
|
||||
}
|
||||
|
||||
/**
|
||||
* Boot driver for the config-tree `dsh web` shape: holds only what exists
|
||||
* independently of (and prior to) cordis — argv facts, the composed patch
|
||||
* set, and finally the root ctx.
|
||||
*/
|
||||
export class AppCLIEntry {
|
||||
/** The root context, set by {@link run}. */
|
||||
ctx!: Context
|
||||
|
||||
private patches: PatchOptions[] = []
|
||||
|
||||
constructor(private readonly options: AppCLIEntryOptions) {}
|
||||
|
||||
/**
|
||||
* Run the boot chain: layered env → patch composition → Loader include
|
||||
* boot (dev row before await) → fail-loud triple.
|
||||
* @returns the settled root context and the listening port.
|
||||
*/
|
||||
async run(): Promise<{ ctx: Context; port: number }> {
|
||||
this.loadEnvLayers()
|
||||
this.composePatches()
|
||||
await this.bootTree()
|
||||
this.assertBoot()
|
||||
const port = this.ctx.get('httpServer')?.port
|
||||
/* v8 ignore next -- the sweep above guarantees an ACTIVE webserver row */
|
||||
if (port === undefined) throw new Error('dsh web: httpServer service missing after settled boot')
|
||||
return { ctx: this.ctx, port }
|
||||
}
|
||||
|
||||
/** Layered .env: ambient > cwd (bin already loaded) > $DSH_HOME (loadEnvFile never overrides). */
|
||||
private loadEnvLayers(): void {
|
||||
loadEnv('dsh web', resolveDshHome())
|
||||
}
|
||||
|
||||
/**
|
||||
* Compose the patch set from the three non-yml config sources: profile
|
||||
* json (user config), CLI flags, and the resolved frontend dist. Patches
|
||||
* replace a row's config wholesale, so each patched row's yml static
|
||||
* values are re-read here (bypass parse) and merged under the overrides.
|
||||
*/
|
||||
private composePatches(): void {
|
||||
const rows = this.parseYmlRows()
|
||||
const overrides = new Map<string, Record<string, unknown>>()
|
||||
const put = (entryId: string, key: string, value: unknown): void => {
|
||||
const bag = overrides.get(entryId) ?? {}
|
||||
bag[key] = value
|
||||
overrides.set(entryId, bag)
|
||||
}
|
||||
|
||||
// Source 1: profile json (missing file = empty; unmapped key = loud).
|
||||
for (const [key, value] of Object.entries(this.readProfile())) {
|
||||
const mapping = PROFILE_MAPPINGS.find(m => m.jsonPath === key)
|
||||
if (mapping === undefined) {
|
||||
throw new Error(`dsh web: profile key "${key}" has no mapping (known: ${PROFILE_MAPPINGS.map(m => m.jsonPath).join(', ')})`)
|
||||
}
|
||||
put(mapping.entryId, mapping.configKey, value)
|
||||
}
|
||||
|
||||
// Source 2: CLI flags (field set disjoint from the json mappings).
|
||||
if (this.options.host !== undefined) put('webserver', 'host', this.options.host)
|
||||
if (this.options.port !== undefined) put('webserver', 'port', this.options.port)
|
||||
|
||||
// Source 3: the frontend dist — an assembly fact of this app, never yml
|
||||
// user config. Workspace knowledge stays here.
|
||||
put('webserver', 'distIndex', this.resolveDistIndex())
|
||||
|
||||
this.patches = [...overrides.entries()].map(([id, bag]) => {
|
||||
const yml = rows.get(id)
|
||||
if (yml === undefined) throw new Error(`dsh web: patch target row "${id}" not found in ${this.options.configPath}`)
|
||||
return { id, config: { ...(yml.config ?? {}) as Record<string, unknown>, ...bag } }
|
||||
})
|
||||
}
|
||||
|
||||
/** Loader include boot; the dev HMR row mounts before await so the fail-loud triple covers it. */
|
||||
private async bootTree(): Promise<void> {
|
||||
const ctx = new Context()
|
||||
ctx.baseUrl = pathToFileURL(join(resolve(this.options.configPath), '..')).href + '/'
|
||||
await ctx.plugin(Loader)
|
||||
ctx.loader.builtins.include = Include
|
||||
await ctx.loader.create({
|
||||
name: 'cordis:include',
|
||||
config: {
|
||||
path: pathToFileURL(resolve(this.options.configPath)).href,
|
||||
...this.patches.length > 0 ? { patches: this.patches } : {},
|
||||
},
|
||||
})
|
||||
if (this.options.dev) {
|
||||
await ctx.loader.create({ name: '@deepseek-ai/dsh-client-hmr' })
|
||||
}
|
||||
this.ctx = ctx
|
||||
await ctx.loader.await()
|
||||
}
|
||||
|
||||
/**
|
||||
* Fail-loud triple: assertEntriesLoaded catches import failures,
|
||||
* installFailLoud catches late apply rejections, and the all-ACTIVE sweep
|
||||
* below catches PENDING fibers (cordis inject waiting has no timeout).
|
||||
*/
|
||||
private assertBoot(): void {
|
||||
installFailLoud('dsh web')
|
||||
assertEntriesLoaded(this.ctx, 'dsh web')
|
||||
const failures: string[] = []
|
||||
for (const entry of this.ctx.loader.entries()) {
|
||||
if (entry.fiber === undefined || entry.disabled) continue
|
||||
const state = entry.fiber.state
|
||||
if (state === FIBER_ACTIVE) continue
|
||||
if (state === FIBER_PENDING) {
|
||||
const missing = Object.keys(entry.fiber.inject).filter(service => this.ctx.get(service) === undefined)
|
||||
failures.push(`${entry.options.name}: pending (waiting for service${missing.length === 1 ? '' : 's'}: ${missing.join(', ') || 'unknown'})`)
|
||||
} else {
|
||||
failures.push(`${entry.options.name}: fiber state ${String(state)}`)
|
||||
}
|
||||
}
|
||||
if (failures.length > 0) {
|
||||
throw new Error(`dsh web: ${String(failures.length)} entr${failures.length === 1 ? 'y' : 'ies'} did not activate\n${failures.join('\n')}`)
|
||||
}
|
||||
}
|
||||
|
||||
/** Bypass parse of the shipped yml (id → row) for patch-merge inputs; Loader still reads the file itself. */
|
||||
private parseYmlRows(): Map<string, { config?: unknown }> {
|
||||
const doc = yaml.load(readFileSync(this.options.configPath, 'utf8'), { schema: includeYamlSchema })
|
||||
if (!Array.isArray(doc)) throw new Error(`dsh web: ${this.options.configPath} is not a top-level entry list`)
|
||||
const rows = new Map<string, { config?: unknown }>()
|
||||
for (const row of doc as { id?: string; config?: unknown }[]) {
|
||||
if (typeof row.id === 'string') rows.set(row.id, row)
|
||||
}
|
||||
return rows
|
||||
}
|
||||
|
||||
/** Profile json under cwd; read-only — never created here, absent = no user config. */
|
||||
private readProfile(): Record<string, unknown> {
|
||||
let raw: string
|
||||
try {
|
||||
raw = readFileSync(join(process.cwd(), PROFILE_DIR, PROFILE_FILE), 'utf8')
|
||||
} catch (error) {
|
||||
if ((error as NodeJS.ErrnoException).code === 'ENOENT') return {}
|
||||
throw error
|
||||
}
|
||||
const parsed: unknown = JSON.parse(raw)
|
||||
if (parsed === null || typeof parsed !== 'object' || Array.isArray(parsed)) {
|
||||
throw new Error(`dsh web: ${PROFILE_DIR}/${PROFILE_FILE} must hold a JSON object`)
|
||||
}
|
||||
return parsed as Record<string, unknown>
|
||||
}
|
||||
|
||||
/** Dist location is workspace knowledge of this app: resolved through the frontend package exports, not configured. */
|
||||
private resolveDistIndex(): string {
|
||||
const require = createRequire(import.meta.url)
|
||||
try {
|
||||
return require.resolve('@deepseek-ai/dsh-frontend/dist/index.html')
|
||||
} catch {
|
||||
throw new Error('dsh web: frontend dist not built; run pnpm --filter @deepseek-ai/dsh-frontend build first')
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,160 +1,66 @@
|
||||
/**
|
||||
* `dsh web` — the web-shape assembly: startHost + dist resolution +
|
||||
* startWebServer + the URL line + signal wiring. Mixing host and carrier
|
||||
* concerns is this app module's job (packages stay single-sided).
|
||||
* `dsh web` — thin bin over the config-tree boot: parse argv, run
|
||||
* AppCLIEntry, print the URL line, wire signals. All composition lives in
|
||||
* cordis.yml; all boot glue lives in AppCLIEntry.
|
||||
*/
|
||||
|
||||
import { parseArgs } from 'node:util'
|
||||
import { networkInterfaces } from 'node:os'
|
||||
import { createRequire } from 'node:module'
|
||||
import { mountWebPlugins, startHost } from '@deepseek-ai/dsh-host-runtime'
|
||||
import { createHostWebPluginRegistry, startWebServer } from '@deepseek-ai/dsh-host-webserver'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
import { AppCLIEntry } from './app-cli-entry.ts'
|
||||
|
||||
const LOOPBACK_HOST = '127.0.0.1'
|
||||
const ALL_INTERFACES_HOST = '0.0.0.0'
|
||||
|
||||
// --- Client composition (composition decisions live in the composing app) ---
|
||||
// The composition layer owns one decision: which plugin packages mount (the
|
||||
// roster). Dependency edges and the boot prefetch tier live in each package's
|
||||
// dshClient declaration.
|
||||
|
||||
/**
|
||||
* Dev-only plugin: the client HMR driver. Whether it composes in is a
|
||||
* deployment decision — the dev graph includes its row, the prod graph does
|
||||
* not mount it at all.
|
||||
*/
|
||||
const CLIENT_HMR_ID = '@deepseek-ai/dsh-client-hmr'
|
||||
|
||||
/** Bundle stat-poll interval for --dev (held here so the startup log states the real value). */
|
||||
const CLIENT_BUNDLE_POLL_MS = 500
|
||||
|
||||
/** The client plugin roster (flat; per-row boot behavior comes from manifests). */
|
||||
const CLIENT_PACKAGES = [
|
||||
'@deepseek-ai/dsh-client-connection',
|
||||
'@deepseek-ai/dsh-client-runtime',
|
||||
'@deepseek-ai/dsh-client-ui-theme',
|
||||
'@deepseek-ai/dsh-client-i18n',
|
||||
'@deepseek-ai/dsh-client-ui-layout',
|
||||
'@deepseek-ai/dsh-client-ui-sidebar',
|
||||
'@deepseek-ai/dsh-client-ui-conversation',
|
||||
'@deepseek-ai/dsh-client-ui-question',
|
||||
'@deepseek-ai/dsh-client-ui-trajectory',
|
||||
] as const
|
||||
const CONFIG_PATH = fileURLToPath(new URL('../cordis.yml', import.meta.url))
|
||||
|
||||
export async function runWeb(argv: string[]): Promise<void> {
|
||||
const { values } = parseArgs({
|
||||
args: argv,
|
||||
options: {
|
||||
host: { type: 'string', default: LOOPBACK_HOST },
|
||||
port: { type: 'string', default: '3080' },
|
||||
host: { type: 'string' },
|
||||
port: { type: 'string' },
|
||||
dev: { type: 'boolean', default: false },
|
||||
},
|
||||
allowPositionals: false,
|
||||
})
|
||||
if (values.host !== LOOPBACK_HOST && values.host !== ALL_INTERFACES_HOST) {
|
||||
if (values.host !== undefined && values.host !== LOOPBACK_HOST && values.host !== ALL_INTERFACES_HOST) {
|
||||
process.stderr.write(
|
||||
`dsh web: invalid --host ${values.host}; expected ${LOOPBACK_HOST} or ${ALL_INTERFACES_HOST}\n`,
|
||||
)
|
||||
process.exit(1)
|
||||
}
|
||||
const hostAddress = values.host
|
||||
const port = Number(values.port)
|
||||
if (!Number.isInteger(port) || port < 0 || port > 65535) {
|
||||
process.stderr.write(`dsh web: invalid --port ${values.port}\n`)
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
// A missing DEEPSEEK_API_KEY throws here (plugin load is fail-loud, uncaught by design).
|
||||
const host = await startHost({
|
||||
boot: {
|
||||
persistenceRoot: './.sessions',
|
||||
workspaceContext: { maxBytes: 65_536 },
|
||||
sessionTitleLlm: true,
|
||||
},
|
||||
})
|
||||
|
||||
// Client plugin chain: in-memory Loader tree over the composed roster, then
|
||||
// the registry that feeds the __DSH_BOOT__ entry graph and
|
||||
// /plugins/<id>/client.js. All row content comes from dshClient discovery
|
||||
// over the mounted roster (dev adds the HMR driver row and turns on the
|
||||
// bundle watch that drives rebuilt frames).
|
||||
const roster = [...CLIENT_PACKAGES, ...values.dev ? [CLIENT_HMR_ID] : []]
|
||||
const mounted = await mountWebPlugins(host.ctx, roster, import.meta.url)
|
||||
const webPlugins = createHostWebPluginRegistry({
|
||||
ctx: host.ctx,
|
||||
loader: mounted.loader,
|
||||
resolvePkgJson: mounted.resolvePkgJson,
|
||||
onError: (err: Error) => { process.stderr.write(`dsh web: plugin rescan: ${String(err)}\n`) },
|
||||
...values.dev ? { watch: { intervalMs: CLIENT_BUNDLE_POLL_MS } } : {},
|
||||
})
|
||||
if (values.dev) {
|
||||
// Dev visibility (the registry is a library and never prints): list what
|
||||
// the bundle watch covers, then log every observed rebuild. This is a
|
||||
// second onRebuilt subscription — the SSE relay inside the webserver is
|
||||
// unaffected (multicast).
|
||||
const revs = new Map(webPlugins.graph().entries.map(row => [row.id, row.rev]))
|
||||
const bundlePaths = [...revs.keys()]
|
||||
.map(id => webPlugins.clientPath(id))
|
||||
.filter((path): path is string => path !== undefined)
|
||||
console.log(
|
||||
`dsh web: watching ${String(bundlePaths.length)} plugin bundles (${String(CLIENT_BUNDLE_POLL_MS)}ms poll):\n ${bundlePaths.join('\n ')}`,
|
||||
)
|
||||
webPlugins.onRebuilt((id, rev) => {
|
||||
console.log(`dsh web: plugin rebuilt: ${id} rev ${revs.get(id) ?? '?'} -> ${rev}`)
|
||||
revs.set(id, rev)
|
||||
})
|
||||
}
|
||||
// Published so the webserver invariant companion can audit manifest/bundle
|
||||
// consistency; nothing else reads this key.
|
||||
host.ctx.reflect.provide('webPlugins', webPlugins)
|
||||
|
||||
// Dist location is workspace knowledge of this app: resolved through
|
||||
// @deepseek-ai/dsh-frontend's package exports, not configured.
|
||||
const require = createRequire(import.meta.url)
|
||||
let distIndex: string
|
||||
try {
|
||||
distIndex = require.resolve('@deepseek-ai/dsh-frontend/dist/index.html')
|
||||
} catch {
|
||||
process.stderr.write('dsh web: frontend dist not built; run pnpm --filter @deepseek-ai/dsh-frontend build first\n')
|
||||
await host.dispose()
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
let exiting = false
|
||||
async function shutdown(code: number): Promise<void> {
|
||||
if (exiting) return
|
||||
exiting = true
|
||||
try {
|
||||
await server.close()
|
||||
await host.dispose()
|
||||
} finally {
|
||||
process.exit(code)
|
||||
let port: number | undefined
|
||||
if (values.port !== undefined) {
|
||||
port = Number(values.port)
|
||||
if (!Number.isInteger(port) || port < 0 || port > 65535) {
|
||||
process.stderr.write(`dsh web: invalid --port ${values.port}\n`)
|
||||
process.exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
let server: Awaited<ReturnType<typeof startWebServer>>
|
||||
try {
|
||||
server = await startWebServer(
|
||||
{ host: hostAddress, port, distIndex, apiHandler: host.handler, webPlugins },
|
||||
(err: Error) => {
|
||||
process.stderr.write(`dsh web: ${String(err)}\n`)
|
||||
void shutdown(1)
|
||||
},
|
||||
)
|
||||
} catch (error: unknown) {
|
||||
// listen failed (EADDRINUSE…): no server to close, dispose the host directly.
|
||||
process.stderr.write(`dsh web: ${String(error)}\n`)
|
||||
await host.dispose()
|
||||
process.exit(1)
|
||||
const entry = new AppCLIEntry({
|
||||
configPath: CONFIG_PATH,
|
||||
dev: values.dev,
|
||||
...values.host !== undefined ? { host: values.host } : {},
|
||||
...port !== undefined ? { port } : {},
|
||||
})
|
||||
const { ctx, port: boundPort } = await entry.run()
|
||||
|
||||
let exiting = false
|
||||
const shutdown = (code: number): void => {
|
||||
if (exiting) return
|
||||
exiting = true
|
||||
void Promise.resolve(ctx.fiber.dispose()).finally(() => { process.exit(code) })
|
||||
}
|
||||
|
||||
const lan = hostAddress === ALL_INTERFACES_HOST
|
||||
const lanCandidate = values.host === ALL_INTERFACES_HOST
|
||||
? Object.values(networkInterfaces()).flat()
|
||||
.find(iface => iface !== undefined && iface.family === 'IPv4' && !iface.internal)
|
||||
: undefined
|
||||
const localUrl = `http://${LOOPBACK_HOST}:${server.port}`
|
||||
console.log(`dsh web: ${localUrl}${lan === undefined ? '' : ` (LAN: http://${lan.address}:${server.port})`}`)
|
||||
const localUrl = `http://${LOOPBACK_HOST}:${boundPort}`
|
||||
console.log(`dsh web: ${localUrl}${lanCandidate === undefined ? '' : ` (LAN: http://${lanCandidate.address}:${boundPort})`}`)
|
||||
|
||||
process.on('SIGTERM', () => { void shutdown(0) })
|
||||
process.on('SIGINT', () => { void shutdown(130) })
|
||||
process.on('SIGTERM', () => { shutdown(0) })
|
||||
process.on('SIGINT', () => { shutdown(130) })
|
||||
}
|
||||
|
||||
@@ -24,7 +24,6 @@
|
||||
"@deepseek-ai/dsh-client-ui-primitives": "workspace:^",
|
||||
"@deepseek-ai/dsh-client-ui-slots": "workspace:^",
|
||||
"@deepseek-ai/dsh-client-web-react": "workspace:^",
|
||||
"@deepseek-ai/dsh-host-webserver": "workspace:^",
|
||||
"@types/node": "^22.0.0",
|
||||
"@types/react": "~18.3.1",
|
||||
"@types/react-dom": "~18.3.0",
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
* loader holding, module-table seeding, AppRoot gate, plugin assembly — lives
|
||||
* in @deepseek-ai/dsh-client-web; this file only finds the mount point.
|
||||
*/
|
||||
import { bootWebShell } from '@deepseek-ai/dsh-client-web'
|
||||
import { AppWebEntry } from '@deepseek-ai/dsh-client-web'
|
||||
|
||||
const el = document.getElementById('root')
|
||||
if (el === null) throw new Error('web app: missing #root')
|
||||
bootWebShell(el)
|
||||
void new AppWebEntry(el).run()
|
||||
|
||||
@@ -41,7 +41,7 @@ describe('web e2e: fresh round trip through the real assembly', () => {
|
||||
scaffold = await launchWebScaffold({
|
||||
...(MODE === 'record' ? {} : { replayFixture: FIXTURE, paceMs: 15 }),
|
||||
})
|
||||
scaffold.host.ctx.on('session/event', (_session, event: SessionEvent) => { sessionEvents.push(event) })
|
||||
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)
|
||||
@@ -117,7 +117,6 @@ describe('web e2e: fresh round trip through the real assembly', () => {
|
||||
it.skipIf(MODE === 'record')('stayed clean: no pageerrors, no reconnect self-healing, no server errors', async () => {
|
||||
expect(tripwire.pageErrors).toEqual([])
|
||||
expect(tripwire.warnings).toEqual([])
|
||||
expect(scaffold.serverErrors).toEqual([])
|
||||
await assertFixtureInventory(SNAPSHOT_DIR, ['session.jsonl', 'ui.expected.md'])
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,35 +1,42 @@
|
||||
// Shared scaffold for the keyless browser e2e lane (Agent Note:
|
||||
// .agents/notes/implemented/testing/2026-07-24-web-gui-browser-e2e-lane.md).
|
||||
// Boots the REAL web assembly in-process from the exported production
|
||||
// functions — startHost (bootHost spine) + mountWebPlugins + registry +
|
||||
// startWebServer — so a real chromium exercises the real HTTP/SSE wire,
|
||||
// apiproxy, agent loop, tools, and persistence. Modes ride $DSH_SNAPSHOT:
|
||||
// replay (default, keyless: `llm: false` + dsh-llm-replay in providers mode),
|
||||
// record (real DeepSeek adapter + key, harvests fixtures from live session
|
||||
// memory), refresh (keyless replay that rewrites the committed goldens).
|
||||
// Boots the REAL web composition — the shipped apps/cli/cordis.yml through
|
||||
// the vendored Loader (the same include boot AppCLIEntry drives), patched the
|
||||
// snapshot way — so a real chromium exercises the real HTTP/SSE wire, the
|
||||
// api-gateway, agent loop, tools, and persistence. Modes ride $DSH_SNAPSHOT:
|
||||
// replay (default, keyless: llm-deepseek row disabled, dsh-llm-replay row
|
||||
// inserted in providers mode), record (real adapter + key, harvests fixtures
|
||||
// from live session memory), refresh (keyless replay that rewrites goldens).
|
||||
//
|
||||
// Assembly divergence from `dsh web` (apps/cli/src/web.ts), deliberate: the
|
||||
// shipped shell opts into sessionTitleLlm, whose fire-and-forget title call
|
||||
// shares the session's replay cursor — nondeterministic ordering against the
|
||||
// loop's own calls — so this lane keeps bootHost's disabled default and
|
||||
// sidebar titles come from the deterministic fallback service.
|
||||
// Composition divergences from `dsh web`, all deliberate, all via include
|
||||
// patches over the SAME tree (never a second yml): temp persistenceRoot;
|
||||
// workspace-context disabled (recorded fixtures must not embed this repo's
|
||||
// AGENTS.md); session-title-llm disabled (its fire-and-forget title call
|
||||
// would race the loop for the session's replay cursor); webserver pinned to
|
||||
// port 0 with the built dist; keyless modes disable llm-deepseek and fill
|
||||
// the open llm seam post-boot with installLlmReplay on the settled root ctx
|
||||
// (the plugin-row path discards the ReplayHandle; the direct install keeps
|
||||
// assertConsumed for the teardown fixture-consumption check).
|
||||
import { existsSync, readFileSync } from 'node:fs'
|
||||
import { mkdtemp, readFile, readdir, rm, utimes, writeFile } from 'node:fs/promises'
|
||||
import { tmpdir } from 'node:os'
|
||||
import { join } from 'node:path'
|
||||
import { join, resolve } from 'node:path'
|
||||
import { pathToFileURL } from 'node:url'
|
||||
import type { Page } from 'playwright'
|
||||
import { expect } from 'vitest'
|
||||
import { Context } from 'cordis'
|
||||
import Loader from '@cordisjs/plugin-loader'
|
||||
import Include, { type PatchOptions } from '@cordisjs/plugin-include'
|
||||
import { scrubRequestHeaders } from '@deepseek-ai/dsh-acp-snapshot'
|
||||
import { installLlmReplay, parseSessionLog } from '@deepseek-ai/dsh-llm-replay'
|
||||
import { assertEntriesLoaded } from '@deepseek-ai/dsh-app-boot'
|
||||
import type { ReplayHandle } from '@deepseek-ai/dsh-llm-replay'
|
||||
import { startHost, mountWebPlugins } from '@deepseek-ai/dsh-host-runtime'
|
||||
import type { RunningHost } from '@deepseek-ai/dsh-host-runtime'
|
||||
import { createHostWebPluginRegistry, startWebServer } from '@deepseek-ai/dsh-host-webserver'
|
||||
import { installLlmReplay, parseSessionLog } from '@deepseek-ai/dsh-llm-replay'
|
||||
import SessionStore, { SESSION_FORMAT_VERSION, SessionId } from '@deepseek-ai/dsh-session'
|
||||
import type { Session, SessionEvent, SessionHeader } from '@deepseek-ai/dsh-session'
|
||||
import SessionPersistenceJsonl from '@deepseek-ai/dsh-session-persistence-jsonl'
|
||||
import { Context } from 'cordis'
|
||||
// Empty type imports carry the httpServer/agents/sessionPersistence Context merges.
|
||||
import type {} from '@deepseek-ai/dsh-host-webserver'
|
||||
import type {} from '@deepseek-ai/dsh-agent'
|
||||
import { DIST_INDEX, REPO_ROOT, requireDist } from './support.ts'
|
||||
|
||||
/** Snapshot mode for the lane, from $DSH_SNAPSHOT (same vocabulary as the ACP/TUI suites). */
|
||||
@@ -46,27 +53,15 @@ export function webSnapshotMode(): WebSnapshotMode {
|
||||
throw new Error(`DSH_SNAPSHOT must be replay, record, or refresh; got ${JSON.stringify(value)}`)
|
||||
}
|
||||
|
||||
// Replay must run in providers mode (never catch-all): with `llm: false` no
|
||||
// adapter exists, so a catch-all would leave resolveModelContext unroutable
|
||||
// and compact-basic's post-step pressure check would warn every step. The
|
||||
// published contextWindow keeps that pressure path provably inert for small
|
||||
// fixtures.
|
||||
const PROVIDERS = [{ id: 'deepseek', name: 'DeepSeek', models: [{ id: 'deepseek-v4-flash', contextWindow: 128_000 }] }]
|
||||
/** The shipped composition under test: apps/cli's config tree. */
|
||||
const CONFIG_PATH = join(REPO_ROOT, 'apps/cli/cordis.yml')
|
||||
|
||||
// The shipped client roster (apps/cli/src/web.ts CLIENT_PACKAGES, sans the
|
||||
// --dev HMR row). apps/web depends on every entry, so its URL anchors the
|
||||
// Loader's bare-specifier resolution.
|
||||
const CLIENT_PACKAGES = [
|
||||
'@deepseek-ai/dsh-client-connection',
|
||||
'@deepseek-ai/dsh-client-runtime',
|
||||
'@deepseek-ai/dsh-client-ui-theme',
|
||||
'@deepseek-ai/dsh-client-i18n',
|
||||
'@deepseek-ai/dsh-client-ui-layout',
|
||||
'@deepseek-ai/dsh-client-ui-sidebar',
|
||||
'@deepseek-ai/dsh-client-ui-conversation',
|
||||
'@deepseek-ai/dsh-client-ui-question',
|
||||
'@deepseek-ai/dsh-client-ui-trajectory',
|
||||
] as const
|
||||
// Replay publishes the provider catalog the gateway routes to (providers
|
||||
// mode, never catch-all: with llm-deepseek disabled no adapter exists, so a
|
||||
// catch-all would leave resolveModelContext unroutable and compact-basic's
|
||||
// post-step pressure check would warn every step). The published
|
||||
// contextWindow keeps that pressure path provably inert for small fixtures.
|
||||
const REPLAY_PROVIDERS = [{ id: 'deepseek', name: 'DeepSeek', models: [{ id: 'deepseek-v4-flash', contextWindow: 128_000 }] }]
|
||||
|
||||
/** Repo-root .env → process.env for record mode (never overrides set vars); the smoke-real convention. */
|
||||
function loadRootEnv(): void {
|
||||
@@ -78,20 +73,18 @@ function loadRootEnv(): void {
|
||||
}
|
||||
}
|
||||
|
||||
/** A booted web scaffold: real assembly, mode-selected model backend, temp world. */
|
||||
/** A booted web scaffold: real composition, mode-selected model backend, temp world. */
|
||||
export interface WebScaffold {
|
||||
/** The active snapshot mode this scaffold booted under. */
|
||||
mode: WebSnapshotMode
|
||||
/** Browser-facing origin (http://127.0.0.1:<bound port>). */
|
||||
baseUrl: string
|
||||
/** The running host (ctx is the documented in-process barrier seam). */
|
||||
host: RunningHost
|
||||
/** Settled root context (the in-process barrier seam; headless event subscription is its sanctioned use). */
|
||||
ctx: Context
|
||||
/** Temp project directory sessions run in (bash/fs tool cwd). */
|
||||
workspaceCwd: string
|
||||
/** Temp persistence root (seeded sessions land here through the real API). */
|
||||
persistenceRoot: string
|
||||
/** Errors the web server reported asynchronously; assert empty at scenario end. */
|
||||
serverErrors: string[]
|
||||
/** Await a settled turn end: in-process turn/end, then the agent's idle flip (which follows the persistence flush). */
|
||||
whenTurnSettled(timeoutMs?: number): Promise<SessionId>
|
||||
/** Tear everything down; asserts the replay fixture was fully consumed first (replay/refresh). */
|
||||
@@ -101,10 +94,11 @@ export interface WebScaffold {
|
||||
/** Options for {@link launchWebScaffold}. */
|
||||
export interface LaunchOptions {
|
||||
/**
|
||||
* Replay fixture (session.jsonl) served by dsh-llm-replay in replay/refresh
|
||||
* modes; ignored in record mode (the real adapter answers). Omit for
|
||||
* scenarios issuing no model calls — a stray stream then fails loud with
|
||||
* NO_ADAPTER on the open seam.
|
||||
* Replay fixture (session.jsonl) served by the inserted dsh-llm-replay row
|
||||
* in replay/refresh modes; ignored in record mode (the real adapter
|
||||
* answers). Omit for scenarios issuing no model calls — a stray stream then
|
||||
* fails loud with NO_ADAPTER (llm-deepseek is disabled and no replay row
|
||||
* mounts).
|
||||
*/
|
||||
replayFixture?: string
|
||||
/** Per-chunk replay pacing (ms) so the browser observes genuinely incremental SSE; replay/refresh only. */
|
||||
@@ -112,7 +106,7 @@ export interface LaunchOptions {
|
||||
}
|
||||
|
||||
/**
|
||||
* Boot the real web assembly under the current snapshot mode.
|
||||
* Boot the real web composition under the current snapshot mode.
|
||||
* @param options - replay fixture selection and pacing.
|
||||
* @returns the running scaffold.
|
||||
*/
|
||||
@@ -127,83 +121,90 @@ export async function launchWebScaffold(options: LaunchOptions = {}): Promise<We
|
||||
}
|
||||
const workspaceCwd = await mkdtemp(join(tmpdir(), 'dsh-web-e2e-ws-'))
|
||||
const persistenceRoot = await mkdtemp(join(tmpdir(), 'dsh-web-e2e-sessions-'))
|
||||
const serverErrors: string[] = []
|
||||
let host: RunningHost | undefined
|
||||
let server: Awaited<ReturnType<typeof startWebServer>> | undefined
|
||||
let replay: ReplayHandle | undefined
|
||||
|
||||
// The include patch set — the same mechanism AppCLIEntry and the ACP
|
||||
// snapshot overlay use, applied over the SAME shipped tree (a patch id that
|
||||
// stops matching a row fails the boot sweep loudly instead of drifting).
|
||||
const patches: PatchOptions[] = [
|
||||
{ id: 'session-persistence-jsonl', config: { root: persistenceRoot } },
|
||||
// fs/bash cwd default to process.cwd(); the gateway injects the same
|
||||
// value into session.cwd — chdir below anchors all three to the temp
|
||||
// workspace, keeping the composition untouched.
|
||||
{ id: 'workspace-context', disabled: true },
|
||||
{ id: 'session-title-llm', disabled: true },
|
||||
{ id: 'webserver', config: { host: '127.0.0.1', port: 0, distIndex: DIST_INDEX } },
|
||||
...mode === 'record' ? [] : [{ id: 'llm-deepseek', disabled: true }],
|
||||
]
|
||||
|
||||
// Sessions inherit the gateway's process.cwd() default; run the boot from
|
||||
// the temp workspace so tool cwd, session cwd, and fixtures agree.
|
||||
const originalCwd = process.cwd()
|
||||
process.chdir(workspaceCwd)
|
||||
const ctx = new Context()
|
||||
try {
|
||||
host = await startHost({
|
||||
boot: {
|
||||
persistenceRoot,
|
||||
// Keep the request header free of ambient AGENTS.md content so
|
||||
// recorded fixtures do not embed this repo's instructions.
|
||||
workspaceContext: false,
|
||||
cwd: workspaceCwd,
|
||||
// Replay/refresh boot keyless with the llm seam open; record mounts
|
||||
// the real adapter and performs real provider I/O.
|
||||
...(mode === 'record' ? {} : { llm: false as const }),
|
||||
},
|
||||
ctx.baseUrl = pathToFileURL(join(resolve(CONFIG_PATH), '..')).href + '/'
|
||||
await ctx.plugin(Loader)
|
||||
ctx.loader.builtins.include = Include
|
||||
await ctx.loader.create({
|
||||
name: 'cordis:include',
|
||||
config: { path: pathToFileURL(resolve(CONFIG_PATH)).href, patches },
|
||||
})
|
||||
if (mode !== 'record' && options.replayFixture !== undefined) {
|
||||
replay = installLlmReplay(host.ctx, {
|
||||
file: options.replayFixture,
|
||||
providers: PROVIDERS,
|
||||
...(options.paceMs === undefined ? {} : { paceMs: options.paceMs }),
|
||||
})
|
||||
}
|
||||
// Anchor at apps/cli exactly as `dsh web` does: that package declares
|
||||
// every roster entry as a dependency, so the Loader's bare-specifier
|
||||
// resolution and the registry's package.json resolver both work.
|
||||
const anchor = pathToFileURL(join(REPO_ROOT, 'apps/cli/src/web.ts')).href
|
||||
const mounted = await mountWebPlugins(host.ctx, CLIENT_PACKAGES, anchor)
|
||||
const webPlugins = createHostWebPluginRegistry({
|
||||
ctx: host.ctx,
|
||||
loader: mounted.loader,
|
||||
resolvePkgJson: mounted.resolvePkgJson,
|
||||
onError: (err: Error) => { serverErrors.push(String(err)) },
|
||||
})
|
||||
server = await startWebServer(
|
||||
{ host: '127.0.0.1', port: 0, distIndex: DIST_INDEX, apiHandler: host.handler, webPlugins },
|
||||
(err: Error) => { serverErrors.push(String(err)) },
|
||||
)
|
||||
await ctx.loader.await()
|
||||
assertEntriesLoaded(ctx, 'web e2e scaffold')
|
||||
} catch (error) {
|
||||
await server?.close().catch(() => undefined)
|
||||
await host?.dispose().catch(() => undefined)
|
||||
process.chdir(originalCwd)
|
||||
await ctx.fiber.dispose()
|
||||
await rm(workspaceCwd, { recursive: true, force: true }).catch(() => undefined)
|
||||
await rm(persistenceRoot, { recursive: true, force: true }).catch(() => undefined)
|
||||
throw error
|
||||
} finally {
|
||||
if (process.cwd() !== originalCwd) process.chdir(originalCwd)
|
||||
}
|
||||
|
||||
const port = ctx.get('httpServer')?.port
|
||||
if (port === undefined) {
|
||||
await ctx.fiber.dispose()
|
||||
throw new Error('web e2e scaffold: httpServer service missing after settled boot')
|
||||
}
|
||||
// Fill the open llm seam on the settled root ctx (llm-deepseek is disabled
|
||||
// in keyless modes; a scenario with no fixture leaves the seam empty so a
|
||||
// stray stream fails loud with NO_ADAPTER). The direct install, unlike the
|
||||
// plugin row, returns the ReplayHandle for the teardown consumption check.
|
||||
let replayHandle: ReplayHandle | undefined
|
||||
if (mode !== 'record' && options.replayFixture !== undefined) {
|
||||
replayHandle = installLlmReplay(ctx, {
|
||||
file: options.replayFixture,
|
||||
providers: REPLAY_PROVIDERS,
|
||||
...(options.paceMs === undefined ? {} : { paceMs: options.paceMs }),
|
||||
})
|
||||
}
|
||||
const runningHost = host
|
||||
const runningServer = server
|
||||
const replayHandle = replay
|
||||
|
||||
return {
|
||||
mode,
|
||||
baseUrl: `http://127.0.0.1:${server.port}`,
|
||||
host,
|
||||
baseUrl: `http://127.0.0.1:${port}`,
|
||||
ctx,
|
||||
workspaceCwd,
|
||||
persistenceRoot,
|
||||
serverErrors,
|
||||
// Barrier stack: the in-process turn/end identifies the session, then
|
||||
// agent.whenIdle() covers the persistence flush (the idle flip follows
|
||||
// the flush), and the caller's browser settled-poll comes last because
|
||||
// host completion strictly precedes render.
|
||||
whenTurnSettled(timeoutMs = mode === 'record' ? 180_000 : 30_000): Promise<SessionId> {
|
||||
return new Promise<SessionId>((resolve, reject) => {
|
||||
return new Promise<SessionId>((resolveSettled, reject) => {
|
||||
const timer = setTimeout(() => {
|
||||
off()
|
||||
reject(new Error(`no turn/end within ${timeoutMs}ms`))
|
||||
}, timeoutMs)
|
||||
const off = runningHost.ctx.on('session/event', (session: { id: SessionId }, event: SessionEvent) => {
|
||||
const off = ctx.on('session/event', (session: { id: SessionId }, event: SessionEvent) => {
|
||||
if (event.type !== 'turn/end') return
|
||||
clearTimeout(timer)
|
||||
off()
|
||||
const agent = runningHost.ctx.agents.get(session.id)
|
||||
const agent = ctx.agents.get(session.id)
|
||||
if (agent === undefined) {
|
||||
reject(new Error(`turn/end for ${session.id} but no live agent`))
|
||||
return
|
||||
}
|
||||
agent.whenIdle().then(() => { resolve(session.id) }, reject)
|
||||
agent.whenIdle().then(() => { resolveSettled(session.id) }, reject)
|
||||
})
|
||||
})
|
||||
},
|
||||
@@ -217,8 +218,7 @@ export async function launchWebScaffold(options: LaunchOptions = {}): Promise<We
|
||||
} catch (error) {
|
||||
failures.push(error)
|
||||
}
|
||||
await runningServer.close().catch((e: unknown) => failures.push(e))
|
||||
await runningHost.dispose().catch((e: unknown) => failures.push(e))
|
||||
await Promise.resolve(ctx.fiber.dispose()).catch((e: unknown) => failures.push(e))
|
||||
await rm(workspaceCwd, { recursive: true, force: true }).catch((e: unknown) => failures.push(e))
|
||||
await rm(persistenceRoot, { recursive: true, force: true }).catch((e: unknown) => failures.push(e))
|
||||
if (failures.length > 0) throw new AggregateError(failures, 'web scaffold teardown failed')
|
||||
@@ -251,7 +251,7 @@ function rawSessionLog(session: Session): string {
|
||||
* @param fixturePath - the committed session.jsonl / seed.jsonl target.
|
||||
*/
|
||||
export async function recordFixture(scaffold: WebScaffold, sessionId: SessionId, fixturePath: string): Promise<void> {
|
||||
const agent = scaffold.host.ctx.agents.get(sessionId)
|
||||
const agent = scaffold.ctx.agents.get(sessionId)
|
||||
if (agent === undefined) throw new Error(`record harvest: no live agent for ${sessionId}`)
|
||||
const tokenized = scrubRequestHeaders(rawSessionLog(agent.session))
|
||||
.split(sessionId).join('{{sessionId}}')
|
||||
@@ -274,20 +274,18 @@ export function fixtureUserPrompts(fixtureText: string): string[] {
|
||||
}
|
||||
|
||||
/**
|
||||
* Seed a recorded session fixture into the scaffold's persistence root through
|
||||
* the REAL backend API (throwaway Context + SessionStore + JSONL plugin — the
|
||||
* semantic-checkpoint precedent), never raw file writes: no knowledge of
|
||||
* bucket hashing, filename encoding, or compression, and malformed shapes
|
||||
* fail loud at seed time. The fixture's recorded cwd is rewritten to the
|
||||
* scaffold workspace so header/path identity and event payload paths agree.
|
||||
* Seed a recorded session fixture into the scaffold's persistence root
|
||||
* through the REAL backend API (throwaway Context + SessionStore + JSONL
|
||||
* plugin — the semantic-checkpoint precedent), never raw file writes: no
|
||||
* knowledge of bucket hashing, filename encoding, or compression, and
|
||||
* malformed shapes fail loud at seed time. The fixture's tokenized identity
|
||||
* ({{sessionId}}/{{cwd}}) is realized for this world before parsing.
|
||||
* @param scaffold - the target scaffold.
|
||||
* @param fixtureText - raw recorded session.jsonl contents.
|
||||
* @param id - the seeded session id (stable for deterministic goldens).
|
||||
* @returns the seeded id.
|
||||
*/
|
||||
export async function seedSession(scaffold: WebScaffold, fixtureText: string, id: string): Promise<SessionId> {
|
||||
// Committed fixtures tokenize run-local identity ({{sessionId}}/{{cwd}},
|
||||
// written by recordFixture); realize both for this world before parsing.
|
||||
const realized = fixtureText
|
||||
.split('{{sessionId}}').join(id)
|
||||
.split('{{cwd}}').join(scaffold.workspaceCwd)
|
||||
@@ -308,22 +306,22 @@ export async function seedSession(scaffold: WebScaffold, fixtureText: string, id
|
||||
cwd: scaffold.workspaceCwd,
|
||||
delegationDepth: 0,
|
||||
}
|
||||
const ctx = new Context()
|
||||
const seeder = new Context()
|
||||
try {
|
||||
await ctx.plugin(SessionStore)
|
||||
// Same root as the host with the plugin's own default compression, so the
|
||||
// host's directory-scan list() sees one consistent encoding.
|
||||
await ctx.plugin(SessionPersistenceJsonl, { root: scaffold.persistenceRoot })
|
||||
await ctx.sessionPersistence.create(meta)
|
||||
await ctx.sessionPersistence.append(meta.id, events)
|
||||
await seeder.plugin(SessionStore)
|
||||
// Same root as the booted tree with the plugin's own default compression,
|
||||
// so the host's directory-scan list() sees one consistent encoding.
|
||||
await seeder.plugin(SessionPersistenceJsonl, { root: scaffold.persistenceRoot })
|
||||
await seeder.sessionPersistence.create(meta)
|
||||
await seeder.sessionPersistence.append(meta.id, events)
|
||||
// Deterministic sidebar order: cold summaries take updatedAt from mtime.
|
||||
const located = ctx.sessionPersistence.locate(meta)
|
||||
const located = seeder.sessionPersistence.locate(meta)
|
||||
if (located !== undefined) {
|
||||
const backdated = new Date(meta.createdAt)
|
||||
await utimes(located.path, backdated, backdated)
|
||||
}
|
||||
} finally {
|
||||
await ctx.fiber.dispose()
|
||||
await seeder.fiber.dispose()
|
||||
}
|
||||
return meta.id
|
||||
}
|
||||
|
||||
@@ -116,7 +116,6 @@ describe('web e2e: seeded history renders through cold resume', () => {
|
||||
// stream would have failed the turn loudly. Cleanliness pins the wire.
|
||||
expect(tripwire.pageErrors).toEqual([])
|
||||
expect(tripwire.warnings).toEqual([])
|
||||
expect(scaffold.serverErrors).toEqual([])
|
||||
await assertFixtureInventory(SNAPSHOT_DIR, ['seed.jsonl', 'ui.expected.md'])
|
||||
})
|
||||
})
|
||||
|
||||
@@ -3,8 +3,8 @@ 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'
|
||||
import { bootWebShell } from '@deepseek-ai/dsh-client-web'
|
||||
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 },
|
||||
@@ -81,13 +81,15 @@ it('projects initial and revised durable titles through the built eight-plugin f
|
||||
const root = document.querySelector<HTMLElement>('#root')
|
||||
if (root === null) throw new Error('snapshot root missing')
|
||||
act(() => {
|
||||
unmount = bootWebShell(root, {
|
||||
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() }
|
||||
})
|
||||
|
||||
const projectLabel = await screen.findByText('fixture', {}, { timeout: 10_000 })
|
||||
|
||||
@@ -1,291 +0,0 @@
|
||||
// Keyless boot-chain smoke over the REAL carrier: startWebServer + entry
|
||||
// graph (__DSH_BOOT__ web2 shape) injection + built shell dist in a real
|
||||
// chromium. First describe: graph injection + the fail-loud half. Second
|
||||
// describe: the settled success pass — all nine REAL tsdown bundles load
|
||||
// through the module system + vendored Loader chain in ?fixture mode (the
|
||||
// infrastructure four ride the immediately prefetch tier, the UI rows fetch
|
||||
// on demand), the three-column frame appears in one flip, and the resident
|
||||
// question completes through the real UI stack. The full model round lands
|
||||
// in smoke-real under the W5 real-host standard.
|
||||
import { existsSync } from 'node:fs'
|
||||
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 { startWebServer } from '@deepseek-ai/dsh-host-webserver'
|
||||
import type { WebBootEntry, WebBootGraph } from '@deepseek-ai/dsh-host-webserver'
|
||||
import { DIST_INDEX, probeFreePort, requireDist, saveFailureShot } from './support.ts'
|
||||
|
||||
const bundlePath = (dir: string): string =>
|
||||
fileURLToPath(new URL(`../../../packages/client/${dir}/lib/client.js`, import.meta.url))
|
||||
|
||||
const LAYOUT_ID = '@deepseek-ai/dsh-client-ui-layout'
|
||||
const SIDEBAR_ID = '@deepseek-ai/dsh-client-ui-sidebar'
|
||||
|
||||
/** id ↔ bundle table for the success pass (the complete Web UI assembly). */
|
||||
const REAL_PLUGINS: { id: string; dir: string; inject?: string[]; immediately?: boolean }[] = [
|
||||
{ id: '@deepseek-ai/dsh-client-connection', dir: 'connection', immediately: true },
|
||||
{ id: '@deepseek-ai/dsh-client-runtime', dir: 'runtime', inject: ['@deepseek-ai/dsh-client-connection'], immediately: true },
|
||||
{ id: '@deepseek-ai/dsh-client-ui-theme', dir: 'ui-theme', immediately: true },
|
||||
{ id: '@deepseek-ai/dsh-client-i18n', dir: 'i18n', immediately: true },
|
||||
{ id: LAYOUT_ID, dir: 'ui-layout', inject: ['@deepseek-ai/dsh-client-runtime'] },
|
||||
{ id: SIDEBAR_ID, dir: 'ui-sidebar', inject: [LAYOUT_ID] },
|
||||
{ id: '@deepseek-ai/dsh-client-ui-conversation', dir: 'ui-conversation', inject: [LAYOUT_ID] },
|
||||
{ id: '@deepseek-ai/dsh-client-ui-question', dir: 'ui-question', inject: ['@deepseek-ai/dsh-client-ui-conversation'] },
|
||||
{ id: '@deepseek-ai/dsh-client-ui-trajectory', dir: 'ui-trajectory', inject: ['@deepseek-ai/dsh-client-ui-conversation'] },
|
||||
]
|
||||
|
||||
const BUNDLE_PATHS = new Map(REAL_PLUGINS.map(p => [p.id, bundlePath(p.dir)]))
|
||||
|
||||
const row = (id: string, extra?: Partial<WebBootEntry>): WebBootEntry =>
|
||||
({ id, url: `/plugins/${id}/client.js?rev=e2e`, rev: 'e2e', ...extra })
|
||||
|
||||
const graphRows: WebBootEntry[] = REAL_PLUGINS.map(p => row(p.id, {
|
||||
...(p.inject !== undefined ? { inject: p.inject } : {}),
|
||||
...(p.immediately === true ? { immediately: true } : {}),
|
||||
}))
|
||||
|
||||
/** Graph for the fail-loud half: the immediately tier, one live UI row, one missing row. */
|
||||
const FAIL_GRAPH: WebBootGraph = {
|
||||
rev: 'e2e-fail',
|
||||
entries: [...graphRows.filter(r => r.immediately === true), row(LAYOUT_ID), row('@probe/absent')],
|
||||
}
|
||||
|
||||
/** Graph for the success pass: the complete assembly. */
|
||||
const OK_GRAPH: WebBootGraph = { rev: 'e2e-ok', entries: graphRows }
|
||||
|
||||
/** Registry stub over a fixed graph (the real HostWebPluginRegistry is webserver-side production code). */
|
||||
function fixedRegistry(graph: WebBootGraph, byId: ReadonlyMap<string, string>) {
|
||||
return {
|
||||
graph: () => graph,
|
||||
clientPath: (id: string) => byId.get(id),
|
||||
onRebuilt: () => () => undefined,
|
||||
}
|
||||
}
|
||||
|
||||
describe('web boot chain (keyless, real carrier)', () => {
|
||||
let server: Awaited<ReturnType<typeof startWebServer>>
|
||||
let browser: Browser
|
||||
let page: Page
|
||||
const pageErrors: string[] = []
|
||||
|
||||
beforeAll(async () => {
|
||||
requireDist()
|
||||
const port = await probeFreePort()
|
||||
const apiHandler = { fetch: () => Promise.resolve(new Response('boot smoke must not call /api', { status: 500 })) }
|
||||
server = await startWebServer({
|
||||
host: '127.0.0.1',
|
||||
port,
|
||||
distIndex: DIST_INDEX,
|
||||
apiHandler,
|
||||
webPlugins: fixedRegistry(FAIL_GRAPH, BUNDLE_PATHS),
|
||||
}, (err) => { pageErrors.push(`server: ${String(err)}`) })
|
||||
browser = await chromium.launch()
|
||||
page = await browser.newPage()
|
||||
page.on('pageerror', e => pageErrors.push(String(e)))
|
||||
await page.goto(`http://127.0.0.1:${port}/`, { waitUntil: 'load' })
|
||||
})
|
||||
|
||||
afterAll(async () => {
|
||||
await browser?.close()
|
||||
await server?.close()
|
||||
})
|
||||
|
||||
it('GET / injects the entry graph verbatim', async () => {
|
||||
onTestFailed(() => saveFailureShot(page, 'smoke-boot-manifest'))
|
||||
const boot = await page.evaluate(() => (window as { __DSH_BOOT__?: unknown }).__DSH_BOOT__)
|
||||
expect(boot).toEqual(FAIL_GRAPH)
|
||||
})
|
||||
|
||||
it('serves a real bundle through the plugins endpoint', async () => {
|
||||
const res = await page.request.get(`${new URL(page.url()).origin}/plugins/${LAYOUT_ID}/client.js`)
|
||||
expect(res.status()).toBe(200)
|
||||
expect(await res.text()).toContain('window.__ModuleLoader__.load')
|
||||
})
|
||||
|
||||
it('boots to the loading page and fail-louds the absent entry', async () => {
|
||||
onTestFailed(() => saveFailureShot(page, 'smoke-boot-fail-loud'))
|
||||
await page.waitForSelector('text=HARNESS', { timeout: 10_000 })
|
||||
await page.waitForSelector('text=Failed to load plugins', { timeout: 10_000 })
|
||||
await page.waitForSelector('text=@probe/absent', { timeout: 2000 })
|
||||
// The real UI must not have flipped in: the gate opens only on settled.
|
||||
expect(await page.locator('[class*="frame"]').count()).toBe(0)
|
||||
})
|
||||
|
||||
it('applies the token sheets before any plugin CSS', async () => {
|
||||
const family = await page.evaluate(() => getComputedStyle(document.body).getPropertyValue('--dsw-font-family'))
|
||||
expect(family.trim().length).toBeGreaterThan(0)
|
||||
})
|
||||
})
|
||||
|
||||
describe('web boot chain success pass (keyless, nine real bundles, ?fixture)', () => {
|
||||
let server: Awaited<ReturnType<typeof startWebServer>>
|
||||
let browser: Browser
|
||||
let page: Page
|
||||
const pageErrors: string[] = []
|
||||
|
||||
beforeAll(async () => {
|
||||
requireDist()
|
||||
const missing = REAL_PLUGINS.filter(p => !existsSync(bundlePath(p.dir)))
|
||||
if (missing.length > 0) throw new Error(`client bundles not built (pnpm --filter <pkg> bundle): ${missing.map(m => m.dir).join(', ')}`)
|
||||
const port = await probeFreePort()
|
||||
// ?fixture never opens HTTP streams; /api is a tripwire like the first describe.
|
||||
const apiHandler = { fetch: () => Promise.resolve(new Response('fixture mode must not call /api', { status: 500 })) }
|
||||
server = await startWebServer({
|
||||
host: '127.0.0.1',
|
||||
port,
|
||||
distIndex: DIST_INDEX,
|
||||
apiHandler,
|
||||
webPlugins: fixedRegistry(OK_GRAPH, BUNDLE_PATHS),
|
||||
}, (err) => { pageErrors.push(`server: ${String(err)}`) })
|
||||
browser = await chromium.launch()
|
||||
page = await browser.newPage()
|
||||
page.on('pageerror', e => pageErrors.push(String(e)))
|
||||
await page.goto(`http://127.0.0.1:${port}/?fixture`, { waitUntil: 'load' })
|
||||
})
|
||||
|
||||
afterAll(async () => {
|
||||
await browser?.close()
|
||||
await server?.close()
|
||||
})
|
||||
|
||||
it('settles and flips to the three-column frame in one pass', async () => {
|
||||
onTestFailed(() => saveFailureShot(page, 'smoke-boot-settled'))
|
||||
await page.waitForSelector('[class*="frame"]', { timeout: 15_000 })
|
||||
// Loading page is gone; the grid carries the three tracks.
|
||||
expect(await page.locator('text=Failed to load plugins').count()).toBe(0)
|
||||
const template = await page.locator('[class*="frame"]').evaluate(el => getComputedStyle(el).gridTemplateColumns)
|
||||
expect(template.split(' ').length).toBe(3)
|
||||
})
|
||||
|
||||
it('every plugin CSS landed with its ownership tag', async () => {
|
||||
const owners = await page.evaluate(() =>
|
||||
[...document.querySelectorAll('style[data-plugin]')].map(s => (s as HTMLElement).dataset['plugin']))
|
||||
expect(owners).toContain(LAYOUT_ID)
|
||||
expect(owners).toContain(SIDEBAR_ID)
|
||||
})
|
||||
|
||||
it('collapsed sidebar animates to a 56px rail with the four controls', async () => {
|
||||
onTestFailed(() => saveFailureShot(page, 'smoke-boot-collapsed-rail'))
|
||||
const frame = page.locator('[class*="frame"]')
|
||||
const firstTrack = async (): Promise<string> => (await frame.evaluate(
|
||||
el => getComputedStyle(el).gridTemplateColumns)).split(' ')[0]!
|
||||
// The tracks transition on the deepsuite curve; assert the animated
|
||||
// settle rather than an instant jump.
|
||||
const settledTrack = async (px: string): Promise<void> => {
|
||||
await expect.poll(firstTrack, { timeout: 2000 }).toBe(px)
|
||||
}
|
||||
// The brand wordmark is decorative svg (aria-hidden) — presence tracks the wide chrome.
|
||||
const brand = () => page.locator('[class*="brand"]').count()
|
||||
await page.getByRole('button', { name: 'Collapse sidebar' }).click()
|
||||
// Mid-collapse the wide chrome is still mounted, fading — not swapped out.
|
||||
expect(await brand()).toBe(1)
|
||||
await settledTrack('56px')
|
||||
await expect.poll(brand, { timeout: 2000 }).toBe(0)
|
||||
for (const name of ['Open sidebar', 'New session', 'New workspace', 'Search sessions', 'Settings']) {
|
||||
await expect(page.getByRole('button', { name }).isVisible(), name).resolves.toBe(true)
|
||||
}
|
||||
await page.getByRole('button', { name: 'Open sidebar' }).click()
|
||||
await settledTrack('280px')
|
||||
await expect(page.getByRole('button', { name: 'Collapse sidebar' }).isVisible()).resolves.toBe(true)
|
||||
// Rail search: collapse again, the search control expands and lands in the box.
|
||||
await page.getByRole('button', { name: 'Collapse sidebar' }).click()
|
||||
await settledTrack('56px')
|
||||
await page.getByRole('button', { name: 'Search sessions' }).click()
|
||||
await settledTrack('280px')
|
||||
// Focus is deferred past the slide (EXPAND_SLIDE_MS) — poll for it.
|
||||
await expect.poll(() => page.evaluate(() =>
|
||||
(document.activeElement as HTMLInputElement | null)?.placeholder ?? ''), { timeout: 2000 }).toContain('Search')
|
||||
})
|
||||
|
||||
it('renders file tool rows and expands fixture reasoning from either click target', async () => {
|
||||
onTestFailed(() => saveFailureShot(page, 'smoke-think-disclosure'))
|
||||
await page.locator('[role="treeitem"]').first().click()
|
||||
await page.locator('[role="treeitem"][aria-selected]').first().click()
|
||||
|
||||
const thinkRoot = page.locator('[data-variant="think"]').first()
|
||||
const think = thinkRoot.getByRole('button')
|
||||
await think.waitFor({ state: 'visible', timeout: 10_000 })
|
||||
expect(await think.getAttribute('aria-expanded')).toBe('false')
|
||||
|
||||
await thinkRoot.getByText(/^思考过程 .*reasoning 内容。$/).click()
|
||||
expect(await think.getAttribute('aria-expanded')).toBe('true')
|
||||
expect(await thinkRoot.locator(':scope > div').count()).toBe(2)
|
||||
|
||||
await think.getByText('Think', { exact: true }).click()
|
||||
expect(await think.getAttribute('aria-expanded')).toBe('false')
|
||||
|
||||
const editRoot = page.locator('[data-variant="edit"]').first()
|
||||
await editRoot.waitFor({ state: 'visible', timeout: 10_000 })
|
||||
expect(await editRoot.getByText('Edit', { exact: true }).count()).toBe(1)
|
||||
expect(await editRoot.getByText('notes/demo.txt', { exact: true }).count()).toBe(1)
|
||||
|
||||
const writeRoot = page.locator('[data-variant="write"]').first()
|
||||
await writeRoot.waitFor({ state: 'visible', timeout: 10_000 })
|
||||
expect(await writeRoot.getByText('Write', { exact: true }).count()).toBe(1)
|
||||
expect(await writeRoot.getByText('notes/new-demo.txt', { exact: true }).count()).toBe(1)
|
||||
})
|
||||
|
||||
it('keeps Markdown semantic while a fixture reply streams and finalizes', async () => {
|
||||
onTestFailed(() => saveFailureShot(page, 'smoke-markdown-stream'))
|
||||
await page.getByRole('button', { name: 'New session', exact: true }).click()
|
||||
const input = page.locator('textarea[placeholder]')
|
||||
await input.waitFor({ timeout: 15_000 })
|
||||
await input.fill('render markdown')
|
||||
await page.getByRole('button', { name: '发送' }).click()
|
||||
|
||||
const streaming = page.locator('[data-streaming="true"]')
|
||||
await streaming.getByRole('heading', { name: 'Markdown fixture' }).waitFor({ timeout: 15_000 })
|
||||
await streaming.waitFor({ state: 'detached', timeout: 15_000 })
|
||||
|
||||
const finalHeading = page.getByRole('heading', { name: 'Markdown fixture' })
|
||||
expect(await finalHeading.evaluate(element => element.tagName)).toBe('H1')
|
||||
expect(await page.locator('pre code').filter({ hasText: 'const markdown = true' }).count()).toBe(1)
|
||||
const external = page.getByRole('link', { name: 'DeepSeek' })
|
||||
expect(await external.getAttribute('target')).toBe('_blank')
|
||||
expect(await external.getAttribute('rel')).toBe('noopener noreferrer')
|
||||
})
|
||||
|
||||
it('renders and completes the resident question through the composer slot', async () => {
|
||||
onTestFailed(() => saveFailureShot(page, 'smoke-question-composer'))
|
||||
const sessionTree = page.getByRole('tree', { name: 'Sessions' })
|
||||
const projectRow = sessionTree.getByRole('treeitem').filter({ hasText: '3 sessions' })
|
||||
if (await projectRow.getAttribute('aria-expanded') === 'false') await projectRow.click()
|
||||
await sessionTree.getByText('Fixture 历史会话', { exact: true }).click()
|
||||
const composer = page.locator('[data-question-key]')
|
||||
await composer.waitFor({ timeout: 15_000 })
|
||||
expect({
|
||||
question: await composer.getByRole('heading').innerText(),
|
||||
progress: await composer.getByText('1 / 3', { exact: true }).innerText(),
|
||||
options: await composer.getByRole('radio').allTextContents(),
|
||||
custom: await composer.getByRole('button', { name: '其他,请填写自定义答案' }).innerText(),
|
||||
}).toMatchInlineSnapshot(`
|
||||
{
|
||||
"custom": "其他,请填写自定义答案",
|
||||
"options": [
|
||||
"1工程落地型推荐更看重能直接做 runtime、tool executor、sandbox、trace 和线上问题排查。",
|
||||
"2研究潜力型更看重 Agent 理解、训练评测思路和长期成长空间。",
|
||||
"3均衡型同时要求工程能力和 Agent 认知,但可能筛选门槛更高。",
|
||||
],
|
||||
"progress": "1 / 3",
|
||||
"question": "你现在更想招哪类 Agent/Harness 候选人?",
|
||||
}
|
||||
`)
|
||||
|
||||
await composer.getByRole('radio', { name: '工程落地型' }).click()
|
||||
await composer.getByText('2 / 3', { exact: true }).waitFor()
|
||||
await composer.getByRole('button', { name: '跳过本题', exact: true }).click()
|
||||
await composer.getByRole('checkbox', { name: '系统设计' }).click()
|
||||
await composer.getByRole('checkbox', { name: 'Agent 产品判断' }).click()
|
||||
await composer.getByRole('checkbox', { name: 'Agent 产品判断' }).press('Enter')
|
||||
|
||||
await composer.waitFor({ state: 'detached' })
|
||||
const restoredInput = page.locator('textarea[placeholder]')
|
||||
await restoredInput.waitFor()
|
||||
expect(await restoredInput.getAttribute('placeholder')).toBe('回复生成中,可停止后再输入')
|
||||
})
|
||||
|
||||
it('stayed clean: no page errors across the whole load chain', () => {
|
||||
expect(pageErrors).toEqual([])
|
||||
})
|
||||
})
|
||||
@@ -1,97 +1,99 @@
|
||||
{"type":"session","version":0,"id":"{{sessionId}}","createdAt":1784893539564,"cwd":"{{cwd}}"}
|
||||
{"type":"turn/start","seq":0,"time":1784893539588,"data":{"turn":1,"trigger":{"kind":"message","source":{"kind":"user","rpcId":"4962b8d4-0422-4f81-8187-642e3e6bab78"}}}}
|
||||
{"type":"user/message","seq":1,"time":1784893539589,"data":{"content":[{"type":"text","text":"Use the bash tool to run exactly: echo WEB_E2E_OK. Then reply with the single word DONE and stop."}],"source":{"kind":"user","rpcId":"4962b8d4-0422-4f81-8187-642e3e6bab78"}},"surfaceOp":"append"}
|
||||
{"type":"session/title","seq":2,"time":1784893539592,"data":{"title":"Use the bash tool to","messageSeqs":[1],"source":{"kind":"fallback"}}}
|
||||
{"type":"step/start","seq":3,"time":1784893539657,"data":{"turn":1,"step":1}}
|
||||
{"type":"request/header","seq":4,"time":1784893539658,"data":{"header":{"config":{"provider":"deepseek","model":"deepseek-v4-flash"},"system":"{{system}}","tools":"{{tools}}","messagePrefix":["{{messagePrefix}}"]},"reason":"initial"}}
|
||||
{"type":"assistant/chunk","seq":5,"time":1784893540271,"data":{"turn":1,"step":1,"chunk":{"type":"block-start","index":0,"blockType":"reasoning"}}}
|
||||
{"type":"assistant/chunk","seq":6,"time":1784893540271,"data":{"turn":1,"step":1,"chunk":{"type":"reasoning-delta","index":0,"text":"The"}}}
|
||||
{"type":"assistant/chunk","seq":7,"time":1784893540366,"data":{"turn":1,"step":1,"chunk":{"type":"reasoning-delta","index":0,"text":" user"}}}
|
||||
{"type":"assistant/chunk","seq":8,"time":1784893540396,"data":{"turn":1,"step":1,"chunk":{"type":"reasoning-delta","index":0,"text":" wants"}}}
|
||||
{"type":"assistant/chunk","seq":9,"time":1784893540396,"data":{"turn":1,"step":1,"chunk":{"type":"reasoning-delta","index":0,"text":" me"}}}
|
||||
{"type":"assistant/chunk","seq":10,"time":1784893540396,"data":{"turn":1,"step":1,"chunk":{"type":"reasoning-delta","index":0,"text":" to"}}}
|
||||
{"type":"assistant/chunk","seq":11,"time":1784893540396,"data":{"turn":1,"step":1,"chunk":{"type":"reasoning-delta","index":0,"text":" run"}}}
|
||||
{"type":"assistant/chunk","seq":12,"time":1784893540397,"data":{"turn":1,"step":1,"chunk":{"type":"reasoning-delta","index":0,"text":" a"}}}
|
||||
{"type":"assistant/chunk","seq":13,"time":1784893540421,"data":{"turn":1,"step":1,"chunk":{"type":"reasoning-delta","index":0,"text":" simple"}}}
|
||||
{"type":"assistant/chunk","seq":14,"time":1784893540447,"data":{"turn":1,"step":1,"chunk":{"type":"reasoning-delta","index":0,"text":" bash"}}}
|
||||
{"type":"assistant/chunk","seq":15,"time":1784893540447,"data":{"turn":1,"step":1,"chunk":{"type":"reasoning-delta","index":0,"text":" command"}}}
|
||||
{"type":"assistant/chunk","seq":16,"time":1784893540448,"data":{"turn":1,"step":1,"chunk":{"type":"reasoning-delta","index":0,"text":" and"}}}
|
||||
{"type":"assistant/chunk","seq":17,"time":1784893540448,"data":{"turn":1,"step":1,"chunk":{"type":"reasoning-delta","index":0,"text":" reply"}}}
|
||||
{"type":"assistant/chunk","seq":18,"time":1784893540475,"data":{"turn":1,"step":1,"chunk":{"type":"reasoning-delta","index":0,"text":" with"}}}
|
||||
{"type":"assistant/chunk","seq":19,"time":1784893540476,"data":{"turn":1,"step":1,"chunk":{"type":"reasoning-delta","index":0,"text":" \""}}}
|
||||
{"type":"assistant/chunk","seq":20,"time":1784893540476,"data":{"turn":1,"step":1,"chunk":{"type":"reasoning-delta","index":0,"text":"D"}}}
|
||||
{"type":"assistant/chunk","seq":21,"time":1784893540476,"data":{"turn":1,"step":1,"chunk":{"type":"reasoning-delta","index":0,"text":"ONE"}}}
|
||||
{"type":"assistant/chunk","seq":22,"time":1784893540476,"data":{"turn":1,"step":1,"chunk":{"type":"reasoning-delta","index":0,"text":"\"."}}}
|
||||
{"type":"assistant/chunk","seq":23,"time":1784893540565,"data":{"turn":1,"step":1,"chunk":{"type":"block-start","index":1,"blockType":"tool-call"}}}
|
||||
{"type":"assistant/chunk","seq":24,"time":1784893540566,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":1,"id":"call_00_yxp0l3itFAMPCLKhz7EU1205","name":"bash","argumentsDelta":""}}}
|
||||
{"type":"assistant/chunk","seq":25,"time":1784893540591,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":1,"id":"call_00_yxp0l3itFAMPCLKhz7EU1205","name":"bash","argumentsDelta":"{"}}}
|
||||
{"type":"assistant/chunk","seq":26,"time":1784893540592,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":1,"id":"call_00_yxp0l3itFAMPCLKhz7EU1205","name":"bash","argumentsDelta":"\""}}}
|
||||
{"type":"assistant/chunk","seq":27,"time":1784893540592,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":1,"id":"call_00_yxp0l3itFAMPCLKhz7EU1205","name":"bash","argumentsDelta":"command"}}}
|
||||
{"type":"assistant/chunk","seq":28,"time":1784893540592,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":1,"id":"call_00_yxp0l3itFAMPCLKhz7EU1205","name":"bash","argumentsDelta":"\""}}}
|
||||
{"type":"assistant/chunk","seq":29,"time":1784893540592,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":1,"id":"call_00_yxp0l3itFAMPCLKhz7EU1205","name":"bash","argumentsDelta":": "}}}
|
||||
{"type":"assistant/chunk","seq":30,"time":1784893540621,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":1,"id":"call_00_yxp0l3itFAMPCLKhz7EU1205","name":"bash","argumentsDelta":"\""}}}
|
||||
{"type":"assistant/chunk","seq":31,"time":1784893540621,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":1,"id":"call_00_yxp0l3itFAMPCLKhz7EU1205","name":"bash","argumentsDelta":"echo"}}}
|
||||
{"type":"assistant/chunk","seq":32,"time":1784893540621,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":1,"id":"call_00_yxp0l3itFAMPCLKhz7EU1205","name":"bash","argumentsDelta":" WEB"}}}
|
||||
{"type":"assistant/chunk","seq":33,"time":1784893540621,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":1,"id":"call_00_yxp0l3itFAMPCLKhz7EU1205","name":"bash","argumentsDelta":"_E"}}}
|
||||
{"type":"assistant/chunk","seq":34,"time":1784893540651,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":1,"id":"call_00_yxp0l3itFAMPCLKhz7EU1205","name":"bash","argumentsDelta":"2"}}}
|
||||
{"type":"assistant/chunk","seq":35,"time":1784893540651,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":1,"id":"call_00_yxp0l3itFAMPCLKhz7EU1205","name":"bash","argumentsDelta":"E"}}}
|
||||
{"type":"assistant/chunk","seq":36,"time":1784893540652,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":1,"id":"call_00_yxp0l3itFAMPCLKhz7EU1205","name":"bash","argumentsDelta":"_OK"}}}
|
||||
{"type":"assistant/chunk","seq":37,"time":1784893540652,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":1,"id":"call_00_yxp0l3itFAMPCLKhz7EU1205","name":"bash","argumentsDelta":"\""}}}
|
||||
{"type":"assistant/chunk","seq":38,"time":1784893540680,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":1,"id":"call_00_yxp0l3itFAMPCLKhz7EU1205","name":"bash","argumentsDelta":", "}}}
|
||||
{"type":"assistant/chunk","seq":39,"time":1784893540680,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":1,"id":"call_00_yxp0l3itFAMPCLKhz7EU1205","name":"bash","argumentsDelta":"\""}}}
|
||||
{"type":"assistant/chunk","seq":40,"time":1784893540709,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":1,"id":"call_00_yxp0l3itFAMPCLKhz7EU1205","name":"bash","argumentsDelta":"description"}}}
|
||||
{"type":"assistant/chunk","seq":41,"time":1784893540710,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":1,"id":"call_00_yxp0l3itFAMPCLKhz7EU1205","name":"bash","argumentsDelta":"\""}}}
|
||||
{"type":"assistant/chunk","seq":42,"time":1784893540710,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":1,"id":"call_00_yxp0l3itFAMPCLKhz7EU1205","name":"bash","argumentsDelta":": "}}}
|
||||
{"type":"assistant/chunk","seq":43,"time":1784893540710,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":1,"id":"call_00_yxp0l3itFAMPCLKhz7EU1205","name":"bash","argumentsDelta":"\""}}}
|
||||
{"type":"assistant/chunk","seq":44,"time":1784893540738,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":1,"id":"call_00_yxp0l3itFAMPCLKhz7EU1205","name":"bash","argumentsDelta":"Print"}}}
|
||||
{"type":"assistant/chunk","seq":45,"time":1784893540738,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":1,"id":"call_00_yxp0l3itFAMPCLKhz7EU1205","name":"bash","argumentsDelta":" WEB"}}}
|
||||
{"type":"assistant/chunk","seq":46,"time":1784893540768,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":1,"id":"call_00_yxp0l3itFAMPCLKhz7EU1205","name":"bash","argumentsDelta":"_E"}}}
|
||||
{"type":"assistant/chunk","seq":47,"time":1784893540768,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":1,"id":"call_00_yxp0l3itFAMPCLKhz7EU1205","name":"bash","argumentsDelta":"2"}}}
|
||||
{"type":"assistant/chunk","seq":48,"time":1784893540768,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":1,"id":"call_00_yxp0l3itFAMPCLKhz7EU1205","name":"bash","argumentsDelta":"E"}}}
|
||||
{"type":"assistant/chunk","seq":49,"time":1784893540768,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":1,"id":"call_00_yxp0l3itFAMPCLKhz7EU1205","name":"bash","argumentsDelta":"_OK"}}}
|
||||
{"type":"assistant/chunk","seq":50,"time":1784893540769,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":1,"id":"call_00_yxp0l3itFAMPCLKhz7EU1205","name":"bash","argumentsDelta":" to"}}}
|
||||
{"type":"assistant/chunk","seq":51,"time":1784893540797,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":1,"id":"call_00_yxp0l3itFAMPCLKhz7EU1205","name":"bash","argumentsDelta":" stdout"}}}
|
||||
{"type":"assistant/chunk","seq":52,"time":1784893540801,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":1,"id":"call_00_yxp0l3itFAMPCLKhz7EU1205","name":"bash","argumentsDelta":"\""}}}
|
||||
{"type":"assistant/chunk","seq":53,"time":1784893540826,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":1,"id":"call_00_yxp0l3itFAMPCLKhz7EU1205","name":"bash","argumentsDelta":"}"}}}
|
||||
{"type":"assistant/chunk","seq":54,"time":1784893540859,"data":{"turn":1,"step":1,"chunk":{"type":"block-end","index":0,"block":{"type":"reasoning","text":"The user wants me to run a simple bash command and reply with \"DONE\"."}}}}
|
||||
{"type":"assistant/chunk","seq":55,"time":1784893540859,"data":{"turn":1,"step":1,"chunk":{"type":"block-end","index":1,"block":{"type":"tool-call","id":"call_00_yxp0l3itFAMPCLKhz7EU1205","name":"bash","arguments":"{\"command\": \"echo WEB_E2E_OK\", \"description\": \"Print WEB_E2E_OK to stdout\"}"}}}}
|
||||
{"type":"assistant/chunk","seq":56,"time":1784893540859,"data":{"turn":1,"step":1,"chunk":{"type":"usage","usage":{"inputTokens":7802,"outputTokens":88,"cacheReadTokens":0,"reasoningTokens":17}}}}
|
||||
{"type":"assistant/chunk","seq":57,"time":1784893540860,"data":{"turn":1,"step":1,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}}
|
||||
{"type":"assistant/message","seq":58,"time":1784893540863,"data":{"turn":1,"step":1,"content":[{"type":"reasoning","text":"The user wants me to run a simple bash command and reply with \"DONE\"."},{"type":"tool-call","id":"call_00_yxp0l3itFAMPCLKhz7EU1205","name":"bash","arguments":"{\"command\": \"echo WEB_E2E_OK\", \"description\": \"Print WEB_E2E_OK to stdout\"}"}],"provenance":{"provider":"deepseek","model":"deepseek-v4-flash"},"usage":{"inputTokens":7802,"outputTokens":88,"cacheReadTokens":0,"reasoningTokens":17}},"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],"surfaceOp":"append"}
|
||||
{"type":"tool/call","seq":59,"time":1784893540864,"data":{"turn":1,"step":1,"callId":"call_00_yxp0l3itFAMPCLKhz7EU1205","name":"bash","arguments":"{\"command\": \"echo WEB_E2E_OK\", \"description\": \"Print WEB_E2E_OK to stdout\"}"}}
|
||||
{"type":"tool/result","seq":60,"time":1784893540878,"data":{"turn":1,"step":1,"callId":"call_00_yxp0l3itFAMPCLKhz7EU1205","content":[{"type":"text","text":"WEB_E2E_OK\n"}],"isError":false},"sourceEventSeqs":[59],"surfaceOp":"append"}
|
||||
{"type":"step/end","seq":61,"time":1784893540881,"data":{"turn":1,"step":1}}
|
||||
{"type":"step/start","seq":62,"time":1784893540881,"data":{"turn":1,"step":2}}
|
||||
{"type":"assistant/chunk","seq":63,"time":1784893541457,"data":{"turn":1,"step":2,"chunk":{"type":"block-start","index":0,"blockType":"reasoning"}}}
|
||||
{"type":"assistant/chunk","seq":64,"time":1784893541457,"data":{"turn":1,"step":2,"chunk":{"type":"reasoning-delta","index":0,"text":"The"}}}
|
||||
{"type":"assistant/chunk","seq":65,"time":1784893541545,"data":{"turn":1,"step":2,"chunk":{"type":"reasoning-delta","index":0,"text":" command"}}}
|
||||
{"type":"assistant/chunk","seq":66,"time":1784893541574,"data":{"turn":1,"step":2,"chunk":{"type":"reasoning-delta","index":0,"text":" executed"}}}
|
||||
{"type":"assistant/chunk","seq":67,"time":1784893541574,"data":{"turn":1,"step":2,"chunk":{"type":"reasoning-delta","index":0,"text":" successfully"}}}
|
||||
{"type":"assistant/chunk","seq":68,"time":1784893541574,"data":{"turn":1,"step":2,"chunk":{"type":"reasoning-delta","index":0,"text":" and"}}}
|
||||
{"type":"assistant/chunk","seq":69,"time":1784893541574,"data":{"turn":1,"step":2,"chunk":{"type":"reasoning-delta","index":0,"text":" printed"}}}
|
||||
{"type":"assistant/chunk","seq":70,"time":1784893541603,"data":{"turn":1,"step":2,"chunk":{"type":"reasoning-delta","index":0,"text":" \""}}}
|
||||
{"type":"assistant/chunk","seq":71,"time":1784893541604,"data":{"turn":1,"step":2,"chunk":{"type":"reasoning-delta","index":0,"text":"WEB"}}}
|
||||
{"type":"assistant/chunk","seq":72,"time":1784893541604,"data":{"turn":1,"step":2,"chunk":{"type":"reasoning-delta","index":0,"text":"_E"}}}
|
||||
{"type":"assistant/chunk","seq":73,"time":1784893541604,"data":{"turn":1,"step":2,"chunk":{"type":"reasoning-delta","index":0,"text":"2"}}}
|
||||
{"type":"assistant/chunk","seq":74,"time":1784893541604,"data":{"turn":1,"step":2,"chunk":{"type":"reasoning-delta","index":0,"text":"E"}}}
|
||||
{"type":"assistant/chunk","seq":75,"time":1784893541604,"data":{"turn":1,"step":2,"chunk":{"type":"reasoning-delta","index":0,"text":"_OK"}}}
|
||||
{"type":"assistant/chunk","seq":76,"time":1784893541633,"data":{"turn":1,"step":2,"chunk":{"type":"reasoning-delta","index":0,"text":"\"."}}}
|
||||
{"type":"assistant/chunk","seq":77,"time":1784893541633,"data":{"turn":1,"step":2,"chunk":{"type":"reasoning-delta","index":0,"text":" I"}}}
|
||||
{"type":"assistant/chunk","seq":78,"time":1784893541675,"data":{"turn":1,"step":2,"chunk":{"type":"reasoning-delta","index":0,"text":" should"}}}
|
||||
{"type":"assistant/chunk","seq":79,"time":1784893541676,"data":{"turn":1,"step":2,"chunk":{"type":"reasoning-delta","index":0,"text":" now"}}}
|
||||
{"type":"assistant/chunk","seq":80,"time":1784893541676,"data":{"turn":1,"step":2,"chunk":{"type":"reasoning-delta","index":0,"text":" reply"}}}
|
||||
{"type":"assistant/chunk","seq":81,"time":1784893541676,"data":{"turn":1,"step":2,"chunk":{"type":"reasoning-delta","index":0,"text":" with"}}}
|
||||
{"type":"assistant/chunk","seq":82,"time":1784893541694,"data":{"turn":1,"step":2,"chunk":{"type":"reasoning-delta","index":0,"text":" \""}}}
|
||||
{"type":"assistant/chunk","seq":83,"time":1784893541694,"data":{"turn":1,"step":2,"chunk":{"type":"reasoning-delta","index":0,"text":"D"}}}
|
||||
{"type":"assistant/chunk","seq":84,"time":1784893541694,"data":{"turn":1,"step":2,"chunk":{"type":"reasoning-delta","index":0,"text":"ONE"}}}
|
||||
{"type":"assistant/chunk","seq":85,"time":1784893541695,"data":{"turn":1,"step":2,"chunk":{"type":"reasoning-delta","index":0,"text":"\"."}}}
|
||||
{"type":"assistant/chunk","seq":86,"time":1784893541718,"data":{"turn":1,"step":2,"chunk":{"type":"block-start","index":1,"blockType":"text"}}}
|
||||
{"type":"assistant/chunk","seq":87,"time":1784893541718,"data":{"turn":1,"step":2,"chunk":{"type":"text-delta","index":1,"text":"D"}}}
|
||||
{"type":"assistant/chunk","seq":88,"time":1784893541718,"data":{"turn":1,"step":2,"chunk":{"type":"text-delta","index":1,"text":"ONE"}}}
|
||||
{"type":"assistant/chunk","seq":89,"time":1784893541719,"data":{"turn":1,"step":2,"chunk":{"type":"block-end","index":0,"block":{"type":"reasoning","text":"The command executed successfully and printed \"WEB_E2E_OK\". I should now reply with \"DONE\"."}}}}
|
||||
{"type":"assistant/chunk","seq":90,"time":1784893541719,"data":{"turn":1,"step":2,"chunk":{"type":"block-end","index":1,"block":{"type":"text","text":"DONE"}}}}
|
||||
{"type":"assistant/chunk","seq":91,"time":1784893541719,"data":{"turn":1,"step":2,"chunk":{"type":"usage","usage":{"inputTokens":228,"outputTokens":25,"cacheReadTokens":7680,"reasoningTokens":22}}}}
|
||||
{"type":"assistant/chunk","seq":92,"time":1784893541719,"data":{"turn":1,"step":2,"chunk":{"type":"finish","reason":{"kind":"stop"}}}}
|
||||
{"type":"assistant/message","seq":93,"time":1784893541720,"data":{"turn":1,"step":2,"content":[{"type":"reasoning","text":"The command executed successfully and printed \"WEB_E2E_OK\". I should now reply with \"DONE\"."},{"type":"text","text":"DONE"}],"provenance":{"provider":"deepseek","model":"deepseek-v4-flash"},"usage":{"inputTokens":228,"outputTokens":25,"cacheReadTokens":7680,"reasoningTokens":22}},"sourceEventSeqs":[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],"surfaceOp":"append"}
|
||||
{"type":"step/end","seq":94,"time":1784893541720,"data":{"turn":1,"step":2}}
|
||||
{"type":"turn/end","seq":95,"time":1784893541721,"data":{"turn":1,"reason":{"kind":"completed"}}}
|
||||
{"type":"session","version":0,"id":"{{sessionId}}","createdAt":1784959629995,"cwd":"{{cwd}}"}
|
||||
{"type":"turn/start","seq":0,"time":1784959630019,"data":{"turn":1,"trigger":{"kind":"message","source":{"kind":"user","rpcId":"f1126e16-eb24-483f-afa3-c247a325a739"}}}}
|
||||
{"type":"user/message","seq":1,"time":1784959630019,"data":{"content":[{"type":"text","text":"Use the bash tool to run exactly: echo WEB_E2E_OK. Then reply with the single word DONE and stop."}],"source":{"kind":"user","rpcId":"f1126e16-eb24-483f-afa3-c247a325a739"}},"surfaceOp":"append"}
|
||||
{"type":"session/title","seq":2,"time":1784959630021,"data":{"title":"Use the bash tool to","messageSeqs":[1],"source":{"kind":"fallback"}}}
|
||||
{"type":"step/start","seq":3,"time":1784959630081,"data":{"turn":1,"step":1}}
|
||||
{"type":"request/header","seq":4,"time":1784959630082,"data":{"header":{"config":{"provider":"deepseek","model":"deepseek-v4-flash"},"system":"{{system}}","tools":"{{tools}}","messagePrefix":["{{messagePrefix}}"]},"reason":"initial"}}
|
||||
{"type":"assistant/chunk","seq":5,"time":1784959630551,"data":{"turn":1,"step":1,"chunk":{"type":"block-start","index":0,"blockType":"reasoning"}}}
|
||||
{"type":"assistant/chunk","seq":6,"time":1784959630551,"data":{"turn":1,"step":1,"chunk":{"type":"reasoning-delta","index":0,"text":"The"}}}
|
||||
{"type":"assistant/chunk","seq":7,"time":1784959630680,"data":{"turn":1,"step":1,"chunk":{"type":"reasoning-delta","index":0,"text":" user"}}}
|
||||
{"type":"assistant/chunk","seq":8,"time":1784959630710,"data":{"turn":1,"step":1,"chunk":{"type":"reasoning-delta","index":0,"text":" wants"}}}
|
||||
{"type":"assistant/chunk","seq":9,"time":1784959630711,"data":{"turn":1,"step":1,"chunk":{"type":"reasoning-delta","index":0,"text":" me"}}}
|
||||
{"type":"assistant/chunk","seq":10,"time":1784959630711,"data":{"turn":1,"step":1,"chunk":{"type":"reasoning-delta","index":0,"text":" to"}}}
|
||||
{"type":"assistant/chunk","seq":11,"time":1784959630711,"data":{"turn":1,"step":1,"chunk":{"type":"reasoning-delta","index":0,"text":" run"}}}
|
||||
{"type":"assistant/chunk","seq":12,"time":1784959630711,"data":{"turn":1,"step":1,"chunk":{"type":"reasoning-delta","index":0,"text":" a"}}}
|
||||
{"type":"assistant/chunk","seq":13,"time":1784959630740,"data":{"turn":1,"step":1,"chunk":{"type":"reasoning-delta","index":0,"text":" specific"}}}
|
||||
{"type":"assistant/chunk","seq":14,"time":1784959630740,"data":{"turn":1,"step":1,"chunk":{"type":"reasoning-delta","index":0,"text":" bash"}}}
|
||||
{"type":"assistant/chunk","seq":15,"time":1784959630741,"data":{"turn":1,"step":1,"chunk":{"type":"reasoning-delta","index":0,"text":" command"}}}
|
||||
{"type":"assistant/chunk","seq":16,"time":1784959630741,"data":{"turn":1,"step":1,"chunk":{"type":"reasoning-delta","index":0,"text":" and"}}}
|
||||
{"type":"assistant/chunk","seq":17,"time":1784959630770,"data":{"turn":1,"step":1,"chunk":{"type":"reasoning-delta","index":0,"text":" then"}}}
|
||||
{"type":"assistant/chunk","seq":18,"time":1784959630771,"data":{"turn":1,"step":1,"chunk":{"type":"reasoning-delta","index":0,"text":" reply"}}}
|
||||
{"type":"assistant/chunk","seq":19,"time":1784959630771,"data":{"turn":1,"step":1,"chunk":{"type":"reasoning-delta","index":0,"text":" with"}}}
|
||||
{"type":"assistant/chunk","seq":20,"time":1784959630771,"data":{"turn":1,"step":1,"chunk":{"type":"reasoning-delta","index":0,"text":" \""}}}
|
||||
{"type":"assistant/chunk","seq":21,"time":1784959630800,"data":{"turn":1,"step":1,"chunk":{"type":"reasoning-delta","index":0,"text":"D"}}}
|
||||
{"type":"assistant/chunk","seq":22,"time":1784959630800,"data":{"turn":1,"step":1,"chunk":{"type":"reasoning-delta","index":0,"text":"ONE"}}}
|
||||
{"type":"assistant/chunk","seq":23,"time":1784959630800,"data":{"turn":1,"step":1,"chunk":{"type":"reasoning-delta","index":0,"text":"\"."}}}
|
||||
{"type":"assistant/chunk","seq":24,"time":1784959630887,"data":{"turn":1,"step":1,"chunk":{"type":"block-start","index":1,"blockType":"tool-call"}}}
|
||||
{"type":"assistant/chunk","seq":25,"time":1784959630888,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":1,"id":"call_00_QLU7wxjCnxqQC9T3SMi71047","name":"bash","argumentsDelta":""}}}
|
||||
{"type":"assistant/chunk","seq":26,"time":1784959630888,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":1,"id":"call_00_QLU7wxjCnxqQC9T3SMi71047","name":"bash","argumentsDelta":"{"}}}
|
||||
{"type":"assistant/chunk","seq":27,"time":1784959630888,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":1,"id":"call_00_QLU7wxjCnxqQC9T3SMi71047","name":"bash","argumentsDelta":"\""}}}
|
||||
{"type":"assistant/chunk","seq":28,"time":1784959630915,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":1,"id":"call_00_QLU7wxjCnxqQC9T3SMi71047","name":"bash","argumentsDelta":"command"}}}
|
||||
{"type":"assistant/chunk","seq":29,"time":1784959630916,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":1,"id":"call_00_QLU7wxjCnxqQC9T3SMi71047","name":"bash","argumentsDelta":"\""}}}
|
||||
{"type":"assistant/chunk","seq":30,"time":1784959630916,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":1,"id":"call_00_QLU7wxjCnxqQC9T3SMi71047","name":"bash","argumentsDelta":": "}}}
|
||||
{"type":"assistant/chunk","seq":31,"time":1784959630916,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":1,"id":"call_00_QLU7wxjCnxqQC9T3SMi71047","name":"bash","argumentsDelta":"\""}}}
|
||||
{"type":"assistant/chunk","seq":32,"time":1784959630941,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":1,"id":"call_00_QLU7wxjCnxqQC9T3SMi71047","name":"bash","argumentsDelta":"echo"}}}
|
||||
{"type":"assistant/chunk","seq":33,"time":1784959630942,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":1,"id":"call_00_QLU7wxjCnxqQC9T3SMi71047","name":"bash","argumentsDelta":" WEB"}}}
|
||||
{"type":"assistant/chunk","seq":34,"time":1784959630942,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":1,"id":"call_00_QLU7wxjCnxqQC9T3SMi71047","name":"bash","argumentsDelta":"_E"}}}
|
||||
{"type":"assistant/chunk","seq":35,"time":1784959630942,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":1,"id":"call_00_QLU7wxjCnxqQC9T3SMi71047","name":"bash","argumentsDelta":"2"}}}
|
||||
{"type":"assistant/chunk","seq":36,"time":1784959630942,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":1,"id":"call_00_QLU7wxjCnxqQC9T3SMi71047","name":"bash","argumentsDelta":"E"}}}
|
||||
{"type":"assistant/chunk","seq":37,"time":1784959630942,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":1,"id":"call_00_QLU7wxjCnxqQC9T3SMi71047","name":"bash","argumentsDelta":"_OK"}}}
|
||||
{"type":"assistant/chunk","seq":38,"time":1784959630974,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":1,"id":"call_00_QLU7wxjCnxqQC9T3SMi71047","name":"bash","argumentsDelta":"\""}}}
|
||||
{"type":"assistant/chunk","seq":39,"time":1784959631006,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":1,"id":"call_00_QLU7wxjCnxqQC9T3SMi71047","name":"bash","argumentsDelta":", "}}}
|
||||
{"type":"assistant/chunk","seq":40,"time":1784959631006,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":1,"id":"call_00_QLU7wxjCnxqQC9T3SMi71047","name":"bash","argumentsDelta":"\""}}}
|
||||
{"type":"assistant/chunk","seq":41,"time":1784959631007,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":1,"id":"call_00_QLU7wxjCnxqQC9T3SMi71047","name":"bash","argumentsDelta":"description"}}}
|
||||
{"type":"assistant/chunk","seq":42,"time":1784959631007,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":1,"id":"call_00_QLU7wxjCnxqQC9T3SMi71047","name":"bash","argumentsDelta":"\""}}}
|
||||
{"type":"assistant/chunk","seq":43,"time":1784959631007,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":1,"id":"call_00_QLU7wxjCnxqQC9T3SMi71047","name":"bash","argumentsDelta":": "}}}
|
||||
{"type":"assistant/chunk","seq":44,"time":1784959631027,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":1,"id":"call_00_QLU7wxjCnxqQC9T3SMi71047","name":"bash","argumentsDelta":"\""}}}
|
||||
{"type":"assistant/chunk","seq":45,"time":1784959631028,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":1,"id":"call_00_QLU7wxjCnxqQC9T3SMi71047","name":"bash","argumentsDelta":"E"}}}
|
||||
{"type":"assistant/chunk","seq":46,"time":1784959631028,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":1,"id":"call_00_QLU7wxjCnxqQC9T3SMi71047","name":"bash","argumentsDelta":"cho"}}}
|
||||
{"type":"assistant/chunk","seq":47,"time":1784959631057,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":1,"id":"call_00_QLU7wxjCnxqQC9T3SMi71047","name":"bash","argumentsDelta":" test"}}}
|
||||
{"type":"assistant/chunk","seq":48,"time":1784959631084,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":1,"id":"call_00_QLU7wxjCnxqQC9T3SMi71047","name":"bash","argumentsDelta":" string"}}}
|
||||
{"type":"assistant/chunk","seq":49,"time":1784959631085,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":1,"id":"call_00_QLU7wxjCnxqQC9T3SMi71047","name":"bash","argumentsDelta":"\""}}}
|
||||
{"type":"assistant/chunk","seq":50,"time":1784959631114,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":1,"id":"call_00_QLU7wxjCnxqQC9T3SMi71047","name":"bash","argumentsDelta":"}"}}}
|
||||
{"type":"assistant/chunk","seq":51,"time":1784959631144,"data":{"turn":1,"step":1,"chunk":{"type":"block-end","index":0,"block":{"type":"reasoning","text":"The user wants me to run a specific bash command and then reply with \"DONE\"."}}}}
|
||||
{"type":"assistant/chunk","seq":52,"time":1784959631144,"data":{"turn":1,"step":1,"chunk":{"type":"block-end","index":1,"block":{"type":"tool-call","id":"call_00_QLU7wxjCnxqQC9T3SMi71047","name":"bash","arguments":"{\"command\": \"echo WEB_E2E_OK\", \"description\": \"Echo test string\"}"}}}}
|
||||
{"type":"assistant/chunk","seq":53,"time":1784959631144,"data":{"turn":1,"step":1,"chunk":{"type":"usage","usage":{"inputTokens":122,"outputTokens":85,"cacheReadTokens":7680,"reasoningTokens":18}}}}
|
||||
{"type":"assistant/chunk","seq":54,"time":1784959631144,"data":{"turn":1,"step":1,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}}
|
||||
{"type":"assistant/message","seq":55,"time":1784959631148,"data":{"turn":1,"step":1,"content":[{"type":"reasoning","text":"The user wants me to run a specific bash command and then reply with \"DONE\"."},{"type":"tool-call","id":"call_00_QLU7wxjCnxqQC9T3SMi71047","name":"bash","arguments":"{\"command\": \"echo WEB_E2E_OK\", \"description\": \"Echo test string\"}"}],"provenance":{"provider":"deepseek","model":"deepseek-v4-flash"},"usage":{"inputTokens":122,"outputTokens":85,"cacheReadTokens":7680,"reasoningTokens":18}},"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],"surfaceOp":"append"}
|
||||
{"type":"tool/call","seq":56,"time":1784959631149,"data":{"turn":1,"step":1,"callId":"call_00_QLU7wxjCnxqQC9T3SMi71047","name":"bash","arguments":"{\"command\": \"echo WEB_E2E_OK\", \"description\": \"Echo test string\"}"}}
|
||||
{"type":"tool/result","seq":57,"time":1784959631163,"data":{"turn":1,"step":1,"callId":"call_00_QLU7wxjCnxqQC9T3SMi71047","content":[{"type":"text","text":"WEB_E2E_OK\n"}],"isError":false},"sourceEventSeqs":[56],"surfaceOp":"append"}
|
||||
{"type":"step/end","seq":58,"time":1784959631165,"data":{"turn":1,"step":1}}
|
||||
{"type":"step/start","seq":59,"time":1784959631166,"data":{"turn":1,"step":2}}
|
||||
{"type":"assistant/chunk","seq":60,"time":1784959631507,"data":{"turn":1,"step":2,"chunk":{"type":"block-start","index":0,"blockType":"reasoning"}}}
|
||||
{"type":"assistant/chunk","seq":61,"time":1784959631507,"data":{"turn":1,"step":2,"chunk":{"type":"reasoning-delta","index":0,"text":"The"}}}
|
||||
{"type":"assistant/chunk","seq":62,"time":1784959631639,"data":{"turn":1,"step":2,"chunk":{"type":"reasoning-delta","index":0,"text":" command"}}}
|
||||
{"type":"assistant/chunk","seq":63,"time":1784959631669,"data":{"turn":1,"step":2,"chunk":{"type":"reasoning-delta","index":0,"text":" output"}}}
|
||||
{"type":"assistant/chunk","seq":64,"time":1784959631697,"data":{"turn":1,"step":2,"chunk":{"type":"reasoning-delta","index":0,"text":" \""}}}
|
||||
{"type":"assistant/chunk","seq":65,"time":1784959631727,"data":{"turn":1,"step":2,"chunk":{"type":"reasoning-delta","index":0,"text":"WEB"}}}
|
||||
{"type":"assistant/chunk","seq":66,"time":1784959631727,"data":{"turn":1,"step":2,"chunk":{"type":"reasoning-delta","index":0,"text":"_E"}}}
|
||||
{"type":"assistant/chunk","seq":67,"time":1784959631727,"data":{"turn":1,"step":2,"chunk":{"type":"reasoning-delta","index":0,"text":"2"}}}
|
||||
{"type":"assistant/chunk","seq":68,"time":1784959631728,"data":{"turn":1,"step":2,"chunk":{"type":"reasoning-delta","index":0,"text":"E"}}}
|
||||
{"type":"assistant/chunk","seq":69,"time":1784959631728,"data":{"turn":1,"step":2,"chunk":{"type":"reasoning-delta","index":0,"text":"_OK"}}}
|
||||
{"type":"assistant/chunk","seq":70,"time":1784959631728,"data":{"turn":1,"step":2,"chunk":{"type":"reasoning-delta","index":0,"text":"\""}}}
|
||||
{"type":"assistant/chunk","seq":71,"time":1784959631761,"data":{"turn":1,"step":2,"chunk":{"type":"reasoning-delta","index":0,"text":" as"}}}
|
||||
{"type":"assistant/chunk","seq":72,"time":1784959631762,"data":{"turn":1,"step":2,"chunk":{"type":"reasoning-delta","index":0,"text":" expected"}}}
|
||||
{"type":"assistant/chunk","seq":73,"time":1784959631762,"data":{"turn":1,"step":2,"chunk":{"type":"reasoning-delta","index":0,"text":"."}}}
|
||||
{"type":"assistant/chunk","seq":74,"time":1784959631762,"data":{"turn":1,"step":2,"chunk":{"type":"reasoning-delta","index":0,"text":" Now"}}}
|
||||
{"type":"assistant/chunk","seq":75,"time":1784959631762,"data":{"turn":1,"step":2,"chunk":{"type":"reasoning-delta","index":0,"text":" I"}}}
|
||||
{"type":"assistant/chunk","seq":76,"time":1784959631762,"data":{"turn":1,"step":2,"chunk":{"type":"reasoning-delta","index":0,"text":" need"}}}
|
||||
{"type":"assistant/chunk","seq":77,"time":1784959631785,"data":{"turn":1,"step":2,"chunk":{"type":"reasoning-delta","index":0,"text":" to"}}}
|
||||
{"type":"assistant/chunk","seq":78,"time":1784959631786,"data":{"turn":1,"step":2,"chunk":{"type":"reasoning-delta","index":0,"text":" reply"}}}
|
||||
{"type":"assistant/chunk","seq":79,"time":1784959631786,"data":{"turn":1,"step":2,"chunk":{"type":"reasoning-delta","index":0,"text":" with"}}}
|
||||
{"type":"assistant/chunk","seq":80,"time":1784959631786,"data":{"turn":1,"step":2,"chunk":{"type":"reasoning-delta","index":0,"text":" just"}}}
|
||||
{"type":"assistant/chunk","seq":81,"time":1784959631786,"data":{"turn":1,"step":2,"chunk":{"type":"reasoning-delta","index":0,"text":" the"}}}
|
||||
{"type":"assistant/chunk","seq":82,"time":1784959631845,"data":{"turn":1,"step":2,"chunk":{"type":"reasoning-delta","index":0,"text":" single"}}}
|
||||
{"type":"assistant/chunk","seq":83,"time":1784959631845,"data":{"turn":1,"step":2,"chunk":{"type":"reasoning-delta","index":0,"text":" word"}}}
|
||||
{"type":"assistant/chunk","seq":84,"time":1784959631845,"data":{"turn":1,"step":2,"chunk":{"type":"reasoning-delta","index":0,"text":" \""}}}
|
||||
{"type":"assistant/chunk","seq":85,"time":1784959631845,"data":{"turn":1,"step":2,"chunk":{"type":"reasoning-delta","index":0,"text":"D"}}}
|
||||
{"type":"assistant/chunk","seq":86,"time":1784959631845,"data":{"turn":1,"step":2,"chunk":{"type":"reasoning-delta","index":0,"text":"ONE"}}}
|
||||
{"type":"assistant/chunk","seq":87,"time":1784959631846,"data":{"turn":1,"step":2,"chunk":{"type":"reasoning-delta","index":0,"text":"\"."}}}
|
||||
{"type":"assistant/chunk","seq":88,"time":1784959631851,"data":{"turn":1,"step":2,"chunk":{"type":"block-start","index":1,"blockType":"text"}}}
|
||||
{"type":"assistant/chunk","seq":89,"time":1784959631851,"data":{"turn":1,"step":2,"chunk":{"type":"text-delta","index":1,"text":"D"}}}
|
||||
{"type":"assistant/chunk","seq":90,"time":1784959631851,"data":{"turn":1,"step":2,"chunk":{"type":"text-delta","index":1,"text":"ONE"}}}
|
||||
{"type":"assistant/chunk","seq":91,"time":1784959631852,"data":{"turn":1,"step":2,"chunk":{"type":"block-end","index":0,"block":{"type":"reasoning","text":"The command output \"WEB_E2E_OK\" as expected. Now I need to reply with just the single word \"DONE\"."}}}}
|
||||
{"type":"assistant/chunk","seq":92,"time":1784959631852,"data":{"turn":1,"step":2,"chunk":{"type":"block-end","index":1,"block":{"type":"text","text":"DONE"}}}}
|
||||
{"type":"assistant/chunk","seq":93,"time":1784959631852,"data":{"turn":1,"step":2,"chunk":{"type":"usage","usage":{"inputTokens":97,"outputTokens":30,"cacheReadTokens":7808,"reasoningTokens":27}}}}
|
||||
{"type":"assistant/chunk","seq":94,"time":1784959631852,"data":{"turn":1,"step":2,"chunk":{"type":"finish","reason":{"kind":"stop"}}}}
|
||||
{"type":"assistant/message","seq":95,"time":1784959631853,"data":{"turn":1,"step":2,"content":[{"type":"reasoning","text":"The command output \"WEB_E2E_OK\" as expected. Now I need to reply with just the single word \"DONE\"."},{"type":"text","text":"DONE"}],"provenance":{"provider":"deepseek","model":"deepseek-v4-flash"},"usage":{"inputTokens":97,"outputTokens":30,"cacheReadTokens":7808,"reasoningTokens":27}},"sourceEventSeqs":[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],"surfaceOp":"append"}
|
||||
{"type":"step/end","seq":96,"time":1784959631854,"data":{"turn":1,"step":2}}
|
||||
{"type":"turn/end","seq":97,"time":1784959631854,"data":{"turn":1,"reason":{"kind":"completed"}}}
|
||||
|
||||
@@ -7,15 +7,15 @@
|
||||
- tab "Trajectory"
|
||||
- tab "Waterfall"
|
||||
- text: "Use the bash tool to run exactly: echo WEB_E2E_OK. Then reply with the single word DONE and stop."
|
||||
- button "Think The user wants me to run a simple bash command and reply with \"DONE\".":
|
||||
- button "Think The user wants me to run a specific bash command and then reply with \"DONE\".":
|
||||
- img
|
||||
- text: Think The user wants me to run a simple bash command and reply with "DONE".
|
||||
- text: Print WEB_E2E_OK to stdout
|
||||
- button "Think The command executed successfully and printed \"WEB_E2E_OK\". I should now reply with \"DONE\".":
|
||||
- text: Think The user wants me to run a specific bash command and then reply with "DONE".
|
||||
- text: Echo test string
|
||||
- button "Think The command output \"WEB_E2E_OK\" as expected. Now I need to reply with just the single word \"DONE\".":
|
||||
- img
|
||||
- text: Think The command executed successfully and printed "WEB_E2E_OK". I should now reply with "DONE".
|
||||
- text: Think The command output "WEB_E2E_OK" as expected. Now I need to reply with just the single word "DONE".
|
||||
- paragraph: DONE
|
||||
- text: cache hit 49% · 15,823 tokens · 1 turns · 2 steps
|
||||
- text: cache hit 99% · 15,822 tokens · 1 turns · 2 steps
|
||||
- textbox "输入消息,Enter 发送,Shift+Enter 换行"
|
||||
- button "添加":
|
||||
- img
|
||||
|
||||
@@ -1,112 +1,116 @@
|
||||
{"type":"session","version":0,"id":"{{sessionId}}","createdAt":1784893580342,"cwd":"{{cwd}}"}
|
||||
{"type":"turn/start","seq":0,"time":1784893580362,"data":{"turn":1,"trigger":{"kind":"message","source":{"kind":"user","rpcId":"ab867856-cd1a-4270-8cee-c240076c58e9"}}}}
|
||||
{"type":"user/message","seq":1,"time":1784893580363,"data":{"content":[{"type":"text","text":"Use the read tool twice in one assistant message: read a.txt and b.txt. Then reply with the single word DONE and stop."}],"source":{"kind":"user","rpcId":"ab867856-cd1a-4270-8cee-c240076c58e9"}},"surfaceOp":"append"}
|
||||
{"type":"session/title","seq":2,"time":1784893580365,"data":{"title":"Use the read tool twice","messageSeqs":[1],"source":{"kind":"fallback"}}}
|
||||
{"type":"step/start","seq":3,"time":1784893580420,"data":{"turn":1,"step":1}}
|
||||
{"type":"request/header","seq":4,"time":1784893580421,"data":{"header":{"config":{"provider":"deepseek","model":"deepseek-v4-flash"},"system":"{{system}}","tools":"{{tools}}","messagePrefix":["{{messagePrefix}}"]},"reason":"initial"}}
|
||||
{"type":"assistant/chunk","seq":5,"time":1784893581003,"data":{"turn":1,"step":1,"chunk":{"type":"block-start","index":0,"blockType":"reasoning"}}}
|
||||
{"type":"assistant/chunk","seq":6,"time":1784893581003,"data":{"turn":1,"step":1,"chunk":{"type":"reasoning-delta","index":0,"text":"The"}}}
|
||||
{"type":"assistant/chunk","seq":7,"time":1784893581092,"data":{"turn":1,"step":1,"chunk":{"type":"reasoning-delta","index":0,"text":" user"}}}
|
||||
{"type":"assistant/chunk","seq":8,"time":1784893581107,"data":{"turn":1,"step":1,"chunk":{"type":"reasoning-delta","index":0,"text":" wants"}}}
|
||||
{"type":"assistant/chunk","seq":9,"time":1784893581108,"data":{"turn":1,"step":1,"chunk":{"type":"reasoning-delta","index":0,"text":" me"}}}
|
||||
{"type":"assistant/chunk","seq":10,"time":1784893581108,"data":{"turn":1,"step":1,"chunk":{"type":"reasoning-delta","index":0,"text":" to"}}}
|
||||
{"type":"assistant/chunk","seq":11,"time":1784893581108,"data":{"turn":1,"step":1,"chunk":{"type":"reasoning-delta","index":0,"text":" read"}}}
|
||||
{"type":"assistant/chunk","seq":12,"time":1784893581135,"data":{"turn":1,"step":1,"chunk":{"type":"reasoning-delta","index":0,"text":" a"}}}
|
||||
{"type":"assistant/chunk","seq":13,"time":1784893581135,"data":{"turn":1,"step":1,"chunk":{"type":"reasoning-delta","index":0,"text":".txt"}}}
|
||||
{"type":"assistant/chunk","seq":14,"time":1784893581136,"data":{"turn":1,"step":1,"chunk":{"type":"reasoning-delta","index":0,"text":" and"}}}
|
||||
{"type":"assistant/chunk","seq":15,"time":1784893581161,"data":{"turn":1,"step":1,"chunk":{"type":"reasoning-delta","index":0,"text":" b"}}}
|
||||
{"type":"assistant/chunk","seq":16,"time":1784893581162,"data":{"turn":1,"step":1,"chunk":{"type":"reasoning-delta","index":0,"text":".txt"}}}
|
||||
{"type":"assistant/chunk","seq":17,"time":1784893581162,"data":{"turn":1,"step":1,"chunk":{"type":"reasoning-delta","index":0,"text":","}}}
|
||||
{"type":"assistant/chunk","seq":18,"time":1784893581189,"data":{"turn":1,"step":1,"chunk":{"type":"reasoning-delta","index":0,"text":" then"}}}
|
||||
{"type":"assistant/chunk","seq":19,"time":1784893581190,"data":{"turn":1,"step":1,"chunk":{"type":"reasoning-delta","index":0,"text":" reply"}}}
|
||||
{"type":"assistant/chunk","seq":20,"time":1784893581190,"data":{"turn":1,"step":1,"chunk":{"type":"reasoning-delta","index":0,"text":" with"}}}
|
||||
{"type":"assistant/chunk","seq":21,"time":1784893581190,"data":{"turn":1,"step":1,"chunk":{"type":"reasoning-delta","index":0,"text":" D"}}}
|
||||
{"type":"assistant/chunk","seq":22,"time":1784893581217,"data":{"turn":1,"step":1,"chunk":{"type":"reasoning-delta","index":0,"text":"ONE"}}}
|
||||
{"type":"assistant/chunk","seq":23,"time":1784893581217,"data":{"turn":1,"step":1,"chunk":{"type":"reasoning-delta","index":0,"text":"."}}}
|
||||
{"type":"assistant/chunk","seq":24,"time":1784893581217,"data":{"turn":1,"step":1,"chunk":{"type":"reasoning-delta","index":0,"text":" Let"}}}
|
||||
{"type":"assistant/chunk","seq":25,"time":1784893581217,"data":{"turn":1,"step":1,"chunk":{"type":"reasoning-delta","index":0,"text":" me"}}}
|
||||
{"type":"assistant/chunk","seq":26,"time":1784893581217,"data":{"turn":1,"step":1,"chunk":{"type":"reasoning-delta","index":0,"text":" read"}}}
|
||||
{"type":"assistant/chunk","seq":27,"time":1784893581258,"data":{"turn":1,"step":1,"chunk":{"type":"reasoning-delta","index":0,"text":" both"}}}
|
||||
{"type":"assistant/chunk","seq":28,"time":1784893581259,"data":{"turn":1,"step":1,"chunk":{"type":"reasoning-delta","index":0,"text":" files"}}}
|
||||
{"type":"assistant/chunk","seq":29,"time":1784893581259,"data":{"turn":1,"step":1,"chunk":{"type":"reasoning-delta","index":0,"text":"."}}}
|
||||
{"type":"assistant/chunk","seq":30,"time":1784893581324,"data":{"turn":1,"step":1,"chunk":{"type":"block-start","index":1,"blockType":"tool-call"}}}
|
||||
{"type":"assistant/chunk","seq":31,"time":1784893581325,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":1,"id":"call_00_8bQPkq98ZAzRTEJA6XM38538","name":"read","argumentsDelta":""}}}
|
||||
{"type":"assistant/chunk","seq":32,"time":1784893581325,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":1,"id":"call_00_8bQPkq98ZAzRTEJA6XM38538","name":"read","argumentsDelta":"{"}}}
|
||||
{"type":"assistant/chunk","seq":33,"time":1784893581325,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":1,"id":"call_00_8bQPkq98ZAzRTEJA6XM38538","name":"read","argumentsDelta":"\""}}}
|
||||
{"type":"assistant/chunk","seq":34,"time":1784893581351,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":1,"id":"call_00_8bQPkq98ZAzRTEJA6XM38538","name":"read","argumentsDelta":"file"}}}
|
||||
{"type":"assistant/chunk","seq":35,"time":1784893581351,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":1,"id":"call_00_8bQPkq98ZAzRTEJA6XM38538","name":"read","argumentsDelta":"_path"}}}
|
||||
{"type":"assistant/chunk","seq":36,"time":1784893581351,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":1,"id":"call_00_8bQPkq98ZAzRTEJA6XM38538","name":"read","argumentsDelta":"\""}}}
|
||||
{"type":"assistant/chunk","seq":37,"time":1784893581351,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":1,"id":"call_00_8bQPkq98ZAzRTEJA6XM38538","name":"read","argumentsDelta":": "}}}
|
||||
{"type":"assistant/chunk","seq":38,"time":1784893581378,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":1,"id":"call_00_8bQPkq98ZAzRTEJA6XM38538","name":"read","argumentsDelta":"\""}}}
|
||||
{"type":"assistant/chunk","seq":39,"time":1784893581378,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":1,"id":"call_00_8bQPkq98ZAzRTEJA6XM38538","name":"read","argumentsDelta":"a"}}}
|
||||
{"type":"assistant/chunk","seq":40,"time":1784893581378,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":1,"id":"call_00_8bQPkq98ZAzRTEJA6XM38538","name":"read","argumentsDelta":".txt"}}}
|
||||
{"type":"assistant/chunk","seq":41,"time":1784893581378,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":1,"id":"call_00_8bQPkq98ZAzRTEJA6XM38538","name":"read","argumentsDelta":"\""}}}
|
||||
{"type":"assistant/chunk","seq":42,"time":1784893581404,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":1,"id":"call_00_8bQPkq98ZAzRTEJA6XM38538","name":"read","argumentsDelta":"}"}}}
|
||||
{"type":"assistant/chunk","seq":43,"time":1784893581462,"data":{"turn":1,"step":1,"chunk":{"type":"block-start","index":2,"blockType":"tool-call"}}}
|
||||
{"type":"assistant/chunk","seq":44,"time":1784893581462,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":2,"id":"call_01_5FMIBJA9HHyktFY8KSlk9459","name":"read","argumentsDelta":""}}}
|
||||
{"type":"assistant/chunk","seq":45,"time":1784893581486,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":2,"id":"call_01_5FMIBJA9HHyktFY8KSlk9459","name":"read","argumentsDelta":"{"}}}
|
||||
{"type":"assistant/chunk","seq":46,"time":1784893581486,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":2,"id":"call_01_5FMIBJA9HHyktFY8KSlk9459","name":"read","argumentsDelta":"\""}}}
|
||||
{"type":"assistant/chunk","seq":47,"time":1784893581486,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":2,"id":"call_01_5FMIBJA9HHyktFY8KSlk9459","name":"read","argumentsDelta":"file"}}}
|
||||
{"type":"assistant/chunk","seq":48,"time":1784893581486,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":2,"id":"call_01_5FMIBJA9HHyktFY8KSlk9459","name":"read","argumentsDelta":"_path"}}}
|
||||
{"type":"assistant/chunk","seq":49,"time":1784893581486,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":2,"id":"call_01_5FMIBJA9HHyktFY8KSlk9459","name":"read","argumentsDelta":"\""}}}
|
||||
{"type":"assistant/chunk","seq":50,"time":1784893581486,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":2,"id":"call_01_5FMIBJA9HHyktFY8KSlk9459","name":"read","argumentsDelta":": "}}}
|
||||
{"type":"assistant/chunk","seq":51,"time":1784893581513,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":2,"id":"call_01_5FMIBJA9HHyktFY8KSlk9459","name":"read","argumentsDelta":"\""}}}
|
||||
{"type":"assistant/chunk","seq":52,"time":1784893581513,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":2,"id":"call_01_5FMIBJA9HHyktFY8KSlk9459","name":"read","argumentsDelta":"b"}}}
|
||||
{"type":"assistant/chunk","seq":53,"time":1784893581514,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":2,"id":"call_01_5FMIBJA9HHyktFY8KSlk9459","name":"read","argumentsDelta":".txt"}}}
|
||||
{"type":"assistant/chunk","seq":54,"time":1784893581514,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":2,"id":"call_01_5FMIBJA9HHyktFY8KSlk9459","name":"read","argumentsDelta":"\""}}}
|
||||
{"type":"assistant/chunk","seq":55,"time":1784893581539,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":2,"id":"call_01_5FMIBJA9HHyktFY8KSlk9459","name":"read","argumentsDelta":"}"}}}
|
||||
{"type":"assistant/chunk","seq":56,"time":1784893581597,"data":{"turn":1,"step":1,"chunk":{"type":"block-end","index":0,"block":{"type":"reasoning","text":"The user wants me to read a.txt and b.txt, then reply with DONE. Let me read both files."}}}}
|
||||
{"type":"assistant/chunk","seq":57,"time":1784893581597,"data":{"turn":1,"step":1,"chunk":{"type":"block-end","index":1,"block":{"type":"tool-call","id":"call_00_8bQPkq98ZAzRTEJA6XM38538","name":"read","arguments":"{\"file_path\": \"a.txt\"}"}}}}
|
||||
{"type":"assistant/chunk","seq":58,"time":1784893581597,"data":{"turn":1,"step":1,"chunk":{"type":"block-end","index":2,"block":{"type":"tool-call","id":"call_01_5FMIBJA9HHyktFY8KSlk9459","name":"read","arguments":"{\"file_path\": \"b.txt\"}"}}}}
|
||||
{"type":"assistant/chunk","seq":59,"time":1784893581597,"data":{"turn":1,"step":1,"chunk":{"type":"usage","usage":{"inputTokens":124,"outputTokens":100,"cacheReadTokens":7680,"reasoningTokens":24}}}}
|
||||
{"type":"assistant/chunk","seq":60,"time":1784893581597,"data":{"turn":1,"step":1,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}}
|
||||
{"type":"assistant/message","seq":61,"time":1784893581601,"data":{"turn":1,"step":1,"content":[{"type":"reasoning","text":"The user wants me to read a.txt and b.txt, then reply with DONE. Let me read both files."},{"type":"tool-call","id":"call_00_8bQPkq98ZAzRTEJA6XM38538","name":"read","arguments":"{\"file_path\": \"a.txt\"}"},{"type":"tool-call","id":"call_01_5FMIBJA9HHyktFY8KSlk9459","name":"read","arguments":"{\"file_path\": \"b.txt\"}"}],"provenance":{"provider":"deepseek","model":"deepseek-v4-flash"},"usage":{"inputTokens":124,"outputTokens":100,"cacheReadTokens":7680,"reasoningTokens":24}},"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],"surfaceOp":"append"}
|
||||
{"type":"tool/call","seq":62,"time":1784893581602,"data":{"turn":1,"step":1,"callId":"call_00_8bQPkq98ZAzRTEJA6XM38538","name":"read","arguments":"{\"file_path\": \"a.txt\"}"}}
|
||||
{"type":"tool/call","seq":63,"time":1784893581604,"data":{"turn":1,"step":1,"callId":"call_01_5FMIBJA9HHyktFY8KSlk9459","name":"read","arguments":"{\"file_path\": \"b.txt\"}"}}
|
||||
{"type":"tool/result","seq":64,"time":1784893581608,"data":{"turn":1,"step":1,"callId":"call_00_8bQPkq98ZAzRTEJA6XM38538","content":[{"type":"text","text":"<path>{{cwd}}/a.txt</path>\n<type>file</type>\n<content>\n1: alpha\n\n(End of file - total 1 lines)\n</content>"}],"isError":false},"sourceEventSeqs":[62],"surfaceOp":"append"}
|
||||
{"type":"tool/result","seq":65,"time":1784893581609,"data":{"turn":1,"step":1,"callId":"call_01_5FMIBJA9HHyktFY8KSlk9459","content":[{"type":"text","text":"<path>{{cwd}}/b.txt</path>\n<type>file</type>\n<content>\n1: beta\n\n(End of file - total 1 lines)\n</content>"}],"isError":false},"sourceEventSeqs":[63],"surfaceOp":"append"}
|
||||
{"type":"step/end","seq":66,"time":1784893581611,"data":{"turn":1,"step":1}}
|
||||
{"type":"step/start","seq":67,"time":1784893581611,"data":{"turn":1,"step":2}}
|
||||
{"type":"assistant/chunk","seq":68,"time":1784893582137,"data":{"turn":1,"step":2,"chunk":{"type":"block-start","index":0,"blockType":"reasoning"}}}
|
||||
{"type":"assistant/chunk","seq":69,"time":1784893582137,"data":{"turn":1,"step":2,"chunk":{"type":"reasoning-delta","index":0,"text":"Both"}}}
|
||||
{"type":"assistant/chunk","seq":70,"time":1784893582257,"data":{"turn":1,"step":2,"chunk":{"type":"reasoning-delta","index":0,"text":" files"}}}
|
||||
{"type":"assistant/chunk","seq":71,"time":1784893582259,"data":{"turn":1,"step":2,"chunk":{"type":"reasoning-delta","index":0,"text":" have"}}}
|
||||
{"type":"assistant/chunk","seq":72,"time":1784893582260,"data":{"turn":1,"step":2,"chunk":{"type":"reasoning-delta","index":0,"text":" been"}}}
|
||||
{"type":"assistant/chunk","seq":73,"time":1784893582260,"data":{"turn":1,"step":2,"chunk":{"type":"reasoning-delta","index":0,"text":" read"}}}
|
||||
{"type":"assistant/chunk","seq":74,"time":1784893582260,"data":{"turn":1,"step":2,"chunk":{"type":"reasoning-delta","index":0,"text":"."}}}
|
||||
{"type":"assistant/chunk","seq":75,"time":1784893582277,"data":{"turn":1,"step":2,"chunk":{"type":"reasoning-delta","index":0,"text":" a"}}}
|
||||
{"type":"assistant/chunk","seq":76,"time":1784893582303,"data":{"turn":1,"step":2,"chunk":{"type":"reasoning-delta","index":0,"text":".txt"}}}
|
||||
{"type":"assistant/chunk","seq":77,"time":1784893582303,"data":{"turn":1,"step":2,"chunk":{"type":"reasoning-delta","index":0,"text":" contains"}}}
|
||||
{"type":"assistant/chunk","seq":78,"time":1784893582304,"data":{"turn":1,"step":2,"chunk":{"type":"reasoning-delta","index":0,"text":" \""}}}
|
||||
{"type":"assistant/chunk","seq":79,"time":1784893582304,"data":{"turn":1,"step":2,"chunk":{"type":"reasoning-delta","index":0,"text":"alpha"}}}
|
||||
{"type":"assistant/chunk","seq":80,"time":1784893582304,"data":{"turn":1,"step":2,"chunk":{"type":"reasoning-delta","index":0,"text":"\""}}}
|
||||
{"type":"assistant/chunk","seq":81,"time":1784893582330,"data":{"turn":1,"step":2,"chunk":{"type":"reasoning-delta","index":0,"text":" and"}}}
|
||||
{"type":"assistant/chunk","seq":82,"time":1784893582330,"data":{"turn":1,"step":2,"chunk":{"type":"reasoning-delta","index":0,"text":" b"}}}
|
||||
{"type":"assistant/chunk","seq":83,"time":1784893582330,"data":{"turn":1,"step":2,"chunk":{"type":"reasoning-delta","index":0,"text":".txt"}}}
|
||||
{"type":"assistant/chunk","seq":84,"time":1784893582330,"data":{"turn":1,"step":2,"chunk":{"type":"reasoning-delta","index":0,"text":" contains"}}}
|
||||
{"type":"assistant/chunk","seq":85,"time":1784893582331,"data":{"turn":1,"step":2,"chunk":{"type":"reasoning-delta","index":0,"text":" \""}}}
|
||||
{"type":"assistant/chunk","seq":86,"time":1784893582331,"data":{"turn":1,"step":2,"chunk":{"type":"reasoning-delta","index":0,"text":"beta"}}}
|
||||
{"type":"assistant/chunk","seq":87,"time":1784893582356,"data":{"turn":1,"step":2,"chunk":{"type":"reasoning-delta","index":0,"text":"\"."}}}
|
||||
{"type":"assistant/chunk","seq":88,"time":1784893582357,"data":{"turn":1,"step":2,"chunk":{"type":"reasoning-delta","index":0,"text":" Now"}}}
|
||||
{"type":"assistant/chunk","seq":89,"time":1784893582384,"data":{"turn":1,"step":2,"chunk":{"type":"reasoning-delta","index":0,"text":" I"}}}
|
||||
{"type":"assistant/chunk","seq":90,"time":1784893582384,"data":{"turn":1,"step":2,"chunk":{"type":"reasoning-delta","index":0,"text":" just"}}}
|
||||
{"type":"assistant/chunk","seq":91,"time":1784893582384,"data":{"turn":1,"step":2,"chunk":{"type":"reasoning-delta","index":0,"text":" need"}}}
|
||||
{"type":"assistant/chunk","seq":92,"time":1784893582384,"data":{"turn":1,"step":2,"chunk":{"type":"reasoning-delta","index":0,"text":" to"}}}
|
||||
{"type":"assistant/chunk","seq":93,"time":1784893582385,"data":{"turn":1,"step":2,"chunk":{"type":"reasoning-delta","index":0,"text":" reply"}}}
|
||||
{"type":"assistant/chunk","seq":94,"time":1784893582411,"data":{"turn":1,"step":2,"chunk":{"type":"reasoning-delta","index":0,"text":" with"}}}
|
||||
{"type":"assistant/chunk","seq":95,"time":1784893582411,"data":{"turn":1,"step":2,"chunk":{"type":"reasoning-delta","index":0,"text":" the"}}}
|
||||
{"type":"assistant/chunk","seq":96,"time":1784893582438,"data":{"turn":1,"step":2,"chunk":{"type":"reasoning-delta","index":0,"text":" single"}}}
|
||||
{"type":"assistant/chunk","seq":97,"time":1784893582438,"data":{"turn":1,"step":2,"chunk":{"type":"reasoning-delta","index":0,"text":" word"}}}
|
||||
{"type":"assistant/chunk","seq":98,"time":1784893582438,"data":{"turn":1,"step":2,"chunk":{"type":"reasoning-delta","index":0,"text":" D"}}}
|
||||
{"type":"assistant/chunk","seq":99,"time":1784893582438,"data":{"turn":1,"step":2,"chunk":{"type":"reasoning-delta","index":0,"text":"ONE"}}}
|
||||
{"type":"assistant/chunk","seq":100,"time":1784893582438,"data":{"turn":1,"step":2,"chunk":{"type":"reasoning-delta","index":0,"text":"."}}}
|
||||
{"type":"assistant/chunk","seq":101,"time":1784893582467,"data":{"turn":1,"step":2,"chunk":{"type":"block-start","index":1,"blockType":"text"}}}
|
||||
{"type":"assistant/chunk","seq":102,"time":1784893582468,"data":{"turn":1,"step":2,"chunk":{"type":"text-delta","index":1,"text":"D"}}}
|
||||
{"type":"assistant/chunk","seq":103,"time":1784893582468,"data":{"turn":1,"step":2,"chunk":{"type":"text-delta","index":1,"text":"ONE"}}}
|
||||
{"type":"assistant/chunk","seq":104,"time":1784893582468,"data":{"turn":1,"step":2,"chunk":{"type":"block-end","index":0,"block":{"type":"reasoning","text":"Both files have been read. a.txt contains \"alpha\" and b.txt contains \"beta\". Now I just need to reply with the single word DONE."}}}}
|
||||
{"type":"assistant/chunk","seq":105,"time":1784893582468,"data":{"turn":1,"step":2,"chunk":{"type":"block-end","index":1,"block":{"type":"text","text":"DONE"}}}}
|
||||
{"type":"assistant/chunk","seq":106,"time":1784893582468,"data":{"turn":1,"step":2,"chunk":{"type":"usage","usage":{"inputTokens":340,"outputTokens":35,"cacheReadTokens":7680,"reasoningTokens":32}}}}
|
||||
{"type":"assistant/chunk","seq":107,"time":1784893582468,"data":{"turn":1,"step":2,"chunk":{"type":"finish","reason":{"kind":"stop"}}}}
|
||||
{"type":"assistant/message","seq":108,"time":1784893582469,"data":{"turn":1,"step":2,"content":[{"type":"reasoning","text":"Both files have been read. a.txt contains \"alpha\" and b.txt contains \"beta\". Now I just need to reply with the single word DONE."},{"type":"text","text":"DONE"}],"provenance":{"provider":"deepseek","model":"deepseek-v4-flash"},"usage":{"inputTokens":340,"outputTokens":35,"cacheReadTokens":7680,"reasoningTokens":32}},"sourceEventSeqs":[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],"surfaceOp":"append"}
|
||||
{"type":"step/end","seq":109,"time":1784893582470,"data":{"turn":1,"step":2}}
|
||||
{"type":"turn/end","seq":110,"time":1784893582470,"data":{"turn":1,"reason":{"kind":"completed"}}}
|
||||
{"type":"session","version":0,"id":"{{sessionId}}","createdAt":1784959659982,"cwd":"{{cwd}}"}
|
||||
{"type":"turn/start","seq":0,"time":1784959659995,"data":{"turn":1,"trigger":{"kind":"message","source":{"kind":"user","rpcId":"11e49fc6-9f3e-4570-aef3-21677733cf23"}}}}
|
||||
{"type":"user/message","seq":1,"time":1784959659995,"data":{"content":[{"type":"text","text":"Use the read tool twice in one assistant message: read a.txt and b.txt. Then reply with the single word DONE and stop."}],"source":{"kind":"user","rpcId":"11e49fc6-9f3e-4570-aef3-21677733cf23"}},"surfaceOp":"append"}
|
||||
{"type":"session/title","seq":2,"time":1784959659997,"data":{"title":"Use the read tool twice","messageSeqs":[1],"source":{"kind":"fallback"}}}
|
||||
{"type":"step/start","seq":3,"time":1784959660056,"data":{"turn":1,"step":1}}
|
||||
{"type":"request/header","seq":4,"time":1784959660057,"data":{"header":{"config":{"provider":"deepseek","model":"deepseek-v4-flash"},"system":"{{system}}","tools":"{{tools}}","messagePrefix":["{{messagePrefix}}"]},"reason":"initial"}}
|
||||
{"type":"assistant/chunk","seq":5,"time":1784959660422,"data":{"turn":1,"step":1,"chunk":{"type":"block-start","index":0,"blockType":"reasoning"}}}
|
||||
{"type":"assistant/chunk","seq":6,"time":1784959660422,"data":{"turn":1,"step":1,"chunk":{"type":"reasoning-delta","index":0,"text":"The"}}}
|
||||
{"type":"assistant/chunk","seq":7,"time":1784959660513,"data":{"turn":1,"step":1,"chunk":{"type":"reasoning-delta","index":0,"text":" user"}}}
|
||||
{"type":"assistant/chunk","seq":8,"time":1784959660538,"data":{"turn":1,"step":1,"chunk":{"type":"reasoning-delta","index":0,"text":" wants"}}}
|
||||
{"type":"assistant/chunk","seq":9,"time":1784959660539,"data":{"turn":1,"step":1,"chunk":{"type":"reasoning-delta","index":0,"text":" me"}}}
|
||||
{"type":"assistant/chunk","seq":10,"time":1784959660539,"data":{"turn":1,"step":1,"chunk":{"type":"reasoning-delta","index":0,"text":" to"}}}
|
||||
{"type":"assistant/chunk","seq":11,"time":1784959660539,"data":{"turn":1,"step":1,"chunk":{"type":"reasoning-delta","index":0,"text":" read"}}}
|
||||
{"type":"assistant/chunk","seq":12,"time":1784959660539,"data":{"turn":1,"step":1,"chunk":{"type":"reasoning-delta","index":0,"text":" two"}}}
|
||||
{"type":"assistant/chunk","seq":13,"time":1784959660565,"data":{"turn":1,"step":1,"chunk":{"type":"reasoning-delta","index":0,"text":" files"}}}
|
||||
{"type":"assistant/chunk","seq":14,"time":1784959660566,"data":{"turn":1,"step":1,"chunk":{"type":"reasoning-delta","index":0,"text":" ("}}}
|
||||
{"type":"assistant/chunk","seq":15,"time":1784959660593,"data":{"turn":1,"step":1,"chunk":{"type":"reasoning-delta","index":0,"text":"a"}}}
|
||||
{"type":"assistant/chunk","seq":16,"time":1784959660594,"data":{"turn":1,"step":1,"chunk":{"type":"reasoning-delta","index":0,"text":".txt"}}}
|
||||
{"type":"assistant/chunk","seq":17,"time":1784959660594,"data":{"turn":1,"step":1,"chunk":{"type":"reasoning-delta","index":0,"text":" and"}}}
|
||||
{"type":"assistant/chunk","seq":18,"time":1784959660594,"data":{"turn":1,"step":1,"chunk":{"type":"reasoning-delta","index":0,"text":" b"}}}
|
||||
{"type":"assistant/chunk","seq":19,"time":1784959660594,"data":{"turn":1,"step":1,"chunk":{"type":"reasoning-delta","index":0,"text":".txt"}}}
|
||||
{"type":"assistant/chunk","seq":20,"time":1784959660594,"data":{"turn":1,"step":1,"chunk":{"type":"reasoning-delta","index":0,"text":")"}}}
|
||||
{"type":"assistant/chunk","seq":21,"time":1784959660619,"data":{"turn":1,"step":1,"chunk":{"type":"reasoning-delta","index":0,"text":" and"}}}
|
||||
{"type":"assistant/chunk","seq":22,"time":1784959660620,"data":{"turn":1,"step":1,"chunk":{"type":"reasoning-delta","index":0,"text":" then"}}}
|
||||
{"type":"assistant/chunk","seq":23,"time":1784959660620,"data":{"turn":1,"step":1,"chunk":{"type":"reasoning-delta","index":0,"text":" reply"}}}
|
||||
{"type":"assistant/chunk","seq":24,"time":1784959660648,"data":{"turn":1,"step":1,"chunk":{"type":"reasoning-delta","index":0,"text":" with"}}}
|
||||
{"type":"assistant/chunk","seq":25,"time":1784959660648,"data":{"turn":1,"step":1,"chunk":{"type":"reasoning-delta","index":0,"text":" \""}}}
|
||||
{"type":"assistant/chunk","seq":26,"time":1784959660648,"data":{"turn":1,"step":1,"chunk":{"type":"reasoning-delta","index":0,"text":"D"}}}
|
||||
{"type":"assistant/chunk","seq":27,"time":1784959660649,"data":{"turn":1,"step":1,"chunk":{"type":"reasoning-delta","index":0,"text":"ONE"}}}
|
||||
{"type":"assistant/chunk","seq":28,"time":1784959660674,"data":{"turn":1,"step":1,"chunk":{"type":"reasoning-delta","index":0,"text":"\"."}}}
|
||||
{"type":"assistant/chunk","seq":29,"time":1784959660674,"data":{"turn":1,"step":1,"chunk":{"type":"reasoning-delta","index":0,"text":" Let"}}}
|
||||
{"type":"assistant/chunk","seq":30,"time":1784959660675,"data":{"turn":1,"step":1,"chunk":{"type":"reasoning-delta","index":0,"text":" me"}}}
|
||||
{"type":"assistant/chunk","seq":31,"time":1784959660675,"data":{"turn":1,"step":1,"chunk":{"type":"reasoning-delta","index":0,"text":" read"}}}
|
||||
{"type":"assistant/chunk","seq":32,"time":1784959660675,"data":{"turn":1,"step":1,"chunk":{"type":"reasoning-delta","index":0,"text":" both"}}}
|
||||
{"type":"assistant/chunk","seq":33,"time":1784959660704,"data":{"turn":1,"step":1,"chunk":{"type":"reasoning-delta","index":0,"text":" files"}}}
|
||||
{"type":"assistant/chunk","seq":34,"time":1784959660704,"data":{"turn":1,"step":1,"chunk":{"type":"reasoning-delta","index":0,"text":" simultaneously"}}}
|
||||
{"type":"assistant/chunk","seq":35,"time":1784959660704,"data":{"turn":1,"step":1,"chunk":{"type":"reasoning-delta","index":0,"text":"."}}}
|
||||
{"type":"assistant/chunk","seq":36,"time":1784959660782,"data":{"turn":1,"step":1,"chunk":{"type":"block-start","index":1,"blockType":"tool-call"}}}
|
||||
{"type":"assistant/chunk","seq":37,"time":1784959660782,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":1,"id":"call_00_PEK9aIA8oMzElfTpENlC4855","name":"read","argumentsDelta":""}}}
|
||||
{"type":"assistant/chunk","seq":38,"time":1784959660783,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":1,"id":"call_00_PEK9aIA8oMzElfTpENlC4855","name":"read","argumentsDelta":"{"}}}
|
||||
{"type":"assistant/chunk","seq":39,"time":1784959660783,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":1,"id":"call_00_PEK9aIA8oMzElfTpENlC4855","name":"read","argumentsDelta":"\""}}}
|
||||
{"type":"assistant/chunk","seq":40,"time":1784959660808,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":1,"id":"call_00_PEK9aIA8oMzElfTpENlC4855","name":"read","argumentsDelta":"file"}}}
|
||||
{"type":"assistant/chunk","seq":41,"time":1784959660809,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":1,"id":"call_00_PEK9aIA8oMzElfTpENlC4855","name":"read","argumentsDelta":"_path"}}}
|
||||
{"type":"assistant/chunk","seq":42,"time":1784959660809,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":1,"id":"call_00_PEK9aIA8oMzElfTpENlC4855","name":"read","argumentsDelta":"\""}}}
|
||||
{"type":"assistant/chunk","seq":43,"time":1784959660809,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":1,"id":"call_00_PEK9aIA8oMzElfTpENlC4855","name":"read","argumentsDelta":": "}}}
|
||||
{"type":"assistant/chunk","seq":44,"time":1784959660834,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":1,"id":"call_00_PEK9aIA8oMzElfTpENlC4855","name":"read","argumentsDelta":"\""}}}
|
||||
{"type":"assistant/chunk","seq":45,"time":1784959660835,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":1,"id":"call_00_PEK9aIA8oMzElfTpENlC4855","name":"read","argumentsDelta":"a"}}}
|
||||
{"type":"assistant/chunk","seq":46,"time":1784959660835,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":1,"id":"call_00_PEK9aIA8oMzElfTpENlC4855","name":"read","argumentsDelta":".txt"}}}
|
||||
{"type":"assistant/chunk","seq":47,"time":1784959660835,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":1,"id":"call_00_PEK9aIA8oMzElfTpENlC4855","name":"read","argumentsDelta":"\""}}}
|
||||
{"type":"assistant/chunk","seq":48,"time":1784959660869,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":1,"id":"call_00_PEK9aIA8oMzElfTpENlC4855","name":"read","argumentsDelta":"}"}}}
|
||||
{"type":"assistant/chunk","seq":49,"time":1784959660917,"data":{"turn":1,"step":1,"chunk":{"type":"block-start","index":2,"blockType":"tool-call"}}}
|
||||
{"type":"assistant/chunk","seq":50,"time":1784959660918,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":2,"id":"call_01_WPiM0aANEzq64FXGm8ra0024","name":"read","argumentsDelta":""}}}
|
||||
{"type":"assistant/chunk","seq":51,"time":1784959660944,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":2,"id":"call_01_WPiM0aANEzq64FXGm8ra0024","name":"read","argumentsDelta":"{"}}}
|
||||
{"type":"assistant/chunk","seq":52,"time":1784959660944,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":2,"id":"call_01_WPiM0aANEzq64FXGm8ra0024","name":"read","argumentsDelta":"\""}}}
|
||||
{"type":"assistant/chunk","seq":53,"time":1784959660944,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":2,"id":"call_01_WPiM0aANEzq64FXGm8ra0024","name":"read","argumentsDelta":"file"}}}
|
||||
{"type":"assistant/chunk","seq":54,"time":1784959660944,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":2,"id":"call_01_WPiM0aANEzq64FXGm8ra0024","name":"read","argumentsDelta":"_path"}}}
|
||||
{"type":"assistant/chunk","seq":55,"time":1784959660944,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":2,"id":"call_01_WPiM0aANEzq64FXGm8ra0024","name":"read","argumentsDelta":"\""}}}
|
||||
{"type":"assistant/chunk","seq":56,"time":1784959660945,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":2,"id":"call_01_WPiM0aANEzq64FXGm8ra0024","name":"read","argumentsDelta":": "}}}
|
||||
{"type":"assistant/chunk","seq":57,"time":1784959660970,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":2,"id":"call_01_WPiM0aANEzq64FXGm8ra0024","name":"read","argumentsDelta":"\""}}}
|
||||
{"type":"assistant/chunk","seq":58,"time":1784959660970,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":2,"id":"call_01_WPiM0aANEzq64FXGm8ra0024","name":"read","argumentsDelta":"b"}}}
|
||||
{"type":"assistant/chunk","seq":59,"time":1784959660970,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":2,"id":"call_01_WPiM0aANEzq64FXGm8ra0024","name":"read","argumentsDelta":".txt"}}}
|
||||
{"type":"assistant/chunk","seq":60,"time":1784959660970,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":2,"id":"call_01_WPiM0aANEzq64FXGm8ra0024","name":"read","argumentsDelta":"\""}}}
|
||||
{"type":"assistant/chunk","seq":61,"time":1784959660997,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":2,"id":"call_01_WPiM0aANEzq64FXGm8ra0024","name":"read","argumentsDelta":"}"}}}
|
||||
{"type":"assistant/chunk","seq":62,"time":1784959661054,"data":{"turn":1,"step":1,"chunk":{"type":"block-end","index":0,"block":{"type":"reasoning","text":"The user wants me to read two files (a.txt and b.txt) and then reply with \"DONE\". Let me read both files simultaneously."}}}}
|
||||
{"type":"assistant/chunk","seq":63,"time":1784959661054,"data":{"turn":1,"step":1,"chunk":{"type":"block-end","index":1,"block":{"type":"tool-call","id":"call_00_PEK9aIA8oMzElfTpENlC4855","name":"read","arguments":"{\"file_path\": \"a.txt\"}"}}}}
|
||||
{"type":"assistant/chunk","seq":64,"time":1784959661054,"data":{"turn":1,"step":1,"chunk":{"type":"block-end","index":2,"block":{"type":"tool-call","id":"call_01_WPiM0aANEzq64FXGm8ra0024","name":"read","arguments":"{\"file_path\": \"b.txt\"}"}}}}
|
||||
{"type":"assistant/chunk","seq":65,"time":1784959661054,"data":{"turn":1,"step":1,"chunk":{"type":"usage","usage":{"inputTokens":124,"outputTokens":106,"cacheReadTokens":7680,"reasoningTokens":30}}}}
|
||||
{"type":"assistant/chunk","seq":66,"time":1784959661054,"data":{"turn":1,"step":1,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}}
|
||||
{"type":"assistant/message","seq":67,"time":1784959661058,"data":{"turn":1,"step":1,"content":[{"type":"reasoning","text":"The user wants me to read two files (a.txt and b.txt) and then reply with \"DONE\". Let me read both files simultaneously."},{"type":"tool-call","id":"call_00_PEK9aIA8oMzElfTpENlC4855","name":"read","arguments":"{\"file_path\": \"a.txt\"}"},{"type":"tool-call","id":"call_01_WPiM0aANEzq64FXGm8ra0024","name":"read","arguments":"{\"file_path\": \"b.txt\"}"}],"provenance":{"provider":"deepseek","model":"deepseek-v4-flash"},"usage":{"inputTokens":124,"outputTokens":106,"cacheReadTokens":7680,"reasoningTokens":30}},"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],"surfaceOp":"append"}
|
||||
{"type":"tool/call","seq":68,"time":1784959661059,"data":{"turn":1,"step":1,"callId":"call_00_PEK9aIA8oMzElfTpENlC4855","name":"read","arguments":"{\"file_path\": \"a.txt\"}"}}
|
||||
{"type":"tool/call","seq":69,"time":1784959661061,"data":{"turn":1,"step":1,"callId":"call_01_WPiM0aANEzq64FXGm8ra0024","name":"read","arguments":"{\"file_path\": \"b.txt\"}"}}
|
||||
{"type":"tool/result","seq":70,"time":1784959661065,"data":{"turn":1,"step":1,"callId":"call_00_PEK9aIA8oMzElfTpENlC4855","content":[{"type":"text","text":"<path>{{cwd}}/a.txt</path>\n<type>file</type>\n<content>\n1: alpha\n\n(End of file - total 1 lines)\n</content>"}],"isError":false},"sourceEventSeqs":[68],"surfaceOp":"append"}
|
||||
{"type":"tool/result","seq":71,"time":1784959661066,"data":{"turn":1,"step":1,"callId":"call_01_WPiM0aANEzq64FXGm8ra0024","content":[{"type":"text","text":"<path>{{cwd}}/b.txt</path>\n<type>file</type>\n<content>\n1: beta\n\n(End of file - total 1 lines)\n</content>"}],"isError":false},"sourceEventSeqs":[69],"surfaceOp":"append"}
|
||||
{"type":"step/end","seq":72,"time":1784959661068,"data":{"turn":1,"step":1}}
|
||||
{"type":"step/start","seq":73,"time":1784959661069,"data":{"turn":1,"step":2}}
|
||||
{"type":"assistant/chunk","seq":74,"time":1784959661540,"data":{"turn":1,"step":2,"chunk":{"type":"block-start","index":0,"blockType":"reasoning"}}}
|
||||
{"type":"assistant/chunk","seq":75,"time":1784959661540,"data":{"turn":1,"step":2,"chunk":{"type":"reasoning-delta","index":0,"text":"Both"}}}
|
||||
{"type":"assistant/chunk","seq":76,"time":1784959661644,"data":{"turn":1,"step":2,"chunk":{"type":"reasoning-delta","index":0,"text":" files"}}}
|
||||
{"type":"assistant/chunk","seq":77,"time":1784959661671,"data":{"turn":1,"step":2,"chunk":{"type":"reasoning-delta","index":0,"text":" have"}}}
|
||||
{"type":"assistant/chunk","seq":78,"time":1784959661671,"data":{"turn":1,"step":2,"chunk":{"type":"reasoning-delta","index":0,"text":" been"}}}
|
||||
{"type":"assistant/chunk","seq":79,"time":1784959661672,"data":{"turn":1,"step":2,"chunk":{"type":"reasoning-delta","index":0,"text":" read"}}}
|
||||
{"type":"assistant/chunk","seq":80,"time":1784959661672,"data":{"turn":1,"step":2,"chunk":{"type":"reasoning-delta","index":0,"text":"."}}}
|
||||
{"type":"assistant/chunk","seq":81,"time":1784959661672,"data":{"turn":1,"step":2,"chunk":{"type":"reasoning-delta","index":0,"text":" a"}}}
|
||||
{"type":"assistant/chunk","seq":82,"time":1784959661698,"data":{"turn":1,"step":2,"chunk":{"type":"reasoning-delta","index":0,"text":".txt"}}}
|
||||
{"type":"assistant/chunk","seq":83,"time":1784959661699,"data":{"turn":1,"step":2,"chunk":{"type":"reasoning-delta","index":0,"text":" contains"}}}
|
||||
{"type":"assistant/chunk","seq":84,"time":1784959661699,"data":{"turn":1,"step":2,"chunk":{"type":"reasoning-delta","index":0,"text":" \""}}}
|
||||
{"type":"assistant/chunk","seq":85,"time":1784959661699,"data":{"turn":1,"step":2,"chunk":{"type":"reasoning-delta","index":0,"text":"alpha"}}}
|
||||
{"type":"assistant/chunk","seq":86,"time":1784959661699,"data":{"turn":1,"step":2,"chunk":{"type":"reasoning-delta","index":0,"text":"\""}}}
|
||||
{"type":"assistant/chunk","seq":87,"time":1784959661728,"data":{"turn":1,"step":2,"chunk":{"type":"reasoning-delta","index":0,"text":" and"}}}
|
||||
{"type":"assistant/chunk","seq":88,"time":1784959661729,"data":{"turn":1,"step":2,"chunk":{"type":"reasoning-delta","index":0,"text":" b"}}}
|
||||
{"type":"assistant/chunk","seq":89,"time":1784959661729,"data":{"turn":1,"step":2,"chunk":{"type":"reasoning-delta","index":0,"text":".txt"}}}
|
||||
{"type":"assistant/chunk","seq":90,"time":1784959661729,"data":{"turn":1,"step":2,"chunk":{"type":"reasoning-delta","index":0,"text":" contains"}}}
|
||||
{"type":"assistant/chunk","seq":91,"time":1784959661729,"data":{"turn":1,"step":2,"chunk":{"type":"reasoning-delta","index":0,"text":" \""}}}
|
||||
{"type":"assistant/chunk","seq":92,"time":1784959661729,"data":{"turn":1,"step":2,"chunk":{"type":"reasoning-delta","index":0,"text":"beta"}}}
|
||||
{"type":"assistant/chunk","seq":93,"time":1784959661753,"data":{"turn":1,"step":2,"chunk":{"type":"reasoning-delta","index":0,"text":"\"."}}}
|
||||
{"type":"assistant/chunk","seq":94,"time":1784959661753,"data":{"turn":1,"step":2,"chunk":{"type":"reasoning-delta","index":0,"text":" I"}}}
|
||||
{"type":"assistant/chunk","seq":95,"time":1784959661753,"data":{"turn":1,"step":2,"chunk":{"type":"reasoning-delta","index":0,"text":"'ll"}}}
|
||||
{"type":"assistant/chunk","seq":96,"time":1784959661753,"data":{"turn":1,"step":2,"chunk":{"type":"reasoning-delta","index":0,"text":" reply"}}}
|
||||
{"type":"assistant/chunk","seq":97,"time":1784959661780,"data":{"turn":1,"step":2,"chunk":{"type":"reasoning-delta","index":0,"text":" with"}}}
|
||||
{"type":"assistant/chunk","seq":98,"time":1784959661781,"data":{"turn":1,"step":2,"chunk":{"type":"reasoning-delta","index":0,"text":" \""}}}
|
||||
{"type":"assistant/chunk","seq":99,"time":1784959661781,"data":{"turn":1,"step":2,"chunk":{"type":"reasoning-delta","index":0,"text":"D"}}}
|
||||
{"type":"assistant/chunk","seq":100,"time":1784959661781,"data":{"turn":1,"step":2,"chunk":{"type":"reasoning-delta","index":0,"text":"ONE"}}}
|
||||
{"type":"assistant/chunk","seq":101,"time":1784959661781,"data":{"turn":1,"step":2,"chunk":{"type":"reasoning-delta","index":0,"text":"\""}}}
|
||||
{"type":"assistant/chunk","seq":102,"time":1784959661781,"data":{"turn":1,"step":2,"chunk":{"type":"reasoning-delta","index":0,"text":" as"}}}
|
||||
{"type":"assistant/chunk","seq":103,"time":1784959661808,"data":{"turn":1,"step":2,"chunk":{"type":"reasoning-delta","index":0,"text":" instructed"}}}
|
||||
{"type":"assistant/chunk","seq":104,"time":1784959661808,"data":{"turn":1,"step":2,"chunk":{"type":"reasoning-delta","index":0,"text":"."}}}
|
||||
{"type":"assistant/chunk","seq":105,"time":1784959661808,"data":{"turn":1,"step":2,"chunk":{"type":"block-start","index":1,"blockType":"text"}}}
|
||||
{"type":"assistant/chunk","seq":106,"time":1784959661809,"data":{"turn":1,"step":2,"chunk":{"type":"text-delta","index":1,"text":"D"}}}
|
||||
{"type":"assistant/chunk","seq":107,"time":1784959661809,"data":{"turn":1,"step":2,"chunk":{"type":"text-delta","index":1,"text":"ONE"}}}
|
||||
{"type":"assistant/chunk","seq":108,"time":1784959661809,"data":{"turn":1,"step":2,"chunk":{"type":"block-end","index":0,"block":{"type":"reasoning","text":"Both files have been read. a.txt contains \"alpha\" and b.txt contains \"beta\". I'll reply with \"DONE\" as instructed."}}}}
|
||||
{"type":"assistant/chunk","seq":109,"time":1784959661809,"data":{"turn":1,"step":2,"chunk":{"type":"block-end","index":1,"block":{"type":"text","text":"DONE"}}}}
|
||||
{"type":"assistant/chunk","seq":110,"time":1784959661809,"data":{"turn":1,"step":2,"chunk":{"type":"usage","usage":{"inputTokens":216,"outputTokens":33,"cacheReadTokens":7808,"reasoningTokens":30}}}}
|
||||
{"type":"assistant/chunk","seq":111,"time":1784959661809,"data":{"turn":1,"step":2,"chunk":{"type":"finish","reason":{"kind":"stop"}}}}
|
||||
{"type":"assistant/message","seq":112,"time":1784959661810,"data":{"turn":1,"step":2,"content":[{"type":"reasoning","text":"Both files have been read. a.txt contains \"alpha\" and b.txt contains \"beta\". I'll reply with \"DONE\" as instructed."},{"type":"text","text":"DONE"}],"provenance":{"provider":"deepseek","model":"deepseek-v4-flash"},"usage":{"inputTokens":216,"outputTokens":33,"cacheReadTokens":7808,"reasoningTokens":30}},"sourceEventSeqs":[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],"surfaceOp":"append"}
|
||||
{"type":"step/end","seq":113,"time":1784959661811,"data":{"turn":1,"step":2}}
|
||||
{"type":"turn/end","seq":114,"time":1784959661811,"data":{"turn":1,"reason":{"kind":"completed"}}}
|
||||
|
||||
@@ -7,20 +7,20 @@
|
||||
- tab "Trajectory"
|
||||
- tab "Waterfall"
|
||||
- text: "Use the read tool twice in one assistant message: read a.txt and b.txt. Then reply with the single word DONE and stop."
|
||||
- button "Think The user wants me to read a.txt and b.txt, then reply with DONE. Let me read both files.":
|
||||
- button "Think The user wants me to read two files (a.txt and b.txt) and then reply with \"DONE\". Let me read both files simultaneously.":
|
||||
- img
|
||||
- text: Think The user wants me to read a.txt and b.txt, then reply with DONE. Let me read both files.
|
||||
- text: Think The user wants me to read two files (a.txt and b.txt) and then reply with "DONE". Let me read both files simultaneously.
|
||||
- button:
|
||||
- img
|
||||
- text: Read a.txt
|
||||
- button:
|
||||
- img
|
||||
- text: Read b.txt
|
||||
- button "Think Both files have been read. a.txt contains \"alpha\" and b.txt contains \"beta\". Now I just need to reply with the single word DONE.":
|
||||
- button "Think Both files have been read. a.txt contains \"alpha\" and b.txt contains \"beta\". I'll reply with \"DONE\" as instructed.":
|
||||
- img
|
||||
- text: Think Both files have been read. a.txt contains "alpha" and b.txt contains "beta". Now I just need to reply with the single word DONE.
|
||||
- text: Think Both files have been read. a.txt contains "alpha" and b.txt contains "beta". I'll reply with "DONE" as instructed.
|
||||
- paragraph: DONE
|
||||
- text: cache hit 97% · 15,959 tokens · 1 turns · 2 steps
|
||||
- text: cache hit 98% · 15,967 tokens · 1 turns · 2 steps
|
||||
- textbox "输入消息,Enter 发送,Shift+Enter 换行"
|
||||
- button "添加":
|
||||
- img
|
||||
|
||||
@@ -16,10 +16,7 @@ export function requireDist(): void {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* OS-assigned free port, released before use. startWebServer echoes
|
||||
* options.port instead of the bound one, so passing 0 directly is unusable.
|
||||
*/
|
||||
/** OS-assigned free port, released before use (the spawned `dsh web` needs a concrete --port). */
|
||||
export function probeFreePort(): Promise<number> {
|
||||
return new Promise((resolvePort, reject) => {
|
||||
const probe = createServer()
|
||||
|
||||
@@ -30,9 +30,6 @@
|
||||
{
|
||||
"path": "../../packages/client/web"
|
||||
},
|
||||
{
|
||||
"path": "../../packages/host/webserver"
|
||||
},
|
||||
{
|
||||
"path": "../../packages/client/modules"
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ export default defineConfig({
|
||||
{ find: /^@deepseek-ai\/dsh-client-web-react$/, replacement: src('../../packages/client/web-react/src/index.ts') },
|
||||
{ find: /^@deepseek-ai\/dsh-client-ui-slots$/, replacement: src('../../packages/client/ui-slots/src/index.ts') },
|
||||
{ find: /^@deepseek-ai\/dsh-client-ui-primitives$/, replacement: src('../../packages/client/ui-primitives/src/index.ts') },
|
||||
{ find: /^@deepseek-ai\/dsh-client-modules$/, replacement: src('../../packages/client/modules/src/index.ts') },
|
||||
{ find: /^@deepseek-ai\/dsh-client-modules\/client$/, replacement: src('../../packages/client/modules/src/client/index.ts') },
|
||||
],
|
||||
},
|
||||
define: {
|
||||
|
||||
Reference in New Issue
Block a user