feat(webworker): add selectable preview fixtures

This commit is contained in:
imccyu
2026-08-24 10:37:04 +08:00
parent 181a0e18ef
commit e883dc2354
43 changed files with 2084 additions and 108 deletions
+9 -7
View File
@@ -1,12 +1,14 @@
/**
* Worker-preview bootstrap: the one module preview.html adds ahead of the
* stock entry tag. Connecting the worker host installs the boot globals and
* settles `__DSH_BOOT_READY__`, where the stock entry's pre-boot await holds,
* so everything after this module is the served startup chain verbatim. A
* failed handshake rejects the deferred into the boot page's failure
* rendering; this module owns no page painting.
* stock entry tag. The runtime's optional source stage owns the pre-Cordis
* chooser; the unchanged Host connector then owns the Worker handshake.
* Everything after those calls is the served startup chain verbatim.
*/
import DshWorker from '@deepseek-ai/dsh-experimental-webworker-runtime/worker?worker'
import { connectWorkerHost, IMAGE_FILE_NAME } from '@deepseek-ai/dsh-experimental-webworker-runtime/client'
import {
chooseWorkerHostSource, connectWorkerHost, IMAGE_FILE_NAME,
} from '@deepseek-ai/dsh-experimental-webworker-runtime/client'
await connectWorkerHost(new DshWorker({ name: 'dsh-host' }), { image: `preview/${IMAGE_FILE_NAME}` })
const image = `preview/${IMAGE_FILE_NAME}`
const source = await chooseWorkerHostSource({ image })
await connectWorkerHost(new DshWorker({ name: 'dsh-host' }), { image, overlays: source.overlays })
+191 -44
View File
@@ -8,27 +8,33 @@
* Two milestones prove that happened — the host's `tree active` boot line,
* whose lowering contract must be the one this checkout's packer emits, and the
* workspace hero, which paints only after the client tree comes up over the
* tunnel. The same page then creates a Workspace and Session, lists skills,
* and writes through the settings and credentials providers, exercising the
* upstream Chokidar instances over the Worker filesystem implementation.
* tunnel. The same page opens the seeded Workspace and showcase Session,
* verifies its tool/subagent/history examples, then writes through the
* settings and credentials providers. That keeps the upstream Chokidar
* instances exercised over the Worker filesystem implementation.
*
* The site is served the way a static host serves it: bytes from `dist/` with
* no rewrite rules, so a missing file is a 404 rather than the index page.
*/
import { existsSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs'
import { existsSync, mkdirSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs'
import { readFile } from 'node:fs/promises'
import { createServer } from 'node:http'
import type { IncomingMessage, ServerResponse } from 'node:http'
import { tmpdir } from 'node:os'
import { extname, join, normalize } from 'node:path'
import { dirname, extname, join, normalize } from 'node:path'
import { fileURLToPath } from 'node:url'
import { chromium } from 'playwright'
import type { Browser } from 'playwright'
import { expect, it } from 'vitest'
import {
composeProfile, configTrees, indexWorkspacePackages, packVfsImage, WRAPPER_CONTRACT,
composeProfile, configTrees, indexWorkspacePackages, packVfsImage, packVfsOverlay,
previewFixtures, WRAPPER_CONTRACT,
} from '@deepseek-ai/dsh-experimental-webworker-packer'
import { IMAGE_FILE_NAME } from '@deepseek-ai/dsh-experimental-webworker-runtime'
import {
IMAGE_FILE_NAME, PREVIEW_FIXTURE_MANIFEST_FILE, PREVIEW_FIXTURE_MANIFEST_VERSION,
type PreviewFixtureManifest,
} from '@deepseek-ai/dsh-experimental-webworker-runtime'
import { captureStableAria, compareOrRefreshGolden, webSnapshotMode } from './scaffold.ts'
import { newEnglishPage, REPO_ROOT, saveFailureShot } from './support.ts'
const DIST_ROOT = fileURLToPath(new URL('../dist', import.meta.url))
@@ -36,9 +42,22 @@ const DIST_ROOT = fileURLToPath(new URL('../dist', import.meta.url))
/** Where the client looks for the image: the runtime's own name, beside the page. */
const IMAGE_FILE = join(DIST_ROOT, 'preview', IMAGE_FILE_NAME)
/** Built-in source catalog read by the pre-boot chooser. */
const FIXTURE_MANIFEST_FILE = join(DIST_ROOT, 'preview', PREVIEW_FIXTURE_MANIFEST_FILE)
/** Keyless browser golden for the pre-Worker source chooser. */
const SOURCE_CHOOSER_EXPECTED = fileURLToPath(new URL('./snapshots/preview-boot/source-chooser.expected.md', import.meta.url))
const SNAPSHOT_MODE = webSnapshotMode()
/** Profile the preview deployment composes; `build:preview` packs the same one. */
const PROFILE = 'web'
/** Stable labels authored by the deterministic VFS example fixture. */
const SHOWCASE_TITLE = 'WebWorker Preview Showcase'
const SHOWCASE_TAIL = 'Preview tour complete'
const SHOWCASE_OLDEST = 'History checkpoint 01: verify deterministic preview state.'
/** Pages the preview needs; the Vite build emits both. */
const PAGES = ['index.html', 'preview.html']
@@ -77,6 +96,12 @@ interface Site {
close(): Promise<void>
}
interface PreviewAssets {
/** Static-host-relative path to a generated file outside `dist/`. */
readonly overrides: ReadonlyMap<string, string>
cleanup(): void
}
/**
* Fail before the browser opens a page the build never produced.
* @throws When either preview page is missing from `dist/`.
@@ -89,20 +114,25 @@ function requirePreviewPages(): void {
}
/**
* The image file to serve, packed here when `dist/` carries none: `pnpm run
* build` emits the pages but only `build:preview` packs, so this lane packs
* for itself rather than skipping the deployment it is here to accept. An
* image already in place is used as it stands — the worker refuses one lowered
* against another wrapper contract, and that refusal names the rebuild. A
* self-packed image lands in a temp directory, never in `dist/`: the
* The base image, fixture manifest, and overlays to serve, packed here when
* `dist/` does not carry the complete set: `pnpm run build` emits the pages but
* only `build:preview` packs these files, so this lane packs for itself rather
* than skipping the deployment it accepts. A complete built set is used as it
* stands — the worker refuses a base lowered against another wrapper contract.
* Self-packed files land in a temp directory, never in `dist/`: the
* client-artifact digest record treats `dist/` as build-owned, so a test write
* there fails the record check for every later consumer.
* @returns The file to answer `preview/<image>` with, and its teardown.
* @returns Static-path overrides and their teardown.
* @throws When the closure leaves dependencies unresolved, which would pack an
* incomplete image the tree fails on later and further from the cause.
*/
function requireVfsImage(): { path: string; cleanup(): void } {
if (existsSync(IMAGE_FILE)) return { path: IMAGE_FILE, cleanup: () => {} }
function requireVfsAssets(): PreviewAssets {
const fixtureDefinitions = previewFixtures(REPO_ROOT)
const fixtureFiles = fixtureDefinitions.map(fixture =>
join(DIST_ROOT, 'preview', 'fixtures', `${fixture.id}.tar.gz`))
if ([IMAGE_FILE, FIXTURE_MANIFEST_FILE, ...fixtureFiles].every(existsSync)) {
return { overrides: new Map(), cleanup: () => {} }
}
const packed = packVfsImage({
config: composeProfile(REPO_ROOT, PROFILE),
profile: PROFILE,
@@ -114,23 +144,48 @@ function requireVfsImage(): { path: string; cleanup(): void } {
throw new Error(`preview boot: ${String(packed.missing.length)} dependencies did not resolve: ${packed.missing.join(', ')}`)
}
const directory = mkdtempSync(join(tmpdir(), 'dsh-preview-boot-'))
const path = join(directory, IMAGE_FILE_NAME)
writeFileSync(path, packed.image)
return { path, cleanup: () => { rmSync(directory, { recursive: true, force: true }) } }
const overrides = new Map<string, string>()
const writeAsset = (relativePath: string, bytes: Uint8Array | string): void => {
const path = join(directory, relativePath)
mkdirSync(dirname(path), { recursive: true })
writeFileSync(path, bytes)
overrides.set(relativePath, path)
}
writeAsset(`preview/${IMAGE_FILE_NAME}`, packed.image)
const fixtures = fixtureDefinitions.map((fixture) => {
const relativePath = `preview/fixtures/${fixture.id}.tar.gz`
writeAsset(relativePath, packVfsOverlay(fixture.trees).image)
return {
id: fixture.id,
label: fixture.label,
description: fixture.description,
overlays: [`fixtures/${fixture.id}.tar.gz`],
}
})
const manifest: PreviewFixtureManifest = {
version: PREVIEW_FIXTURE_MANIFEST_VERSION,
defaultFixture: fixtures[0]?.id ?? null,
fixtures,
}
writeAsset(`preview/${PREVIEW_FIXTURE_MANIFEST_FILE}`, `${JSON.stringify(manifest, null, 2)}\n`)
return { overrides, cleanup: () => { rmSync(directory, { recursive: true, force: true }) } }
}
/**
* Answer one request with the file it names under `dist/`; the image path
* answers from wherever {@link requireVfsImage} put the file.
* Answer one request with its generated override or the file under `dist/`.
* @param request - Incoming request; only its path is read.
* @param response - Response to write the bytes or the 404 to.
* @param imagePath - File behind `preview/<image>`.
* @param overrides - Generated deployment files used when `dist/` has none.
*/
async function respond(request: IncomingMessage, response: ServerResponse, imagePath: string): Promise<void> {
async function respond(
request: IncomingMessage,
response: ServerResponse,
overrides: ReadonlyMap<string, string>,
): Promise<void> {
const path = new URL(request.url ?? '/', 'http://127.0.0.1').pathname
const relative = normalize(decodeURIComponent(path)).replace(/^\/+/, '')
try {
const body = await readFile(relative === `preview/${IMAGE_FILE_NAME}` ? imagePath : join(DIST_ROOT, relative))
const body = await readFile(overrides.get(relative) ?? join(DIST_ROOT, relative))
response.writeHead(200, { 'content-type': MIME[extname(relative)] ?? 'application/octet-stream' })
response.end(body)
} catch {
@@ -144,11 +199,11 @@ async function respond(request: IncomingMessage, response: ServerResponse, image
/**
* Serve `dist/` over loopback with static-host semantics.
* @param imagePath - File behind `preview/<image>`.
* @param overrides - Generated deployment files used when `dist/` has none.
* @returns The origin to navigate, and its teardown.
*/
async function serveDist(imagePath: string): Promise<Site> {
const server = createServer((request, response) => { void respond(request, response, imagePath) })
async function serveDist(overrides: ReadonlyMap<string, string>): Promise<Site> {
const server = createServer((request, response) => { void respond(request, response, overrides) })
await new Promise<void>((listening) => { server.listen(0, '127.0.0.1', listening) })
const address = server.address()
if (address === null || typeof address === 'string') throw new Error('preview boot: the static server bound no port')
@@ -188,12 +243,13 @@ async function within<T>(work: Promise<T>, ms: number, stalled: string): Promise
it('boots the packed worker deployment to an interactive page', async () => {
requirePreviewPages()
const image = requireVfsImage()
const assets = requireVfsAssets()
try {
const site = await serveDist(image.path)
const site = await serveDist(assets.overrides)
try {
const browser = await chromium.launch({ headless: true, args: ['--no-sandbox', '--disable-dev-shm-usage'] })
try {
await bootEmptyPreview(site.origin, browser)
await bootPreview(site.origin, browser)
} finally {
await browser.close()
@@ -202,7 +258,7 @@ it('boots the packed worker deployment to an interactive page', async () => {
await site.close()
}
} finally {
image.cleanup()
assets.cleanup()
}
}, 600_000)
@@ -227,26 +283,34 @@ async function bootPreview(origin: string, browser: Browser): Promise<void> {
})
try {
await page.goto(`${origin}/preview.html`, { waitUntil: 'domcontentloaded' })
await page.getByRole('heading', { name: '选择 Preview 数据源' }).waitFor()
expect(await page.locator('input[name="preview-source"][value="vfs-example"]').isChecked()).toBe(true)
expect(await page.getByText('空白环境', { exact: true }).count()).toBe(1)
expect(await page.getByText('WebFS 目录', { exact: true }).count()).toBe(1)
expect(await page.locator('input[name="preview-source"][value="webfs"]').isDisabled()).toBe(true)
expect(await page.getByRole('textbox', { name: 'Choose workspace' }).count()).toBe(0)
await compareOrRefreshGolden(
SOURCE_CHOOSER_EXPECTED,
await captureStableAria(page, '[data-preview-source-card]', '/__preview_no_workspace__'),
SNAPSHOT_MODE,
)
await page.getByRole('button', { name: '启动 Preview' }).click()
const bootLine = await within(treeActive, BOOT_TIMEOUT_MS, `preview boot: the worker never reported "${TREE_ACTIVE}"`)
// The activated tree ran bodies lowered against the contract this
// checkout's packer emits; a dist built before a contract change would
// report the older one.
expect(bootLine).toContain(`image lowering=${WRAPPER_CONTRACT}`)
expect(bootLine).toContain('data overlays=1')
// The hero's workspace picker is the client tree's first interactive
// surface, so it appears only once the startup chain completed over the
// tunnel.
await page.getByRole('textbox', { name: 'Choose workspace' }).waitFor({ timeout: HERO_TIMEOUT_MS })
const continueButton = page.getByRole('button', { name: 'Continue' })
if (await continueButton.isVisible()) await continueButton.click()
await page.getByRole('button', { name: 'Configure later' }).click()
await page.getByRole('textbox', { name: 'Choose workspace' }).click()
const dialog = page.getByRole('dialog', { name: 'Select Workspace Directory' })
await dialog.waitFor({ timeout: 10_000 })
await dialog.getByRole('button', { name: 'Edit path' }).click()
const pathInput = dialog.getByRole('textbox', { name: 'Edit path' })
await pathInput.fill('/dsh/workspace')
await pathInput.press('Enter')
await dialog.getByRole('button', { name: 'Open', exact: true }).click()
await continueButton.waitFor({ timeout: 30_000 })
await continueButton.click()
const configureLater = page.getByRole('button', { name: 'Configure later' })
await configureLater.waitFor({ timeout: 30_000 })
await configureLater.click()
await page.locator('textarea:enabled[placeholder="Describe what you want to build"]')
.waitFor({ timeout: 30_000 })
@@ -296,9 +360,7 @@ async function bootPreview(origin: string, browser: Browser): Promise<void> {
const refreshed = await api.skills.list({ sessionId })
if (!refreshed.result.ok) throw new Error(`skill.list refresh failed: ${refreshed.result.error.message}`)
}
await createDirectory('/dsh/workspace', '.agents')
await createDirectory('/dsh/workspace/.agents', 'skills')
await createDirectory('/dsh/workspace/.agents/skills', 'placeholder')
await createDirectory('/dsh/workspace/.agents/skills', 'runtime-created')
const settings = await api.settings.describe({})
if (!settings.result.ok) throw new Error(`settings.describe failed: ${settings.result.error.message}`)
const shell = settings.result.value.namespaces.find(namespace => namespace.ns === 'shell')
@@ -317,8 +379,31 @@ async function bootPreview(origin: string, browser: Browser): Promise<void> {
credentialConfigured: credentials.result.value.credentials.PREVIEW_TEST_SECRET?.configured,
}
})
expect(exercised.skillCount).toBeGreaterThanOrEqual(0)
expect(exercised.skillCount).toBeGreaterThan(0)
expect(exercised.credentialConfigured).toBe(true)
const sessions = page.getByRole('tree', { name: 'Sessions' })
const showcase = sessions.getByRole('treeitem').filter({ hasText: SHOWCASE_TITLE })
await expect.poll(() => showcase.count(), { timeout: 15_000 }).toBe(1)
await showcase.click()
await page.getByText(SHOWCASE_TAIL, { exact: true }).waitFor({ timeout: 30_000 })
expect(await page.getByText(SHOWCASE_OLDEST, { exact: true }).count()).toBe(0)
await page.getByText('PREVIEW.md', { exact: true }).waitFor()
await page.getByText('src/preview.ts', { exact: true }).waitFor()
await page.getByText('Update to-do list', { exact: true }).waitFor()
await page.getByText('Error: ENOENT: no such file, open missing.txt', { exact: true }).waitFor()
const subagents = page.getByRole('button', { name: '2 subagents' })
await subagents.waitFor({ timeout: 15_000 })
await subagents.hover()
const catalog = page.getByRole('tree', { name: 'Subagent sessions' })
await catalog.getByRole('treeitem', { name: /Review preview architecture/ }).waitFor()
await catalog.getByRole('treeitem', { name: /Continue preview verification/ }).waitFor()
await catalog.press('Escape')
await page.getByRole('button', { name: 'Load earlier', exact: true }).click()
await page.getByText(SHOWCASE_OLDEST, { exact: true }).waitFor({ timeout: 15_000 })
expect(pageErrors.map(error => error.message)).toEqual([])
expect(consoleErrors.filter(line =>
/watchFile|failed to watch|node-addon-landlock-run\.probe|sandbox backend is usable|SANDBOX_UNAVAILABLE/i.test(line))).toEqual([])
@@ -329,3 +414,65 @@ async function bootPreview(origin: string, browser: Browser): Promise<void> {
: new AggregateError([error, ...pageErrors], 'preview boot failed, with uncaught page errors')
}
}
/** Verify the chooser can boot the untouched base image and reach first-run UI. */
async function bootEmptyPreview(origin: string, browser: Browser): Promise<void> {
const page = await newEnglishPage(browser)
const pageErrors: Error[] = []
const consoleErrors: string[] = []
const failedResponses: string[] = []
page.on('pageerror', (error) => { pageErrors.push(error) })
page.on('response', (response) => {
if (response.status() >= 400) failedResponses.push(new URL(response.url()).pathname)
})
const treeActive = new Promise<string>((reported) => {
page.on('console', (message) => {
const text = message.text()
if (text.includes(TREE_ACTIVE)) reported(text)
if (message.type() === 'error' || message.type() === 'warning') consoleErrors.push(text)
})
})
try {
await page.goto(`${origin}/preview.html?preview-fixture=none`, { waitUntil: 'domcontentloaded' })
expect(await page.getByRole('heading', { name: '选择 Preview 数据源' }).count()).toBe(0)
const bootLine = await within(
treeActive,
BOOT_TIMEOUT_MS,
`empty preview boot: the worker never reported "${TREE_ACTIVE}"`,
)
expect(bootLine).toContain(`image lowering=${WRAPPER_CONTRACT}`)
expect(bootLine).toContain('data overlays=0')
await page.getByRole('textbox', { name: 'Choose workspace' }).waitFor({ timeout: HERO_TIMEOUT_MS })
const sessionCount = await page.evaluate(async () => {
const transport = (globalThis as typeof globalThis & {
__DSH_TRANSPORT__?: { fetch(input: string, init: RequestInit): Promise<Response> }
}).__DSH_TRANSPORT__
if (transport === undefined) throw new Error('empty preview transport is absent after boot')
const response = await transport.fetch('/api/session/list', {
method: 'POST',
headers: { 'content-type': 'application/json' },
body: JSON.stringify({
type: 'client-request', rpcId: 'empty-preview-session-list', method: 'session/list',
payload: { args: { _request: {} } },
}),
})
const body = await response.json() as {
result: { ok: true; value: { items: unknown[] } } | { ok: false; error: { message: string } }
}
if (!body.result.ok) throw new Error(`empty session/list failed: ${body.result.error.message}`)
return body.result.value.items.length
})
expect(sessionCount).toBe(0)
expect(pageErrors.map(error => error.message)).toEqual([])
expect(failedResponses).toEqual(['/plugins/events'])
expect(consoleErrors.filter(line => !line.includes('Failed to load resource: the server responded with a status of 404')))
.toEqual([])
} catch (error) {
await saveFailureShot(page, 'preview-boot-empty')
throw pageErrors.length === 0
? error
: new AggregateError([error, ...pageErrors], 'empty preview boot failed, with uncaught page errors')
} finally {
await page.close()
}
}
@@ -0,0 +1,15 @@
- form "选择 Preview 数据源":
- heading "选择 Preview 数据源" [level=1]
- paragraph: 数据会在 Worker 和应用启动前挂载;刷新页面可重新选择。
- group "文件系统来源":
- text: 文件系统来源
- radio "空白环境 只加载基础运行时,用于验证首次启动与新建 Workspace。"
- strong: 空白环境
- text: 只加载基础运行时,用于验证首次启动与新建 Workspace。
- radio "内置综合示例 示例 Workspace、工具卡、子代理与分页会话。" [checked]
- strong: 内置综合示例
- text: 示例 Workspace、工具卡、子代理与分页会话。
- radio "WebFS 目录 需要用户授权的目录来源,将在 WebFS provider 接入后开放。" [disabled]
- strong: WebFS 目录
- text: 需要用户授权的目录来源,将在 WebFS provider 接入后开放。
- button "启动 Preview"
+2 -1
View File
@@ -241,7 +241,8 @@
"packages/experimental/webworker-runtime": {
"entry": [
"tests/**/*.spec.ts",
"tests/compile/transform-corpus-check.ts"
"tests/compile/transform-corpus-check.ts",
"tests/fixtures/vfs-example/workspace/src/preview.ts"
],
"project": [
"src/**/*.ts",
@@ -1,6 +1,6 @@
{
"name": "@deepseek-ai/dsh-experimental-webworker-packer",
"description": "Build-time packer for the browser runtime's VFS image: materializes a profile's package closure into one gzip-compressed tar the worker mounts, with every module body pre-transformed",
"description": "Build-time packer for the browser runtime's base VFS image and ordered data-overlay archives",
"version": "0.1.1-rc.2",
"private": true,
"repository": {
@@ -1,17 +1,23 @@
#!/usr/bin/env node
/**
* Pack a VFS image from this repository: compose the profile, materialize the
* closure, lower every module body, write the gzip-compressed tar.
* Pack a Preview deployment from this repository: compose and lower the base
* image, then write each named fixture overlay and their manifest.
*
* Usage: dsh-pack-vfs-image --out <file> [--profile web] [--root /dsh]
* node --import tsx/esm src/bin.ts --out ../../apps/web/dist/preview/vfs-image.tar.gz
* @module @deepseek-ai/dsh-experimental-webworker-packer/src/bin
*/
import { mkdirSync, writeFileSync } from 'node:fs'
import { dirname, isAbsolute, resolve } from 'node:path'
import { dirname, isAbsolute, join, resolve } from 'node:path'
import { fileURLToPath } from 'node:url'
import { packVfsImage } from './pack.ts'
import { composeProfile, configTrees, describePack, indexWorkspacePackages } from './repository.ts'
import {
PREVIEW_FIXTURE_MANIFEST_FILE, PREVIEW_FIXTURE_MANIFEST_VERSION,
type PreviewFixtureManifest,
} from '@deepseek-ai/dsh-experimental-webworker-runtime'
import { packVfsImage, packVfsOverlay } from './pack.ts'
import {
composeProfile, configTrees, describePack, indexWorkspacePackages, previewFixtures,
} from './repository.ts'
/**
* Read one `--flag value` pair.
@@ -54,4 +60,30 @@ if (result.missing.length > 0) {
mkdirSync(dirname(outputFile), { recursive: true })
writeFileSync(outputFile, result.image)
process.stdout.write(describePack(result, repoRoot, outputFile).join('\n'))
const fixtureDefinitions = previewFixtures(repoRoot)
const fixtureDirectory = join(dirname(outputFile), 'fixtures')
mkdirSync(fixtureDirectory, { recursive: true })
const fixtureLines: string[] = []
const fixtures = fixtureDefinitions.map((fixture) => {
const packed = packVfsOverlay(fixture.trees)
const file = `fixtures/${fixture.id}.tar.gz`
writeFileSync(join(dirname(outputFile), file), packed.image)
fixtureLines.push(` fixture overlay ${fixture.id} (${String(packed.image.byteLength)} B compressed)`)
return {
id: fixture.id,
label: fixture.label,
description: fixture.description,
overlays: [file],
}
})
const manifest: PreviewFixtureManifest = {
version: PREVIEW_FIXTURE_MANIFEST_VERSION,
defaultFixture: fixtures[0]?.id ?? null,
fixtures,
}
writeFileSync(
join(dirname(outputFile), PREVIEW_FIXTURE_MANIFEST_FILE),
`${JSON.stringify(manifest, null, 2)}\n`,
)
process.stdout.write([...describePack(result, repoRoot, outputFile), ...fixtureLines, ''].join('\n'))
@@ -7,9 +7,10 @@ export {
type ImageFiles, type TransformOutcome,
} from './transform-image.ts'
export {
CONFIG_PATH, DEFAULT_ROOT, MANIFEST_PATH, packVfsImage,
type ConfigTree, type PackOptions, type PackResult,
CONFIG_PATH, DEFAULT_ROOT, MANIFEST_PATH, packVfsImage, packVfsOverlay,
type ConfigTree, type ImageTree, type PackOptions, type PackOverlayResult, type PackResult,
} from './pack.ts'
export {
composeProfile, configTrees, describePack, indexWorkspacePackages,
composeProfile, configTrees, describePack, indexWorkspacePackages, previewFixtures,
type PreviewFixture,
} from './repository.ts'
@@ -19,6 +19,7 @@ import { gzipSync } from 'node:zlib'
import {
lowerModuleSource, MemoryVfs, packTar, WorkerModuleLoader,
DEFAULT_ROOT, IMAGE_CONFIG_PATH, IMAGE_EMPTY_DIRECTORIES, IMAGE_MANIFEST_PATH,
IMAGE_OVERLAY_DIRECTORIES,
} from '@deepseek-ai/dsh-experimental-webworker-runtime'
import picomatch from 'picomatch'
import yaml from 'js-yaml'
@@ -52,12 +53,16 @@ const workspaceExcluded = picomatch([...EXCLUDE, ...EXCLUDE_WORKSPACE], { dot: t
/** Page-asset matcher over image paths ({@link PAGE_ASSETS}). */
const pageAsset = picomatch([...PAGE_ASSETS], { dot: true })
/** One directory tree to copy in verbatim beside the composition. */
export interface ConfigTree {
/** One directory tree to copy into the image at a caller-selected mount. */
export interface ImageTree {
/** Image path to mount it at, relative to the virtual root. */
readonly mount: string
/** Absolute source directory. */
readonly directory: string
}
/** One configuration tree whose plugin rows may extend the package roster. */
export interface ConfigTree extends ImageTree {
/**
* Whether plugin names inside its `.yml` files join the materialization closure.
* An agent preset mounts plugins the base composition never lists, and creating a
@@ -119,6 +124,14 @@ export interface PackResult {
readonly contract: string
}
/** One deterministic data-overlay archive and its uncompressed entries. */
export interface PackOverlayResult {
/** Gzip-compressed ustar bytes consumed by the Worker host. */
readonly image: Uint8Array
/** Every path in the overlay before compression. */
readonly files: ImageFiles
}
const readJson = (file: string): Record<string, unknown> =>
JSON.parse(readFileSync(file, 'utf8')) as Record<string, unknown>
@@ -204,20 +217,28 @@ function resolveDependency(fromDirectory: string, name: string): string | undefi
/**
* Collect files under one directory. Traversal mechanics live here — nested
* `node_modules` never mounts (the image is flat) and dot directories are
* tooling residue at any depth — while every judgement call comes in through
* `keep` (the {@link EXCLUDE} tables and the npm publish view).
* package/config collection flattens nested `node_modules` and prunes dot
* directories, while seed collection preserves every directory. Every file
* judgement comes in through `keep` (the {@link EXCLUDE} tables and the npm
* publish view, or an unconditional seed predicate).
* @param root - Source directory.
* @param into - Image entries to add to.
* @param prefix - Image path prefix.
* @param keep - Filter over root-relative paths.
* @param preserveDirectories - Whether dot directories and nested `node_modules`
* are ordinary fixture content rather than package-manager residue.
*/
function collectTree(root: string, into: ImageFiles, prefix: string, keep: (relativePath: string) => boolean): void {
function collectTree(
root: string,
into: ImageFiles,
prefix: string,
keep: (relativePath: string) => boolean,
preserveDirectories = false,
): void {
const walk = (directory: string): void => {
for (const entry of readdirSync(directory, { withFileTypes: true })) {
if (entry.isDirectory()) {
if (entry.name === 'node_modules') continue
if (entry.name.startsWith('.')) continue
if (!preserveDirectories && (entry.name === 'node_modules' || entry.name.startsWith('.'))) continue
walk(join(directory, entry.name))
continue
}
@@ -621,3 +642,32 @@ export function packVfsImage(options: PackOptions): PackResult {
contract: WRAPPER_CONTRACT,
}
}
/**
* Pack opaque data trees into one ordered VFS overlay.
*
* Overlay mounts are restricted to the runtime-owned data directories, so an
* overlay cannot replace configuration, the lowering manifest, or modules.
* Files bypass package excludes and module reachability processing; later
* trees replace earlier files at the same path.
* @param trees - Absolute source directories and their data-directory mounts.
* @returns Deterministic compressed archive plus its uncompressed entries.
*/
export function packVfsOverlay(trees: readonly ImageTree[]): PackOverlayResult {
const files: ImageFiles = {}
for (const tree of trees) {
if (!existsSync(tree.directory)) {
throw new Error(`vfs overlay: tree ${tree.mount} is missing at ${tree.directory}`)
}
const mount = tree.mount.replace(/^\.\//, '').replace(/\/$/, '')
const first = mount.split('/')[0]
if (mount === '' || first === undefined || !IMAGE_OVERLAY_DIRECTORIES.includes(first)
|| mount.split('/').some(segment => segment === '' || segment === '.' || segment === '..')) {
throw new Error(
`vfs overlay: mount ${JSON.stringify(tree.mount)} must stay under ${IMAGE_OVERLAY_DIRECTORIES.join(' or ')}`,
)
}
collectTree(tree.directory, files, mount, () => true, true)
}
return { image: compressImage(packTar(files)), files }
}
@@ -12,7 +12,7 @@ import { existsSync, mkdtempSync, readFileSync, readdirSync, rmSync } from 'node
import { tmpdir } from 'node:os'
import { join, relative } from 'node:path'
import { DSH_HOME_ENV } from '@deepseek-ai/dsh-home-paths'
import type { ConfigTree, PackResult } from './pack.ts'
import type { ConfigTree, ImageTree, PackResult } from './pack.ts'
/**
* Repository directories scanned for workspace and vendored packages. The
@@ -28,6 +28,21 @@ const CLI_PACKAGE = 'apps/cli'
/** Composition entry point: the `dsh` CLI, run from source. */
const CLI_ENTRY = `${CLI_PACKAGE}/src/bin.ts`
/** Repository-owned deterministic filesystem content offered by the preview. */
const PREVIEW_EXAMPLE_ROOT = 'packages/experimental/webworker-runtime/tests/fixtures/vfs-example'
/** One built-in Preview source and the trees packed into its overlay. */
export interface PreviewFixture {
/** URL/query-safe identifier. */
readonly id: string
/** User-facing chooser label. */
readonly label: string
/** User-facing chooser detail. */
readonly description: string
/** Opaque trees packed into this fixture's overlay archive. */
readonly trees: readonly ImageTree[]
}
/**
* Index every workspace and vendored package by name.
* @param repoRoot - Absolute repository root.
@@ -130,6 +145,23 @@ export function configTrees(repoRoot: string): ConfigTree[] {
})
}
/**
* Built-in filesystem fixtures offered by the repository preview.
* Session and Workspace semantics remain opaque here; the owning runtime tests
* validate those files through their production readers.
* @param repoRoot - Absolute repository root.
* @returns Named chooser entries and their overlay trees.
*/
export function previewFixtures(repoRoot: string): PreviewFixture[] {
const root = join(repoRoot, PREVIEW_EXAMPLE_ROOT)
return [{
id: 'vfs-example',
label: '内置综合示例',
description: '示例 Workspace、工具卡、子代理与分页会话。',
trees: ['home', 'workspace'].map(mount => ({ mount, directory: join(root, mount) })),
}]
}
/**
* Render one pack as the lines a build log should carry.
*
@@ -26,8 +26,8 @@ import {
} from '@deepseek-ai/dsh-experimental-webworker-runtime/src/module-system/module-loader.ts'
import { inflateImage } from '@deepseek-ai/dsh-experimental-webworker-runtime/src/storage/image-gzip.ts'
import { loadVfsImage } from '@deepseek-ai/dsh-experimental-webworker-runtime/src/storage/memory.ts'
import { indexWorkspacePackages } from '../src/repository.ts'
import { DEFAULT_ROOT, MANIFEST_PATH, packVfsImage } from '../src/pack.ts'
import { indexWorkspacePackages, previewFixtures } from '../src/repository.ts'
import { DEFAULT_ROOT, MANIFEST_PATH, packVfsImage, packVfsOverlay } from '../src/pack.ts'
const repoRoot = fileURLToPath(new URL('../../../../', import.meta.url))
@@ -37,6 +37,32 @@ const LANDLOCK = '@deepseek-ai/node-addon-landlock-run'
const workspaces = indexWorkspacePackages(repoRoot)
describe('preview example overlays', () => {
it('packs source-looking paths and dot directories into a separate overlay', () => {
const fixture = previewFixtures(repoRoot)[0]
expect(fixture?.id).toBe('vfs-example')
const result = packVfsOverlay(fixture?.trees ?? [])
expect(new TextDecoder().decode(result.files['workspace/src/preview.ts']))
.toContain("previewStatus = 'ready'")
expect(new TextDecoder().decode(result.files['workspace/.agents/skills/preview-tour/SKILL.md']))
.toContain('name: preview-tour')
expect(Object.keys(result.files).filter(path => path.endsWith('/session.jsonl'))).toHaveLength(3)
})
it('fails loud when a declared seed tree is absent', () => {
expect(() => packVfsOverlay([
{ mount: 'workspace', directory: join(repoRoot, 'missing-preview-seed') },
])).toThrow(/tree workspace is missing/)
})
it('refuses overlays that could replace runtime files', () => {
const fixture = previewFixtures(repoRoot)[0]
expect(() => packVfsOverlay([
{ mount: 'config', directory: fixture?.trees[0]?.directory ?? repoRoot },
])).toThrow(/must stay under home or workspace/)
})
})
/**
* The pack consumes built `lib/` output. An unbuilt checkout (the unit
* coverage lane runs before any build) self-skips; the built lanes and every
@@ -57,6 +57,9 @@
"@deepseek-ai/dsh-invariants": "workspace:^",
"@deepseek-ai/dsh-sandbox-local": "workspace:^",
"@deepseek-ai/dsh-sandbox-policy": "workspace:^",
"@deepseek-ai/dsh-session": "workspace:^",
"@deepseek-ai/dsh-session-persistence-jsonl": "workspace:^",
"@deepseek-ai/dsh-subagent": "workspace:^",
"@deepseek-ai/dsh-subprocess-local": "workspace:^",
"@deepseek-ai/node-addon-landlock-run": "workspace:^",
"@types/picomatch": "^3.0.2",
@@ -156,9 +156,10 @@ export class WorkerTunnel {
/**
* Open the tunnel: the worker assembles its host from this frame.
* @param image - VFS image URL the worker fetches.
* @param overlays - Ordered data overlay URLs applied before boot.
*/
init(image: string): void {
this.worker.postMessage({ t: 'init', image })
init(image: string, overlays: readonly string[] = []): void {
this.worker.postMessage({ t: 'init', image, overlays })
}
/** Fetch-shaped entry: one request frame, one Response (streamed when the worker streams). */
@@ -9,14 +9,20 @@
* @module @deepseek-ai/dsh-experimental-webworker-runtime/client
*/
import { IMAGE_FILE_NAME } from '../image-layout.ts'
import { PREVIEW_FIXTURE_MANIFEST_FILE } from '../fixture-manifest.ts'
import { WorkerApiClient } from './api-client.ts'
import { WorkerTunnel, type TunnelFetch } from './client.ts'
import { applyIndexInjections } from './apply-injections.ts'
import { choosePreviewSource } from './source-chooser.ts'
export { WorkerApiClient } from './api-client.ts'
export { WorkerTunnel, type TunnelFetch } from './client.ts'
export { applyIndexInjections } from './apply-injections.ts'
export { IMAGE_FILE_NAME } from '../image-layout.ts'
export {
parsePreviewFixtureManifest, PREVIEW_FIXTURE_MANIFEST_FILE, PREVIEW_FIXTURE_MANIFEST_VERSION,
type PreviewFixtureManifest, type PreviewFixtureManifestEntry,
} from '../fixture-manifest.ts'
/** Transport global the connection plugin reads instead of building an HTTP carrier. */
interface ClientTransportGlobal {
@@ -35,9 +41,25 @@ export interface WorkerHostConnectOptions {
/**
* VFS image URL, the one deployment-shaped input. Defaults to
* {@link IMAGE_FILE_NAME} beside the page; a deployment that packs the
* image elsewhere passes its own URL.
* image elsewhere passes its own URL. Data overlays are independent.
*/
readonly image?: string | URL
/** Ordered data overlay URLs, resolved against the page like the base image. */
readonly overlays?: readonly (string | URL)[]
}
/** Inputs for the optional pre-boot filesystem-source chooser. */
export interface WorkerHostSourceOptions {
/** Base VFS image URL; defaults to {@link IMAGE_FILE_NAME} beside the page. */
readonly image?: string | URL
/** Fixture catalog URL; defaults to {@link PREVIEW_FIXTURE_MANIFEST_FILE} beside the image. */
readonly fixtureManifest?: string | URL
}
/** Filesystem inputs selected before {@link connectWorkerHost}. */
export interface WorkerHostSource {
/** Ordered data overlays to pass through unchanged to the Host connection. */
readonly overlays: readonly URL[]
}
/** A page connected to a worker-hosted harness, ready to run a shell entry. */
@@ -53,12 +75,51 @@ interface BootReadyGlobal {
__DSH_BOOT_READY__?: PromiseWithResolvers<void>
}
function bootReadyGate(): PromiseWithResolvers<void> {
return (globalThis as BootReadyGlobal).__DSH_BOOT_READY__ ??= Promise.withResolvers<void>()
}
/**
* Install the page boot barrier before an asynchronous source chooser waits
* for user input. The later {@link connectWorkerHost} call settles the same
* barrier.
*/
function holdWorkerHostBoot(): void {
const ready = bootReadyGate()
// A chooser may remain open indefinitely; if a later connection fails before
// the stock entry subscribes, retain the rejection without browser noise.
void ready.promise.catch(() => {})
}
/**
* Run the optional pre-boot source-selection stage. Calling this stage holds
* the stock shell until the caller passes its result to {@link connectWorkerHost};
* callers that need no chooser call `connectWorkerHost` directly and receive
* the base image with an empty overlay list.
* @param options - Base image and optional fixture-catalog locations.
* @returns The ordered overlays selected by the user.
*/
export async function chooseWorkerHostSource(
options: WorkerHostSourceOptions = {},
): Promise<WorkerHostSource> {
holdWorkerHostBoot()
const image = new URL(options.image ?? IMAGE_FILE_NAME, document.baseURI)
const manifest = new URL(options.fixtureManifest ?? PREVIEW_FIXTURE_MANIFEST_FILE, image)
try {
const overlays = await choosePreviewSource(manifest)
return { overlays }
} catch (reason) {
bootReadyGate().reject(reason)
throw reason
}
}
/**
* Connect a spawned host worker and complete the pre-Cordis handshake.
*
* The caller constructs the Worker so its bundler resolves the bundle URL
* statically; the opening `init` frame then carries the image location, the
* only input the worker takes from outside.
* statically; the opening `init` frame then carries the base image and ordered
* overlay locations.
*
* Order is fixed by the web boot protocol: the transport global must exist
* before any bundle executes; the injection table then reproduces the served
@@ -70,17 +131,20 @@ interface BootReadyGlobal {
* row has taken effect, and surfaces a failed handshake instead of
* proceeding on missing globals.
* @param worker - The host worker.
* @param options - Image location override.
* @param options - Base-image and overlay location overrides.
* @returns The connection; hand `loadBundle` to the shell entry's boot seam.
*/
export async function connectWorkerHost(worker: Worker, options?: WorkerHostConnectOptions): Promise<WorkerHostConnection> {
const ready = (globalThis as BootReadyGlobal).__DSH_BOOT_READY__ ??= Promise.withResolvers<void>()
const ready = bootReadyGate()
// The handshake may fail before any entry awaits the promise; this no-op
// subscription keeps that from surfacing as an unhandled rejection.
void ready.promise.catch(() => {})
try {
const tunnel = new WorkerTunnel(worker)
tunnel.init(new URL(options?.image ?? IMAGE_FILE_NAME, document.baseURI).href)
tunnel.init(
new URL(options?.image ?? IMAGE_FILE_NAME, document.baseURI).href,
(options?.overlays ?? []).map(overlay => new URL(overlay, document.baseURI).href),
)
const payload = await tunnel.bootPayload()
;(globalThis as ClientTransportGlobal).__DSH_TRANSPORT__ = {
createApiClient: () => new WorkerApiClient(tunnel),
@@ -0,0 +1,178 @@
/** Pre-boot filesystem-source chooser for static WebWorker previews. */
import {
parsePreviewFixtureManifest, type PreviewFixtureManifestEntry,
} from '../fixture-manifest.ts'
const EMPTY_SOURCE = 'none'
const WEBFS_SOURCE = 'webfs'
const PREVIEW_FIXTURE_QUERY = 'preview-fixture'
interface PreviewSourceChoice {
readonly id: string
readonly label: string
readonly description: string
readonly overlays: readonly URL[]
readonly disabled?: boolean
}
const CHOOSER_STYLE = `
:root { color-scheme: light dark; }
body { margin: 0; }
[data-preview-source-chooser] {
min-height: 100vh;
display: grid;
place-items: center;
padding: 24px;
box-sizing: border-box;
color: #171717;
background: radial-gradient(circle at 50% 35%, #eef4ff 0, #f8fafc 42%, #f3f4f6 100%);
font: 14px/1.5 ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
}
[data-preview-source-card] {
width: min(560px, 100%);
box-sizing: border-box;
padding: 28px;
border: 1px solid #d8dee9;
border-radius: 20px;
background: rgba(255, 255, 255, 0.94);
box-shadow: 0 20px 60px rgba(15, 23, 42, 0.12);
}
[data-preview-source-card] h1 { margin: 0 0 6px; font-size: 24px; line-height: 1.25; }
[data-preview-source-card] > p { margin: 0 0 22px; color: #5b6472; }
[data-preview-source-card] fieldset { display: grid; gap: 10px; margin: 0; padding: 0; border: 0; }
[data-preview-source-card] legend { margin-bottom: 10px; font-weight: 650; }
[data-preview-source-option] {
display: grid;
grid-template-columns: auto 1fr;
gap: 2px 12px;
padding: 14px;
border: 1px solid #d8dee9;
border-radius: 12px;
cursor: pointer;
}
[data-preview-source-option]:has(input:checked) { border-color: #4777df; background: #edf3ff; }
[data-preview-source-option]:has(input:disabled) { cursor: not-allowed; opacity: 0.55; }
[data-preview-source-option] input { grid-row: 1 / span 2; margin: 4px 0 0; }
[data-preview-source-option] strong { font-size: 15px; }
[data-preview-source-option] span { color: #667085; }
[data-preview-source-submit] {
width: 100%;
margin-top: 20px;
padding: 11px 16px;
border: 0;
border-radius: 10px;
color: white;
background: #315fc7;
font: inherit;
font-weight: 650;
cursor: pointer;
}
[data-preview-source-submit]:disabled { cursor: not-allowed; opacity: 0.5; }
@media (prefers-color-scheme: dark) {
[data-preview-source-chooser] { color: #f4f4f5; background: radial-gradient(circle at 50% 35%, #172554 0, #111827 45%, #09090b 100%); }
[data-preview-source-card] { border-color: #374151; background: rgba(24, 24, 27, 0.96); box-shadow: 0 20px 60px rgba(0, 0, 0, 0.35); }
[data-preview-source-card] > p, [data-preview-source-option] span { color: #a1a1aa; }
[data-preview-source-option] { border-color: #3f3f46; }
[data-preview-source-option]:has(input:checked) { border-color: #7aa2ff; background: #172554; }
}
`
const ENTITIES: Readonly<Record<string, string>> = {
'&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;',
}
function escapeMarkup(value: string): string {
return value.replace(/[&<>"']/g, character => ENTITIES[character] ?? character)
}
function optionMarkup(choice: PreviewSourceChoice, selected: string): string {
return `<label data-preview-source-option>
<input type="radio" name="preview-source" value="${choice.id}"${choice.id === selected ? ' checked' : ''}${choice.disabled === true ? ' disabled' : ''}>
<strong>${escapeMarkup(choice.label)}</strong>
<span>${escapeMarkup(choice.description)}</span>
</label>`
}
function fixtureChoices(entries: readonly PreviewFixtureManifestEntry[], manifestUrl: URL): PreviewSourceChoice[] {
return entries.map(entry => ({
id: entry.id,
label: entry.label,
description: entry.description,
overlays: entry.overlays.map(overlay => new URL(overlay, manifestUrl)),
}))
}
/**
* Render the source chooser and wait for an enabled selection.
* @param manifestUrl - Built-in fixture catalog URL.
* @returns Ordered overlay URLs selected for the Worker mount.
*/
export async function choosePreviewSource(manifestUrl: URL): Promise<readonly URL[]> {
const requested = new URL(location.href).searchParams.get(PREVIEW_FIXTURE_QUERY)
if (requested === EMPTY_SOURCE) return []
const response = await fetch(manifestUrl)
if (!response.ok) {
throw new Error(`preview source chooser: fixture manifest returned ${String(response.status)}`)
}
const manifest = parsePreviewFixtureManifest(await response.json())
const choices: PreviewSourceChoice[] = [
{
id: EMPTY_SOURCE,
label: '空白环境',
description: '只加载基础运行时,用于验证首次启动与新建 Workspace。',
overlays: [],
},
...fixtureChoices(manifest.fixtures, manifestUrl),
{
id: WEBFS_SOURCE,
label: 'WebFS 目录',
description: '需要用户授权的目录来源,将在 WebFS provider 接入后开放。',
overlays: [],
disabled: true,
},
]
if (requested !== null) {
const requestedChoice = choices.find(choice => choice.id === requested && choice.disabled !== true)
if (requestedChoice === undefined) {
throw new Error(`preview source chooser: unknown or interactive source "${requested}"`)
}
return requestedChoice.overlays
}
const root = document.getElementById('root')
if (root === null) throw new Error('preview source chooser: missing #root')
const selected = manifest.defaultFixture ?? EMPTY_SOURCE
const style = document.createElement('style')
style.dataset.previewSourceStyle = ''
style.textContent = CHOOSER_STYLE
document.head.append(style)
root.innerHTML = `<main data-preview-source-chooser>
<form data-preview-source-card aria-labelledby="preview-source-title">
<h1 id="preview-source-title">选择 Preview 数据源</h1>
<p>数据会在 Worker 和应用启动前挂载;刷新页面可重新选择。</p>
<fieldset>
<legend>文件系统来源</legend>
${choices.map(choice => optionMarkup(choice, selected)).join('')}
</fieldset>
<button data-preview-source-submit type="submit">启动 Preview</button>
</form>
</main>`
const form = root.querySelector<HTMLFormElement>('[data-preview-source-card]')
if (form === null) throw new Error('preview source chooser: form was not rendered')
const sourceId = await new Promise<string>((resolve, reject) => {
form.addEventListener('submit', (event) => {
event.preventDefault()
const value = new FormData(form).get('preview-source')
if (typeof value === 'string') resolve(value)
else reject(new Error('preview source chooser: no source selected'))
}, { once: true })
})
const choice = choices.find(candidate => candidate.id === sourceId && candidate.disabled !== true)
if (choice === undefined) throw new Error(`preview source chooser: unavailable source "${sourceId}"`)
root.replaceChildren()
style.remove()
return choice.overlays
}
@@ -0,0 +1,67 @@
/** Browser-readable catalog of built-in Preview filesystem overlays. */
/** Manifest format version emitted beside the base VFS image. */
export const PREVIEW_FIXTURE_MANIFEST_VERSION = 1
/** Leaf name resolved beside the base image. */
export const PREVIEW_FIXTURE_MANIFEST_FILE = 'fixtures.json'
/** One selectable built-in fixture and its ordered overlay archives. */
export interface PreviewFixtureManifestEntry {
readonly id: string
readonly label: string
readonly description: string
readonly overlays: readonly string[]
}
/** Complete built-in fixture catalog consumed before Worker startup. */
export interface PreviewFixtureManifest {
readonly version: number
readonly defaultFixture: string | null
readonly fixtures: readonly PreviewFixtureManifestEntry[]
}
function recordOf(value: unknown): Record<string, unknown> | undefined {
return typeof value === 'object' && value !== null && !Array.isArray(value)
? value as Record<string, unknown>
: undefined
}
/**
* Validate the static fixture catalog before it controls Worker fetches.
* @param value - Parsed JSON response.
* @returns A detached manifest with unique ids and non-empty overlay lists.
*/
export function parsePreviewFixtureManifest(value: unknown): PreviewFixtureManifest {
const record = recordOf(value)
if (record?.version !== PREVIEW_FIXTURE_MANIFEST_VERSION || !Array.isArray(record.fixtures)) {
throw new Error(`preview fixture manifest must use version ${String(PREVIEW_FIXTURE_MANIFEST_VERSION)}`)
}
const fixtures: PreviewFixtureManifestEntry[] = []
const ids = new Set<string>()
for (const value of record.fixtures) {
const fixture = recordOf(value)
const id = fixture?.id
const label = fixture?.label
const description = fixture?.description
const overlays = fixture?.overlays
const overlayUrls = Array.isArray(overlays)
? overlays.filter((overlay): overlay is string => typeof overlay === 'string' && overlay.length > 0)
: []
if (typeof id !== 'string' || !/^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/.test(id)
|| id === 'none' || id === 'webfs'
|| typeof label !== 'string' || label.length === 0
|| typeof description !== 'string' || description.length === 0
|| !Array.isArray(overlays) || overlays.length === 0 || overlayUrls.length !== overlays.length) {
throw new Error('preview fixture manifest contains an invalid fixture entry')
}
if (ids.has(id)) throw new Error(`preview fixture manifest repeats id "${id}"`)
ids.add(id)
fixtures.push({ id, label, description, overlays: overlayUrls })
}
const defaultFixture = record.defaultFixture
if (defaultFixture !== null && (typeof defaultFixture !== 'string' || !ids.has(defaultFixture))) {
throw new Error('preview fixture manifest defaultFixture does not name a fixture')
}
return { version: PREVIEW_FIXTURE_MANIFEST_VERSION, defaultFixture, fixtures }
}
@@ -9,9 +9,9 @@
export const DEFAULT_ROOT = '/dsh'
/**
* Leaf name of the packed image: one gzip member holding the ustar archive. The
* app build writes it beside the page and the page's boot fetches it from there,
* so the extension is part of what a deployment serves.
* Leaf name of the packed base image: one gzip member holding the ustar archive.
* The app build writes it beside the page and the page's boot fetches it from
* there, so the extension is part of what a deployment serves.
*/
export const IMAGE_FILE_NAME = 'vfs-image.tar.gz'
@@ -27,6 +27,12 @@ export const IMAGE_HOME_DIRECTORY = 'home'
/** Working directories the host tree expects to exist, empty. */
export const IMAGE_EMPTY_DIRECTORIES: readonly string[] = ['home/', 'workspace/', 'tmp/']
/**
* Top-level directories an overlay archive may populate. Runtime code,
* configuration, and the lowering manifest remain owned by the base image.
*/
export const IMAGE_OVERLAY_DIRECTORIES: readonly string[] = ['home', 'workspace']
/**
* Identity of the lowered code shape, recorded in the image manifest by the
* packer and required by the worker host: an image lowered by an older transform
@@ -34,9 +34,13 @@ export {
} from './worker-host.ts'
export {
DEFAULT_ROOT, IMAGE_CONFIG_PATH, IMAGE_EMPTY_DIRECTORIES, IMAGE_FILE_NAME, IMAGE_HOME_DIRECTORY,
IMAGE_MANIFEST_PATH, LOWERING_VERSION, WRAPPER_PARAMS,
IMAGE_MANIFEST_PATH, IMAGE_OVERLAY_DIRECTORIES, LOWERING_VERSION, WRAPPER_PARAMS,
} from './image-layout.ts'
export { loadVfsImage, MemoryVfs } from './storage/memory.ts'
export {
parsePreviewFixtureManifest, PREVIEW_FIXTURE_MANIFEST_FILE, PREVIEW_FIXTURE_MANIFEST_VERSION,
type PreviewFixtureManifest, type PreviewFixtureManifestEntry,
} from './fixture-manifest.ts'
export { loadVfsImage, loadVfsOverlay, MemoryVfs } from './storage/memory.ts'
export { inflateImage, inflateImageStream } from './storage/image-gzip.ts'
export { packTar, parseTar, type TarEntry } from './storage/tar.ts'
export { requireActiveVfs, setActiveVfs } from './storage/active.ts'
@@ -413,6 +413,7 @@ export function watchAsync(
throw(reason?: unknown): Promise<IteratorResult<WatchEvent>> {
close()
// AsyncIterator.throw forwards the caller's exact reason, including non-Error values.
// oxlint-disable-next-line typescript/prefer-promise-reject-errors
return Promise.reject(reason)
},
}
@@ -23,6 +23,7 @@ type StreamStatics = typeof import('node:stream').Stream & {
const nodeStream = Stream as unknown as StreamRuntime
/* oxlint-disable typescript/unbound-method -- readable-stream's namespace statics do not read `this`. */
const {
Duplex, PassThrough, Readable, Stream: StreamBase, Transform, Writable,
addAbortSignal, compose, destroy, finished, isDisturbed, isErrored, isReadable, pipeline, promises,
@@ -31,6 +32,7 @@ const streamStatics = StreamBase as unknown as StreamStatics
const {
getDefaultHighWaterMark, isDestroyed, isWritable, setDefaultHighWaterMark,
} = streamStatics
/* oxlint-enable typescript/unbound-method */
// readable-stream tracks Node 18's 16 KiB byte default; this repository runs
// Node 22+, whose generic and file streams use 64 KiB.
@@ -76,13 +76,13 @@ function statsOf(stats: VfsStats): ShellStats {
*/
export function hostFileSystem(): ShellFileSystem {
const vfs = (): ReturnType<typeof requireActiveVfs> => requireActiveVfs()
const stat = async (path: string): Promise<ShellStats | undefined> => {
const stat = (path: string): Promise<ShellStats | undefined> => {
try {
return statsOf(vfs().statSync(path) as VfsStats)
return Promise.resolve(statsOf(vfs().statSync(path) as VfsStats))
} catch {
// Absence is the answer callers branch on; every other failure mode of
// the in-memory backend is also "this path holds nothing readable".
return undefined
return Promise.resolve(undefined)
}
}
// Several members take no await: the face is asynchronous because a process
@@ -101,18 +101,22 @@ export function hostFileSystem(): ShellFileSystem {
if ((await stat(path))?.directory === true) throw filesystemError('EISDIR', 'read', path)
return vfs().readFileSync(path, 'utf8') as string
},
writeText: async (path: string, text: string, append = false): Promise<void> => {
writeText: (path: string, text: string, append = false): Promise<void> => {
if (append) vfs().appendFileSync(path, text)
else vfs().writeFileSync(path, text)
return Promise.resolve()
},
mkdir: async (path: string, recursive: boolean): Promise<void> => {
mkdir: (path: string, recursive: boolean): Promise<void> => {
vfs().mkdirSync(path, { recursive })
return Promise.resolve()
},
remove: async (path: string, options: { recursive: boolean; force: boolean }): Promise<void> => {
remove: (path: string, options: { recursive: boolean; force: boolean }): Promise<void> => {
vfs().rmSync(path, options)
return Promise.resolve()
},
rename: async (from: string, to: string): Promise<void> => {
rename: (from: string, to: string): Promise<void> => {
vfs().renameSync(from, to)
return Promise.resolve()
},
}
}
@@ -5,6 +5,7 @@
* @module @deepseek-ai/dsh-experimental-webworker-runtime/src/storage/memory
*/
import { dirname, join, normalize, resolve, SEP } from '../module-system/posix-path.ts'
import { IMAGE_OVERLAY_DIRECTORIES } from '../image-layout.ts'
import { parseTar } from './tar.ts'
import type {
Vfs, VfsBigIntStats, VfsDir, VfsDirent, VfsEncoding, VfsError, VfsFileHandle, VfsMutation,
@@ -773,3 +774,38 @@ export function loadVfsImage(image: Uint8Array, root = '/dsh', vfs = new MemoryV
}
return vfs
}
/**
* Apply one ordered data overlay to an already mounted base image.
*
* Overlay entries may replace files only under the layout's data directories;
* module code, configuration, and the lowering manifest cannot be shadowed.
* Paths containing traversal segments are refused before normalization. Later
* overlays win for files, while file/directory type conflicts fail loud.
* @param image - Uncompressed ustar overlay archive.
* @param root - Virtual root shared with the base image.
* @param vfs - Mounted filesystem to update.
* @returns The same filesystem after applying the overlay.
*/
export function loadVfsOverlay(image: Uint8Array, root: string, vfs: MemoryVfs): MemoryVfs {
for (const entry of parseTar(image)) {
const relativeName = entry.name.startsWith('./') ? entry.name.slice(2) : entry.name
const path = relativeName.endsWith('/') ? relativeName.slice(0, -1) : relativeName
const segments = path.split('/')
if (path === '' || relativeName.startsWith(SEP)
|| segments.some(segment => segment === '' || segment === '.' || segment === '..')
|| !IMAGE_OVERLAY_DIRECTORIES.includes(segments[0] ?? '')) {
throw new Error(`webworker vfs: overlay entry must stay under ${IMAGE_OVERLAY_DIRECTORIES.join('/ or ')}, received "${entry.name}"`)
}
const target = join(root, path)
if (entry.directory) {
vfs.seedDirectory(target, { mode: entry.mode })
continue
}
if (vfs.existsSync(target) && vfs.statSync(target).isDirectory()) {
throw new Error(`webworker vfs: overlay file cannot replace directory "${target}"`)
}
vfs.seed(target, entry.bytes, { mode: entry.mode })
}
return vfs
}
@@ -33,12 +33,13 @@ export interface TunnelAbortFrame {
/** Frames the worker accepts. */
/**
* First inbound frame: the image URL, the one input the worker assembly
* takes from outside.
* First inbound frame: the base image URL and ordered data overlays selected
* before the worker assembly starts.
*/
export interface TunnelInitFrame {
readonly t: 'init'
readonly image: string
readonly overlays: readonly string[]
}
/** Every frame the page sends the worker. */
@@ -142,7 +143,10 @@ export function parseInboundFrame(data: unknown): TunnelInboundFrame {
if (typeof frame.image !== 'string') {
throw new Error('webworker tunnel: init frame needs a string image url')
}
return { t: 'init', image: frame.image }
if (!Array.isArray(frame.overlays) || frame.overlays.some(overlay => typeof overlay !== 'string')) {
throw new Error('webworker tunnel: init frame needs an array of string overlay urls')
}
return { t: 'init', image: frame.image, overlays: frame.overlays as string[] }
}
const id = frame.id
if (typeof id !== 'string' && typeof id !== 'number') {
@@ -29,7 +29,7 @@ import { installProcessGlobal } from './node/globals/process.ts'
import type { RequestListener } from './transport/synthetic-http.ts'
import { TunnelServer, type TunnelPort } from './transport/tunnel.ts'
import { inflateImage, inflateImageStream } from './storage/image-gzip.ts'
import { loadVfsImage, MemoryVfs } from './storage/memory.ts'
import { loadVfsImage, loadVfsOverlay, MemoryVfs } from './storage/memory.ts'
import { setActiveVfs } from './storage/active.ts'
import {
DEFAULT_ROOT, IMAGE_CONFIG_PATH, IMAGE_EMPTY_DIRECTORIES, IMAGE_HOME_DIRECTORY, IMAGE_MANIFEST_PATH,
@@ -87,6 +87,8 @@ export interface WorkerHostOptions {
readonly requestListener: () => Promise<RequestListener>
/** Image bytes, or the URL the worker fetches them from. */
readonly image: Uint8Array | string
/** Ordered data overlays applied after the base image and before boot. */
readonly overlays?: readonly (Uint8Array | string)[]
/** Virtual root; defaults to {@link DEFAULT_ROOT}. */
readonly root?: string
/** Composed configuration inside the image; defaults to `<root>/config/cordis.yml`. */
@@ -182,8 +184,12 @@ export function createWorkerHost(options: WorkerHostOptions): WorkerHost {
const home = join(root, IMAGE_HOME_DIRECTORY)
installProcessGlobal({ cwd: root, env: { DSH_HOME: home, HOME: home, ...options.env } })
const bytes = await readImage(options.image)
const [bytes, overlays] = await Promise.all([
readImage(options.image),
Promise.all((options.overlays ?? []).map(readImage)),
])
const mounted = loadVfsImage(bytes, root)
for (const overlay of overlays) loadVfsOverlay(overlay, root, mounted)
// Belt and braces over the image's own empty-directory entries: a hand
// -built image without them still boots.
for (const directory of IMAGE_EMPTY_DIRECTORIES) {
@@ -248,7 +254,7 @@ export function createWorkerHost(options: WorkerHostOptions): WorkerHost {
const shared = ctx.get('connection') !== undefined
const handler = directFetchHandler(ctx, toFetchHandler(apiProxy))
const usage = loader.usage()
console.info(`webworker host: tree active (modules=${String(usage.modules)}, preset root overlay=${presetOverlay ? 'applied' : 'already in roster'}, direct lane=${shared ? 'connection.createSharedFetchHandler (interceptors kept)' : 'api surface only'}, als causality=${options.alsCausality === undefined ? 'inert' : 'snapshot/restore'}, image lowering=${LOWERING_VERSION})`)
console.info(`webworker host: tree active (modules=${String(usage.modules)}, data overlays=${String(overlays.length)}, preset root overlay=${presetOverlay ? 'applied' : 'already in roster'}, direct lane=${shared ? 'connection.createSharedFetchHandler (interceptors kept)' : 'api surface only'}, als causality=${options.alsCausality === undefined ? 'inert' : 'snapshot/restore'}, image lowering=${LOWERING_VERSION})`)
tunnel.serve({
directFetch: (request: Request) => handler.fetch(request),
@@ -4,9 +4,9 @@
* listener; the assembly owns everything else (process global, VFS image,
* Cordis tree, tunnel server).
*
* The assembly needs the image location before it can exist, and it arrives in
* the tunnel's opening `init` frame — this bundle reads nothing from its own
* URL, so the deployment decides where both the bundle and the image live.
* The assembly needs the base image and selected overlays before it can exist;
* they arrive in the tunnel's opening `init` frame. This bundle reads nothing
* from its own URL, so the deployment decides where every archive lives.
* Messages before `init` queue here; requests during boot queue inside the
* host, which attaches its handler before its first await.
*/
@@ -52,12 +52,16 @@ self.addEventListener('message', (event: MessageEvent) => {
if (typeof data.image !== 'string') {
throw new Error('webworker: init frame needs a string image url')
}
if (!Array.isArray(data.overlays) || data.overlays.some(overlay => typeof overlay !== 'string')) {
throw new Error('webworker: init frame needs an array of string overlay urls')
}
const created = createWorkerHost({
staticModules: createNodeBuiltins(),
staticModulePrefixes: REPLACED_PREFIXES,
requestListener: whenRequestListener,
alsCausality,
image: data.image,
overlays: data.overlays as string[],
})
host = created
for (const queued of pending) {
@@ -0,0 +1,150 @@
// @vitest-environment jsdom
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import type { PreviewFixtureManifest } from '../../src/fixture-manifest.ts'
import { choosePreviewSource } from '../../src/client/source-chooser.ts'
const MANIFEST_URL = new URL('https://preview.test/preview/fixtures.json')
const MANIFEST: PreviewFixtureManifest = {
version: 1,
defaultFixture: 'example',
fixtures: [{
id: 'example',
label: 'Example & <demo> "quoted" \'single\'',
description: 'A deterministic example.',
overlays: ['fixtures/base.tar.gz', 'fixtures/tail.tar.gz'],
}],
}
function setLocation(search = ''): void {
history.replaceState({}, '', `/preview.html${search}`)
}
function installManifest(manifest: PreviewFixtureManifest = MANIFEST): ReturnType<typeof vi.fn> {
const fetch = vi.fn(async () => Response.json(manifest))
vi.stubGlobal('fetch', fetch)
return fetch
}
function submitChooser(): void {
const form = document.querySelector<HTMLFormElement>('[data-preview-source-card]')
if (form === null) throw new Error('test chooser form was not rendered')
form.dispatchEvent(new Event('submit', { bubbles: true, cancelable: true }))
}
describe('Preview source chooser', () => {
beforeEach(() => {
document.head.replaceChildren()
document.body.innerHTML = '<div id="root"></div>'
setLocation()
})
afterEach(() => {
vi.restoreAllMocks()
vi.unstubAllGlobals()
})
it('bypasses the chooser and manifest for an explicit empty source', async () => {
setLocation('?preview-fixture=none')
const fetch = installManifest()
await expect(choosePreviewSource(MANIFEST_URL)).resolves.toEqual([])
expect(fetch).not.toHaveBeenCalled()
expect(document.querySelector('[data-preview-source-chooser]')).toBeNull()
})
it('bypasses the chooser and resolves an explicit built-in fixture', async () => {
document.body.replaceChildren()
setLocation('?preview-fixture=example')
const fetch = installManifest()
await expect(choosePreviewSource(MANIFEST_URL)).resolves.toEqual([
new URL('https://preview.test/preview/fixtures/base.tar.gz'),
new URL('https://preview.test/preview/fixtures/tail.tar.gz'),
])
expect(fetch).toHaveBeenCalledOnce()
expect(document.querySelector('[data-preview-source-chooser]')).toBeNull()
})
it.each(['', 'missing', 'webfs'])(
'fails loud for the explicit unavailable source %j without opening the chooser',
async (source) => {
setLocation(`?preview-fixture=${source}`)
installManifest()
await expect(choosePreviewSource(MANIFEST_URL)).rejects.toThrow(/unknown or interactive source/)
expect(document.querySelector('[data-preview-source-chooser]')).toBeNull()
},
)
it('shows the chooser only when the query is absent and returns its default selection', async () => {
installManifest()
const selected = choosePreviewSource(MANIFEST_URL)
await vi.waitFor(() => {
expect(document.querySelector('[data-preview-source-chooser]')).not.toBeNull()
})
expect(document.querySelector<HTMLInputElement>('input[value="example"]')?.checked).toBe(true)
expect(document.querySelector<HTMLInputElement>('input[value="webfs"]')?.disabled).toBe(true)
expect(document.querySelector('[data-preview-source-card]')?.textContent)
.toContain(MANIFEST.fixtures[0]?.label)
expect(document.querySelector('[data-preview-source-card] script')).toBeNull()
submitChooser()
await expect(selected).resolves.toEqual([
new URL('https://preview.test/preview/fixtures/base.tar.gz'),
new URL('https://preview.test/preview/fixtures/tail.tar.gz'),
])
expect(document.getElementById('root')?.childElementCount).toBe(0)
expect(document.querySelector('[data-preview-source-style]')).toBeNull()
})
it('selects the empty source when the manifest has no default', async () => {
installManifest({ ...MANIFEST, defaultFixture: null })
const selected = choosePreviewSource(MANIFEST_URL)
await vi.waitFor(() => {
expect(document.querySelector<HTMLInputElement>('input[value="none"]')?.checked).toBe(true)
})
submitChooser()
await expect(selected).resolves.toEqual([])
})
it('reports manifest, mount, form, selection, and catalog failures', async () => {
vi.stubGlobal('fetch', vi.fn(async () => new Response('missing', { status: 404 })))
await expect(choosePreviewSource(MANIFEST_URL)).rejects.toThrow(/returned 404/)
installManifest()
document.body.replaceChildren()
await expect(choosePreviewSource(MANIFEST_URL)).rejects.toThrow(/missing #root/)
document.body.innerHTML = '<div id="root"></div>'
const root = document.getElementById('root')
if (root === null) throw new Error('test root is missing')
vi.spyOn(root, 'querySelector').mockReturnValueOnce(null)
await expect(choosePreviewSource(MANIFEST_URL)).rejects.toThrow(/form was not rendered/)
document.head.replaceChildren()
document.body.innerHTML = '<div id="root"></div>'
const missingSelection = choosePreviewSource(MANIFEST_URL)
await vi.waitFor(() => { expect(document.querySelector('form')).not.toBeNull() })
document.querySelectorAll('input[name="preview-source"]').forEach((input) => {
input.removeAttribute('name')
})
submitChooser()
await expect(missingSelection).rejects.toThrow(/no source selected/)
document.head.replaceChildren()
document.body.innerHTML = '<div id="root"></div>'
const unavailableSelection = choosePreviewSource(MANIFEST_URL)
await vi.waitFor(() => { expect(document.querySelector('form')).not.toBeNull() })
const selected = document.querySelector<HTMLInputElement>('input:checked')
if (selected === null) throw new Error('test selection is missing')
selected.value = 'missing'
submitChooser()
await expect(unavailableSelection).rejects.toThrow(/unavailable source/)
})
})
@@ -0,0 +1,48 @@
import { describe, expect, it } from 'vitest'
import {
parsePreviewFixtureManifest, PREVIEW_FIXTURE_MANIFEST_VERSION,
} from '../src/fixture-manifest.ts'
describe('Preview fixture manifest', () => {
it('accepts a unique named fixture with ordered overlays', () => {
expect(parsePreviewFixtureManifest({
version: PREVIEW_FIXTURE_MANIFEST_VERSION,
defaultFixture: 'example',
fixtures: [{
id: 'example',
label: 'Example',
description: 'A deterministic example.',
overlays: ['fixtures/base.tar.gz', 'fixtures/tail.tar.gz'],
}],
})).toEqual({
version: PREVIEW_FIXTURE_MANIFEST_VERSION,
defaultFixture: 'example',
fixtures: [{
id: 'example',
label: 'Example',
description: 'A deterministic example.',
overlays: ['fixtures/base.tar.gz', 'fixtures/tail.tar.gz'],
}],
})
})
it.each([
[{ version: 2, defaultFixture: null, fixtures: [] }, /must use version/],
[{ version: 1, defaultFixture: 'missing', fixtures: [] }, /defaultFixture/],
[{
version: 1,
defaultFixture: 'duplicate',
fixtures: [
{ id: 'duplicate', label: 'One', description: 'First.', overlays: ['one.tar.gz'] },
{ id: 'duplicate', label: 'Two', description: 'Second.', overlays: ['two.tar.gz'] },
],
}, /repeats id/],
[{
version: 1,
defaultFixture: 'none',
fixtures: [{ id: 'none', label: 'None', description: 'Reserved.', overlays: ['none.tar.gz'] }],
}, /invalid fixture entry/],
])('rejects malformed catalogs', (value, error) => {
expect(() => parsePreviewFixtureManifest(value)).toThrow(error)
})
})
@@ -0,0 +1,178 @@
{"type":"session","version":0,"id":"preview-architecture-review","createdAt":1787472100000,"cwd":"/dsh/workspace","parentSession":"preview-showcase","seedLength":169,"origin":"subagent","delegationDepth":1,"agentPreset":"standard"}
{"type":"turn/start","data":{"turn":1},"seq":0,"time":1787472000000}
{"type":"user/message","data":{"id":"preview-user-01","role":"user","content":[{"type":"text","text":"History checkpoint 01: verify deterministic preview state."}],"source":{"kind":"user"}},"surfaceOp":"append","seq":1,"time":1787472000001}
{"type":"session/title","data":{"title":"WebWorker Preview Showcase","messageSeqs":[],"source":{"kind":"user"}},"seq":2,"time":1787472000002}
{"type":"step/start","data":{"turn":1,"step":1},"seq":3,"time":1787472000003}
{"type":"assistant/message","data":{"turn":1,"step":1,"message":{"id":"preview-assistant-01","role":"assistant","content":[{"type":"text","text":"Checkpoint 01 is recorded."}],"source":{"kind":"model","provider":"preview-fixture","model":"deterministic"}}},"sourceEventSeqs":[],"surfaceOp":"append","seq":4,"time":1787472000004}
{"type":"step/end","data":{"turn":1,"step":1},"seq":5,"time":1787472000005}
{"type":"turn/end","data":{"turn":1,"reason":{"kind":"completed"}},"seq":6,"time":1787472000006}
{"type":"turn/start","data":{"turn":2},"seq":7,"time":1787472000007}
{"type":"user/message","data":{"id":"preview-user-02","role":"user","content":[{"type":"text","text":"History checkpoint 02: verify deterministic preview state."}],"source":{"kind":"user"}},"surfaceOp":"append","seq":8,"time":1787472000008}
{"type":"step/start","data":{"turn":2,"step":1},"seq":9,"time":1787472000009}
{"type":"assistant/message","data":{"turn":2,"step":1,"message":{"id":"preview-assistant-02","role":"assistant","content":[{"type":"text","text":"Checkpoint 02 is recorded."}],"source":{"kind":"model","provider":"preview-fixture","model":"deterministic"}}},"sourceEventSeqs":[],"surfaceOp":"append","seq":10,"time":1787472000010}
{"type":"step/end","data":{"turn":2,"step":1},"seq":11,"time":1787472000011}
{"type":"turn/end","data":{"turn":2,"reason":{"kind":"completed"}},"seq":12,"time":1787472000012}
{"type":"turn/start","data":{"turn":3},"seq":13,"time":1787472000013}
{"type":"user/message","data":{"id":"preview-user-03","role":"user","content":[{"type":"text","text":"History checkpoint 03: verify deterministic preview state."}],"source":{"kind":"user"}},"surfaceOp":"append","seq":14,"time":1787472000014}
{"type":"step/start","data":{"turn":3,"step":1},"seq":15,"time":1787472000015}
{"type":"assistant/message","data":{"turn":3,"step":1,"message":{"id":"preview-assistant-03","role":"assistant","content":[{"type":"text","text":"Checkpoint 03 is recorded."}],"source":{"kind":"model","provider":"preview-fixture","model":"deterministic"}}},"sourceEventSeqs":[],"surfaceOp":"append","seq":16,"time":1787472000016}
{"type":"step/end","data":{"turn":3,"step":1},"seq":17,"time":1787472000017}
{"type":"turn/end","data":{"turn":3,"reason":{"kind":"completed"}},"seq":18,"time":1787472000018}
{"type":"turn/start","data":{"turn":4},"seq":19,"time":1787472000019}
{"type":"user/message","data":{"id":"preview-user-04","role":"user","content":[{"type":"text","text":"History checkpoint 04: verify deterministic preview state."}],"source":{"kind":"user"}},"surfaceOp":"append","seq":20,"time":1787472000020}
{"type":"step/start","data":{"turn":4,"step":1},"seq":21,"time":1787472000021}
{"type":"assistant/message","data":{"turn":4,"step":1,"message":{"id":"preview-assistant-04","role":"assistant","content":[{"type":"text","text":"Checkpoint 04 is recorded."}],"source":{"kind":"model","provider":"preview-fixture","model":"deterministic"}}},"sourceEventSeqs":[],"surfaceOp":"append","seq":22,"time":1787472000022}
{"type":"step/end","data":{"turn":4,"step":1},"seq":23,"time":1787472000023}
{"type":"turn/end","data":{"turn":4,"reason":{"kind":"completed"}},"seq":24,"time":1787472000024}
{"type":"turn/start","data":{"turn":5},"seq":25,"time":1787472000025}
{"type":"user/message","data":{"id":"preview-user-05","role":"user","content":[{"type":"text","text":"History checkpoint 05: verify deterministic preview state."}],"source":{"kind":"user"}},"surfaceOp":"append","seq":26,"time":1787472000026}
{"type":"step/start","data":{"turn":5,"step":1},"seq":27,"time":1787472000027}
{"type":"assistant/message","data":{"turn":5,"step":1,"message":{"id":"preview-assistant-05","role":"assistant","content":[{"type":"text","text":"Checkpoint 05 is recorded."}],"source":{"kind":"model","provider":"preview-fixture","model":"deterministic"}}},"sourceEventSeqs":[],"surfaceOp":"append","seq":28,"time":1787472000028}
{"type":"step/end","data":{"turn":5,"step":1},"seq":29,"time":1787472000029}
{"type":"turn/end","data":{"turn":5,"reason":{"kind":"completed"}},"seq":30,"time":1787472000030}
{"type":"turn/start","data":{"turn":6},"seq":31,"time":1787472000031}
{"type":"user/message","data":{"id":"preview-user-06","role":"user","content":[{"type":"text","text":"History checkpoint 06: verify deterministic preview state."}],"source":{"kind":"user"}},"surfaceOp":"append","seq":32,"time":1787472000032}
{"type":"step/start","data":{"turn":6,"step":1},"seq":33,"time":1787472000033}
{"type":"assistant/message","data":{"turn":6,"step":1,"message":{"id":"preview-assistant-06","role":"assistant","content":[{"type":"text","text":"Checkpoint 06 is recorded."}],"source":{"kind":"model","provider":"preview-fixture","model":"deterministic"}}},"sourceEventSeqs":[],"surfaceOp":"append","seq":34,"time":1787472000034}
{"type":"step/end","data":{"turn":6,"step":1},"seq":35,"time":1787472000035}
{"type":"turn/end","data":{"turn":6,"reason":{"kind":"completed"}},"seq":36,"time":1787472000036}
{"type":"turn/start","data":{"turn":7},"seq":37,"time":1787472000037}
{"type":"user/message","data":{"id":"preview-user-07","role":"user","content":[{"type":"text","text":"History checkpoint 07: verify deterministic preview state."}],"source":{"kind":"user"}},"surfaceOp":"append","seq":38,"time":1787472000038}
{"type":"step/start","data":{"turn":7,"step":1},"seq":39,"time":1787472000039}
{"type":"assistant/message","data":{"turn":7,"step":1,"message":{"id":"preview-assistant-07","role":"assistant","content":[{"type":"text","text":"Checkpoint 07 is recorded."}],"source":{"kind":"model","provider":"preview-fixture","model":"deterministic"}}},"sourceEventSeqs":[],"surfaceOp":"append","seq":40,"time":1787472000040}
{"type":"step/end","data":{"turn":7,"step":1},"seq":41,"time":1787472000041}
{"type":"turn/end","data":{"turn":7,"reason":{"kind":"completed"}},"seq":42,"time":1787472000042}
{"type":"turn/start","data":{"turn":8},"seq":43,"time":1787472000043}
{"type":"user/message","data":{"id":"preview-user-08","role":"user","content":[{"type":"text","text":"History checkpoint 08: verify deterministic preview state."}],"source":{"kind":"user"}},"surfaceOp":"append","seq":44,"time":1787472000044}
{"type":"step/start","data":{"turn":8,"step":1},"seq":45,"time":1787472000045}
{"type":"assistant/message","data":{"turn":8,"step":1,"message":{"id":"preview-assistant-08","role":"assistant","content":[{"type":"text","text":"Checkpoint 08 is recorded."}],"source":{"kind":"model","provider":"preview-fixture","model":"deterministic"}}},"sourceEventSeqs":[],"surfaceOp":"append","seq":46,"time":1787472000046}
{"type":"step/end","data":{"turn":8,"step":1},"seq":47,"time":1787472000047}
{"type":"turn/end","data":{"turn":8,"reason":{"kind":"completed"}},"seq":48,"time":1787472000048}
{"type":"turn/start","data":{"turn":9},"seq":49,"time":1787472000049}
{"type":"user/message","data":{"id":"preview-user-09","role":"user","content":[{"type":"text","text":"History checkpoint 09: verify deterministic preview state."}],"source":{"kind":"user"}},"surfaceOp":"append","seq":50,"time":1787472000050}
{"type":"step/start","data":{"turn":9,"step":1},"seq":51,"time":1787472000051}
{"type":"assistant/message","data":{"turn":9,"step":1,"message":{"id":"preview-assistant-09","role":"assistant","content":[{"type":"text","text":"Checkpoint 09 is recorded."}],"source":{"kind":"model","provider":"preview-fixture","model":"deterministic"}}},"sourceEventSeqs":[],"surfaceOp":"append","seq":52,"time":1787472000052}
{"type":"step/end","data":{"turn":9,"step":1},"seq":53,"time":1787472000053}
{"type":"turn/end","data":{"turn":9,"reason":{"kind":"completed"}},"seq":54,"time":1787472000054}
{"type":"turn/start","data":{"turn":10},"seq":55,"time":1787472000055}
{"type":"user/message","data":{"id":"preview-user-10","role":"user","content":[{"type":"text","text":"History checkpoint 10: verify deterministic preview state."}],"source":{"kind":"user"}},"surfaceOp":"append","seq":56,"time":1787472000056}
{"type":"step/start","data":{"turn":10,"step":1},"seq":57,"time":1787472000057}
{"type":"assistant/message","data":{"turn":10,"step":1,"message":{"id":"preview-assistant-10","role":"assistant","content":[{"type":"text","text":"Checkpoint 10 is recorded."}],"source":{"kind":"model","provider":"preview-fixture","model":"deterministic"}}},"sourceEventSeqs":[],"surfaceOp":"append","seq":58,"time":1787472000058}
{"type":"step/end","data":{"turn":10,"step":1},"seq":59,"time":1787472000059}
{"type":"turn/end","data":{"turn":10,"reason":{"kind":"completed"}},"seq":60,"time":1787472000060}
{"type":"turn/start","data":{"turn":11},"seq":61,"time":1787472000061}
{"type":"user/message","data":{"id":"preview-user-11","role":"user","content":[{"type":"text","text":"History checkpoint 11: verify deterministic preview state."}],"source":{"kind":"user"}},"surfaceOp":"append","seq":62,"time":1787472000062}
{"type":"step/start","data":{"turn":11,"step":1},"seq":63,"time":1787472000063}
{"type":"assistant/message","data":{"turn":11,"step":1,"message":{"id":"preview-assistant-11","role":"assistant","content":[{"type":"text","text":"Checkpoint 11 is recorded."}],"source":{"kind":"model","provider":"preview-fixture","model":"deterministic"}}},"sourceEventSeqs":[],"surfaceOp":"append","seq":64,"time":1787472000064}
{"type":"step/end","data":{"turn":11,"step":1},"seq":65,"time":1787472000065}
{"type":"turn/end","data":{"turn":11,"reason":{"kind":"completed"}},"seq":66,"time":1787472000066}
{"type":"turn/start","data":{"turn":12},"seq":67,"time":1787472000067}
{"type":"user/message","data":{"id":"preview-user-12","role":"user","content":[{"type":"text","text":"History checkpoint 12: verify deterministic preview state."}],"source":{"kind":"user"}},"surfaceOp":"append","seq":68,"time":1787472000068}
{"type":"step/start","data":{"turn":12,"step":1},"seq":69,"time":1787472000069}
{"type":"assistant/message","data":{"turn":12,"step":1,"message":{"id":"preview-assistant-12","role":"assistant","content":[{"type":"text","text":"Checkpoint 12 is recorded."}],"source":{"kind":"model","provider":"preview-fixture","model":"deterministic"}}},"sourceEventSeqs":[],"surfaceOp":"append","seq":70,"time":1787472000070}
{"type":"step/end","data":{"turn":12,"step":1},"seq":71,"time":1787472000071}
{"type":"turn/end","data":{"turn":12,"reason":{"kind":"completed"}},"seq":72,"time":1787472000072}
{"type":"turn/start","data":{"turn":13},"seq":73,"time":1787472000073}
{"type":"user/message","data":{"id":"preview-user-13","role":"user","content":[{"type":"text","text":"History checkpoint 13: verify deterministic preview state."}],"source":{"kind":"user"}},"surfaceOp":"append","seq":74,"time":1787472000074}
{"type":"step/start","data":{"turn":13,"step":1},"seq":75,"time":1787472000075}
{"type":"assistant/message","data":{"turn":13,"step":1,"message":{"id":"preview-assistant-13","role":"assistant","content":[{"type":"text","text":"Checkpoint 13 is recorded."}],"source":{"kind":"model","provider":"preview-fixture","model":"deterministic"}}},"sourceEventSeqs":[],"surfaceOp":"append","seq":76,"time":1787472000076}
{"type":"step/end","data":{"turn":13,"step":1},"seq":77,"time":1787472000077}
{"type":"turn/end","data":{"turn":13,"reason":{"kind":"completed"}},"seq":78,"time":1787472000078}
{"type":"turn/start","data":{"turn":14},"seq":79,"time":1787472000079}
{"type":"user/message","data":{"id":"preview-user-14","role":"user","content":[{"type":"text","text":"History checkpoint 14: verify deterministic preview state."}],"source":{"kind":"user"}},"surfaceOp":"append","seq":80,"time":1787472000080}
{"type":"step/start","data":{"turn":14,"step":1},"seq":81,"time":1787472000081}
{"type":"assistant/message","data":{"turn":14,"step":1,"message":{"id":"preview-assistant-14","role":"assistant","content":[{"type":"text","text":"Checkpoint 14 is recorded."}],"source":{"kind":"model","provider":"preview-fixture","model":"deterministic"}}},"sourceEventSeqs":[],"surfaceOp":"append","seq":82,"time":1787472000082}
{"type":"step/end","data":{"turn":14,"step":1},"seq":83,"time":1787472000083}
{"type":"turn/end","data":{"turn":14,"reason":{"kind":"completed"}},"seq":84,"time":1787472000084}
{"type":"turn/start","data":{"turn":15},"seq":85,"time":1787472000085}
{"type":"user/message","data":{"id":"preview-user-15","role":"user","content":[{"type":"text","text":"History checkpoint 15: verify deterministic preview state."}],"source":{"kind":"user"}},"surfaceOp":"append","seq":86,"time":1787472000086}
{"type":"step/start","data":{"turn":15,"step":1},"seq":87,"time":1787472000087}
{"type":"assistant/message","data":{"turn":15,"step":1,"message":{"id":"preview-assistant-15","role":"assistant","content":[{"type":"text","text":"Checkpoint 15 is recorded."}],"source":{"kind":"model","provider":"preview-fixture","model":"deterministic"}}},"sourceEventSeqs":[],"surfaceOp":"append","seq":88,"time":1787472000088}
{"type":"step/end","data":{"turn":15,"step":1},"seq":89,"time":1787472000089}
{"type":"turn/end","data":{"turn":15,"reason":{"kind":"completed"}},"seq":90,"time":1787472000090}
{"type":"turn/start","data":{"turn":16},"seq":91,"time":1787472000091}
{"type":"user/message","data":{"id":"preview-user-16","role":"user","content":[{"type":"text","text":"History checkpoint 16: verify deterministic preview state."}],"source":{"kind":"user"}},"surfaceOp":"append","seq":92,"time":1787472000092}
{"type":"step/start","data":{"turn":16,"step":1},"seq":93,"time":1787472000093}
{"type":"assistant/message","data":{"turn":16,"step":1,"message":{"id":"preview-assistant-16","role":"assistant","content":[{"type":"text","text":"Checkpoint 16 is recorded."}],"source":{"kind":"model","provider":"preview-fixture","model":"deterministic"}}},"sourceEventSeqs":[],"surfaceOp":"append","seq":94,"time":1787472000094}
{"type":"step/end","data":{"turn":16,"step":1},"seq":95,"time":1787472000095}
{"type":"turn/end","data":{"turn":16,"reason":{"kind":"completed"}},"seq":96,"time":1787472000096}
{"type":"turn/start","data":{"turn":17},"seq":97,"time":1787472000097}
{"type":"user/message","data":{"id":"preview-user-17","role":"user","content":[{"type":"text","text":"History checkpoint 17: verify deterministic preview state."}],"source":{"kind":"user"}},"surfaceOp":"append","seq":98,"time":1787472000098}
{"type":"step/start","data":{"turn":17,"step":1},"seq":99,"time":1787472000099}
{"type":"assistant/message","data":{"turn":17,"step":1,"message":{"id":"preview-assistant-17","role":"assistant","content":[{"type":"text","text":"Checkpoint 17 is recorded."}],"source":{"kind":"model","provider":"preview-fixture","model":"deterministic"}}},"sourceEventSeqs":[],"surfaceOp":"append","seq":100,"time":1787472000100}
{"type":"step/end","data":{"turn":17,"step":1},"seq":101,"time":1787472000101}
{"type":"turn/end","data":{"turn":17,"reason":{"kind":"completed"}},"seq":102,"time":1787472000102}
{"type":"turn/start","data":{"turn":18},"seq":103,"time":1787472000103}
{"type":"user/message","data":{"id":"preview-user-18","role":"user","content":[{"type":"text","text":"History checkpoint 18: verify deterministic preview state."}],"source":{"kind":"user"}},"surfaceOp":"append","seq":104,"time":1787472000104}
{"type":"step/start","data":{"turn":18,"step":1},"seq":105,"time":1787472000105}
{"type":"assistant/message","data":{"turn":18,"step":1,"message":{"id":"preview-assistant-18","role":"assistant","content":[{"type":"text","text":"Checkpoint 18 is recorded."}],"source":{"kind":"model","provider":"preview-fixture","model":"deterministic"}}},"sourceEventSeqs":[],"surfaceOp":"append","seq":106,"time":1787472000106}
{"type":"step/end","data":{"turn":18,"step":1},"seq":107,"time":1787472000107}
{"type":"turn/end","data":{"turn":18,"reason":{"kind":"completed"}},"seq":108,"time":1787472000108}
{"type":"turn/start","data":{"turn":19},"seq":109,"time":1787472000109}
{"type":"user/message","data":{"id":"preview-user-19","role":"user","content":[{"type":"text","text":"History checkpoint 19: verify deterministic preview state."}],"source":{"kind":"user"}},"surfaceOp":"append","seq":110,"time":1787472000110}
{"type":"step/start","data":{"turn":19,"step":1},"seq":111,"time":1787472000111}
{"type":"assistant/message","data":{"turn":19,"step":1,"message":{"id":"preview-assistant-19","role":"assistant","content":[{"type":"text","text":"Checkpoint 19 is recorded."}],"source":{"kind":"model","provider":"preview-fixture","model":"deterministic"}}},"sourceEventSeqs":[],"surfaceOp":"append","seq":112,"time":1787472000112}
{"type":"step/end","data":{"turn":19,"step":1},"seq":113,"time":1787472000113}
{"type":"turn/end","data":{"turn":19,"reason":{"kind":"completed"}},"seq":114,"time":1787472000114}
{"type":"turn/start","data":{"turn":20},"seq":115,"time":1787472000115}
{"type":"user/message","data":{"id":"preview-user-20","role":"user","content":[{"type":"text","text":"History checkpoint 20: verify deterministic preview state."}],"source":{"kind":"user"}},"surfaceOp":"append","seq":116,"time":1787472000116}
{"type":"step/start","data":{"turn":20,"step":1},"seq":117,"time":1787472000117}
{"type":"assistant/message","data":{"turn":20,"step":1,"message":{"id":"preview-assistant-20","role":"assistant","content":[{"type":"text","text":"Checkpoint 20 is recorded."}],"source":{"kind":"model","provider":"preview-fixture","model":"deterministic"}}},"sourceEventSeqs":[],"surfaceOp":"append","seq":118,"time":1787472000118}
{"type":"step/end","data":{"turn":20,"step":1},"seq":119,"time":1787472000119}
{"type":"turn/end","data":{"turn":20,"reason":{"kind":"completed"}},"seq":120,"time":1787472000120}
{"type":"turn/start","data":{"turn":21},"seq":121,"time":1787472000121}
{"type":"user/message","data":{"id":"preview-user-21","role":"user","content":[{"type":"text","text":"History checkpoint 21: verify deterministic preview state."}],"source":{"kind":"user"}},"surfaceOp":"append","seq":122,"time":1787472000122}
{"type":"step/start","data":{"turn":21,"step":1},"seq":123,"time":1787472000123}
{"type":"assistant/message","data":{"turn":21,"step":1,"message":{"id":"preview-assistant-21","role":"assistant","content":[{"type":"text","text":"Checkpoint 21 is recorded."}],"source":{"kind":"model","provider":"preview-fixture","model":"deterministic"}}},"sourceEventSeqs":[],"surfaceOp":"append","seq":124,"time":1787472000124}
{"type":"step/end","data":{"turn":21,"step":1},"seq":125,"time":1787472000125}
{"type":"turn/end","data":{"turn":21,"reason":{"kind":"completed"}},"seq":126,"time":1787472000126}
{"type":"turn/start","data":{"turn":22},"seq":127,"time":1787472000127}
{"type":"user/message","data":{"id":"preview-user-22","role":"user","content":[{"type":"text","text":"History checkpoint 22: verify deterministic preview state."}],"source":{"kind":"user"}},"surfaceOp":"append","seq":128,"time":1787472000128}
{"type":"step/start","data":{"turn":22,"step":1},"seq":129,"time":1787472000129}
{"type":"assistant/message","data":{"turn":22,"step":1,"message":{"id":"preview-assistant-22","role":"assistant","content":[{"type":"text","text":"Checkpoint 22 is recorded."}],"source":{"kind":"model","provider":"preview-fixture","model":"deterministic"}}},"sourceEventSeqs":[],"surfaceOp":"append","seq":130,"time":1787472000130}
{"type":"step/end","data":{"turn":22,"step":1},"seq":131,"time":1787472000131}
{"type":"turn/end","data":{"turn":22,"reason":{"kind":"completed"}},"seq":132,"time":1787472000132}
{"type":"turn/start","data":{"turn":23},"seq":133,"time":1787472000133}
{"type":"user/message","data":{"id":"preview-user-23","role":"user","content":[{"type":"text","text":"History checkpoint 23: verify deterministic preview state."}],"source":{"kind":"user"}},"surfaceOp":"append","seq":134,"time":1787472000134}
{"type":"step/start","data":{"turn":23,"step":1},"seq":135,"time":1787472000135}
{"type":"assistant/message","data":{"turn":23,"step":1,"message":{"id":"preview-assistant-23","role":"assistant","content":[{"type":"text","text":"Checkpoint 23 is recorded."}],"source":{"kind":"model","provider":"preview-fixture","model":"deterministic"}}},"sourceEventSeqs":[],"surfaceOp":"append","seq":136,"time":1787472000136}
{"type":"step/end","data":{"turn":23,"step":1},"seq":137,"time":1787472000137}
{"type":"turn/end","data":{"turn":23,"reason":{"kind":"completed"}},"seq":138,"time":1787472000138}
{"type":"turn/start","data":{"turn":24},"seq":139,"time":1787472000139}
{"type":"user/message","data":{"id":"preview-user-24","role":"user","content":[{"type":"text","text":"History checkpoint 24: verify deterministic preview state."}],"source":{"kind":"user"}},"surfaceOp":"append","seq":140,"time":1787472000140}
{"type":"step/start","data":{"turn":24,"step":1},"seq":141,"time":1787472000141}
{"type":"assistant/message","data":{"turn":24,"step":1,"message":{"id":"preview-assistant-24","role":"assistant","content":[{"type":"text","text":"Checkpoint 24 is recorded."}],"source":{"kind":"model","provider":"preview-fixture","model":"deterministic"}}},"sourceEventSeqs":[],"surfaceOp":"append","seq":142,"time":1787472000142}
{"type":"step/end","data":{"turn":24,"step":1},"seq":143,"time":1787472000143}
{"type":"turn/end","data":{"turn":24,"reason":{"kind":"completed"}},"seq":144,"time":1787472000144}
{"type":"turn/start","data":{"turn":25},"seq":145,"time":1787472000145}
{"type":"user/message","data":{"id":"preview-user-25","role":"user","content":[{"type":"text","text":"History checkpoint 25: verify deterministic preview state."}],"source":{"kind":"user"}},"surfaceOp":"append","seq":146,"time":1787472000146}
{"type":"step/start","data":{"turn":25,"step":1},"seq":147,"time":1787472000147}
{"type":"assistant/message","data":{"turn":25,"step":1,"message":{"id":"preview-assistant-25","role":"assistant","content":[{"type":"text","text":"Checkpoint 25 is recorded."}],"source":{"kind":"model","provider":"preview-fixture","model":"deterministic"}}},"sourceEventSeqs":[],"surfaceOp":"append","seq":148,"time":1787472000148}
{"type":"step/end","data":{"turn":25,"step":1},"seq":149,"time":1787472000149}
{"type":"turn/end","data":{"turn":25,"reason":{"kind":"completed"}},"seq":150,"time":1787472000150}
{"type":"turn/start","data":{"turn":26},"seq":151,"time":1787472000151}
{"type":"user/message","data":{"id":"preview-user-26","role":"user","content":[{"type":"text","text":"History checkpoint 26: verify deterministic preview state."}],"source":{"kind":"user"}},"surfaceOp":"append","seq":152,"time":1787472000152}
{"type":"step/start","data":{"turn":26,"step":1},"seq":153,"time":1787472000153}
{"type":"assistant/message","data":{"turn":26,"step":1,"message":{"id":"preview-assistant-26","role":"assistant","content":[{"type":"text","text":"Checkpoint 26 is recorded."}],"source":{"kind":"model","provider":"preview-fixture","model":"deterministic"}}},"sourceEventSeqs":[],"surfaceOp":"append","seq":154,"time":1787472000154}
{"type":"step/end","data":{"turn":26,"step":1},"seq":155,"time":1787472000155}
{"type":"turn/end","data":{"turn":26,"reason":{"kind":"completed"}},"seq":156,"time":1787472000156}
{"type":"turn/start","data":{"turn":27},"seq":157,"time":1787472000157}
{"type":"user/message","data":{"id":"preview-user-27","role":"user","content":[{"type":"text","text":"History checkpoint 27: verify deterministic preview state."}],"source":{"kind":"user"}},"surfaceOp":"append","seq":158,"time":1787472000158}
{"type":"step/start","data":{"turn":27,"step":1},"seq":159,"time":1787472000159}
{"type":"assistant/message","data":{"turn":27,"step":1,"message":{"id":"preview-assistant-27","role":"assistant","content":[{"type":"text","text":"Checkpoint 27 is recorded."}],"source":{"kind":"model","provider":"preview-fixture","model":"deterministic"}}},"sourceEventSeqs":[],"surfaceOp":"append","seq":160,"time":1787472000160}
{"type":"step/end","data":{"turn":27,"step":1},"seq":161,"time":1787472000161}
{"type":"turn/end","data":{"turn":27,"reason":{"kind":"completed"}},"seq":162,"time":1787472000162}
{"type":"turn/start","data":{"turn":28},"seq":163,"time":1787472000163}
{"type":"user/message","data":{"id":"preview-user-28","role":"user","content":[{"type":"text","text":"History checkpoint 28: verify deterministic preview state."}],"source":{"kind":"user"}},"surfaceOp":"append","seq":164,"time":1787472000164}
{"type":"step/start","data":{"turn":28,"step":1},"seq":165,"time":1787472000165}
{"type":"assistant/message","data":{"turn":28,"step":1,"message":{"id":"preview-assistant-28","role":"assistant","content":[{"type":"text","text":"Checkpoint 28 is recorded."}],"source":{"kind":"model","provider":"preview-fixture","model":"deterministic"}}},"sourceEventSeqs":[],"surfaceOp":"append","seq":166,"time":1787472000166}
{"type":"step/end","data":{"turn":28,"step":1},"seq":167,"time":1787472000167}
{"type":"turn/end","data":{"turn":28,"reason":{"kind":"completed"}},"seq":168,"time":1787472000168}
{"type":"session/end-seed","data":{},"seq":169,"time":1787472100000}
{"type":"turn/start","data":{"turn":29},"seq":170,"time":1787472100001}
{"type":"user/message","data":{"id":"preview-review-user","role":"user","content":[{"type":"text","text":"Review whether the preview fixture is isolated from future WebFS data."}],"source":{"kind":"user"}},"surfaceOp":"append","seq":171,"time":1787472100002}
{"type":"subagent/descriptor","data":{"version":2,"mode":"one-shot","provider":"fork","label":"Review preview architecture"},"seq":172,"time":1787472100003}
{"type":"step/start","data":{"turn":29,"step":1},"seq":173,"time":1787472100004}
{"type":"assistant/message","data":{"turn":29,"step":1,"message":{"id":"preview-review-assistant","role":"assistant","content":[{"type":"text","text":"The bundled fixture is static image content; future WebFS state remains user-owned."}],"source":{"kind":"model","provider":"preview-fixture","model":"deterministic"}}},"sourceEventSeqs":[],"surfaceOp":"append","seq":174,"time":1787472100005}
{"type":"step/end","data":{"turn":29,"step":1},"seq":175,"time":1787472100006}
{"type":"turn/end","data":{"turn":29,"reason":{"kind":"completed"}},"seq":176,"time":1787472100007}
@@ -0,0 +1,8 @@
{"type":"session","version":0,"id":"preview-follow-up-builder","createdAt":1787472200000,"cwd":"/dsh/workspace","parentSession":"preview-showcase","origin":"subagent","delegationDepth":1,"agentPreset":"standard"}
{"type":"turn/start","data":{"turn":1},"seq":0,"time":1787472200000}
{"type":"user/message","data":{"id":"preview-builder-user","role":"user","content":[{"type":"text","text":"Check that the Preview workspace can support follow-up tasks."}],"source":{"kind":"user"}},"surfaceOp":"append","seq":1,"time":1787472200001}
{"type":"subagent/descriptor","data":{"version":2,"mode":"continuable","provider":"spawn","label":"Continue preview verification"},"seq":2,"time":1787472200002}
{"type":"step/start","data":{"turn":1,"step":1},"seq":3,"time":1787472200003}
{"type":"assistant/message","data":{"turn":1,"step":1,"message":{"id":"preview-builder-assistant","role":"assistant","content":[{"type":"text","text":"This child is continuable and ready for another verification turn."}],"source":{"kind":"model","provider":"preview-fixture","model":"deterministic"}}},"sourceEventSeqs":[],"surfaceOp":"append","seq":4,"time":1787472200004}
{"type":"step/end","data":{"turn":1,"step":1},"seq":5,"time":1787472200005}
{"type":"turn/end","data":{"turn":1,"reason":{"kind":"completed"}},"seq":6,"time":1787472200006}
@@ -0,0 +1,200 @@
{"type":"session","version":0,"id":"preview-showcase","createdAt":1787472000000,"cwd":"/dsh/workspace","delegationDepth":0,"agentPreset":"standard"}
{"type":"turn/start","data":{"turn":1},"seq":0,"time":1787472000000}
{"type":"user/message","data":{"id":"preview-user-01","role":"user","content":[{"type":"text","text":"History checkpoint 01: verify deterministic preview state."}],"source":{"kind":"user"}},"surfaceOp":"append","seq":1,"time":1787472000001}
{"type":"session/title","data":{"title":"WebWorker Preview Showcase","messageSeqs":[],"source":{"kind":"user"}},"seq":2,"time":1787472000002}
{"type":"step/start","data":{"turn":1,"step":1},"seq":3,"time":1787472000003}
{"type":"assistant/message","data":{"turn":1,"step":1,"message":{"id":"preview-assistant-01","role":"assistant","content":[{"type":"text","text":"Checkpoint 01 is recorded."}],"source":{"kind":"model","provider":"preview-fixture","model":"deterministic"}}},"sourceEventSeqs":[],"surfaceOp":"append","seq":4,"time":1787472000004}
{"type":"step/end","data":{"turn":1,"step":1},"seq":5,"time":1787472000005}
{"type":"turn/end","data":{"turn":1,"reason":{"kind":"completed"}},"seq":6,"time":1787472000006}
{"type":"turn/start","data":{"turn":2},"seq":7,"time":1787472000007}
{"type":"user/message","data":{"id":"preview-user-02","role":"user","content":[{"type":"text","text":"History checkpoint 02: verify deterministic preview state."}],"source":{"kind":"user"}},"surfaceOp":"append","seq":8,"time":1787472000008}
{"type":"step/start","data":{"turn":2,"step":1},"seq":9,"time":1787472000009}
{"type":"assistant/message","data":{"turn":2,"step":1,"message":{"id":"preview-assistant-02","role":"assistant","content":[{"type":"text","text":"Checkpoint 02 is recorded."}],"source":{"kind":"model","provider":"preview-fixture","model":"deterministic"}}},"sourceEventSeqs":[],"surfaceOp":"append","seq":10,"time":1787472000010}
{"type":"step/end","data":{"turn":2,"step":1},"seq":11,"time":1787472000011}
{"type":"turn/end","data":{"turn":2,"reason":{"kind":"completed"}},"seq":12,"time":1787472000012}
{"type":"turn/start","data":{"turn":3},"seq":13,"time":1787472000013}
{"type":"user/message","data":{"id":"preview-user-03","role":"user","content":[{"type":"text","text":"History checkpoint 03: verify deterministic preview state."}],"source":{"kind":"user"}},"surfaceOp":"append","seq":14,"time":1787472000014}
{"type":"step/start","data":{"turn":3,"step":1},"seq":15,"time":1787472000015}
{"type":"assistant/message","data":{"turn":3,"step":1,"message":{"id":"preview-assistant-03","role":"assistant","content":[{"type":"text","text":"Checkpoint 03 is recorded."}],"source":{"kind":"model","provider":"preview-fixture","model":"deterministic"}}},"sourceEventSeqs":[],"surfaceOp":"append","seq":16,"time":1787472000016}
{"type":"step/end","data":{"turn":3,"step":1},"seq":17,"time":1787472000017}
{"type":"turn/end","data":{"turn":3,"reason":{"kind":"completed"}},"seq":18,"time":1787472000018}
{"type":"turn/start","data":{"turn":4},"seq":19,"time":1787472000019}
{"type":"user/message","data":{"id":"preview-user-04","role":"user","content":[{"type":"text","text":"History checkpoint 04: verify deterministic preview state."}],"source":{"kind":"user"}},"surfaceOp":"append","seq":20,"time":1787472000020}
{"type":"step/start","data":{"turn":4,"step":1},"seq":21,"time":1787472000021}
{"type":"assistant/message","data":{"turn":4,"step":1,"message":{"id":"preview-assistant-04","role":"assistant","content":[{"type":"text","text":"Checkpoint 04 is recorded."}],"source":{"kind":"model","provider":"preview-fixture","model":"deterministic"}}},"sourceEventSeqs":[],"surfaceOp":"append","seq":22,"time":1787472000022}
{"type":"step/end","data":{"turn":4,"step":1},"seq":23,"time":1787472000023}
{"type":"turn/end","data":{"turn":4,"reason":{"kind":"completed"}},"seq":24,"time":1787472000024}
{"type":"turn/start","data":{"turn":5},"seq":25,"time":1787472000025}
{"type":"user/message","data":{"id":"preview-user-05","role":"user","content":[{"type":"text","text":"History checkpoint 05: verify deterministic preview state."}],"source":{"kind":"user"}},"surfaceOp":"append","seq":26,"time":1787472000026}
{"type":"step/start","data":{"turn":5,"step":1},"seq":27,"time":1787472000027}
{"type":"assistant/message","data":{"turn":5,"step":1,"message":{"id":"preview-assistant-05","role":"assistant","content":[{"type":"text","text":"Checkpoint 05 is recorded."}],"source":{"kind":"model","provider":"preview-fixture","model":"deterministic"}}},"sourceEventSeqs":[],"surfaceOp":"append","seq":28,"time":1787472000028}
{"type":"step/end","data":{"turn":5,"step":1},"seq":29,"time":1787472000029}
{"type":"turn/end","data":{"turn":5,"reason":{"kind":"completed"}},"seq":30,"time":1787472000030}
{"type":"turn/start","data":{"turn":6},"seq":31,"time":1787472000031}
{"type":"user/message","data":{"id":"preview-user-06","role":"user","content":[{"type":"text","text":"History checkpoint 06: verify deterministic preview state."}],"source":{"kind":"user"}},"surfaceOp":"append","seq":32,"time":1787472000032}
{"type":"step/start","data":{"turn":6,"step":1},"seq":33,"time":1787472000033}
{"type":"assistant/message","data":{"turn":6,"step":1,"message":{"id":"preview-assistant-06","role":"assistant","content":[{"type":"text","text":"Checkpoint 06 is recorded."}],"source":{"kind":"model","provider":"preview-fixture","model":"deterministic"}}},"sourceEventSeqs":[],"surfaceOp":"append","seq":34,"time":1787472000034}
{"type":"step/end","data":{"turn":6,"step":1},"seq":35,"time":1787472000035}
{"type":"turn/end","data":{"turn":6,"reason":{"kind":"completed"}},"seq":36,"time":1787472000036}
{"type":"turn/start","data":{"turn":7},"seq":37,"time":1787472000037}
{"type":"user/message","data":{"id":"preview-user-07","role":"user","content":[{"type":"text","text":"History checkpoint 07: verify deterministic preview state."}],"source":{"kind":"user"}},"surfaceOp":"append","seq":38,"time":1787472000038}
{"type":"step/start","data":{"turn":7,"step":1},"seq":39,"time":1787472000039}
{"type":"assistant/message","data":{"turn":7,"step":1,"message":{"id":"preview-assistant-07","role":"assistant","content":[{"type":"text","text":"Checkpoint 07 is recorded."}],"source":{"kind":"model","provider":"preview-fixture","model":"deterministic"}}},"sourceEventSeqs":[],"surfaceOp":"append","seq":40,"time":1787472000040}
{"type":"step/end","data":{"turn":7,"step":1},"seq":41,"time":1787472000041}
{"type":"turn/end","data":{"turn":7,"reason":{"kind":"completed"}},"seq":42,"time":1787472000042}
{"type":"turn/start","data":{"turn":8},"seq":43,"time":1787472000043}
{"type":"user/message","data":{"id":"preview-user-08","role":"user","content":[{"type":"text","text":"History checkpoint 08: verify deterministic preview state."}],"source":{"kind":"user"}},"surfaceOp":"append","seq":44,"time":1787472000044}
{"type":"step/start","data":{"turn":8,"step":1},"seq":45,"time":1787472000045}
{"type":"assistant/message","data":{"turn":8,"step":1,"message":{"id":"preview-assistant-08","role":"assistant","content":[{"type":"text","text":"Checkpoint 08 is recorded."}],"source":{"kind":"model","provider":"preview-fixture","model":"deterministic"}}},"sourceEventSeqs":[],"surfaceOp":"append","seq":46,"time":1787472000046}
{"type":"step/end","data":{"turn":8,"step":1},"seq":47,"time":1787472000047}
{"type":"turn/end","data":{"turn":8,"reason":{"kind":"completed"}},"seq":48,"time":1787472000048}
{"type":"turn/start","data":{"turn":9},"seq":49,"time":1787472000049}
{"type":"user/message","data":{"id":"preview-user-09","role":"user","content":[{"type":"text","text":"History checkpoint 09: verify deterministic preview state."}],"source":{"kind":"user"}},"surfaceOp":"append","seq":50,"time":1787472000050}
{"type":"step/start","data":{"turn":9,"step":1},"seq":51,"time":1787472000051}
{"type":"assistant/message","data":{"turn":9,"step":1,"message":{"id":"preview-assistant-09","role":"assistant","content":[{"type":"text","text":"Checkpoint 09 is recorded."}],"source":{"kind":"model","provider":"preview-fixture","model":"deterministic"}}},"sourceEventSeqs":[],"surfaceOp":"append","seq":52,"time":1787472000052}
{"type":"step/end","data":{"turn":9,"step":1},"seq":53,"time":1787472000053}
{"type":"turn/end","data":{"turn":9,"reason":{"kind":"completed"}},"seq":54,"time":1787472000054}
{"type":"turn/start","data":{"turn":10},"seq":55,"time":1787472000055}
{"type":"user/message","data":{"id":"preview-user-10","role":"user","content":[{"type":"text","text":"History checkpoint 10: verify deterministic preview state."}],"source":{"kind":"user"}},"surfaceOp":"append","seq":56,"time":1787472000056}
{"type":"step/start","data":{"turn":10,"step":1},"seq":57,"time":1787472000057}
{"type":"assistant/message","data":{"turn":10,"step":1,"message":{"id":"preview-assistant-10","role":"assistant","content":[{"type":"text","text":"Checkpoint 10 is recorded."}],"source":{"kind":"model","provider":"preview-fixture","model":"deterministic"}}},"sourceEventSeqs":[],"surfaceOp":"append","seq":58,"time":1787472000058}
{"type":"step/end","data":{"turn":10,"step":1},"seq":59,"time":1787472000059}
{"type":"turn/end","data":{"turn":10,"reason":{"kind":"completed"}},"seq":60,"time":1787472000060}
{"type":"turn/start","data":{"turn":11},"seq":61,"time":1787472000061}
{"type":"user/message","data":{"id":"preview-user-11","role":"user","content":[{"type":"text","text":"History checkpoint 11: verify deterministic preview state."}],"source":{"kind":"user"}},"surfaceOp":"append","seq":62,"time":1787472000062}
{"type":"step/start","data":{"turn":11,"step":1},"seq":63,"time":1787472000063}
{"type":"assistant/message","data":{"turn":11,"step":1,"message":{"id":"preview-assistant-11","role":"assistant","content":[{"type":"text","text":"Checkpoint 11 is recorded."}],"source":{"kind":"model","provider":"preview-fixture","model":"deterministic"}}},"sourceEventSeqs":[],"surfaceOp":"append","seq":64,"time":1787472000064}
{"type":"step/end","data":{"turn":11,"step":1},"seq":65,"time":1787472000065}
{"type":"turn/end","data":{"turn":11,"reason":{"kind":"completed"}},"seq":66,"time":1787472000066}
{"type":"turn/start","data":{"turn":12},"seq":67,"time":1787472000067}
{"type":"user/message","data":{"id":"preview-user-12","role":"user","content":[{"type":"text","text":"History checkpoint 12: verify deterministic preview state."}],"source":{"kind":"user"}},"surfaceOp":"append","seq":68,"time":1787472000068}
{"type":"step/start","data":{"turn":12,"step":1},"seq":69,"time":1787472000069}
{"type":"assistant/message","data":{"turn":12,"step":1,"message":{"id":"preview-assistant-12","role":"assistant","content":[{"type":"text","text":"Checkpoint 12 is recorded."}],"source":{"kind":"model","provider":"preview-fixture","model":"deterministic"}}},"sourceEventSeqs":[],"surfaceOp":"append","seq":70,"time":1787472000070}
{"type":"step/end","data":{"turn":12,"step":1},"seq":71,"time":1787472000071}
{"type":"turn/end","data":{"turn":12,"reason":{"kind":"completed"}},"seq":72,"time":1787472000072}
{"type":"turn/start","data":{"turn":13},"seq":73,"time":1787472000073}
{"type":"user/message","data":{"id":"preview-user-13","role":"user","content":[{"type":"text","text":"History checkpoint 13: verify deterministic preview state."}],"source":{"kind":"user"}},"surfaceOp":"append","seq":74,"time":1787472000074}
{"type":"step/start","data":{"turn":13,"step":1},"seq":75,"time":1787472000075}
{"type":"assistant/message","data":{"turn":13,"step":1,"message":{"id":"preview-assistant-13","role":"assistant","content":[{"type":"text","text":"Checkpoint 13 is recorded."}],"source":{"kind":"model","provider":"preview-fixture","model":"deterministic"}}},"sourceEventSeqs":[],"surfaceOp":"append","seq":76,"time":1787472000076}
{"type":"step/end","data":{"turn":13,"step":1},"seq":77,"time":1787472000077}
{"type":"turn/end","data":{"turn":13,"reason":{"kind":"completed"}},"seq":78,"time":1787472000078}
{"type":"turn/start","data":{"turn":14},"seq":79,"time":1787472000079}
{"type":"user/message","data":{"id":"preview-user-14","role":"user","content":[{"type":"text","text":"History checkpoint 14: verify deterministic preview state."}],"source":{"kind":"user"}},"surfaceOp":"append","seq":80,"time":1787472000080}
{"type":"step/start","data":{"turn":14,"step":1},"seq":81,"time":1787472000081}
{"type":"assistant/message","data":{"turn":14,"step":1,"message":{"id":"preview-assistant-14","role":"assistant","content":[{"type":"text","text":"Checkpoint 14 is recorded."}],"source":{"kind":"model","provider":"preview-fixture","model":"deterministic"}}},"sourceEventSeqs":[],"surfaceOp":"append","seq":82,"time":1787472000082}
{"type":"step/end","data":{"turn":14,"step":1},"seq":83,"time":1787472000083}
{"type":"turn/end","data":{"turn":14,"reason":{"kind":"completed"}},"seq":84,"time":1787472000084}
{"type":"turn/start","data":{"turn":15},"seq":85,"time":1787472000085}
{"type":"user/message","data":{"id":"preview-user-15","role":"user","content":[{"type":"text","text":"History checkpoint 15: verify deterministic preview state."}],"source":{"kind":"user"}},"surfaceOp":"append","seq":86,"time":1787472000086}
{"type":"step/start","data":{"turn":15,"step":1},"seq":87,"time":1787472000087}
{"type":"assistant/message","data":{"turn":15,"step":1,"message":{"id":"preview-assistant-15","role":"assistant","content":[{"type":"text","text":"Checkpoint 15 is recorded."}],"source":{"kind":"model","provider":"preview-fixture","model":"deterministic"}}},"sourceEventSeqs":[],"surfaceOp":"append","seq":88,"time":1787472000088}
{"type":"step/end","data":{"turn":15,"step":1},"seq":89,"time":1787472000089}
{"type":"turn/end","data":{"turn":15,"reason":{"kind":"completed"}},"seq":90,"time":1787472000090}
{"type":"turn/start","data":{"turn":16},"seq":91,"time":1787472000091}
{"type":"user/message","data":{"id":"preview-user-16","role":"user","content":[{"type":"text","text":"History checkpoint 16: verify deterministic preview state."}],"source":{"kind":"user"}},"surfaceOp":"append","seq":92,"time":1787472000092}
{"type":"step/start","data":{"turn":16,"step":1},"seq":93,"time":1787472000093}
{"type":"assistant/message","data":{"turn":16,"step":1,"message":{"id":"preview-assistant-16","role":"assistant","content":[{"type":"text","text":"Checkpoint 16 is recorded."}],"source":{"kind":"model","provider":"preview-fixture","model":"deterministic"}}},"sourceEventSeqs":[],"surfaceOp":"append","seq":94,"time":1787472000094}
{"type":"step/end","data":{"turn":16,"step":1},"seq":95,"time":1787472000095}
{"type":"turn/end","data":{"turn":16,"reason":{"kind":"completed"}},"seq":96,"time":1787472000096}
{"type":"turn/start","data":{"turn":17},"seq":97,"time":1787472000097}
{"type":"user/message","data":{"id":"preview-user-17","role":"user","content":[{"type":"text","text":"History checkpoint 17: verify deterministic preview state."}],"source":{"kind":"user"}},"surfaceOp":"append","seq":98,"time":1787472000098}
{"type":"step/start","data":{"turn":17,"step":1},"seq":99,"time":1787472000099}
{"type":"assistant/message","data":{"turn":17,"step":1,"message":{"id":"preview-assistant-17","role":"assistant","content":[{"type":"text","text":"Checkpoint 17 is recorded."}],"source":{"kind":"model","provider":"preview-fixture","model":"deterministic"}}},"sourceEventSeqs":[],"surfaceOp":"append","seq":100,"time":1787472000100}
{"type":"step/end","data":{"turn":17,"step":1},"seq":101,"time":1787472000101}
{"type":"turn/end","data":{"turn":17,"reason":{"kind":"completed"}},"seq":102,"time":1787472000102}
{"type":"turn/start","data":{"turn":18},"seq":103,"time":1787472000103}
{"type":"user/message","data":{"id":"preview-user-18","role":"user","content":[{"type":"text","text":"History checkpoint 18: verify deterministic preview state."}],"source":{"kind":"user"}},"surfaceOp":"append","seq":104,"time":1787472000104}
{"type":"step/start","data":{"turn":18,"step":1},"seq":105,"time":1787472000105}
{"type":"assistant/message","data":{"turn":18,"step":1,"message":{"id":"preview-assistant-18","role":"assistant","content":[{"type":"text","text":"Checkpoint 18 is recorded."}],"source":{"kind":"model","provider":"preview-fixture","model":"deterministic"}}},"sourceEventSeqs":[],"surfaceOp":"append","seq":106,"time":1787472000106}
{"type":"step/end","data":{"turn":18,"step":1},"seq":107,"time":1787472000107}
{"type":"turn/end","data":{"turn":18,"reason":{"kind":"completed"}},"seq":108,"time":1787472000108}
{"type":"turn/start","data":{"turn":19},"seq":109,"time":1787472000109}
{"type":"user/message","data":{"id":"preview-user-19","role":"user","content":[{"type":"text","text":"History checkpoint 19: verify deterministic preview state."}],"source":{"kind":"user"}},"surfaceOp":"append","seq":110,"time":1787472000110}
{"type":"step/start","data":{"turn":19,"step":1},"seq":111,"time":1787472000111}
{"type":"assistant/message","data":{"turn":19,"step":1,"message":{"id":"preview-assistant-19","role":"assistant","content":[{"type":"text","text":"Checkpoint 19 is recorded."}],"source":{"kind":"model","provider":"preview-fixture","model":"deterministic"}}},"sourceEventSeqs":[],"surfaceOp":"append","seq":112,"time":1787472000112}
{"type":"step/end","data":{"turn":19,"step":1},"seq":113,"time":1787472000113}
{"type":"turn/end","data":{"turn":19,"reason":{"kind":"completed"}},"seq":114,"time":1787472000114}
{"type":"turn/start","data":{"turn":20},"seq":115,"time":1787472000115}
{"type":"user/message","data":{"id":"preview-user-20","role":"user","content":[{"type":"text","text":"History checkpoint 20: verify deterministic preview state."}],"source":{"kind":"user"}},"surfaceOp":"append","seq":116,"time":1787472000116}
{"type":"step/start","data":{"turn":20,"step":1},"seq":117,"time":1787472000117}
{"type":"assistant/message","data":{"turn":20,"step":1,"message":{"id":"preview-assistant-20","role":"assistant","content":[{"type":"text","text":"Checkpoint 20 is recorded."}],"source":{"kind":"model","provider":"preview-fixture","model":"deterministic"}}},"sourceEventSeqs":[],"surfaceOp":"append","seq":118,"time":1787472000118}
{"type":"step/end","data":{"turn":20,"step":1},"seq":119,"time":1787472000119}
{"type":"turn/end","data":{"turn":20,"reason":{"kind":"completed"}},"seq":120,"time":1787472000120}
{"type":"turn/start","data":{"turn":21},"seq":121,"time":1787472000121}
{"type":"user/message","data":{"id":"preview-user-21","role":"user","content":[{"type":"text","text":"History checkpoint 21: verify deterministic preview state."}],"source":{"kind":"user"}},"surfaceOp":"append","seq":122,"time":1787472000122}
{"type":"step/start","data":{"turn":21,"step":1},"seq":123,"time":1787472000123}
{"type":"assistant/message","data":{"turn":21,"step":1,"message":{"id":"preview-assistant-21","role":"assistant","content":[{"type":"text","text":"Checkpoint 21 is recorded."}],"source":{"kind":"model","provider":"preview-fixture","model":"deterministic"}}},"sourceEventSeqs":[],"surfaceOp":"append","seq":124,"time":1787472000124}
{"type":"step/end","data":{"turn":21,"step":1},"seq":125,"time":1787472000125}
{"type":"turn/end","data":{"turn":21,"reason":{"kind":"completed"}},"seq":126,"time":1787472000126}
{"type":"turn/start","data":{"turn":22},"seq":127,"time":1787472000127}
{"type":"user/message","data":{"id":"preview-user-22","role":"user","content":[{"type":"text","text":"History checkpoint 22: verify deterministic preview state."}],"source":{"kind":"user"}},"surfaceOp":"append","seq":128,"time":1787472000128}
{"type":"step/start","data":{"turn":22,"step":1},"seq":129,"time":1787472000129}
{"type":"assistant/message","data":{"turn":22,"step":1,"message":{"id":"preview-assistant-22","role":"assistant","content":[{"type":"text","text":"Checkpoint 22 is recorded."}],"source":{"kind":"model","provider":"preview-fixture","model":"deterministic"}}},"sourceEventSeqs":[],"surfaceOp":"append","seq":130,"time":1787472000130}
{"type":"step/end","data":{"turn":22,"step":1},"seq":131,"time":1787472000131}
{"type":"turn/end","data":{"turn":22,"reason":{"kind":"completed"}},"seq":132,"time":1787472000132}
{"type":"turn/start","data":{"turn":23},"seq":133,"time":1787472000133}
{"type":"user/message","data":{"id":"preview-user-23","role":"user","content":[{"type":"text","text":"History checkpoint 23: verify deterministic preview state."}],"source":{"kind":"user"}},"surfaceOp":"append","seq":134,"time":1787472000134}
{"type":"step/start","data":{"turn":23,"step":1},"seq":135,"time":1787472000135}
{"type":"assistant/message","data":{"turn":23,"step":1,"message":{"id":"preview-assistant-23","role":"assistant","content":[{"type":"text","text":"Checkpoint 23 is recorded."}],"source":{"kind":"model","provider":"preview-fixture","model":"deterministic"}}},"sourceEventSeqs":[],"surfaceOp":"append","seq":136,"time":1787472000136}
{"type":"step/end","data":{"turn":23,"step":1},"seq":137,"time":1787472000137}
{"type":"turn/end","data":{"turn":23,"reason":{"kind":"completed"}},"seq":138,"time":1787472000138}
{"type":"turn/start","data":{"turn":24},"seq":139,"time":1787472000139}
{"type":"user/message","data":{"id":"preview-user-24","role":"user","content":[{"type":"text","text":"History checkpoint 24: verify deterministic preview state."}],"source":{"kind":"user"}},"surfaceOp":"append","seq":140,"time":1787472000140}
{"type":"step/start","data":{"turn":24,"step":1},"seq":141,"time":1787472000141}
{"type":"assistant/message","data":{"turn":24,"step":1,"message":{"id":"preview-assistant-24","role":"assistant","content":[{"type":"text","text":"Checkpoint 24 is recorded."}],"source":{"kind":"model","provider":"preview-fixture","model":"deterministic"}}},"sourceEventSeqs":[],"surfaceOp":"append","seq":142,"time":1787472000142}
{"type":"step/end","data":{"turn":24,"step":1},"seq":143,"time":1787472000143}
{"type":"turn/end","data":{"turn":24,"reason":{"kind":"completed"}},"seq":144,"time":1787472000144}
{"type":"turn/start","data":{"turn":25},"seq":145,"time":1787472000145}
{"type":"user/message","data":{"id":"preview-user-25","role":"user","content":[{"type":"text","text":"History checkpoint 25: verify deterministic preview state."}],"source":{"kind":"user"}},"surfaceOp":"append","seq":146,"time":1787472000146}
{"type":"step/start","data":{"turn":25,"step":1},"seq":147,"time":1787472000147}
{"type":"assistant/message","data":{"turn":25,"step":1,"message":{"id":"preview-assistant-25","role":"assistant","content":[{"type":"text","text":"Checkpoint 25 is recorded."}],"source":{"kind":"model","provider":"preview-fixture","model":"deterministic"}}},"sourceEventSeqs":[],"surfaceOp":"append","seq":148,"time":1787472000148}
{"type":"step/end","data":{"turn":25,"step":1},"seq":149,"time":1787472000149}
{"type":"turn/end","data":{"turn":25,"reason":{"kind":"completed"}},"seq":150,"time":1787472000150}
{"type":"turn/start","data":{"turn":26},"seq":151,"time":1787472000151}
{"type":"user/message","data":{"id":"preview-user-26","role":"user","content":[{"type":"text","text":"History checkpoint 26: verify deterministic preview state."}],"source":{"kind":"user"}},"surfaceOp":"append","seq":152,"time":1787472000152}
{"type":"step/start","data":{"turn":26,"step":1},"seq":153,"time":1787472000153}
{"type":"assistant/message","data":{"turn":26,"step":1,"message":{"id":"preview-assistant-26","role":"assistant","content":[{"type":"text","text":"Checkpoint 26 is recorded."}],"source":{"kind":"model","provider":"preview-fixture","model":"deterministic"}}},"sourceEventSeqs":[],"surfaceOp":"append","seq":154,"time":1787472000154}
{"type":"step/end","data":{"turn":26,"step":1},"seq":155,"time":1787472000155}
{"type":"turn/end","data":{"turn":26,"reason":{"kind":"completed"}},"seq":156,"time":1787472000156}
{"type":"turn/start","data":{"turn":27},"seq":157,"time":1787472000157}
{"type":"user/message","data":{"id":"preview-user-27","role":"user","content":[{"type":"text","text":"History checkpoint 27: verify deterministic preview state."}],"source":{"kind":"user"}},"surfaceOp":"append","seq":158,"time":1787472000158}
{"type":"step/start","data":{"turn":27,"step":1},"seq":159,"time":1787472000159}
{"type":"assistant/message","data":{"turn":27,"step":1,"message":{"id":"preview-assistant-27","role":"assistant","content":[{"type":"text","text":"Checkpoint 27 is recorded."}],"source":{"kind":"model","provider":"preview-fixture","model":"deterministic"}}},"sourceEventSeqs":[],"surfaceOp":"append","seq":160,"time":1787472000160}
{"type":"step/end","data":{"turn":27,"step":1},"seq":161,"time":1787472000161}
{"type":"turn/end","data":{"turn":27,"reason":{"kind":"completed"}},"seq":162,"time":1787472000162}
{"type":"turn/start","data":{"turn":28},"seq":163,"time":1787472000163}
{"type":"user/message","data":{"id":"preview-user-28","role":"user","content":[{"type":"text","text":"History checkpoint 28: verify deterministic preview state."}],"source":{"kind":"user"}},"surfaceOp":"append","seq":164,"time":1787472000164}
{"type":"step/start","data":{"turn":28,"step":1},"seq":165,"time":1787472000165}
{"type":"assistant/message","data":{"turn":28,"step":1,"message":{"id":"preview-assistant-28","role":"assistant","content":[{"type":"text","text":"Checkpoint 28 is recorded."}],"source":{"kind":"model","provider":"preview-fixture","model":"deterministic"}}},"sourceEventSeqs":[],"surfaceOp":"append","seq":166,"time":1787472000166}
{"type":"step/end","data":{"turn":28,"step":1},"seq":167,"time":1787472000167}
{"type":"turn/end","data":{"turn":28,"reason":{"kind":"completed"}},"seq":168,"time":1787472000168}
{"type":"turn/start","data":{"turn":29},"seq":169,"time":1787472000169}
{"type":"user/message","data":{"id":"preview-gallery-user","role":"user","content":[{"type":"text","text":"Show the seeded workspace, tool cards, subagents, and pagination in one tour."}],"source":{"kind":"user"}},"surfaceOp":"append","seq":170,"time":1787472000170}
{"type":"step/start","data":{"turn":29,"step":1},"seq":171,"time":1787472000171}
{"type":"assistant/message","data":{"turn":29,"step":1,"message":{"id":"preview-gallery-tools","role":"assistant","content":[{"type":"reasoning","text":"I will inspect the deterministic workspace and collect each preview surface."},{"type":"tool-call","id":"preview-read","name":"read","arguments":"{\"file_path\":\"PREVIEW.md\"}"},{"type":"tool-call","id":"preview-write","name":"write","arguments":"{\"file_path\":\"src/preview.ts\",\"content\":\"export const previewStatus = 'ready'\\n\\nexport const previewFeatures = ['tools', 'subagents', 'pagination'] as const\\n\"}"},{"type":"tool-call","id":"preview-bash","name":"bash","arguments":"{\"command\":\"printf 'preview ready\\\\n'\",\"description\":\"Print the preview readiness marker\"}"},{"type":"tool-call","id":"preview-glob","name":"glob","arguments":"{\"pattern\":\"**/*\",\"path\":\".\"}"},{"type":"tool-call","id":"preview-grep","name":"grep","arguments":"{\"pattern\":\"preview\",\"path\":\".\",\"include\":\"*.{md,ts,json}\"}"},{"type":"tool-call","id":"preview-web-search","name":"web_search","arguments":"{\"queries\":[\"Web Worker filesystem compatibility\"]}"},{"type":"tool-call","id":"preview-todo","name":"todo_write","arguments":"{\"todos\":[{\"content\":\"Inspect tool cards\",\"status\":\"completed\"},{\"content\":\"Open both subagents\",\"status\":\"completed\"},{\"content\":\"Load earlier history\",\"status\":\"in_progress\"}]}"},{"type":"tool-call","id":"preview-subagent","name":"subagent","arguments":"{\"description\":\"Continue preview verification\",\"prompt\":\"Check the remaining preview cases.\",\"run_in_background\":true}"},{"type":"tool-call","id":"preview-subagent-fork","name":"subagent_fork","arguments":"{\"description\":\"Review preview architecture\",\"prompt\":\"Review the fixture architecture.\",\"run_in_background\":false}"},{"type":"tool-call","id":"preview-failure","name":"read","arguments":"{\"file_path\":\"missing.txt\"}"}],"source":{"kind":"model","provider":"preview-fixture","model":"deterministic"}}},"sourceEventSeqs":[],"surfaceOp":"append","seq":172,"time":1787472000172}
{"type":"tool/call","data":{"turn":29,"step":1,"callId":"preview-read","name":"read","arguments":"{\"file_path\":\"PREVIEW.md\"}"},"seq":173,"time":1787472000173}
{"type":"tool/result","data":{"turn":29,"step":1,"message":{"id":"preview-read-result","role":"user","content":[{"type":"tool-result","toolCallId":"preview-read","content":[{"type":"text","text":"<path>PREVIEW.md</path>\n<type>file</type>\n<content>\n1: # Preview Workspace\n2: \n3: This deterministic workspace is bundled with the browser-only preview.\n4: \n5: - `src/preview.ts` is the file changed by the example write result.\n6: - `data/tasks.json` mirrors the completed preview checklist.\n7: - `.agents/skills/preview-tour/SKILL.md` proves dot directories survive image packing.\n8: \n9: Refresh the preview to restore these image bytes.\n\n(End of file - total 9 lines)\n</content>"}],"isError":false}],"source":{"kind":"tool","callId":"preview-read"}},"meta":{"path":"PREVIEW.md","offset":1,"lines":[{"number":1,"text":"# Preview Workspace"},{"number":2,"text":""},{"number":3,"text":"This deterministic workspace is bundled with the browser-only preview."},{"number":4,"text":""},{"number":5,"text":"- `src/preview.ts` is the file changed by the example write result."},{"number":6,"text":"- `data/tasks.json` mirrors the completed preview checklist."},{"number":7,"text":"- `.agents/skills/preview-tour/SKILL.md` proves dot directories survive image packing."},{"number":8,"text":""},{"number":9,"text":"Refresh the preview to restore these image bytes."}],"totalLines":9,"lang":"md"}},"surfaceOp":"append","seq":174,"time":1787472000174}
{"type":"tool/call","data":{"turn":29,"step":1,"callId":"preview-write","name":"write","arguments":"{\"file_path\":\"src/preview.ts\",\"content\":\"export const previewStatus = 'ready'\\n\\nexport const previewFeatures = ['tools', 'subagents', 'pagination'] as const\\n\"}"},"seq":175,"time":1787472000175}
{"type":"tool/result","data":{"turn":29,"step":1,"message":{"id":"preview-write-result","role":"user","content":[{"type":"tool-result","toolCallId":"preview-write","content":[{"type":"text","text":"<path>src/preview.ts</path>\n<type>file</type>\n<content>\nUpdated file\n</content>"}],"isError":false}],"source":{"kind":"tool","callId":"preview-write"}},"meta":{"diffs":[{"path":"src/preview.ts","oldText":"export const previewStatus = 'draft'\n","newText":"export const previewStatus = 'ready'\n\nexport const previewFeatures = ['tools', 'subagents', 'pagination'] as const\n"}]}},"surfaceOp":"append","seq":176,"time":1787472000176}
{"type":"tool/call","data":{"turn":29,"step":1,"callId":"preview-bash","name":"bash","arguments":"{\"command\":\"printf 'preview ready\\\\n'\",\"description\":\"Print the preview readiness marker\"}"},"seq":177,"time":1787472000177}
{"type":"tool/result","data":{"turn":29,"step":1,"message":{"id":"preview-bash-result","role":"user","content":[{"type":"tool-result","toolCallId":"preview-bash","content":[{"type":"text","text":"preview ready\n"}],"isError":false}],"source":{"kind":"tool","callId":"preview-bash"}}},"surfaceOp":"append","seq":178,"time":1787472000178}
{"type":"tool/call","data":{"turn":29,"step":1,"callId":"preview-glob","name":"glob","arguments":"{\"pattern\":\"**/*\",\"path\":\".\"}"},"seq":179,"time":1787472000179}
{"type":"tool/result","data":{"turn":29,"step":1,"message":{"id":"preview-glob-result","role":"user","content":[{"type":"tool-result","toolCallId":"preview-glob","content":[{"type":"text","text":"PREVIEW.md\ndata/tasks.json\nsrc/preview.ts"}],"isError":false}],"source":{"kind":"tool","callId":"preview-glob"}},"meta":{"shape":"paths","paths":["PREVIEW.md","data/tasks.json","src/preview.ts"],"truncated":false,"total":3}},"surfaceOp":"append","seq":180,"time":1787472000180}
{"type":"tool/call","data":{"turn":29,"step":1,"callId":"preview-grep","name":"grep","arguments":"{\"pattern\":\"preview\",\"path\":\".\",\"include\":\"*.{md,ts,json}\"}"},"seq":181,"time":1787472000181}
{"type":"tool/result","data":{"turn":29,"step":1,"message":{"id":"preview-grep-result","role":"user","content":[{"type":"tool-result","toolCallId":"preview-grep","content":[{"type":"text","text":"PREVIEW.md:3:This deterministic workspace is bundled with the browser-only preview.\nsrc/preview.ts:1:export const previewStatus = 'ready'"}],"isError":false}],"source":{"kind":"tool","callId":"preview-grep"}},"meta":{"shape":"matches","files":[{"path":"PREVIEW.md","matches":[{"lineNumber":3,"line":"This deterministic workspace is bundled with the browser-only preview."}]},{"path":"src/preview.ts","matches":[{"lineNumber":1,"line":"export const previewStatus = 'ready'"}]}],"truncated":false,"total":2}},"surfaceOp":"append","seq":182,"time":1787472000182}
{"type":"tool/call","data":{"turn":29,"step":1,"callId":"preview-web-search","name":"web_search","arguments":"{\"queries\":[\"Web Worker filesystem compatibility\"]}"},"seq":183,"time":1787472000183}
{"type":"tool/result","data":{"turn":29,"step":1,"message":{"id":"preview-web-search-result","role":"user","content":[{"type":"tool-result","toolCallId":"preview-web-search","content":[{"type":"text","text":"Browser workers can host deterministic in-memory filesystems.\n\nSources:\n1. MDN Web Workers API — https://developer.mozilla.org/docs/Web/API/Web_Workers_API"}],"isError":false}],"source":{"kind":"tool","callId":"preview-web-search"}},"meta":{"sources":[{"url":"https://developer.mozilla.org/docs/Web/API/Web_Workers_API","title":"Web Workers API","snippet":"Web Workers run scripts in background threads."}],"truncated":false,"answer":"Browser workers can host deterministic in-memory filesystems."}},"surfaceOp":"append","seq":184,"time":1787472000184}
{"type":"tool/call","data":{"turn":29,"step":1,"callId":"preview-todo","name":"todo_write","arguments":"{\"todos\":[{\"content\":\"Inspect tool cards\",\"status\":\"completed\"},{\"content\":\"Open both subagents\",\"status\":\"completed\"},{\"content\":\"Load earlier history\",\"status\":\"in_progress\"}]}"},"seq":185,"time":1787472000185}
{"type":"todo/write","data":{"todos":[{"content":"Inspect tool cards","status":"completed"},{"content":"Open both subagents","status":"completed"},{"content":"Load earlier history","status":"in_progress"}]},"seq":186,"time":1787472000186}
{"type":"tool/result","data":{"turn":29,"step":1,"message":{"id":"preview-todo-result","role":"user","content":[{"type":"tool-result","toolCallId":"preview-todo","content":[{"type":"text","text":"Updated todo list: 0 pending, 1 in progress, 2 completed."}],"isError":false}],"source":{"kind":"tool","callId":"preview-todo"}}},"surfaceOp":"append","seq":187,"time":1787472000187}
{"type":"tool/call","data":{"turn":29,"step":1,"callId":"preview-subagent","name":"subagent","arguments":"{\"description\":\"Continue preview verification\",\"prompt\":\"Check the remaining preview cases.\",\"run_in_background\":true}"},"seq":188,"time":1787472000188}
{"type":"tool/result","data":{"turn":29,"step":1,"message":{"id":"preview-subagent-result","role":"user","content":[{"type":"tool-result","toolCallId":"preview-subagent","content":[{"type":"text","text":"started subagent preview-follow-up-builder"}],"isError":false}],"source":{"kind":"tool","callId":"preview-subagent"}}},"surfaceOp":"append","seq":189,"time":1787472000189}
{"type":"tool/call","data":{"turn":29,"step":1,"callId":"preview-subagent-fork","name":"subagent_fork","arguments":"{\"description\":\"Review preview architecture\",\"prompt\":\"Review the fixture architecture.\",\"run_in_background\":false}"},"seq":190,"time":1787472000190}
{"type":"tool/result","data":{"turn":29,"step":1,"message":{"id":"preview-subagent-fork-result","role":"user","content":[{"type":"tool-result","toolCallId":"preview-subagent-fork","content":[{"type":"text","text":"The preview fixture remains separate from user-owned WebFS data."}],"isError":false}],"source":{"kind":"tool","callId":"preview-subagent-fork"}}},"surfaceOp":"append","seq":191,"time":1787472000191}
{"type":"tool/call","data":{"turn":29,"step":1,"callId":"preview-failure","name":"read","arguments":"{\"file_path\":\"missing.txt\"}"},"seq":192,"time":1787472000192}
{"type":"tool/result","data":{"turn":29,"step":1,"message":{"id":"preview-failure-result","role":"user","content":[{"type":"tool-result","toolCallId":"preview-failure","content":[{"type":"text","text":"Error: ENOENT: no such file, open missing.txt"}],"isError":true}],"source":{"kind":"tool","callId":"preview-failure"}},"error":{"name":"FsError","code":"ENOENT"}},"surfaceOp":"append","seq":193,"time":1787472000193}
{"type":"step/end","data":{"turn":29,"step":1},"seq":194,"time":1787472000194}
{"type":"step/start","data":{"turn":29,"step":2},"seq":195,"time":1787472000195}
{"type":"assistant/message","data":{"turn":29,"step":2,"message":{"id":"preview-gallery-final","role":"assistant","content":[{"type":"text","text":"## Preview tour complete\n\nThe workspace, specialized tool cards, two subagent histories, and an earlier history page are ready to inspect."}],"source":{"kind":"model","provider":"preview-fixture","model":"deterministic"}}},"sourceEventSeqs":[],"surfaceOp":"append","seq":196,"time":1787472000196}
{"type":"step/end","data":{"turn":29,"step":2},"seq":197,"time":1787472000197}
{"type":"turn/end","data":{"turn":29,"reason":{"kind":"completed"}},"seq":198,"time":1787472000198}
@@ -0,0 +1,24 @@
{
"unit": {
"name": "session_projcache",
"version": 3
},
"global": null,
"tables": {
"sessions": {
"preview-showcase": {
"identity": {
"createdAt": 1787472000000,
"cwd": "/dsh/workspace"
},
"rows": {
"title": {
"ver": 1,
"seq": 198,
"val": "WebWorker Preview Showcase"
}
}
}
}
}
}
@@ -0,0 +1,8 @@
---
name: preview-tour
description: Inspect the bundled Preview workspace and its deterministic Session examples.
---
# Preview tour
Read the workspace files, inspect the tool gallery, open both subagent histories, and load the earlier conversation page.
@@ -0,0 +1,9 @@
# Preview Workspace
This deterministic workspace is bundled with the browser-only preview.
- `src/preview.ts` is the file changed by the example write result.
- `data/tasks.json` mirrors the completed preview checklist.
- `.agents/skills/preview-tour/SKILL.md` proves dot directories survive image packing.
Refresh the preview to restore these image bytes.
@@ -0,0 +1,17 @@
{
"title": "Preview verification",
"tasks": [
{
"name": "Inspect tool cards",
"status": "completed"
},
{
"name": "Open both subagents",
"status": "completed"
},
{
"name": "Load earlier history",
"status": "completed"
}
]
}
@@ -0,0 +1,3 @@
export const previewStatus = 'ready'
export const previewFeatures = ['tools', 'subagents', 'pagination'] as const
@@ -108,10 +108,9 @@ it('reports that a synchronous run cannot happen, without throwing at the probe'
})
expect(spawnSync(launcherPath(), ['--ro', '/', '--', 'echo', 'x']).error?.message)
.toContain('commands run asynchronously')
expect(spawnSync(launcherPath(), ['--probe', '--'])).toMatchObject({
status: LAUNCHER_FAILURE_EXIT,
stderr: expect.any(Buffer),
})
const failedProbe = spawnSync(launcherPath(), ['--probe', '--'])
expect(failedProbe.status).toBe(LAUNCHER_FAILURE_EXIT)
expect(Buffer.isBuffer(failedProbe.stderr)).toBe(true)
})
it('keeps the native Landlock package API and CLI failure contract', async () => {
@@ -4,7 +4,7 @@
*/
import { describe, expect, it } from 'vitest'
import { packTar, parseTar } from '../../src/storage/tar.ts'
import { loadVfsImage } from '../../src/storage/memory.ts'
import { loadVfsImage, loadVfsOverlay } from '../../src/storage/memory.ts'
const encoder = new TextEncoder()
@@ -38,4 +38,21 @@ describe('tar codec', () => {
expect(vfs.existsSync('/dsh/workspace')).toBe(true)
expect(vfs.existsSync('/dsh/absent')).toBe(false)
})
it('applies ordered data overlays without exposing runtime paths', () => {
const vfs = loadVfsImage(packTar({
'config/cordis.yml': encoder.encode('- id: subject\n'),
'workspace/status.txt': encoder.encode('base'),
}), '/dsh')
loadVfsOverlay(packTar({
'workspace/status.txt': encoder.encode('fixture'),
'home/sessions/example/session.jsonl': encoder.encode('{}\n'),
}), '/dsh', vfs)
expect(vfs.readFileSync('/dsh/workspace/status.txt', 'utf8')).toBe('fixture')
expect(vfs.readFileSync('/dsh/home/sessions/example/session.jsonl', 'utf8')).toBe('{}\n')
expect(() => loadVfsOverlay(packTar({
'config/cordis.yml': encoder.encode('replaced'),
}), '/dsh', vfs)).toThrow(/overlay entry must stay under home\/ or workspace/)
expect(vfs.readFileSync('/dsh/config/cordis.yml', 'utf8')).toBe('- id: subject\n')
})
})
@@ -0,0 +1,22 @@
import { describe, expect, it } from 'vitest'
import { parseInboundFrame } from '../../src/transport/frames.ts'
describe('tunnel init frame', () => {
it('retains the selected overlay order', () => {
expect(parseInboundFrame({
t: 'init',
image: 'base.tar.gz',
overlays: ['workspace.tar.gz', 'session.tar.gz'],
})).toEqual({
t: 'init',
image: 'base.tar.gz',
overlays: ['workspace.tar.gz', 'session.tar.gz'],
})
})
it('rejects a missing or non-string overlay list', () => {
expect(() => parseInboundFrame({ t: 'init', image: 'base.tar.gz' })).toThrow(/array of string overlay urls/)
expect(() => parseInboundFrame({ t: 'init', image: 'base.tar.gz', overlays: [1] }))
.toThrow(/array of string overlay urls/)
})
})
@@ -54,6 +54,27 @@ function stubWorker(): {
}
}
// The opening frame preserves overlay order for deterministic pre-boot mounts.
{
const { worker, sent } = stubWorker()
const tunnel = new WorkerTunnel(worker)
tunnel.init('https://preview.test/base.tar.gz', [
'https://preview.test/first.tar.gz',
'https://preview.test/second.tar.gz',
])
check('the init frame carries ordered overlays', sent[0], {
t: 'init',
image: 'https://preview.test/base.tar.gz',
overlays: ['https://preview.test/first.tar.gz', 'https://preview.test/second.tar.gz'],
})
const direct = stubWorker()
new WorkerTunnel(direct.worker).init('https://preview.test/base.tar.gz')
check('the direct init path defaults to no overlays', direct.sent[0], {
t: 'init', image: 'https://preview.test/base.tar.gz', overlays: [],
})
}
// A normal reply resolves and says nothing on the console.
{
const { worker, sent, deliver } = stubWorker()
@@ -0,0 +1,118 @@
import { readFileSync, readdirSync } from 'node:fs'
import { join, relative } from 'node:path'
import { describe, expect, it } from 'vitest'
import { Session, SessionId, type SessionEvent } from '@deepseek-ai/dsh-session'
import { scanLog } from '@deepseek-ai/dsh-session-persistence-jsonl/src/format.ts'
import { foldSubagentDescriptor } from '@deepseek-ai/dsh-subagent'
import {
buildVfsExampleFiles,
VFS_EXAMPLE_OLDEST_MESSAGE,
VFS_EXAMPLE_ROOT,
VFS_EXAMPLE_SESSION_IDS,
VFS_EXAMPLE_TAIL_MESSAGE,
VFS_EXAMPLE_TITLE,
} from './vfs-example-fixture.ts'
function filesUnder(root: string): string[] {
const files: string[] = []
const visit = (directory: string): void => {
for (const entry of readdirSync(directory, { withFileTypes: true })) {
const path = join(directory, entry.name)
if (entry.isDirectory()) visit(path)
else if (entry.isFile()) files.push(relative(root, path).replaceAll('\\', '/'))
}
}
visit(root)
return files.sort()
}
function readSession(id: string): ReturnType<typeof scanLog> {
return scanLog(readFileSync(
join(VFS_EXAMPLE_ROOT, 'home/sessions/--dsh-workspace--', id, 'session.jsonl'),
))
}
function textOf(event: SessionEvent): string {
if (event.type === 'user/message') {
return event.data.content.flatMap(block => block.type === 'text' ? [block.text] : []).join('\n')
}
if (event.type === 'assistant/message') {
return event.data.message.content.flatMap(block => block.type === 'text' ? [block.text] : []).join('\n')
}
return ''
}
describe('WebWorker preview VFS example', () => {
it('matches its deterministic source byte for byte', () => {
const expected = buildVfsExampleFiles()
expect(filesUnder(VFS_EXAMPLE_ROOT)).toEqual([...expected.keys()].sort())
for (const [path, content] of expected) {
expect(readFileSync(join(VFS_EXAMPLE_ROOT, path), 'utf8'), path).toBe(content)
}
})
it('seeds the cold-list title cache against the main log identity', () => {
const cache = JSON.parse(readFileSync(
join(VFS_EXAMPLE_ROOT, 'home/storages/session_projcache.json'),
'utf8',
)) as {
unit: { name: string; version: number }
tables: { sessions: Record<string, { identity: { createdAt: number; cwd: string }; rows: { title: unknown } }> }
}
expect(cache.unit).toEqual({ name: 'session_projcache', version: 3 })
expect(cache.tables.sessions[VFS_EXAMPLE_SESSION_IDS.main]).toMatchObject({
identity: { createdAt: 1_787_472_000_000, cwd: '/dsh/workspace' },
rows: { title: { ver: 1, val: VFS_EXAMPLE_TITLE } },
})
})
it('restores the main production log with paging and tool coverage', () => {
const { meta, events } = readSession(VFS_EXAMPLE_SESSION_IDS.main)
expect(meta).toMatchObject({
id: VFS_EXAMPLE_SESSION_IDS.main,
cwd: '/dsh/workspace',
delegationDepth: 0,
agentPreset: 'standard',
})
expect(events.map(event => event.seq)).toEqual(events.map((_, index) => index))
expect(events.at(-1)).toMatchObject({ type: 'turn/end', data: { reason: { kind: 'completed' } } })
expect(() => Session.fromRestore(SessionId(meta.id), events, meta)).not.toThrow()
const messages = events.filter(event =>
(event.type === 'user/message' || event.type === 'assistant/message') && event.surfaceOp === 'append')
expect(messages.length).toBeGreaterThan(50)
expect(messages.some(event => textOf(event).includes(VFS_EXAMPLE_OLDEST_MESSAGE))).toBe(true)
expect(messages.some(event => textOf(event).includes(VFS_EXAMPLE_TAIL_MESSAGE))).toBe(true)
expect(events.some(event => event.type === 'session/title'
&& (event.data as { title?: unknown }).title === VFS_EXAMPLE_TITLE)).toBe(true)
const tools = events.flatMap(event => event.type === 'tool/call' ? [event.data.name] : [])
expect(new Set(tools)).toEqual(new Set([
'read', 'write', 'bash', 'glob', 'grep', 'web_search', 'todo_write', 'subagent', 'subagent_fork',
]))
expect(events.some(event => event.type === 'todo/write')).toBe(true)
expect(events.some(event => event.type === 'tool/result' && event.data.message.content[0].isError === true)).toBe(true)
})
it('restores one-shot and continuable child Sessions with durable descriptors', () => {
const expected = [
[VFS_EXAMPLE_SESSION_IDS.oneShot, 'one-shot'],
[VFS_EXAMPLE_SESSION_IDS.continuable, 'continuable'],
] as const
for (const [id, mode] of expected) {
const { meta, events } = readSession(id)
expect(meta).toMatchObject({
id,
cwd: '/dsh/workspace',
parentSession: VFS_EXAMPLE_SESSION_IDS.main,
origin: 'subagent',
delegationDepth: 1,
agentPreset: 'standard',
})
expect(events.map(event => event.seq)).toEqual(events.map((_, index) => index))
expect(events.at(-1)).toMatchObject({ type: 'turn/end', data: { reason: { kind: 'completed' } } })
expect(foldSubagentDescriptor(events.slice(meta.seedLength ?? 0))).toMatchObject({ mode })
expect(() => Session.fromRestore(SessionId(meta.id), events, meta)).not.toThrow()
}
})
})
@@ -0,0 +1,432 @@
/** Deterministic source for the filesystem tree bundled into the WebWorker preview. */
import { fileURLToPath } from 'node:url'
import { SessionId, type SessionEvent, type SessionHeader } from '@deepseek-ai/dsh-session'
import {
eventLines, projectKey, toHeaderLine,
} from '@deepseek-ai/dsh-session-persistence-jsonl/src/format.ts'
import { snapshotSubagentDescriptor } from '@deepseek-ai/dsh-subagent'
/** Root copied by the preview image's repository adapter. */
export const VFS_EXAMPLE_ROOT = fileURLToPath(new URL('./fixtures/vfs-example', import.meta.url))
/** Durable ids used by browser assertions and subagent parent links. */
export const VFS_EXAMPLE_SESSION_IDS = {
main: SessionId('preview-showcase'),
oneShot: SessionId('preview-architecture-review'),
continuable: SessionId('preview-follow-up-builder'),
} as const
/** Stable title rendered in the root Session list. */
export const VFS_EXAMPLE_TITLE = 'WebWorker Preview Showcase'
/** Oldest prompt, intentionally outside the first 50-message history page. */
export const VFS_EXAMPLE_OLDEST_MESSAGE = 'History checkpoint 01: verify deterministic preview state.'
/** Settled tail marker used by browser acceptance and the demonstration GIF. */
export const VFS_EXAMPLE_TAIL_MESSAGE = 'Preview tour complete'
const WORKSPACE = '/dsh/workspace'
const CREATED_AT = 1_787_472_000_000
const HISTORICAL_TURNS = 28
const PREVIEW_GUIDE = `# Preview Workspace
This deterministic workspace is bundled with the browser-only preview.
- \`src/preview.ts\` is the file changed by the example write result.
- \`data/tasks.json\` mirrors the completed preview checklist.
- \`.agents/skills/preview-tour/SKILL.md\` proves dot directories survive image packing.
Refresh the preview to restore these image bytes.
`
const PREVIEW_SOURCE_BEFORE = 'export const previewStatus = \'draft\'\n'
const PREVIEW_SOURCE = `export const previewStatus = 'ready'
export const previewFeatures = ['tools', 'subagents', 'pagination'] as const
`
const TASKS = `${JSON.stringify({
title: 'Preview verification',
tasks: [
{ name: 'Inspect tool cards', status: 'completed' },
{ name: 'Open both subagents', status: 'completed' },
{ name: 'Load earlier history', status: 'completed' },
],
}, null, 2)}\n`
const SKILL = `---
name: preview-tour
description: Inspect the bundled Preview workspace and its deterministic Session examples.
---
# Preview tour
Read the workspace files, inspect the tool gallery, open both subagent histories, and load the earlier conversation page.
`
interface EventDraft {
readonly type: string
readonly data: unknown
readonly surfaceOp?: 'append'
readonly sourceEventSeqs?: number[]
readonly ignorable?: true
}
class EventLog {
readonly events: SessionEvent[]
private nextTime: number
constructor(time: number, seed: readonly SessionEvent[] = []) {
this.events = seed.map(event => structuredClone(event))
this.nextTime = Math.max(time, (this.events.at(-1)?.time ?? time - 1) + 1)
}
add(draft: EventDraft): number {
const seq = this.events.length
this.events.push({ ...draft, seq, time: this.nextTime++ } as unknown as SessionEvent)
return seq
}
}
function userMessage(id: string, text: string): EventDraft {
return {
type: 'user/message',
data: {
id,
role: 'user',
content: [{ type: 'text', text }],
source: { kind: 'user' },
},
surfaceOp: 'append',
}
}
function assistantMessage(id: string, turn: number, step: number, content: unknown[]): EventDraft {
return {
type: 'assistant/message',
data: {
turn,
step,
message: {
id,
role: 'assistant',
content,
source: { kind: 'model', provider: 'preview-fixture', model: 'deterministic' },
},
},
sourceEventSeqs: [],
surfaceOp: 'append',
}
}
interface GalleryCall {
readonly id: string
readonly name: string
readonly args: Record<string, unknown>
readonly result: string
readonly meta?: unknown
readonly error?: { readonly name: string; readonly code: string }
readonly todos?: Array<{ readonly content: string; readonly status: 'pending' | 'in_progress' | 'completed' }>
}
function readResult(): { text: string; meta: unknown } {
const lines = PREVIEW_GUIDE.trimEnd().split('\n').map((text, index) => ({ number: index + 1, text }))
return {
text: `<path>PREVIEW.md</path>\n<type>file</type>\n<content>\n${lines.map(line => `${String(line.number)}: ${line.text}`).join('\n')}\n\n(End of file - total ${String(lines.length)} lines)\n</content>`,
meta: { path: 'PREVIEW.md', offset: 1, lines, totalLines: lines.length, lang: 'md' },
}
}
function galleryCalls(): GalleryCall[] {
const read = readResult()
return [
{
id: 'preview-read',
name: 'read',
args: { file_path: 'PREVIEW.md' },
result: read.text,
meta: read.meta,
},
{
id: 'preview-write',
name: 'write',
args: { file_path: 'src/preview.ts', content: PREVIEW_SOURCE },
result: '<path>src/preview.ts</path>\n<type>file</type>\n<content>\nUpdated file\n</content>',
meta: { diffs: [{ path: 'src/preview.ts', oldText: PREVIEW_SOURCE_BEFORE, newText: PREVIEW_SOURCE }] },
},
{
id: 'preview-bash',
name: 'bash',
args: { command: "printf 'preview ready\\n'", description: 'Print the preview readiness marker' },
result: 'preview ready\n',
},
{
id: 'preview-glob',
name: 'glob',
args: { pattern: '**/*', path: '.' },
result: 'PREVIEW.md\ndata/tasks.json\nsrc/preview.ts',
meta: {
shape: 'paths',
paths: ['PREVIEW.md', 'data/tasks.json', 'src/preview.ts'],
truncated: false,
total: 3,
},
},
{
id: 'preview-grep',
name: 'grep',
args: { pattern: 'preview', path: '.', include: '*.{md,ts,json}' },
result: 'PREVIEW.md:3:This deterministic workspace is bundled with the browser-only preview.\nsrc/preview.ts:1:export const previewStatus = \'ready\'',
meta: {
shape: 'matches',
files: [
{ path: 'PREVIEW.md', matches: [{ lineNumber: 3, line: 'This deterministic workspace is bundled with the browser-only preview.' }] },
{ path: 'src/preview.ts', matches: [{ lineNumber: 1, line: "export const previewStatus = 'ready'" }] },
],
truncated: false,
total: 2,
},
},
{
id: 'preview-web-search',
name: 'web_search',
args: { queries: ['Web Worker filesystem compatibility'] },
result: 'Browser workers can host deterministic in-memory filesystems.\n\nSources:\n1. MDN Web Workers API — https://developer.mozilla.org/docs/Web/API/Web_Workers_API',
meta: {
sources: [{
url: 'https://developer.mozilla.org/docs/Web/API/Web_Workers_API',
title: 'Web Workers API',
snippet: 'Web Workers run scripts in background threads.',
}],
truncated: false,
answer: 'Browser workers can host deterministic in-memory filesystems.',
},
},
{
id: 'preview-todo',
name: 'todo_write',
args: {
todos: [
{ content: 'Inspect tool cards', status: 'completed' },
{ content: 'Open both subagents', status: 'completed' },
{ content: 'Load earlier history', status: 'in_progress' },
],
},
result: 'Updated todo list: 0 pending, 1 in progress, 2 completed.',
todos: [
{ content: 'Inspect tool cards', status: 'completed' },
{ content: 'Open both subagents', status: 'completed' },
{ content: 'Load earlier history', status: 'in_progress' },
],
},
{
id: 'preview-subagent',
name: 'subagent',
args: { description: 'Continue preview verification', prompt: 'Check the remaining preview cases.', run_in_background: true },
result: `started subagent ${VFS_EXAMPLE_SESSION_IDS.continuable}`,
},
{
id: 'preview-subagent-fork',
name: 'subagent_fork',
args: { description: 'Review preview architecture', prompt: 'Review the fixture architecture.', run_in_background: false },
result: 'The preview fixture remains separate from user-owned WebFS data.',
},
{
id: 'preview-failure',
name: 'read',
args: { file_path: 'missing.txt' },
result: 'Error: ENOENT: no such file, open missing.txt',
error: { name: 'FsError', code: 'ENOENT' },
},
]
}
function addClosedTextTurn(log: EventLog, turn: number): void {
const checkpoint = String(turn).padStart(2, '0')
log.add({ type: 'turn/start', data: { turn } })
log.add(userMessage(`preview-user-${checkpoint}`, `History checkpoint ${checkpoint}: verify deterministic preview state.`))
if (turn === 1) {
log.add({
type: 'session/title',
data: { title: VFS_EXAMPLE_TITLE, messageSeqs: [], source: { kind: 'user' } },
})
}
log.add({ type: 'step/start', data: { turn, step: 1 } })
log.add(assistantMessage(
`preview-assistant-${checkpoint}`,
turn,
1,
[{ type: 'text', text: `Checkpoint ${checkpoint} is recorded.` }],
))
log.add({ type: 'step/end', data: { turn, step: 1 } })
log.add({ type: 'turn/end', data: { turn, reason: { kind: 'completed' } } })
}
function mainLog(): { readonly events: SessionEvent[]; readonly forkSeedLength: number } {
const log = new EventLog(CREATED_AT)
for (let turn = 1; turn <= HISTORICAL_TURNS; turn++) addClosedTextTurn(log, turn)
const forkSeedLength = log.events.length
const turn = HISTORICAL_TURNS + 1
const calls = galleryCalls()
log.add({ type: 'turn/start', data: { turn } })
log.add(userMessage('preview-gallery-user', 'Show the seeded workspace, tool cards, subagents, and pagination in one tour.'))
log.add({ type: 'step/start', data: { turn, step: 1 } })
log.add(assistantMessage('preview-gallery-tools', turn, 1, [
{ type: 'reasoning', text: 'I will inspect the deterministic workspace and collect each preview surface.' },
...calls.map(call => ({ type: 'tool-call', id: call.id, name: call.name, arguments: JSON.stringify(call.args) })),
]))
for (const call of calls) {
log.add({
type: 'tool/call',
data: { turn, step: 1, callId: call.id, name: call.name, arguments: JSON.stringify(call.args) },
})
if (call.todos !== undefined) log.add({ type: 'todo/write', data: { todos: call.todos } })
log.add({
type: 'tool/result',
data: {
turn,
step: 1,
message: {
id: `${call.id}-result`,
role: 'user',
content: [{
type: 'tool-result',
toolCallId: call.id,
content: [{ type: 'text', text: call.result }],
isError: call.error !== undefined,
}],
source: { kind: 'tool', callId: call.id },
},
...call.meta === undefined ? {} : { meta: call.meta },
...call.error === undefined ? {} : { error: call.error },
},
surfaceOp: 'append',
})
}
log.add({ type: 'step/end', data: { turn, step: 1 } })
log.add({ type: 'step/start', data: { turn, step: 2 } })
log.add(assistantMessage('preview-gallery-final', turn, 2, [{
type: 'text',
text: `## ${VFS_EXAMPLE_TAIL_MESSAGE}\n\nThe workspace, specialized tool cards, two subagent histories, and an earlier history page are ready to inspect.`,
}]))
log.add({ type: 'step/end', data: { turn, step: 2 } })
log.add({ type: 'turn/end', data: { turn, reason: { kind: 'completed' } } })
return { events: log.events, forkSeedLength }
}
function oneShotLog(seed: readonly SessionEvent[]): SessionEvent[] {
const log = new EventLog(CREATED_AT + 100_000, seed)
log.add({ type: 'session/end-seed', data: {} })
const turn = HISTORICAL_TURNS + 1
log.add({ type: 'turn/start', data: { turn } })
log.add(userMessage('preview-review-user', 'Review whether the preview fixture is isolated from future WebFS data.'))
log.add({
type: 'subagent/descriptor',
data: snapshotSubagentDescriptor({
mode: 'one-shot', provider: 'fork', label: 'Review preview architecture',
}),
})
log.add({ type: 'step/start', data: { turn, step: 1 } })
log.add(assistantMessage('preview-review-assistant', turn, 1, [{
type: 'text',
text: 'The bundled fixture is static image content; future WebFS state remains user-owned.',
}]))
log.add({ type: 'step/end', data: { turn, step: 1 } })
log.add({ type: 'turn/end', data: { turn, reason: { kind: 'completed' } } })
return log.events
}
function continuableLog(): SessionEvent[] {
const log = new EventLog(CREATED_AT + 200_000)
log.add({ type: 'turn/start', data: { turn: 1 } })
log.add(userMessage('preview-builder-user', 'Check that the Preview workspace can support follow-up tasks.'))
log.add({
type: 'subagent/descriptor',
data: snapshotSubagentDescriptor({
mode: 'continuable', provider: 'spawn', label: 'Continue preview verification',
}),
})
log.add({ type: 'step/start', data: { turn: 1, step: 1 } })
log.add(assistantMessage('preview-builder-assistant', 1, 1, [{
type: 'text',
text: 'This child is continuable and ready for another verification turn.',
}]))
log.add({ type: 'step/end', data: { turn: 1, step: 1 } })
log.add({ type: 'turn/end', data: { turn: 1, reason: { kind: 'completed' } } })
return log.events
}
function header(
id: SessionHeader['id'],
createdAt: number,
child?: { readonly parentSession: SessionHeader['id']; readonly mode: 'one-shot' | 'continuable'; readonly seedLength?: number },
): SessionHeader {
return {
version: 0,
id,
createdAt,
cwd: WORKSPACE,
delegationDepth: child === undefined ? 0 : 1,
agentPreset: 'standard',
...child === undefined ? {} : {
parentSession: child.parentSession,
origin: 'subagent' as const,
...child.seedLength === undefined ? {} : { seedLength: child.seedLength },
},
}
}
function renderLog(meta: SessionHeader, events: readonly SessionEvent[]): string {
return `${JSON.stringify(toHeaderLine(meta))}\n${eventLines(events, true)}\n`
}
/** Build every committed fixture file as repository-relative UTF-8 text. */
export function buildVfsExampleFiles(): ReadonlyMap<string, string> {
const main = mainLog()
const project = projectKey(WORKSPACE)
const sessionPath = (id: string): string => `home/sessions/${project}/${id}/session.jsonl`
const projectionCache = `${JSON.stringify({
unit: { name: 'session_projcache', version: 3 },
global: null,
tables: {
sessions: {
[VFS_EXAMPLE_SESSION_IDS.main]: {
identity: { createdAt: CREATED_AT, cwd: WORKSPACE },
rows: {
title: { ver: 1, seq: main.events.at(-1)?.seq ?? -1, val: VFS_EXAMPLE_TITLE },
},
},
},
},
}, null, 2)}\n`
return new Map([
['workspace/PREVIEW.md', PREVIEW_GUIDE],
['workspace/src/preview.ts', PREVIEW_SOURCE],
['workspace/data/tasks.json', TASKS],
['workspace/.agents/skills/preview-tour/SKILL.md', SKILL],
['home/storages/session_projcache.json', projectionCache],
[sessionPath(VFS_EXAMPLE_SESSION_IDS.main), renderLog(
header(VFS_EXAMPLE_SESSION_IDS.main, CREATED_AT),
main.events,
)],
[sessionPath(VFS_EXAMPLE_SESSION_IDS.oneShot), renderLog(
header(VFS_EXAMPLE_SESSION_IDS.oneShot, CREATED_AT + 100_000, {
parentSession: VFS_EXAMPLE_SESSION_IDS.main,
mode: 'one-shot',
seedLength: main.forkSeedLength,
}),
oneShotLog(main.events.slice(0, main.forkSeedLength)),
)],
[sessionPath(VFS_EXAMPLE_SESSION_IDS.continuable), renderLog(
header(VFS_EXAMPLE_SESSION_IDS.continuable, CREATED_AT + 200_000, {
parentSession: VFS_EXAMPLE_SESSION_IDS.main,
mode: 'continuable',
}),
continuableLog(),
)],
])
}
+9
View File
@@ -4848,6 +4848,15 @@ importers:
'@deepseek-ai/dsh-sandbox-policy':
specifier: workspace:^
version: link:../../sandbox/sandbox-policy
'@deepseek-ai/dsh-session':
specifier: workspace:^
version: link:../../core/session
'@deepseek-ai/dsh-session-persistence-jsonl':
specifier: workspace:^
version: link:../../session/session-persistence-jsonl
'@deepseek-ai/dsh-subagent':
specifier: workspace:^
version: link:../../subagent/subagent
'@deepseek-ai/dsh-subprocess-local':
specifier: workspace:^
version: link:../../subprocess/subprocess-local
+13 -1
View File
@@ -1,7 +1,7 @@
import { describe, expect, it } from 'vitest'
import { type SessionEvent } from '@deepseek-ai/dsh-session'
import { parseSessionLog } from '@deepseek-ai/dsh-llm-replay'
import { canonicalSessionFixture } from './session-fixture-layout.ts'
import { canonicalSessionFixture, isPhysicalSessionFixture } from './session-fixture-layout.ts'
const HEADER = ' {"type":"session","version":0,"id":"fixture","createdAt":1,"delegationDepth":0} '
@@ -68,3 +68,15 @@ describe('canonicalSessionFixture', () => {
.toThrow(/broken\.jsonl: session snapshot line 2: malformed text-chunks storage row/)
})
})
describe('isPhysicalSessionFixture', () => {
it('excludes only persisted logs under the WebWorker example root', () => {
expect(isPhysicalSessionFixture(
'packages/experimental/webworker-runtime/tests/fixtures/vfs-example/home/sessions/--dsh-workspace--/main/session.jsonl',
)).toBe(true)
expect(isPhysicalSessionFixture(
'packages/experimental/webworker-runtime/tests/fixtures/vfs-example/home/sessions/README.jsonl',
)).toBe(false)
expect(isPhysicalSessionFixture('apps/web/tests/snapshots/example/session.jsonl')).toBe(false)
})
})
+15
View File
@@ -7,6 +7,10 @@ import { resolve } from 'node:path'
import { packChunkRuns, type SessionEvent } from '@deepseek-ai/dsh-session'
import { parseSessionLog } from '@deepseek-ai/dsh-llm-replay'
/** Physical persistence artifacts validated by the WebWorker runtime fixture spec. */
const PHYSICAL_SESSION_FIXTURE_ROOT =
'packages/experimental/webworker-runtime/tests/fixtures/vfs-example/home/sessions/'
/** One repository session fixture and its canonical projected representation. */
export interface SessionFixtureLayout {
/** Repository-relative path with `/` separators. */
@@ -17,6 +21,16 @@ export interface SessionFixtureLayout {
canonical: string
}
/**
* Whether a repository JSONL is a production-layout persistence artifact rather
* than an envelope-free replay snapshot owned by this script.
* @param path - Repository-relative path with `/` separators.
* @returns True only for Session logs under the WebWorker VFS example root.
*/
export function isPhysicalSessionFixture(path: string): boolean {
return path.startsWith(PHYSICAL_SESSION_FIXTURE_ROOT) && path.endsWith('/session.jsonl')
}
function isSessionHeader(value: unknown): boolean {
return value !== null && typeof value === 'object' && (value as { type?: unknown }).type === 'session'
}
@@ -109,6 +123,7 @@ function discoverJsonlFiles(root: string): string[] {
*/
export function inspectSessionFixtureLayouts(root: string): SessionFixtureLayout[] {
return discoverJsonlFiles(root).flatMap((path) => {
if (isPhysicalSessionFixture(path)) return []
const source = readFileSync(resolve(root, path), 'utf8')
const canonical = canonicalSessionFixture(source, path)
return canonical === undefined ? [] : [{ path, source, canonical }]