refactor: apply repository naming contract

Apply the accepted pre-release package, service, type, directory, and role renames as one repository-wide change.
This commit is contained in:
Tianyi Cui
2026-08-13 00:36:22 +08:00
parent 101df7cf58
commit a2d0f7f411
3281 changed files with 21730 additions and 21592 deletions

View File

@@ -1,5 +1,5 @@
{
"name": "@deepseek-ai/dsh-frontend",
"name": "@deepseek-ai/dsh-web-frontend",
"description": "Web application entry: vite build over the @deepseek-ai/dsh-client-web shell library; dist/ served by apps/cli's dsh web",
"version": "0.0.1-rc.2",
"publishConfig": {

View File

@@ -34,7 +34,7 @@ const HEADER_EXPECTED = join(SNAPSHOT_DIR, 'header.expected.md')
const SHIPPED_PRESETS = fileURLToPath(new URL('../../cli/config/agent-presets', import.meta.url))
const MODE = webSnapshotMode()
const SEED_ID = 'agent-preset-selection-web-e2e'
/** A project skill only a preset that mounts `skill-local` can discover. */
/** A project skill only a preset that mounts `skill-filesystem` can discover. */
const SKILL_NAME = 'preset-catalog-demo'
/**

View File

@@ -43,7 +43,7 @@ const PLUGINS: readonly (WebBootEntry & { bundlePath: string })[] = [
'@deepseek-ai/dsh-client-ui-sidebar',
],
},
{ id: '@deepseek-ai/dsh-session-export', bundlePath: 'packages/session-query/session-export/lib/client.js', url: '/plugins/session-export.js', rev: 'fx', inject: ['@deepseek-ai/dsh-client-ui-command', '@deepseek-ai/dsh-client-ui-conversation'] },
{ id: '@deepseek-ai/dsh-session-log-download', bundlePath: 'packages/session-query/session-log-download/lib/client.js', url: '/plugins/session-log-download.js', rev: 'fx', inject: ['@deepseek-ai/dsh-client-ui-commands', '@deepseek-ai/dsh-client-ui-conversation'] },
{ id: '@deepseek-ai/dsh-client-ui-trajectory', bundlePath: 'packages/client/ui-trajectory/lib/client.js', url: '/plugins/ui-trajectory.js', rev: 'fx', inject: ['@deepseek-ai/dsh-client-ui-conversation'] },
]

View File

@@ -1,8 +1,8 @@
// Web e2e scenario: the session-header background-task list over the real
// Web e2e scenario: the session-header background-job list over the real
// host. No model call is involved — a genuine `run_in_background` bash call
// registers with `ctx.tasks`, and the assertion chain is the whole delivery
// path: registry change feed → api-proxy `session/tasks` frame → the client's
// `tasksBySession` mirror → the header action.
// registers with `ctx.jobs`, and the assertion chain is the whole delivery
// path: registry change feed → api-proxy `session/jobs` frame → the client's
// `jobsBySession` mirror → the header action.
import { readFile } from 'node:fs/promises'
import { fileURLToPath } from 'node:url'
import { join } from 'node:path'
@@ -12,7 +12,7 @@ import { afterAll, beforeAll, describe, expect, it, onTestFailed } from 'vitest'
import type { Agent } from '@deepseek-ai/dsh-agent'
import { CallId } from '@deepseek-ai/dsh-llm'
import { SessionId } from '@deepseek-ai/dsh-session'
import { TaskId } from '@deepseek-ai/dsh-tasks'
import { JobId } from '@deepseek-ai/dsh-jobs'
import {
assertFixtureInventory, captureStableAria, compareOrRefreshGolden,
launchWebScaffold, seedSession, watchConsole, webSnapshotMode, type WebScaffold,
@@ -20,11 +20,11 @@ import {
import { newEnglishPage, saveFailureShot } from './support.ts'
const FIXTURE = fileURLToPath(new URL('./snapshots/fresh-round-trip/session.jsonl', import.meta.url))
const SNAPSHOT_DIR = fileURLToPath(new URL('./snapshots/background-task-list', import.meta.url))
const SNAPSHOT_DIR = fileURLToPath(new URL('./snapshots/background-job-list', import.meta.url))
const RUNNING_EXPECTED = join(SNAPSHOT_DIR, 'running.expected.md')
const SETTLED_EXPECTED = join(SNAPSHOT_DIR, 'settled.expected.md')
const MODE = webSnapshotMode()
const SEED_ID = 'background-task-list-web-e2e'
const SEED_ID = 'background-job-list-web-e2e'
// Long enough that the running assertions never race the process exiting on
// their own; the test kills it explicitly to reach the settled state.
const COMMAND = 'sleep 45'
@@ -45,13 +45,13 @@ async function liveAgent(scaffold: WebScaffold, sessionId: SessionId): Promise<A
}
}
describe.skipIf(MODE === 'record')('web e2e: background task list', () => {
describe.skipIf(MODE === 'record')('web e2e: background job list', () => {
let scaffold: WebScaffold
let browser: Browser
let page: Page
let tripwire: ReturnType<typeof watchConsole>
let agent: Agent
let taskId: TaskId
let jobId: JobId
beforeAll(async () => {
scaffold = await launchWebScaffold({})
@@ -70,7 +70,7 @@ describe.skipIf(MODE === 'record')('web e2e: background task list', () => {
await sessionRow.click()
// Opening the session drives the Host's ordinary Agent resolution; the
// task owner must be that exact live instance, never a second one.
// job owner must be that exact live instance, never a second one.
// `expect.poll` is test-scoped, so this hook polls by hand.
agent = await liveAgent(scaffold, SessionId(SEED_ID))
}, 120_000)
@@ -80,29 +80,29 @@ describe.skipIf(MODE === 'record')('web e2e: background task list', () => {
await scaffold?.close()
})
it('shows a running background task in the session header without a refresh', async () => {
onTestFailed(() => saveFailureShot(page, 'web-e2e-background-task-running'))
it('shows a running background job in the session header without a refresh', async () => {
onTestFailed(() => saveFailureShot(page, 'web-e2e-background-job-running'))
// Point assertion, not a poll: `expect.poll` retries until a predicate
// holds, so polling for zero passes at t=0 and proves nothing. The
// "renders nothing without a task" branch is owned by the component suite.
const trigger = page.getByRole('button', { name: '1 background task running' })
const trigger = page.getByRole('button', { name: '1 background job running' })
expect(await trigger.count()).toBe(0)
const started = await scaffold.ctx.tools.execute({
signal: new AbortController().signal,
callId: CallId('background-task-list-e2e'),
callId: CallId('background-job-list-e2e'),
name: 'bash',
arguments: { command: COMMAND, description: 'Hold a background slot open', run_in_background: true },
agent,
})
const reported = started.content.map(block => block.type === 'text' ? block.text : '').join('')
const matched = /\bbash-\d+\b/.exec(reported)
if (matched === null) throw new Error(`background bash reported no task id: ${reported}`)
taskId = TaskId(matched[0])
if (matched === null) throw new Error(`background bash reported no job id: ${reported}`)
jobId = JobId(matched[0])
await trigger.waitFor({ timeout: 15_000 })
await trigger.click()
const row = page.getByRole('list', { name: 'Background tasks' }).getByRole('listitem').first()
const row = page.getByRole('list', { name: 'Background jobs' }).getByRole('listitem').first()
await row.waitFor({ timeout: 10_000 })
await expect.poll(() => row.textContent()).toContain(COMMAND)
@@ -113,12 +113,12 @@ describe.skipIf(MODE === 'record')('web e2e: background task list', () => {
}, 60_000)
it('flips the open list to the cancelled outcome when the registry settles it', async () => {
onTestFailed(() => saveFailureShot(page, 'web-e2e-background-task-settled'))
expect(scaffold.ctx.tasks.kill(taskId, agent, 'web e2e cancellation')).toBe('requested')
onTestFailed(() => saveFailureShot(page, 'web-e2e-background-job-settled'))
expect(scaffold.ctx.jobs.kill(jobId, agent, 'web e2e cancellation')).toBe('requested')
// The trigger drops its live count once the task leaves running/stopping,
// which is also the proof that settlement reached the browser unprompted.
const idle = page.getByRole('button', { name: '1 background task' })
const idle = page.getByRole('button', { name: '1 background job' })
await idle.waitFor({ timeout: 20_000 })
const snapshot = await captureStableAria(page, '[class*="menu"]', scaffold.workspaceCwd)

View File

@@ -268,7 +268,7 @@ async function openSeed(page: Page, fixture: ChatScrollFixture, tailMarker?: str
if (await searchButton.getAttribute('aria-expanded') !== 'true') await searchButton.click()
const search = page.getByRole('textbox', { name: 'Search sessions...', exact: true })
// Cold summaries initially show the temporary workspace basename, so the
// persisted first-message marker is the stable user-facing identity. The
// persisted first-prompt marker is the stable user-facing identity. The
// query itself triggers lazy content-index reconciliation; no transient
// empty-state paint is used as a barrier.
await search.fill(fixture.markers.user(1))

View File

@@ -39,11 +39,11 @@ const PACKAGE_FILES: Readonly<Record<string, string>> = {
'packages/llm/token-meter/package.json': '{"name":"@deepseek-ai/dsh-token-meter"}\n',
'packages/llm/token-meter/src/index.ts': 'export {}\n',
'packages/llm/token-meter/tests/token-meter.spec.ts': 'export {}\n',
'packages/skill/skill-local/README.md': '# Local skill provider\n',
'packages/skill/skill-local/package.json': '{"name":"@deepseek-ai/dsh-skill-local"}\n',
'packages/skill/skill-local/src/index.ts': 'export {}\n',
'packages/skill/skill-local/src/invariant.ts': 'export {}\n',
'packages/skill/skill-local/tests/skill-local.spec.ts': 'export {}\n',
'packages/skill/skill-filesystem/README.md': '# Local skill provider\n',
'packages/skill/skill-filesystem/package.json': '{"name":"@deepseek-ai/dsh-skill-filesystem"}\n',
'packages/skill/skill-filesystem/src/index.ts': 'export {}\n',
'packages/skill/skill-filesystem/src/invariant.ts': 'export {}\n',
'packages/skill/skill-filesystem/tests/skill-filesystem.spec.ts': 'export {}\n',
}
/** Materialize a stable package inventory inside the isolated session workspace. */

View File

@@ -8,7 +8,7 @@ import { chromium } from 'playwright'
import { expect, it } from 'vitest'
import { Context } from '@deepseek-ai/cordis'
import type { Fiber } from '@deepseek-ai/cordis'
import LocalSubprocessService from '@deepseek-ai/dsh-subprocess-local'
import LocalSubprocessRuntime from '@deepseek-ai/dsh-subprocess-local'
import type { SubprocessHandle, SubprocessSpawnSpec } from '@deepseek-ai/dsh-subprocess'
import { REPO_ROOT } from './support.ts'
@@ -88,7 +88,7 @@ it('hot-reloads a real client-plugin source edit without refreshing the page', a
let browser: Awaited<ReturnType<typeof chromium.launch>> | undefined
const failures: unknown[] = []
try {
subprocessFiber = await subprocessCtx.plugin(LocalSubprocessService)
subprocessFiber = await subprocessCtx.plugin(LocalSubprocessRuntime)
watcher = subprocessCtx.subprocess.spawn(spawnSpec(['pnpm', 'run', 'dev:web'], REPO_ROOT))
await waitForOutput(watcher, /dev-web: watching/, 'pnpm run dev:web')
host = subprocessCtx.subprocess.spawn(spawnSpec(

View File

@@ -238,7 +238,7 @@ describe('web e2e: lifecycle & chrome (workspace flow / reload / dark mode)', ()
it.skipIf(MODE === 'record')('cascades the dark theme from the body attribute to painted surfaces', async () => {
onTestFailed(() => saveFailureShot(page, 'web-e2e-lifecycle-dark'))
// This scenario pins the ThemeService's DOM contract directly (the
// This scenario pins the ThemeRuntime's DOM contract directly (the
// body[data-ds-dark-theme] attribute -> stylesheet cascade); the REAL
// user gesture above it (Settings -> Appearance cubes) is owned by
// settings-chrome.e2e.ts. Driving the attribute here keeps the cascade

View File

@@ -58,7 +58,7 @@ describe('minimal agent preset', () => {
const presetFileSystem = scaffold.ctx.agentPresets.serviceFor(agentHandle.agent, 'fs')
expect(presetFileSystem).toBeDefined()
expect(presetFileSystem?.sandboxMode).toBeUndefined()
expect(scaffold.ctx.agentPresets.serviceFor(agentHandle.agent, 'compact')).toBeUndefined()
expect(scaffold.ctx.agentPresets.serviceFor(agentHandle.agent, 'compaction')).toBeUndefined()
const stateDir = join(scaffold.workspaceCwd, 'persistent-state')
await mkdir(stateDir)

View File

@@ -9,5 +9,5 @@
- insert:
- id: directory-picker-browse
name: '@deepseek-ai/dsh-host-directory-picker-browse'
- id: ui-directory-picker
name: '@deepseek-ai/dsh-client-ui-directory-picker'
- id: ui-directory-picker-browse
name: '@deepseek-ai/dsh-client-ui-directory-picker-browse'

View File

@@ -11,7 +11,7 @@
name: '@deepseek-ai/dsh-bash-sandbox'
disabled: true
- id: permission
name: '@deepseek-ai/dsh-permission'
name: '@deepseek-ai/dsh-permission-presets'
disabled: true
- insert:
- id: pwsh-local

View File

@@ -1,5 +1,5 @@
// Web e2e scenario: the resident question composer. The shipped composition
// already exposes ask_user_question (the ui-question row's node half mounts
// already exposes ask_user_question (the ui-user-questions row's node half mounts
// the tool), so a recorded turn where the model asks blocks mid-turn on the
// real userInteraction seam: the composer renders in the browser, the test
// answers through it, and the turn completes with the answer in the log.

View File

@@ -14,7 +14,7 @@
// Composition divergences from `dsh web`, all deliberate, all via include
// patches after the shipped bundle layers, over the SAME tree (never a
// second yml): temp persistenceRoot; host-level skill roots confined to the
// temp workspace while project skill discovery remains real; workspace-context
// temp workspace while project skill discovery remains real; agent-instructions
// 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
@@ -40,7 +40,7 @@ import {
healProfilesModuleFallback,
loadOverlayPatches,
} from '@deepseek-ai/dsh-app-boot'
import { dshHomePath } from '@deepseek-ai/dsh-paths'
import { dshHomePath } from '@deepseek-ai/dsh-home-paths'
// Client packages must not be imported here: these e2e type-check in the Host
// aggregate, so a Client import pulls that package's whole project — and every
// project it references — into the Host build graph. Mirrored from
@@ -69,9 +69,9 @@ import SessionStore, {
type SessionEvent,
type SessionHeader,
} from '@deepseek-ai/dsh-session'
import SessionPersistenceJsonl from '@deepseek-ai/dsh-session-persistence-jsonl'
import JsonlSessionPersistence from '@deepseek-ai/dsh-session-persistence-jsonl'
import * as ToolCordis from '@deepseek-ai/dsh-tool-cordis'
// Empty type imports carry the httpServer/agents/sessionPersistence Context merges.
// Empty type imports carry the webServer/agents/sessionPersistence Context merges.
import type {} from '@deepseek-ai/dsh-host-webserver'
import type {} from '@deepseek-ai/dsh-agent'
import { provideCmdline } from '@deepseek-ai/dsh-cmdline'
@@ -101,7 +101,7 @@ const SHIPPED_PRESET_DIR = join(REPO_ROOT, 'apps/cli/config/agent-presets')
// 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 resolveModelInfo unroutable and compact-basic's
// catch-all would leave resolveModelInfo unroutable and compaction-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 = [{
@@ -166,7 +166,7 @@ export interface WebScaffold {
baseUrl: string
/** Settled root context (the in-process readiness barrier; headless event subscription is its sanctioned use). */
ctx: Context
/** Temp project directory sessions run in (bash/fs tool cwd). */
/** Temp project directory sessions run in (shell/fs tool cwd). */
workspaceCwd: string
/** Temp persistence root (seeded sessions land here through the real API). */
persistenceRoot: string
@@ -402,7 +402,7 @@ export async function launchWebScaffold(options: LaunchOptions = {}): Promise<We
// cannot change replay requests or conversation goldens. Project roots stay
// enabled against the same empty temp workspace, preserving the real seam.
{
id: 'skill-local',
id: 'skill-filesystem',
config: {
dshHome: join(workspaceCwd, '.dsh-home'),
agentsHome: join(workspaceCwd, '.agents-home'),
@@ -413,16 +413,16 @@ export async function launchWebScaffold(options: LaunchOptions = {}): Promise<We
// 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: 'agent-instructions', disabled: true },
{ id: 'session-title-llm', disabled: true },
// Fixture sessions must never leave the process: the shipped row defaults
// to the production OTLP endpoint (or whatever DSH_TELEMETRY_OTLP_URL
// names in the ambient environment). A scenario that pins a real backend
// disclosure passes a local dead endpoint instead of disabling the row.
options.telemetryUrl === undefined
? { id: 'telemetry-otel', disabled: true }
? { id: 'session-telemetry-otel', disabled: true }
: {
id: 'telemetry-otel',
id: 'session-telemetry-otel',
config: {
mode: 'FULL',
exporter: { url: options.telemetryUrl },
@@ -434,7 +434,7 @@ export async function launchWebScaffold(options: LaunchOptions = {}): Promise<We
config: { host: '127.0.0.1', port: 0 },
},
// The bundle's web-runtime row resolves the same built dist under test
// (apps/web IS @deepseek-ai/dsh-frontend); only the URL line is silenced.
// (apps/web IS @deepseek-ai/dsh-web-frontend); only the URL line is silenced.
// Preserve the composed surface-context choice because a patch replaces
// the row's complete config.
{ id: 'web-runtime', config: { printUrl: false, surfaceContext } },
@@ -452,7 +452,7 @@ export async function launchWebScaffold(options: LaunchOptions = {}): Promise<We
{ id: 'directory-picker', disabled: true },
{ insert: [
{ id: 'directory-picker-browse', name: '@deepseek-ai/dsh-host-directory-picker-browse' },
{ id: 'ui-directory-picker', name: '@deepseek-ai/dsh-client-ui-directory-picker' },
{ id: 'ui-directory-picker-browse', name: '@deepseek-ai/dsh-client-ui-directory-picker-browse' },
] },
...options.agentPresets === undefined
? []
@@ -527,9 +527,9 @@ export async function launchWebScaffold(options: LaunchOptions = {}): Promise<We
op: 'set', path: [WELCOME_NOTICE_ACK_FIELD], value: WELCOME_NOTICE_VERSION,
}])
}
const boundPort = ctx.get('httpServer')?.port
const boundPort = ctx.get('webServer')?.port
if (boundPort === undefined) {
throw new Error('web e2e scaffold: httpServer service missing after settled boot')
throw new Error('web e2e scaffold: webServer service missing after settled boot')
}
port = boundPort
@@ -725,7 +725,7 @@ export async function seedSession(
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.plugin(JsonlSessionPersistence, { root: scaffold.persistenceRoot })
await seeder.sessionPersistence.create(meta)
await seeder.sessionPersistence.append(meta.id, events)
// Deterministic sidebar order: cold summaries take updatedAt from mtime.

View File

@@ -15,7 +15,7 @@ import {
foldScheduleEvents,
resolveEveryOccurrence,
type EveryScheduleRecord,
} from '@deepseek-ai/dsh-tool-schedule'
} from '@deepseek-ai/dsh-schedule'
import {
assertFixtureInventory,
captureStableAria,
@@ -167,7 +167,7 @@ function requestText(options: GenerateOptions): string {
/** Require one assembled request to preserve the reminder-content trust boundary. */
function expectReminderFraming(options: GenerateOptions): void {
const reminder = options.messages.find(message => (
message.source.kind === 'plugin' && message.source.plugin === 'tool-schedule'
message.source.kind === 'plugin' && message.source.plugin === 'schedule'
))
expect(reminder?.role).toBe('user')
const text = reminder?.content.find(block => block.type === 'text')?.text
@@ -242,7 +242,7 @@ describe.skipIf(MODE === 'record')('web e2e: conversational reminders', () => {
.toBe(AT_BROWSER_ZONE)
const cwd = join(scaffold.workspaceCwd, 'workspace')
const workspace = await scaffold.ctx.workspace.resolveByPath(cwd)
const workspace = await scaffold.ctx.workspaceRegistry.resolveByPath(cwd)
if (workspace === undefined) throw new Error('connected Web workspace was not registered')
afterHandle = await scaffold.ctx.agents.create({
@@ -421,7 +421,7 @@ describe.skipIf(MODE === 'record')('web e2e: conversational reminders', () => {
const batch = everyHandle.agent.session.events.find(event => (
event.type === 'user/message'
&& event.data.source.kind === 'plugin'
&& event.data.source.plugin === 'tool-schedule'
&& event.data.source.plugin === 'schedule'
&& event.data.content.some(block => block.type === 'text'
&& block.text.startsWith('[SCHEDULE REMINDER BATCH]'))
))

View File

@@ -19,7 +19,7 @@ import { createUserMessage } from '@deepseek-ai/dsh-llm'
import type { ContentBlock, Message } from '@deepseek-ai/dsh-llm'
import { deriveEventMessage, SessionId } from '@deepseek-ai/dsh-session'
import type { SessionEvent } from '@deepseek-ai/dsh-session'
import type { TokenMeterService } from '@deepseek-ai/dsh-token-meter'
import type { TokenMeter } from '@deepseek-ai/dsh-token-meter'
import { join } from 'node:path'
import {
assertFixtureInventory, captureStableAria, compareOrRefreshGolden, fixtureUserPrompts,
@@ -46,13 +46,13 @@ const PROMPT = 'Use the read tool twice in one assistant message: read a.txt and
* presentation through the real host and browser.
* @param raw - the seed fixture text, already realized (placeholder-free) so
* the shadow price below is computed from the exact strings the host folds.
* @param meter - the composed token meter; the appended `compact/summary`'s
* @param meter - the composed token meter; the appended `compaction/summary`'s
* shadow price must be the exact heuristic price of the shadowed nodes, the
* way compact-basic derives it, because the token-meter projections subtract
* way compaction-basic derives it, because the token-meter projections subtract
* it verbatim.
* @returns the fixture with a manual compaction lifecycle appended.
*/
function withCompaction(raw: string, meter: TokenMeterService): string {
function withCompaction(raw: string, meter: TokenMeter): string {
const lines = raw.trimEnd().split('\n')
const events = lines.slice(1).map(line => JSON.parse(line) as {
type: string
@@ -96,7 +96,7 @@ function withCompaction(raw: string, meter: TokenMeterService): string {
data: { commandId, name: 'compact', args: '', source: { kind: 'user' } },
})
const startSeq = at({
type: 'compact/start',
type: 'compaction/start',
data: { compactionId, sourceCommandId: commandId, turn: null },
})
// Load-bearing exactness: the projections subtract this count verbatim, so
@@ -125,7 +125,7 @@ function withCompaction(raw: string, meter: TokenMeterService): string {
return total + priceRow(event)
}, 0)
const summarySeq = at({
type: 'compact/summary',
type: 'compaction/summary',
data: {
compactionId,
sourceCommandId: commandId,
@@ -155,7 +155,7 @@ function withCompaction(raw: string, meter: TokenMeterService): string {
sourceEventSeqs: [startSeq, summarySeq, ...surfaceSeqs],
})
at({
type: 'compact/end',
type: 'compaction/end',
data: { compactionId, sourceCommandId: commandId, turn: null },
})
at({
@@ -302,7 +302,7 @@ describe('web e2e: seeded history renders through cold resume', () => {
+ '\n</system-reminder>',
}],
source: {
kind: 'workspace-instructions',
kind: 'agent-instructions',
form: 'instructions',
baseline: true,
changes: [{
@@ -463,9 +463,9 @@ describe('web e2e: seeded history renders through cold resume', () => {
if (done?.type !== 'command/done') throw new Error('feedback command did not settle')
const [sessionLine, userLine, extraLine] = done.data.text?.split('\n') ?? []
expect(sessionLine).toBe(`Feedback recorded for session ${SEED_ID}`)
expect(userLine).toMatch(/^User: [0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}\./i)
expect(userLine).toMatch(/^Anonymous user: [0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}\./i)
expect(extraLine).toBeUndefined()
const userId = userLine?.match(/^User: ([0-9a-f-]+)/i)?.[1]
const userId = userLine?.match(/^Anonymous user: ([0-9a-f-]+)/i)?.[1]
if (userId === undefined) throw new Error('feedback command omitted the user id')
const snapshot = (await captureStableAria(page, '[class*="centerCol"]', scaffold.workspaceCwd))

View File

@@ -1,6 +1,6 @@
// Web e2e scenarios: the settings surface — the modal shell (trigger, nav,
// section switching, both close paths), the Appearance preference row (the
// real theme gesture — click 深色 and the whole cascade runs: ThemeService preference -> Host settings
// real theme gesture — click 深色 and the whole cascade runs: ThemeRuntime preference -> Host settings
// -> theme/change -> ui-layout's presenter -> body attribute -> alias token +
// browser theme-color metadata)
// the Language row and busy-state Enter preference (both Host-backed), plus

View File

@@ -14,7 +14,7 @@ import { SessionId } from '@deepseek-ai/dsh-session'
import type {} from '@deepseek-ai/dsh-tools'
import type {} from '@deepseek-ai/dsh-sandbox-policy'
import type {} from '@deepseek-ai/dsh-user-approval'
import type {} from '@deepseek-ai/dsh-permission'
import type {} from '@deepseek-ai/dsh-permission-presets'
import type {} from '@deepseek-ai/dsh-agent-presets'
import type {} from '@deepseek-ai/dsh-commands'
import type {} from '@deepseek-ai/dsh-system-prompt'
@@ -29,7 +29,7 @@ const FILE_REFERENCE_PROMPT = fileURLToPath(new URL(
* ripgrep-dependent pair below. The absences are deliberate, not incidental
* gaps: the `cordis_*` toolset executes model-written JavaScript that no
* sandbox row confines, `web_fetch` chooses its own request target, and
* `mcp_*` servers spawn outside `ctx.bash`. The composition Agent Note owns the
* `mcp_*` servers spawn outside `ctx.shell`. The composition Agent Note owns the
* rationale and its sources.
*/
const EXPECTED_TOOLS = [
@@ -40,6 +40,9 @@ const EXPECTED_TOOLS = [
'exit_plan_mode',
'get_goal',
'interrupt_agent',
'job_kill',
'job_list',
'job_output',
'list_agents',
'ralph',
'read',
@@ -48,9 +51,6 @@ const EXPECTED_TOOLS = [
'skill',
'subagent',
'subagent_fork',
'task_kill',
'task_list',
'task_output',
'todo_write',
'update_goal',
'web_search',
@@ -108,7 +108,7 @@ it('assembles the shipped Web catalog, file-reference guidance, and confined acc
)
expect(scaffold.ctx.sandboxPolicy.defaultMode).toBe('workspace-write')
expect(scaffold.ctx.approval.config.policy).toBe('ask')
expect(scaffold.ctx.permission.defaultPreset).toBe('workspace-write')
expect(scaffold.ctx.permissionPresets.defaultPreset).toBe('workspace-write')
const commandHandle = await scaffold.ctx.agents.create({
sessionId: SessionId('shipped-command-catalog'),
@@ -126,11 +126,11 @@ it('assembles the shipped Web catalog, file-reference guidance, and confined acc
}
}, 120_000)
it('lets a preset producer reach the background-task registry', async () => {
it('lets a preset producer reach the background-job registry', async () => {
scaffold = await launchWebScaffold()
const ctx = scaffold.ctx
const handle = await ctx.agents.create({
sessionId: SessionId('shipped-background-task'),
sessionId: SessionId('shipped-background-job'),
meta: { cwd: scaffold.workspaceCwd },
setup: agentCtx => ctx.agentPresets.mount(agentCtx).then(() => undefined),
})
@@ -152,7 +152,7 @@ it('lets a preset producer reach the background-task registry', async () => {
})
expect({ isError: started.isError, content: started.content }).toEqual({
isError: false,
content: [{ type: 'text', text: 'started background task bash-1' }],
content: [{ type: 'text', text: 'started background job bash-1' }],
})
// The controller reads what the producer started: same registry, one
@@ -160,7 +160,7 @@ it('lets a preset producer reach the background-task registry', async () => {
const listed = await ctx.tools.execute({
signal,
callId: CallId('shipped-task-list'),
name: 'task_list',
name: 'job_list',
arguments: {},
agent: handle.agent,
})
@@ -174,8 +174,8 @@ it('lets a preset producer reach the background-task registry', async () => {
const collected = await ctx.tools.execute({
signal,
callId: CallId('shipped-task-output'),
name: 'task_output',
arguments: { task_id: 'bash-1', wait: true },
name: 'job_output',
arguments: { job_id: 'bash-1', wait: true },
agent: handle.agent,
})
expect(collected.isError).toBe(false)

View File

@@ -92,7 +92,7 @@ describe('web e2e: sidebar subagent activity', () => {
agentOptions: { provider: HOLD_PROVIDER, model: HOLD_MODEL },
})
parentHandle.agent.followup(createUserMessage({
content: [{ type: 'text', text: 'Delegate a background task.' }],
content: [{ type: 'text', text: 'Delegate a background job.' }],
source: { kind: 'user' },
}))
await parentHandle.agent.whenIdle()
@@ -114,7 +114,7 @@ describe('web e2e: sidebar subagent activity', () => {
await page.goto(scaffold.baseUrl, { waitUntil: 'load' })
await page.waitForSelector('[class*="frame"]', { timeout: 30_000 })
await connectFreshWorkspace(page, scaffold.workspaceCwd)
const workspace = await scaffold.ctx.workspace.resolveByPath(cwd)
const workspace = await scaffold.ctx.workspaceRegistry.resolveByPath(cwd)
if (workspace === undefined) throw new Error('connected Web workspace was not registered')
await workspace.attachSession(parentHandle.agent.session.id)
}, 60_000)
@@ -136,7 +136,7 @@ describe('web e2e: sidebar subagent activity', () => {
it('pins a running descendant on its visible idle owner row', async () => {
onTestFailed(() => saveFailureShot(page, 'web-e2e-sidebar-subagent-activity'))
const sidebar = page.getByRole('tree', { name: 'Sessions' })
const ownerRow = sidebar.getByRole('treeitem', { name: /1 subagent running Delegate a background task/ })
const ownerRow = sidebar.getByRole('treeitem', { name: /1 subagent running Delegate a background job/ })
await ownerRow.waitFor({ timeout: 10_000 })
expect(parentHandle.agent.status).toBe('idle')
await compareOrRefreshGolden(

View File

@@ -143,8 +143,8 @@ async function detailsTrack(page: Page): Promise<number> {
// the frame never appears.
const UI_PLUGIN_DIRS = [
'connection', 'runtime', 'ui-theme', 'locale', 'ui-layout', 'ui-sidebar',
'ui-settings', 'ui-settings-general', 'ui-models', 'ui-conversation',
'ui-model', 'ui-question', 'ui-trajectory', '../session-query/session-export',
'ui-settings', 'ui-settings-general', 'ui-settings-models', 'ui-conversation',
'ui-model-selection', 'ui-user-questions', 'ui-trajectory', '../session-query/session-log-download',
]
const ROUND_DONE_MARKER = 'WEB_ROUND_DONE'
const notReady = UI_PLUGIN_DIRS.filter((dir) => {

View File

@@ -1,2 +1,2 @@
- list "Background tasks":
- list "Background jobs":
- listitem: bash sleep 45 running {{duration}}

View File

@@ -1,2 +1,2 @@
- list "Background tasks":
- list "Background jobs":
- listitem: "bash sleep 45 signal: SIGTERM {{duration}}"

View File

@@ -30,10 +30,10 @@
- button "Branch into a new conversation":
- img
- text: {{clock}} Ran for {{duration}} TTFT {{duration}} {{throughput}} tok/s
- 'button "feedback Feedback recorded for session session-{{uuid}} User: {{uuid}}. Session sharing is enabled."':
- 'button "feedback Feedback recorded for session session-{{uuid}} Anonymous user: {{uuid}}. Session sharing is enabled."':
- img
- img
- text: "feedback Feedback recorded for session session-{{uuid}} User: {{uuid}}. Session sharing is enabled."
- text: "feedback Feedback recorded for session session-{{uuid}} Anonymous user: {{uuid}}. Session sharing is enabled."
- textbox "Message the agent"
- button "Commands":
- img

View File

@@ -1,7 +1,7 @@
You are an AI agent powered by the DeepSeek Harness SDK.
You are an AI agent powered by DeepSeek Harness.
The DeepSeek Harness implementation checkout is at {{sourceRoot}}. The checkout location and current working directory are separate values and may differ; never infer the working directory from this path. Use pwd to determine the current working directory. Use this checkout only to inspect or extend DSH itself.
You are interacting with the user through the DeepSeek Harness Web GUI at {{webUrl}}. When the user refers to "this page", "this GUI", or "this app" without naming another target, they mean this GUI. The browser provides no implicit DOM, route, or screenshot context. The client-plugin HMR receiver is active, but client-plugin changes reload without a refresh only while `pnpm run dev:web` is also running from this same checkout to rebuild their bundles; verify that watcher before promising automatic updates. Every other change — the apps/web shell and plain packages — requires rebuilding the affected Web artifacts and verifying this existing URL after a page refresh. Starting another server does not update this GUI. The apps/web Vite entry builds the shell but is not a standalone application because only dsh web injects window.__DSH_BOOT__. Do not start a replacement server unless the user asks; if one is needed, use a managed background task and verify its exact URL.
You are interacting with the user through the DeepSeek Harness Web GUI at {{webUrl}}. When the user refers to "this page", "this GUI", or "this app" without naming another target, they mean this GUI. The browser provides no implicit DOM, route, or screenshot context. The client-plugin HMR receiver is active, but client-plugin changes reload without a refresh only while `pnpm run dev:web` is also running from this same checkout to rebuild their bundles; verify that watcher before promising automatic updates. Every other change — the apps/web shell and plain packages — requires rebuilding the affected Web artifacts and verifying this existing URL after a page refresh. Starting another server does not update this GUI. The apps/web Vite entry builds the shell but is not a standalone application because only dsh web injects window.__DSH_BOOT__. Do not start a replacement server unless the user asks; if one is needed, use a managed background job and verify its exact URL.
You are a coding agent powered by the deepseek-v4-flash model. Your working directory is {{cwd}}.

View File

@@ -6,8 +6,8 @@
"kind": "chunks",
"chunks": [
{ "type": "block-start", "index": 0, "blockType": "reasoning" },
{ "type": "reasoning-delta", "index": 0, "text": "There are 4 packages:\n- client/ui-conversation\n- context/session-reference\n- llm/token-meter\n- skill/skill-local\n\nFor this turn (turn 1 of 2), I should randomly pick one package and output its file structure. Let me pick randomly — I'll use $RANDOM or just pick one. Let me use a quick random selection." },
{ "type": "block-end", "index": 0, "block": { "type": "reasoning", "text": "There are 4 packages:\n- client/ui-conversation\n- context/session-reference\n- llm/token-meter\n- skill/skill-local\n\nFor this turn (turn 1 of 2), I should randomly pick one package and output its file structure. Let me pick randomly — I'll use $RANDOM or just pick one. Let me use a quick random selection." } },
{ "type": "reasoning-delta", "index": 0, "text": "There are 4 packages:\n- client/ui-conversation\n- context/session-reference\n- llm/token-meter\n- skill/skill-filesystem\n\nFor this turn (turn 1 of 2), I should randomly pick one package and output its file structure. Let me pick randomly — I'll use $RANDOM or just pick one. Let me use a quick random selection." },
{ "type": "block-end", "index": 0, "block": { "type": "reasoning", "text": "There are 4 packages:\n- client/ui-conversation\n- context/session-reference\n- llm/token-meter\n- skill/skill-filesystem\n\nFor this turn (turn 1 of 2), I should randomly pick one package and output its file structure. Let me pick randomly — I'll use $RANDOM or just pick one. Let me use a quick random selection." } },
{ "type": "block-start", "index": 1, "blockType": "tool-call" },
{ "type": "tool-call-delta", "index": 1, "id": "call_00_dAM4qfVp4LLtRCiJi7d71418", "name": "bash", "argumentsDelta": "{\"command\": \"find packages -maxdepth 2 -mindepth 2 -type d | sort | awk '{print NR\\\": \\\"$0}' && echo \\\"---random pick---\\\" && find packages -maxdepth 2 -mindepth 2 -type d | sort | PATH=/__dsh_fixture_missing__ shuf -n 1\", \"description\": \"Randomly pick one package directory\"}" },
{ "type": "block-end", "index": 1, "block": { "type": "tool-call", "id": "call_00_dAM4qfVp4LLtRCiJi7d71418", "name": "bash", "arguments": "{\"command\": \"find packages -maxdepth 2 -mindepth 2 -type d | sort | awk '{print NR\\\": \\\"$0}' && echo \\\"---random pick---\\\" && find packages -maxdepth 2 -mindepth 2 -type d | sort | PATH=/__dsh_fixture_missing__ shuf -n 1\", \"description\": \"Randomly pick one package directory\"}" } },

View File

@@ -42,20 +42,20 @@
{"type":"assistant/chunk","seq":335,"time":1786073822633,"data":{"turn":1,"step":2,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}}
{"type":"assistant/message","seq":336,"time":1786073822633,"data":{"turn":1,"step":2,"message":{"role":"assistant","content":[{"type":"reasoning","text":"There's a `packages` directory. Let me look inside to see what packages are available."},{"type":"tool-call","id":"call_00_XlYaTHoKielffCybfcXb9253","name":"bash","arguments":"{\"command\": \"ls packages && echo \\\"---\\\" && find packages -maxdepth 2 -type d\", \"description\": \"List packages directory contents\"}"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"4f03ca00-aa2d-4a69-9675-58833e8d1b30"},"usage":{"inputTokens":363,"outputTokens":99,"cacheReadTokens":8064,"reasoningTokens":20}},"sourceEventSeqs":[272,273,274,275,276,277,278,279,280,281,282,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335],"surfaceOp":"append"}
{"type":"tool/call","seq":337,"time":1786073822634,"data":{"turn":1,"step":2,"callId":"call_00_XlYaTHoKielffCybfcXb9253","name":"bash","arguments":"{\"command\": \"ls packages && echo \\\"---\\\" && find packages -maxdepth 2 -type d\", \"description\": \"List packages directory contents\"}"}}
{"type":"tool/result","seq":338,"time":1786073822659,"data":{"turn":1,"step":2,"message":{"source":{"kind":"tool","callId":"call_00_XlYaTHoKielffCybfcXb9253"},"content":[{"type":"tool-result","toolCallId":"call_00_XlYaTHoKielffCybfcXb9253","content":[{"type":"text","text":"client\ncontext\nllm\nskill\n---\npackages\npackages/llm\npackages/llm/token-meter\npackages/context\npackages/context/session-reference\npackages/skill\npackages/skill/skill-local\npackages/client\npackages/client/ui-conversation\n"}],"isError":false}],"role":"user","id":"8a53ae07-7f7a-424f-8095-96f216746bc4"}},"sourceEventSeqs":[337],"surfaceOp":"append"}
{"type":"tool/result","seq":338,"time":1786073822659,"data":{"turn":1,"step":2,"message":{"source":{"kind":"tool","callId":"call_00_XlYaTHoKielffCybfcXb9253"},"content":[{"type":"tool-result","toolCallId":"call_00_XlYaTHoKielffCybfcXb9253","content":[{"type":"text","text":"client\ncontext\nllm\nskill\n---\npackages\npackages/llm\npackages/llm/token-meter\npackages/context\npackages/context/session-reference\npackages/skill\npackages/skill/skill-filesystem\npackages/client\npackages/client/ui-conversation\n"}],"isError":false}],"role":"user","id":"8a53ae07-7f7a-424f-8095-96f216746bc4"}},"sourceEventSeqs":[337],"surfaceOp":"append"}
{"type":"step/end","seq":339,"time":1786073822659,"data":{"turn":1,"step":2}}
{"type":"step/start","seq":340,"time":1786073822668,"data":{"turn":1,"step":3}}
{"type":"assistant/chunk","seq":341,"time":1786073823725,"data":{"turn":1,"step":3,"chunk":{"type":"block-start","index":0,"blockType":"reasoning"}}}
{"type":"reasoning-chunks","seq0":342,"time0":1786073823726,"data":{"turn":1,"step":3,"index":0,"dt":[82,26,0,0,27,0,0,0,23,0,0,0,0,0,21,0,0,0,0,0,21,1,0,0,24,0,1,0,0,0,23,0,0,21,1,0,23,0,0,0,0,0,22,1,0,0,0,0,24,0,0,0,0,0,24,0,0,1,24,1,0,0,17,24,1,0,0,0,29,0,0,16,1,0,31,0,0,18,0],"texts":["There"," are"," ","4"," packages",":\n","-"," client","/ui","-con","vers","ation","\n","-"," context","/s","ession","-reference","\n","-"," ll","m","/t","oken","-meter","\n","-"," skill","/s","kill","-local","\n\n","For"," this"," turn"," (","turn"," ","1"," of"," ","2","),"," I"," should"," randomly"," pick"," one"," package"," and"," output"," its"," file"," structure","."," Let"," me"," pick"," randomly"," —"," I","'ll"," use"," $","R","AND","OM"," or"," just"," pick"," one","."," Let"," me"," use"," a"," quick"," random"," selection","."]}}
{"type":"assistant/chunk","seq":422,"time":1786073824320,"data":{"turn":1,"step":3,"chunk":{"type":"block-start","index":1,"blockType":"tool-call"}}}
{"type":"tool-call-chunks","seq0":423,"time0":1786073824321,"data":{"turn":1,"step":3,"index":1,"dt":[24,1,1,0,0,29,1,14,1,0,0,0,24,0,1,21,0,0,41,1,0,0,0,0,11,0,0,20,0,0,26,0,0,16,1,0,28,1,23,0,1,0,28,0,0,19,0,0,0,0,1,13,1,0,0,0,0,27,1,0,28,0,0,0,28,1,26,1,0,0,19,1,0,0,22,1,0,18,0,23],"id":"call_00_dAM4qfVp4LLtRCiJi7d71418","name":"bash","args":["","{","\"","command","\"",": ","\"","find"," packages"," -","max","depth"," ","2"," -","mind","epth"," ","2"," -","type"," d"," |"," sort"," |"," awk"," '{","print"," NR","\\\":"," \\\"$","0","}'"," &&"," echo"," \\\"","---","random"," pick","---","\\\""," &&"," find"," packages"," -","max","depth"," ","2"," -","mind","epth"," ","2"," -","type"," d"," |"," sort"," |"," sh","uf"," -","n"," ","1","\"",", ","\"","description","\"",": ","\"","Random","ly"," pick"," one"," package"," directory","\"","}"]}}
{"type":"assistant/chunk","seq":504,"time":1786073824907,"data":{"turn":1,"step":3,"chunk":{"type":"block-end","index":0,"block":{"type":"reasoning","text":"There are 4 packages:\n- client/ui-conversation\n- context/session-reference\n- llm/token-meter\n- skill/skill-local\n\nFor this turn (turn 1 of 2), I should randomly pick one package and output its file structure. Let me pick randomly — I'll use $RANDOM or just pick one. Let me use a quick random selection."}}}}
{"type":"assistant/chunk","seq":504,"time":1786073824907,"data":{"turn":1,"step":3,"chunk":{"type":"block-end","index":0,"block":{"type":"reasoning","text":"There are 4 packages:\n- client/ui-conversation\n- context/session-reference\n- llm/token-meter\n- skill/skill-filesystem\n\nFor this turn (turn 1 of 2), I should randomly pick one package and output its file structure. Let me pick randomly — I'll use $RANDOM or just pick one. Let me use a quick random selection."}}}}
{"type":"assistant/chunk","seq":505,"time":1786073824908,"data":{"turn":1,"step":3,"chunk":{"type":"block-end","index":1,"block":{"type":"tool-call","id":"call_00_dAM4qfVp4LLtRCiJi7d71418","name":"bash","arguments":"{\"command\": \"find packages -maxdepth 2 -mindepth 2 -type d | sort | awk '{print NR\\\": \\\"$0}' && echo \\\"---random pick---\\\" && find packages -maxdepth 2 -mindepth 2 -type d | sort | shuf -n 1\", \"description\": \"Randomly pick one package directory\"}"}}}}
{"type":"assistant/chunk","seq":506,"time":1786073824908,"data":{"turn":1,"step":3,"chunk":{"type":"usage","usage":{"inputTokens":156,"outputTokens":202,"cacheReadTokens":8448,"reasoningTokens":80}}}}
{"type":"assistant/chunk","seq":507,"time":1786073824908,"data":{"turn":1,"step":3,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}}
{"type":"assistant/message","seq":508,"time":1786073824909,"data":{"turn":1,"step":3,"message":{"role":"assistant","content":[{"type":"reasoning","text":"There are 4 packages:\n- client/ui-conversation\n- context/session-reference\n- llm/token-meter\n- skill/skill-local\n\nFor this turn (turn 1 of 2), I should randomly pick one package and output its file structure. Let me pick randomly — I'll use $RANDOM or just pick one. Let me use a quick random selection."},{"type":"tool-call","id":"call_00_dAM4qfVp4LLtRCiJi7d71418","name":"bash","arguments":"{\"command\": \"find packages -maxdepth 2 -mindepth 2 -type d | sort | awk '{print NR\\\": \\\"$0}' && echo \\\"---random pick---\\\" && find packages -maxdepth 2 -mindepth 2 -type d | sort | shuf -n 1\", \"description\": \"Randomly pick one package directory\"}"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"9e27c6d1-afa2-464c-b4c9-2ca7a44035f6"},"usage":{"inputTokens":156,"outputTokens":202,"cacheReadTokens":8448,"reasoningTokens":80}},"sourceEventSeqs":[341,342,343,344,345,346,347,348,349,350,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,371,372,373,374,375,376,377,378,379,380,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,400,401,402,403,404,405,406,407,408,409,410,411,412,413,414,415,416,417,418,419,420,421,422,423,424,425,426,427,428,429,430,431,432,433,434,435,436,437,438,439,440,441,442,443,444,445,446,447,448,449,450,451,452,453,454,455,456,457,458,459,460,461,462,463,464,465,466,467,468,469,470,471,472,473,474,475,476,477,478,479,480,481,482,483,484,485,486,487,488,489,490,491,492,493,494,495,496,497,498,499,500,501,502,503,504,505,506,507],"surfaceOp":"append"}
{"type":"assistant/message","seq":508,"time":1786073824909,"data":{"turn":1,"step":3,"message":{"role":"assistant","content":[{"type":"reasoning","text":"There are 4 packages:\n- client/ui-conversation\n- context/session-reference\n- llm/token-meter\n- skill/skill-filesystem\n\nFor this turn (turn 1 of 2), I should randomly pick one package and output its file structure. Let me pick randomly — I'll use $RANDOM or just pick one. Let me use a quick random selection."},{"type":"tool-call","id":"call_00_dAM4qfVp4LLtRCiJi7d71418","name":"bash","arguments":"{\"command\": \"find packages -maxdepth 2 -mindepth 2 -type d | sort | awk '{print NR\\\": \\\"$0}' && echo \\\"---random pick---\\\" && find packages -maxdepth 2 -mindepth 2 -type d | sort | shuf -n 1\", \"description\": \"Randomly pick one package directory\"}"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"9e27c6d1-afa2-464c-b4c9-2ca7a44035f6"},"usage":{"inputTokens":156,"outputTokens":202,"cacheReadTokens":8448,"reasoningTokens":80}},"sourceEventSeqs":[341,342,343,344,345,346,347,348,349,350,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,371,372,373,374,375,376,377,378,379,380,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,400,401,402,403,404,405,406,407,408,409,410,411,412,413,414,415,416,417,418,419,420,421,422,423,424,425,426,427,428,429,430,431,432,433,434,435,436,437,438,439,440,441,442,443,444,445,446,447,448,449,450,451,452,453,454,455,456,457,458,459,460,461,462,463,464,465,466,467,468,469,470,471,472,473,474,475,476,477,478,479,480,481,482,483,484,485,486,487,488,489,490,491,492,493,494,495,496,497,498,499,500,501,502,503,504,505,506,507],"surfaceOp":"append"}
{"type":"tool/call","seq":509,"time":1786073824910,"data":{"turn":1,"step":3,"callId":"call_00_dAM4qfVp4LLtRCiJi7d71418","name":"bash","arguments":"{\"command\": \"find packages -maxdepth 2 -mindepth 2 -type d | sort | awk '{print NR\\\": \\\"$0}' && echo \\\"---random pick---\\\" && find packages -maxdepth 2 -mindepth 2 -type d | sort | shuf -n 1\", \"description\": \"Randomly pick one package directory\"}"}}
{"type":"tool/result","seq":510,"time":1786073824937,"data":{"turn":1,"step":3,"message":{"source":{"kind":"tool","callId":"call_00_dAM4qfVp4LLtRCiJi7d71418"},"content":[{"type":"tool-result","toolCallId":"call_00_dAM4qfVp4LLtRCiJi7d71418","content":[{"type":"text","text":"1: packages/client/ui-conversation\n2: packages/context/session-reference\n3: packages/llm/token-meter\n4: packages/skill/skill-local\n---random pick---\n[stderr]\nbash: shuf: command not found\n[exit code: 127]"}],"isError":false}],"role":"user","id":"3227ba71-d0f0-4b4e-82e0-b01c6cd1ded1"}},"sourceEventSeqs":[509],"surfaceOp":"append"}
{"type":"tool/result","seq":510,"time":1786073824937,"data":{"turn":1,"step":3,"message":{"source":{"kind":"tool","callId":"call_00_dAM4qfVp4LLtRCiJi7d71418"},"content":[{"type":"tool-result","toolCallId":"call_00_dAM4qfVp4LLtRCiJi7d71418","content":[{"type":"text","text":"1: packages/client/ui-conversation\n2: packages/context/session-reference\n3: packages/llm/token-meter\n4: packages/skill/skill-filesystem\n---random pick---\n[stderr]\nbash: shuf: command not found\n[exit code: 127]"}],"isError":false}],"role":"user","id":"3227ba71-d0f0-4b4e-82e0-b01c6cd1ded1"}},"sourceEventSeqs":[509],"surfaceOp":"append"}
{"type":"step/end","seq":511,"time":1786073824938,"data":{"turn":1,"step":3}}
{"type":"step/start","seq":512,"time":1786073824947,"data":{"turn":1,"step":4}}
{"type":"assistant/chunk","seq":513,"time":1786073826168,"data":{"turn":1,"step":4,"chunk":{"type":"block-start","index":0,"blockType":"reasoning"}}}
@@ -175,9 +175,9 @@
{"type":"user/message","seq":1749,"time":1786073844598,"data":{"content":[{"type":"text","text":"<goal_complete>\nObjective: \"做两个turn每个turn输出随机一个包的文件结构。注意你做完一个turn之后直接输出内容停止我们的系统会帮你再开一个turn你看着做一个类似的\"\nThe goal is marked complete and this autonomous run is ending. Write the closing message to the user now: state the outcome, summarize what was done and how it was verified, and point to the concrete results (files, commits, or other artifacts). Report only what earlier rounds and tool results in this session actually establish; when a detail is not in the session, say so instead of inventing it. Note anything the user should review or do next. Address the user directly. Do not call any more tools in this run; further work waits for the user's next instruction.\n</goal_complete>"}],"source":{"kind":"plugin","plugin":"tool-goal","form":"notice","summary":"complete: 做两个turn每个turn输出随机一个包的文件结构。注意你做完一个turn之后直接输出内容停止我们的系统会帮你再开一个turn你看着做一个类似的"},"role":"user","id":"34261fc8-220c-40c0-819d-c9c0332bf965"},"surfaceOp":"append"}
{"type":"assistant/chunk","seq":1750,"time":1786073845862,"data":{"turn":2,"step":6,"chunk":{"type":"block-start","index":0,"blockType":"text"}}}
{"type":"text-chunks","seq0":1751,"time0":1786073845863,"data":{"turn":2,"step":6,"index":0,"dt":[22,1,0,0,22,1,0,0,0,22,0,0,0,0,0,24,0,25,0,0,0,22,1,0,19,1,0,0,23,0,0,27,0,20,0,0,25,0,1,0,0,0,21,0,1,0,0,23,0,0,0,0,0,24,0,0,0,0,0,0,24,0,0,0,0,0,20,0,1,0,0,27,0,0,0,1,20,0,0,1,0,24,0,1,0,0,0,19,0,0,0,0,0,24,0,0,0,0,0,24,1,0,0,0,0,20,0,0,0,0,24,0,0,0,0,24,0,0,1,0,0,23,0,0,0,0,0,23,1,0,0,0,0,22,0,0,1,0,0,22,1,0,0,0,24,1,0,0,0,0,26,0,0,0,1,21,0,0,0,0,24,0,0,0,0,0,26,0,0,0,0,0,20,20,24,23,1,24,0,22,0,45,3,1,0,25,1,23,0,0,0,0,25,0,0,1,17,0,23,26,1,0,0,23,0,0,23,0,22,28,1,0,0,0,0,23,0,0,0,0,1,23,0,1,0,0,0,21,0,1,0,22,0,0,0,23,0,0,24,22,25,21,38,1,0,10,24,27,1,0,0,26,0,0,0,1,0,19,21,27,0,0,29,0,0,0,0,0,23,20,0,21,25,1,54,0,0,30,0,0,0,0,22,24,1,0,25,0,1,19,24,0,24,1,0,23,1,22,0,0,22,1,25,21,0,22,23,28,0,23,1,0,0,0,22,0,0,0,0,0,24,0,1,0,0,0,18,24,26,1,0,20,23,24,1,26,0,1,0,39],"texts":["两个"," turn"," ","都","完成了"," ✅","\n\n","**","总结","","**\n\n","-"," **","Turn"," ","1","**"," —"," ","随机","选中"," `","pack","ages","/","context","/s","ession","-reference","`","","输","出了","文件","结构","\n"," "," ```\n"," "," session","-reference","/\n"," "," ├","──"," READ","ME",".md","\n"," "," ├","──"," package",".json","\n"," "," ├","──"," src","/\n"," "," │"," "," ├","──"," index",".ts","\n"," "," │"," "," └","──"," uri",".ts","\n"," "," └","──"," tests","/\n"," "," └","──"," session","-reference",".s","pec",".ts","\n"," "," ```\n","-"," **","Turn"," ","2","**"," —"," ","随机","选中"," `","pack","ages","/","ll","m","/t","oken","-meter","`","","输","出了","文件","结构","\n"," "," ```\n"," "," token","-meter","/\n"," "," ├","──"," READ","ME",".md","\n"," "," ├","──"," package",".json","\n"," "," ├","──"," src","/\n"," "," │"," "," └","──"," index",".ts","\n"," "," └","──"," tests","/\n"," "," └","──"," token","-meter",".s","pec",".ts","\n"," "," ```\n\n","**","验证","方式","","**"," ","两个","包的","随机","选择","都","通过"," `","python","3"," random",".choice","`"," ","从","工作","区的"," `","pack","ages","/","`"," ","目录","下","实际","枚举","的"," ","4"," ","个","候选","包","中","抽取","","`","ui","-con","vers","ation","`","、","`","session","-reference","`","、","`","token","-meter","`","、","`","skill","-local","`","","文件","结构","则","用"," `","find","`"," ","实际","列出","文件","后","整理","输出","","均已","排除"," `","node","_modules","`"," ","和"," `.","git","`","。","第二次","随机","抽","到了","与"," turn"," ","1"," ","相同的","包","","为避免","重复","换","抽","了","另一个","。\n\n","**","说明","","**"," ","本轮","没有","产生","任何","文件","修改","或","提交","——","任务","本身就是","\"","输出","文件","结构","\"","所有","产物","都在","上面的","消息","文本","里","。\n\n","如果你","还想","看","剩下","两个","包","","`","ui","-con","vers","ation","`","、","`","skill","-local","`","","的结构","","或者其他","格式","的输出","","比如","带","文件","大小的","树","形","图","","随时","告诉我","。"]}}
{"type":"assistant/chunk","seq":2092,"time":1786073848306,"data":{"turn":2,"step":6,"chunk":{"type":"block-end","index":0,"block":{"type":"text","text":"两个 turn 都完成了 ✅\n\n**总结:**\n\n- **Turn 1** — 随机选中 `packages/context/session-reference`,输出了文件结构:\n ```\n session-reference/\n ├── README.md\n ├── package.json\n ├── src/\n │ ├── index.ts\n │ └── uri.ts\n └── tests/\n └── session-reference.spec.ts\n ```\n- **Turn 2** — 随机选中 `packages/llm/token-meter`,输出了文件结构:\n ```\n token-meter/\n ├── README.md\n ├── package.json\n ├── src/\n │ └── index.ts\n └── tests/\n └── token-meter.spec.ts\n ```\n\n**验证方式:** 两个包的随机选择都通过 `python3 random.choice` 从工作区的 `packages/` 目录下实际枚举的 4 个候选包中抽取(`ui-conversation`、`session-reference`、`token-meter`、`skill-local`),文件结构则用 `find` 实际列出文件后整理输出,均已排除 `node_modules` 和 `.git`。第二次随机抽到了与 turn 1 相同的包,为避免重复换抽了另一个。\n\n**说明:** 本轮没有产生任何文件修改或提交——任务本身就是\"输出文件结构\",所有产物都在上面的消息文本里。\n\n如果你还想看剩下两个包`ui-conversation`、`skill-local`)的结构,或者其他格式的输出(比如带文件大小的树形图),随时告诉我。"}}}}
{"type":"assistant/chunk","seq":2092,"time":1786073848306,"data":{"turn":2,"step":6,"chunk":{"type":"block-end","index":0,"block":{"type":"text","text":"两个 turn 都完成了 ✅\n\n**总结:**\n\n- **Turn 1** — 随机选中 `packages/context/session-reference`,输出了文件结构:\n ```\n session-reference/\n ├── README.md\n ├── package.json\n ├── src/\n │ ├── index.ts\n │ └── uri.ts\n └── tests/\n └── session-reference.spec.ts\n ```\n- **Turn 2** — 随机选中 `packages/llm/token-meter`,输出了文件结构:\n ```\n token-meter/\n ├── README.md\n ├── package.json\n ├── src/\n │ └── index.ts\n └── tests/\n └── token-meter.spec.ts\n ```\n\n**验证方式:** 两个包的随机选择都通过 `python3 random.choice` 从工作区的 `packages/` 目录下实际枚举的 4 个候选包中抽取(`ui-conversation`、`session-reference`、`token-meter`、`skill-filesystem`),文件结构则用 `find` 实际列出文件后整理输出,均已排除 `node_modules` 和 `.git`。第二次随机抽到了与 turn 1 相同的包,为避免重复换抽了另一个。\n\n**说明:** 本轮没有产生任何文件修改或提交——任务本身就是\"输出文件结构\",所有产物都在上面的消息文本里。\n\n如果你还想看剩下两个包`ui-conversation`、`skill-filesystem`)的结构,或者其他格式的输出(比如带文件大小的树形图),随时告诉我。"}}}}
{"type":"assistant/chunk","seq":2093,"time":1786073848307,"data":{"turn":2,"step":6,"chunk":{"type":"usage","usage":{"inputTokens":311,"outputTokens":348,"cacheReadTokens":10624,"reasoningTokens":0}}}}
{"type":"assistant/chunk","seq":2094,"time":1786073848307,"data":{"turn":2,"step":6,"chunk":{"type":"finish","reason":{"kind":"stop"}}}}
{"type":"assistant/message","seq":2095,"time":1786073848307,"data":{"turn":2,"step":6,"message":{"role":"assistant","content":[{"type":"text","text":"两个 turn 都完成了 ✅\n\n**总结:**\n\n- **Turn 1** — 随机选中 `packages/context/session-reference`,输出了文件结构:\n ```\n session-reference/\n ├── README.md\n ├── package.json\n ├── src/\n │ ├── index.ts\n │ └── uri.ts\n └── tests/\n └── session-reference.spec.ts\n ```\n- **Turn 2** — 随机选中 `packages/llm/token-meter`,输出了文件结构:\n ```\n token-meter/\n ├── README.md\n ├── package.json\n ├── src/\n │ └── index.ts\n └── tests/\n └── token-meter.spec.ts\n ```\n\n**验证方式:** 两个包的随机选择都通过 `python3 random.choice` 从工作区的 `packages/` 目录下实际枚举的 4 个候选包中抽取(`ui-conversation`、`session-reference`、`token-meter`、`skill-local`),文件结构则用 `find` 实际列出文件后整理输出,均已排除 `node_modules` 和 `.git`。第二次随机抽到了与 turn 1 相同的包,为避免重复换抽了另一个。\n\n**说明:** 本轮没有产生任何文件修改或提交——任务本身就是\"输出文件结构\",所有产物都在上面的消息文本里。\n\n如果你还想看剩下两个包`ui-conversation`、`skill-local`)的结构,或者其他格式的输出(比如带文件大小的树形图),随时告诉我。"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"5a1daadd-6f23-49ea-980b-6f96f67e3f52"},"usage":{"inputTokens":311,"outputTokens":348,"cacheReadTokens":10624,"reasoningTokens":0}},"sourceEventSeqs":[1750,1751,1752,1753,1754,1755,1756,1757,1758,1759,1760,1761,1762,1763,1764,1765,1766,1767,1768,1769,1770,1771,1772,1773,1774,1775,1776,1777,1778,1779,1780,1781,1782,1783,1784,1785,1786,1787,1788,1789,1790,1791,1792,1793,1794,1795,1796,1797,1798,1799,1800,1801,1802,1803,1804,1805,1806,1807,1808,1809,1810,1811,1812,1813,1814,1815,1816,1817,1818,1819,1820,1821,1822,1823,1824,1825,1826,1827,1828,1829,1830,1831,1832,1833,1834,1835,1836,1837,1838,1839,1840,1841,1842,1843,1844,1845,1846,1847,1848,1849,1850,1851,1852,1853,1854,1855,1856,1857,1858,1859,1860,1861,1862,1863,1864,1865,1866,1867,1868,1869,1870,1871,1872,1873,1874,1875,1876,1877,1878,1879,1880,1881,1882,1883,1884,1885,1886,1887,1888,1889,1890,1891,1892,1893,1894,1895,1896,1897,1898,1899,1900,1901,1902,1903,1904,1905,1906,1907,1908,1909,1910,1911,1912,1913,1914,1915,1916,1917,1918,1919,1920,1921,1922,1923,1924,1925,1926,1927,1928,1929,1930,1931,1932,1933,1934,1935,1936,1937,1938,1939,1940,1941,1942,1943,1944,1945,1946,1947,1948,1949,1950,1951,1952,1953,1954,1955,1956,1957,1958,1959,1960,1961,1962,1963,1964,1965,1966,1967,1968,1969,1970,1971,1972,1973,1974,1975,1976,1977,1978,1979,1980,1981,1982,1983,1984,1985,1986,1987,1988,1989,1990,1991,1992,1993,1994,1995,1996,1997,1998,1999,2000,2001,2002,2003,2004,2005,2006,2007,2008,2009,2010,2011,2012,2013,2014,2015,2016,2017,2018,2019,2020,2021,2022,2023,2024,2025,2026,2027,2028,2029,2030,2031,2032,2033,2034,2035,2036,2037,2038,2039,2040,2041,2042,2043,2044,2045,2046,2047,2048,2049,2050,2051,2052,2053,2054,2055,2056,2057,2058,2059,2060,2061,2062,2063,2064,2065,2066,2067,2068,2069,2070,2071,2072,2073,2074,2075,2076,2077,2078,2079,2080,2081,2082,2083,2084,2085,2086,2087,2088,2089,2090,2091,2092,2093,2094],"surfaceOp":"append"}
{"type":"assistant/message","seq":2095,"time":1786073848307,"data":{"turn":2,"step":6,"message":{"role":"assistant","content":[{"type":"text","text":"两个 turn 都完成了 ✅\n\n**总结:**\n\n- **Turn 1** — 随机选中 `packages/context/session-reference`,输出了文件结构:\n ```\n session-reference/\n ├── README.md\n ├── package.json\n ├── src/\n │ ├── index.ts\n │ └── uri.ts\n └── tests/\n └── session-reference.spec.ts\n ```\n- **Turn 2** — 随机选中 `packages/llm/token-meter`,输出了文件结构:\n ```\n token-meter/\n ├── README.md\n ├── package.json\n ├── src/\n │ └── index.ts\n └── tests/\n └── token-meter.spec.ts\n ```\n\n**验证方式:** 两个包的随机选择都通过 `python3 random.choice` 从工作区的 `packages/` 目录下实际枚举的 4 个候选包中抽取(`ui-conversation`、`session-reference`、`token-meter`、`skill-filesystem`),文件结构则用 `find` 实际列出文件后整理输出,均已排除 `node_modules` 和 `.git`。第二次随机抽到了与 turn 1 相同的包,为避免重复换抽了另一个。\n\n**说明:** 本轮没有产生任何文件修改或提交——任务本身就是\"输出文件结构\",所有产物都在上面的消息文本里。\n\n如果你还想看剩下两个包`ui-conversation`、`skill-filesystem`)的结构,或者其他格式的输出(比如带文件大小的树形图),随时告诉我。"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"5a1daadd-6f23-49ea-980b-6f96f67e3f52"},"usage":{"inputTokens":311,"outputTokens":348,"cacheReadTokens":10624,"reasoningTokens":0}},"sourceEventSeqs":[1750,1751,1752,1753,1754,1755,1756,1757,1758,1759,1760,1761,1762,1763,1764,1765,1766,1767,1768,1769,1770,1771,1772,1773,1774,1775,1776,1777,1778,1779,1780,1781,1782,1783,1784,1785,1786,1787,1788,1789,1790,1791,1792,1793,1794,1795,1796,1797,1798,1799,1800,1801,1802,1803,1804,1805,1806,1807,1808,1809,1810,1811,1812,1813,1814,1815,1816,1817,1818,1819,1820,1821,1822,1823,1824,1825,1826,1827,1828,1829,1830,1831,1832,1833,1834,1835,1836,1837,1838,1839,1840,1841,1842,1843,1844,1845,1846,1847,1848,1849,1850,1851,1852,1853,1854,1855,1856,1857,1858,1859,1860,1861,1862,1863,1864,1865,1866,1867,1868,1869,1870,1871,1872,1873,1874,1875,1876,1877,1878,1879,1880,1881,1882,1883,1884,1885,1886,1887,1888,1889,1890,1891,1892,1893,1894,1895,1896,1897,1898,1899,1900,1901,1902,1903,1904,1905,1906,1907,1908,1909,1910,1911,1912,1913,1914,1915,1916,1917,1918,1919,1920,1921,1922,1923,1924,1925,1926,1927,1928,1929,1930,1931,1932,1933,1934,1935,1936,1937,1938,1939,1940,1941,1942,1943,1944,1945,1946,1947,1948,1949,1950,1951,1952,1953,1954,1955,1956,1957,1958,1959,1960,1961,1962,1963,1964,1965,1966,1967,1968,1969,1970,1971,1972,1973,1974,1975,1976,1977,1978,1979,1980,1981,1982,1983,1984,1985,1986,1987,1988,1989,1990,1991,1992,1993,1994,1995,1996,1997,1998,1999,2000,2001,2002,2003,2004,2005,2006,2007,2008,2009,2010,2011,2012,2013,2014,2015,2016,2017,2018,2019,2020,2021,2022,2023,2024,2025,2026,2027,2028,2029,2030,2031,2032,2033,2034,2035,2036,2037,2038,2039,2040,2041,2042,2043,2044,2045,2046,2047,2048,2049,2050,2051,2052,2053,2054,2055,2056,2057,2058,2059,2060,2061,2062,2063,2064,2065,2066,2067,2068,2069,2070,2071,2072,2073,2074,2075,2076,2077,2078,2079,2080,2081,2082,2083,2084,2085,2086,2087,2088,2089,2090,2091,2092,2093,2094],"surfaceOp":"append"}
{"type":"step/end","seq":2096,"time":1786073848308,"data":{"turn":2,"step":6}}
{"type":"turn/end","seq":2097,"time":1786073848308,"data":{"turn":2,"reason":{"kind":"completed"}}}

View File

@@ -175,7 +175,7 @@
- text: 、
- code: token-meter
- text: 、
- code: skill-local
- code: skill-filesystem
- text: ),文件结构则用
- code: find
- text: 实际列出文件后整理输出,均已排除
@@ -190,7 +190,7 @@
- text: 如果你还想看剩下两个包(
- code: ui-conversation
- text: 、
- code: skill-local
- code: skill-filesystem
- text: )的结构,或者其他格式的输出(比如带文件大小的树形图),随时告诉我。
- button "Copy":
- img

File diff suppressed because one or more lines are too long

View File

@@ -45,10 +45,10 @@
- text: Context injection AGENTS.md
- img
- text: permission preset read-only
- 'button "feedback Feedback recorded for session {{seededId}} User: {{uuid}}. Session sharing is not configured." [expanded]':
- 'button "feedback Feedback recorded for session {{seededId}} Anonymous user: {{uuid}}. Session sharing is not configured." [expanded]':
- img
- text: "feedback Feedback recorded for session {{seededId}} User: {{uuid}}. Session sharing is not configured."
- text: "Feedback recorded for session {{seededId}} User: {{uuid}}. Session sharing is not configured."
- text: "feedback Feedback recorded for session {{seededId}} Anonymous user: {{uuid}}. Session sharing is not configured."
- text: "Feedback recorded for session {{seededId}} Anonymous user: {{uuid}}. Session sharing is not configured."
- textbox "Message the agent"
- button "Commands":
- img

View File

@@ -3,4 +3,4 @@
- img
- text: workspace
- treeitem "New Session" [selected]
- treeitem "1 subagent running Delegate a background task. now"
- treeitem "1 subagent running Delegate a background job. now"

View File

@@ -1 +1 @@
You are interacting with the user through the DeepSeek Harness Web GUI at {{webUrl}}. When the user refers to "this page", "this GUI", or "this app" without naming another target, they mean this GUI. The browser provides no implicit DOM, route, or screenshot context. The client-plugin HMR receiver is active, but client-plugin changes reload without a refresh only while `pnpm run dev:web` is also running from this same checkout to rebuild their bundles; verify that watcher before promising automatic updates. Every other change — the apps/web shell and plain packages — requires rebuilding the affected Web artifacts and verifying this existing URL after a page refresh. Starting another server does not update this GUI. The apps/web Vite entry builds the shell but is not a standalone application because only dsh web injects window.__DSH_BOOT__. Do not start a replacement server unless the user asks; if one is needed, use a managed background task and verify its exact URL.
You are interacting with the user through the DeepSeek Harness Web GUI at {{webUrl}}. When the user refers to "this page", "this GUI", or "this app" without naming another target, they mean this GUI. The browser provides no implicit DOM, route, or screenshot context. The client-plugin HMR receiver is active, but client-plugin changes reload without a refresh only while `pnpm run dev:web` is also running from this same checkout to rebuild their bundles; verify that watcher before promising automatic updates. Every other change — the apps/web shell and plain packages — requires rebuilding the affected Web artifacts and verifying this existing URL after a page refresh. Starting another server does not update this GUI. The apps/web Vite entry builds the shell but is not a standalone application because only dsh web injects window.__DSH_BOOT__. Do not start a replacement server unless the user asks; if one is needed, use a managed background job and verify its exact URL.

View File

@@ -1,7 +1,7 @@
// Web e2e scenario: startup auto-selection keeps the hero on screen.
//
// A page load with a workspace already registered runs
// `WorkspacesService.startInitialSelection`: it connects the most recent
// `WorkspaceRuntime.startInitialSelection`: it connects the most recent
// workspace and opens its blank session. `openState` flips to `loading` the
// moment `open()` lands; driving `data-phase=settling` on the conversation
// root from that flip would hide the composer seat and the header

View File

@@ -70,7 +70,7 @@ describe('web e2e: workspace management (create / rename / flat view / hover aff
await dialog.getByRole('button', { name: 'Open', exact: true }).click()
await dialog.waitFor({ state: 'hidden', timeout: 10_000 })
await expect.poll(
() => scaffold.ctx.workspace.resolveByPath(join(parent, name)),
() => scaffold.ctx.workspaceRegistry.resolveByPath(join(parent, name)),
{ timeout: 10_000 },
).not.toBeUndefined()
}
@@ -86,7 +86,7 @@ describe('web e2e: workspace management (create / rename / flat view / hover aff
await dialog.getByRole('button', { name: 'Open', exact: true }).click()
await dialog.waitFor({ state: 'hidden', timeout: 10_000 })
await expect.poll(
() => scaffold.ctx.workspace.resolveByPath(path),
() => scaffold.ctx.workspaceRegistry.resolveByPath(path),
{ timeout: 10_000 },
).not.toBeUndefined()
// First adoption births a blank Session+Agent whose workspace attach must
@@ -143,7 +143,7 @@ describe('web e2e: workspace management (create / rename / flat view / hover aff
await add('beta-ws')
// Durable on the host: both registered, newest first (create prepends),
// each titled after the folder the dialog made.
const titles = scaffold.ctx.workspace.list().map(workspace => workspace.title)
const titles = scaffold.ctx.workspaceRegistry.list().map(workspace => workspace.title)
expect(titles.slice(0, 2)).toEqual(['beta-ws', 'alpha-ws'])
expect(tripwire.pageErrors).toEqual([])
}, 90_000)
@@ -169,7 +169,7 @@ describe('web e2e: workspace management (create / rename / flat view / hover aff
await expect.poll(() => page.getByText('gamma-ws', { exact: true }).count(), { timeout: 10_000 }).toBeGreaterThanOrEqual(1)
expect(await page.getByText('alpha-ws', { exact: true }).count()).toBe(0)
// Host durability, then reload: the projection is rebuilt from the wire.
expect(scaffold.ctx.workspace.list().map(workspace => workspace.title)).toContain('gamma-ws')
expect(scaffold.ctx.workspaceRegistry.list().map(workspace => workspace.title)).toContain('gamma-ws')
const warningStart = tripwire.warnings.length
await page.reload({ waitUntil: 'load' })
await page.waitForSelector('[class*="frame"]', { timeout: 30_000 })
@@ -207,7 +207,7 @@ describe('web e2e: workspace management (create / rename / flat view / hover aff
})
// Register the scaffold's existing project directory through the real UI.
await adoptDirectory(scaffold.workspaceCwd, { waitForAgent: true })
const workspace = await scaffold.ctx.workspace.resolveByPath(scaffold.workspaceCwd)
const workspace = await scaffold.ctx.workspaceRegistry.resolveByPath(scaffold.workspaceCwd)
if (workspace === undefined) throw new Error('GUI did not register the existing project directory')
await workspace.attachSession(SessionId(SEED_ID))
const header = (await scaffold.ctx.sessionPersistence.list())
@@ -248,7 +248,7 @@ describe('web e2e: workspace management (create / rename / flat view / hover aff
await dialog.getByRole('button', { name: 'Delete workspace' }).click()
await expect.poll(() => dialog.count(), { timeout: 10_000 }).toBe(0)
expect(scaffold.ctx.workspace.get(workspace.id)).toBeUndefined()
expect(scaffold.ctx.workspaceRegistry.get(workspace.id)).toBeUndefined()
await expect.poll(
() => page.getByRole('button', { name: `Workspace actions for ${workspace.title}` }).count(),
{ timeout: 10_000 },
@@ -270,10 +270,10 @@ describe('web e2e: workspace management (create / rename / flat view / hover aff
// (no cwd-based blank reuse exists, so the account is never empty).
await adoptDirectory(scaffold.workspaceCwd)
await expect.poll(
() => scaffold.ctx.workspace.resolveByPath(scaffold.workspaceCwd),
() => scaffold.ctx.workspaceRegistry.resolveByPath(scaffold.workspaceCwd),
{ timeout: 10_000 },
).not.toBeUndefined()
const reregistered = await scaffold.ctx.workspace.resolveByPath(scaffold.workspaceCwd)
const reregistered = await scaffold.ctx.workspaceRegistry.resolveByPath(scaffold.workspaceCwd)
expect(reregistered?.id).toBeDefined()
expect(reregistered?.id).not.toBe(workspace.id)
await expect.poll(
@@ -289,7 +289,7 @@ describe('web e2e: workspace management (create / rename / flat view / hover aff
// Restore the deleted-registry state so reload still verifies deletion
// persistence independently of the successful re-registration above.
if (reregistered === undefined) throw new Error('same-path re-registration did not materialize')
await scaffold.ctx.workspace.delete(reregistered.id)
await scaffold.ctx.workspaceRegistry.delete(reregistered.id)
await expect.poll(
() => page.getByRole('button', { name: `Workspace actions for ${reregistered.title}` }).count(),
{ timeout: 10_000 },
@@ -305,7 +305,7 @@ describe('web e2e: workspace management (create / rename / flat view / hover aff
() => page.locator('[role="treeitem"][aria-selected="true"]').count(),
{ timeout: 15_000 },
).toBe(1)
expect(scaffold.ctx.workspace.get(workspace.id)).toBeUndefined()
expect(scaffold.ctx.workspaceRegistry.get(workspace.id)).toBeUndefined()
expect(await readFile(join(scaffold.workspaceCwd, 'workspace', 'a.txt'), 'utf8')).toBe('alpha\n')
await stat(logLocation.path)
expect((await scaffold.ctx.sessionPersistence.inspect(SessionId(SEED_ID))).events.length).toBeGreaterThan(0)
@@ -344,10 +344,10 @@ describe('web e2e: workspace management (create / rename / flat view / hover aff
await adoptDirectory(oldPath)
await expect.poll(
() => scaffold.ctx.workspace.resolveByPath(oldPath),
() => scaffold.ctx.workspaceRegistry.resolveByPath(oldPath),
{ timeout: 10_000 },
).not.toBeUndefined()
const oldWorkspace = await scaffold.ctx.workspace.resolveByPath(oldPath)
const oldWorkspace = await scaffold.ctx.workspaceRegistry.resolveByPath(oldPath)
if (oldWorkspace === undefined) throw new Error('old same-name Workspace was not registered')
const oldRow = page.locator('[role="treeitem"]').filter({ hasText: title }).first()
@@ -355,10 +355,10 @@ describe('web e2e: workspace management (create / rename / flat view / hover aff
await page.getByRole('menuitem', { name: 'Delete workspace' }).click()
await page.getByRole('dialog', { name: 'Delete workspace' })
.getByRole('button', { name: 'Delete workspace' }).click()
await expect.poll(() => scaffold.ctx.workspace.get(oldWorkspace.id), { timeout: 10_000 }).toBeUndefined()
await expect.poll(() => scaffold.ctx.workspaceRegistry.get(oldWorkspace.id), { timeout: 10_000 }).toBeUndefined()
await addNewFolderWorkspace(scaffold.workspaceCwd, title)
const fresh = scaffold.ctx.workspace.list().find(workspace => workspace.title === title)
const fresh = scaffold.ctx.workspaceRegistry.list().find(workspace => workspace.title === title)
expect(fresh?.id).toBeDefined()
expect(fresh?.id).not.toBe(oldWorkspace.id)
expect(fresh?.path).toBe(join(scaffold.workspaceCwd, title))
@@ -581,7 +581,7 @@ describe('web e2e: workspace management (create / rename / flat view / hover aff
await expect.poll(() => page.getByText('Ungrouped', { exact: true }).count(), { timeout: 10_000 }).toBe(0)
// Durable on the host: the registry-global set carries the id while the
// session log itself stays in persistence untouched.
expect([...scaffold.ctx.workspace.archivedSessionIds]).toEqual([SessionId(SEED_ID)])
expect([...scaffold.ctx.workspaceRegistry.archivedSessionIds]).toEqual([SessionId(SEED_ID)])
expect((await scaffold.ctx.sessionPersistence.list()).map(header => header.id)).toContain(SessionId(SEED_ID))
// Reload: the hidden state is rebuilt from the workspace.list baseline.
const warningStart = tripwire.warnings.length
@@ -606,7 +606,7 @@ describe('web e2e: workspace management (create / rename / flat view / hover aff
await adoptDirectory(firstPath, { waitForAgent: true })
await adoptDirectory(secondPath, { waitForAgent: true })
const matchingWorkspaces = scaffold.ctx.workspace.list()
const matchingWorkspaces = scaffold.ctx.workspaceRegistry.list()
.filter(workspace => workspace.title === 'xx')
expect(matchingWorkspaces.map(workspace => workspace.path).sort())
.toEqual([firstPath, secondPath].sort())

View File

@@ -80,7 +80,7 @@
"tests/subagent-interrupt.e2e.ts",
"tests/subagent-interrupt-ui.e2e.ts",
"tests/sidebar-subagent-activity.e2e.ts",
"tests/background-task-list.e2e.ts",
"tests/background-job-list.e2e.ts",
"tests/bash-abort-row.e2e.ts",
"tests/skill-tool-row.e2e.ts",
"tests/turn-tail-actions.e2e.ts",