Merge remote-tracking branch 'origin/master' into mergebot/pr711

# Conflicts:
#	apps/cli/README.i18n.yaml
#	apps/cli/README.md
#	apps/cli/README.zh.md
#	apps/cli/cordis.yml
#	apps/cli/package.json
#	docs/config-catalog.md
#	packages/client/runtime/README.i18n.yaml
#	packages/client/runtime/src/client/contract/sessions.ts
#	packages/client/test-runtime/src/sessions.ts
#	packages/client/ui-workspace/README.i18n.yaml
#	packages/client/ui-workspace/README.md
#	packages/client/ui-workspace/README.zh.md
#	packages/client/ui-workspace/src/client/WorkspaceBrowser.tsx
#	packages/client/ui-workspace/src/client/tree.ts
#	packages/client/ui-workspace/tests/apply.spec.ts
#	packages/client/ui-workspace/tests/tree.spec.ts
#	packages/host/apiproxy/README.i18n.yaml
#	packages/host/apiproxy/src/api-proxy.ts
#	packages/host/apiproxy/src/api/index.ts
#	packages/host/apiproxy/tests/client-handler.spec.ts
#	packages/host/apiproxy/tests/rpc-schemas.spec.ts
#	pnpm-lock.yaml
This commit is contained in:
imccyu committed 2026-07-31 01:28:15 +08:00
commit ecbd8babc1
980 files changed
+30718 -7125

No files matched your search

+67 -20
View File
@@ -7,9 +7,9 @@
*/
import { spawn } from 'node:child_process'
import { existsSync, mkdirSync, statSync } from 'node:fs'
import { copyFile, readFile, rm, writeFile } from 'node:fs/promises'
import { basename, join, resolve, sep } from 'node:path'
import { existsSync, statSync } from 'node:fs'
import { chmod, copyFile, mkdir, readFile, rm, writeFile } from 'node:fs/promises'
import { basename, dirname, join, resolve, sep } from 'node:path'
import { parseArgs } from 'node:util'
const root = resolve(import.meta.dirname, '..')
@@ -254,6 +254,10 @@ class SingleExeBuild {
'--config.node-linker=hoisted',
'--config.auto-install-peers=false',
'--config.link-workspace-packages=true',
// The production closure intentionally omits the patched dev-only
// @earendil-works/pi-tui package. The root frozen install still validates
// every patch; this exception is scoped only to the production deploy.
'--config.allow-unused-patches=true',
this.staging,
])
if (this.cli.dryRun) {
@@ -285,11 +289,12 @@ class SingleExeBuild {
/**
* Package one target; SEA mode accepts one target per invocation.
* @param target - the pkg target triple to build.
* @returns the canonical product path `<out>/dsh-jsonrpc-agent-pkg-<platform>-<arch>`.
* @returns the executable path and, on macOS, its helper path.
*/
async pack(target: Target): Promise<string> {
async pack(target: Target): Promise<string[]> {
const product = join(this.outDir, `${OUTPUT_BASENAME}-${target.platform}-${target.arch}`)
if (!this.cli.dryRun) mkdirSync(this.outDir, { recursive: true })
await this.prepareNativePty(target)
if (!this.cli.dryRun) await mkdir(this.outDir, { recursive: true })
await this.run(`pkg ${target.spec}`, pnpmBin(), [
'dlx',
PKG_SPEC,
@@ -303,7 +308,43 @@ class SingleExeBuild {
if (!this.cli.dryRun && !existsSync(product)) {
throw new Error(`build-exe-for-python-sdk: product ${product} is missing after the pkg run; inspect ${this.outDir}.`)
}
return product
if (target.platform !== 'macos') return [product]
const spawnHelper = `${product}-spawn-helper`
const source = join(this.staging, 'node_modules', 'node-pty', 'prebuilds', `darwin-${target.arch}`, 'spawn-helper')
if (this.cli.dryRun) {
console.log(`build-exe-for-python-sdk: [dry-run] cp ${source} ${spawnHelper}`)
} else {
await copyFile(source, spawnHelper)
await chmod(spawnHelper, 0o755)
}
return [product, spawnHelper]
}
/**
* Put the target node-pty addon in the staged closure. Linux npm installs
* build it from source, but legacy deploy omits that side-effect directory.
* @param target - the pkg target whose native addon is being staged.
*/
private async prepareNativePty(target: Target): Promise<void> {
const stagedBuild = join(this.staging, 'node_modules', 'node-pty', 'build')
if (this.cli.dryRun) console.log(`build-exe-for-python-sdk: [dry-run] rm -rf ${stagedBuild}`)
else await rm(stagedBuild, { recursive: true, force: true })
if (target.platform !== 'linux') return
const source = join(root, 'packages', 'pty', 'pty-local', 'node_modules', 'node-pty', 'build', 'Release', 'pty.node')
const destination = join(stagedBuild, 'Release', 'pty.node')
if (this.cli.dryRun) {
console.log(`build-exe-for-python-sdk: [dry-run] cp ${source} ${destination}`)
return
}
const host = Target.host()
if (target.platform !== host.platform || target.arch !== host.arch) {
throw new Error(
'build-exe-for-python-sdk: build the Linux runtime on its target architecture; '
+ `target ${target.platform}-${target.arch} does not match host ${host.platform}-${host.arch}.`,
)
}
await mkdir(dirname(destination), { recursive: true })
await copyFile(source, destination)
}
/**
@@ -312,33 +353,34 @@ class SingleExeBuild {
*/
printProducts(products: string[]): void {
console.log(this.cli.dryRun ? 'build-exe-for-python-sdk: [dry-run] would produce:' : 'build-exe-for-python-sdk: products:')
for (const product of products) {
for (const path of products) {
if (this.cli.dryRun) {
console.log(` ${product}`)
console.log(` ${path}`)
continue
}
const megabytes = statSync(product).size / (1024 * 1024)
console.log(` ${product} (${megabytes.toFixed(1)} MB)`)
const megabytes = statSync(path).size / (1024 * 1024)
console.log(` ${path} (${megabytes.toFixed(1)} MB)`)
}
}
/**
* Copy each executable into the Python runtime package. The deployed node
* Copy each product into the Python runtime package. The deployed node
* carrier is already in place, and `dist-exe/` retains upload copies.
* @param products - the product paths returned by {@link pack}.
*/
async syncToPythonRuntime(products: string[]): Promise<void> {
const destDir = resolve(root, PYTHON_RUNTIME_DIR)
if (this.cli.dryRun) {
for (const product of products) {
console.log(`build-exe-for-python-sdk: [dry-run] cp ${product} ${join(destDir, basename(product))}`)
for (const path of products) {
console.log(`build-exe-for-python-sdk: [dry-run] cp ${path} ${join(destDir, basename(path))}`)
}
return
}
mkdirSync(destDir, { recursive: true })
for (const product of products) {
const destination = join(destDir, basename(product))
await copyFile(product, destination)
await mkdir(destDir, { recursive: true })
for (const path of products) {
const destination = join(destDir, basename(path))
await copyFile(path, destination)
await chmod(destination, statSync(path).mode & 0o777)
console.log(`build-exe-for-python-sdk: synced ${destination}`)
}
}
@@ -358,7 +400,12 @@ class SingleExeBuild {
}
console.log(`build-exe-for-python-sdk: ${label}: ${printable}`)
await new Promise<void>((resolvePromise, reject) => {
const child = spawn(command, args, { cwd: root, stdio: 'inherit' })
const child = spawn(command, args, {
cwd: root,
stdio: 'inherit',
// Artifact builds must not mutate or validate a developer's Git hooks.
env: { ...process.env, CI: 'true' },
})
child.once('error', (error) => {
reject(new Error(`build-exe-for-python-sdk: ${label} failed to spawn: ${error.message} (${printable})`))
})
@@ -384,7 +431,7 @@ async function main(): Promise<void> {
await pipeline.deployStaging()
await pipeline.injectPkgConfig()
const products: string[] = []
for (const target of cli.targets) products.push(await pipeline.pack(target))
for (const target of cli.targets) products.push(...await pipeline.pack(target))
pipeline.printProducts(products)
await pipeline.syncToPythonRuntime(products)
}
+19 -15
View File
@@ -24,6 +24,10 @@ PLATFORMS = {
}
def runtime_suffixes(executable_name: str) -> tuple[str, ...]:
return ("", "-spawn-helper") if "-macos-" in executable_name else ("",)
def main() -> None:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--package", choices=("sdk", "runtime"), required=True)
@@ -132,17 +136,12 @@ def stage_sdk(destination: Path, version: str) -> None:
def stage_runtime(destination: Path, version: str, executable: Path, executable_name: str) -> None:
if not executable.is_file():
raise FileNotFoundError(f"runtime executable does not exist: {executable}")
if executable.stat().st_mode & stat.S_IXUSR == 0:
raise PermissionError(f"runtime executable is not executable: {executable}")
copy_package(ROOT / "python" / "sdk-runtime", destination)
rewrite_version(destination / "pyproject.toml", version)
runtime_dir = destination / "src" / "deepseek_harness_runtime" / "runtime"
runtime_dir.mkdir(parents=True, exist_ok=True)
destination_executable = runtime_dir / executable_name
shutil.copyfile(executable, destination_executable)
destination_executable.chmod(executable.stat().st_mode & 0o777)
for suffix in runtime_suffixes(executable_name):
shutil.copy2(Path(f"{executable}{suffix}"), runtime_dir / f"{executable_name}{suffix}")
def verify_wheel(
@@ -161,16 +160,21 @@ def verify_wheel(
raise RuntimeError(f"{wheel} has wrong WHEEL tags: {wheel_metadata.get_all('Tag')}")
if metadata.get("Version") != version:
raise RuntimeError(f"{wheel} has version {metadata.get('Version')}, expected {version}")
executables = [name for name in archive.namelist() if "/runtime/dsh-jsonrpc-agent-pkg-" in name]
runtime_files = [
name for name in archive.namelist() if "/runtime/dsh-jsonrpc-agent-pkg-" in name
]
if package == "runtime":
assert platform is not None
if len(executables) != 1 or not executables[0].endswith(f"/runtime/{platform[1]}"):
raise RuntimeError(f"{wheel} must contain exactly {platform[1]}, found {executables}")
mode = archive.getinfo(executables[0]).external_attr >> 16
if mode & stat.S_IXUSR == 0:
raise RuntimeError(f"{wheel} runtime executable lost its executable bit")
elif executables:
raise RuntimeError(f"SDK wheel unexpectedly contains runtime executables: {executables}")
expected_files = [f"{platform[1]}{suffix}" for suffix in runtime_suffixes(platform[1])]
found_files = sorted(Path(name).name for name in runtime_files)
if found_files != expected_files:
raise RuntimeError(f"{wheel} runtime payload must be {expected_files}, found {found_files}")
for runtime_file in runtime_files:
mode = archive.getinfo(runtime_file).external_attr >> 16
if mode & stat.S_IXUSR == 0:
raise RuntimeError(f"{wheel} runtime executable lost its executable bit: {runtime_file}")
elif runtime_files:
raise RuntimeError(f"SDK wheel unexpectedly contains runtime executables: {runtime_files}")
if package == "sdk":
requirements = metadata.get_all("Requires-Dist") or []
expected_requirement = f"deepseek-harness-runtime-bin=={version}"
+41 -7
View File
@@ -1,16 +1,19 @@
/**
* Pins the client-bundle purity gate (tsdown preset resolveId classifier),
* the build-time mirror of the module-edge rules: platform module-table
* entries stay external, inline-safe wire layers inline, and every other
* @deepseek-ai value import — including a bare plugin-package name and a
* cross-plugin /client subpath — must fail the build loudly (cross-plugin
* collaboration goes through cordis services, never module imports).
* Pins shared client-bundle preset contracts: the module-edge purity gate and
* the physical watch dependencies hidden behind virtual CSS Modules.
*/
import { describe, expect, it } from 'vitest'
import { fileURLToPath } from 'node:url'
import { describe, expect, it, vi } from 'vitest'
import { CLIENT_EXTERNALS, clientBundle } from '../packages/client/tsdown.client.ts'
type ResolveId = (source: string) => null | { id: string; external: boolean }
interface CssModulePlugin {
name: string
resolveId?: (source: string, importer: string | undefined) => null | string
load?: (this: { addWatchFile: (id: string) => void }, id: string) => Promise<unknown>
}
function purityResolveId(): ResolveId {
// libEntry is spelled at every call site (no default) so the
// package-invariants text check can see the invariant entry per package.
@@ -21,6 +24,16 @@ function purityResolveId(): ResolveId {
return gate.resolveId as ResolveId
}
function cssModulePlugin(): CssModulePlugin {
const configs = clientBundle('@deepseek-ai/dsh-client-test', ['lib/types/index.js', 'lib/types/invariant.js'])
const plugins = (configs[1] as { plugins: CssModulePlugin[] }).plugins
const plugin = plugins.find(candidate => candidate.name === 'dsh-css-modules-inline')
if (plugin?.resolveId === undefined || plugin.load === undefined) {
throw new Error('CSS Modules plugin missing from client config')
}
return plugin
}
describe('client bundle purity gate', () => {
const resolveId = purityResolveId()
@@ -60,3 +73,24 @@ describe('client bundle purity gate', () => {
expect(dshClientChannels).toEqual(['@deepseek-ai/dsh-client-runtime/client'])
})
})
describe('client bundle CSS Modules watch graph', () => {
it('registers the physical stylesheet read behind a virtual module', async () => {
const plugin = cssModulePlugin()
const importer = fileURLToPath(new URL(
'../packages/client/ui-conversation/src/client/queue/QueueDock.tsx',
import.meta.url,
))
const stylesheet = fileURLToPath(new URL(
'../packages/client/ui-conversation/src/client/queue/QueueDock.module.css',
import.meta.url,
))
const virtualId = plugin.resolveId?.('./QueueDock.module.css', importer)
if (virtualId === null || virtualId === undefined) throw new Error('CSS Modules import was not resolved')
const addWatchFile = vi.fn()
await plugin.load?.call({ addWatchFile }, virtualId)
expect(addWatchFile).toHaveBeenCalledExactlyOnceWith(stylesheet)
})
})
+10 -22
View File
@@ -1,28 +1,16 @@
/**
* Boot the TUI or ACP Code Mode overlay, defaulting to TUI. Each overlay
* includes its base example, selects Code Mode, and adds the worker runtime.
* All require a DeepSeek API key; unsupported arguments fail with usage.
*/
/** Boot the ACP Code Mode overlay. Requires a DeepSeek API key. */
import { spawn } from 'node:child_process'
// Each UI's node invocation matches its base demo script plus the overlay config.
const UIS = new Map([
['tui', [
'--import',
'tsx/esm',
'apps/cli/src/bin.ts',
'--config',
'examples/tui-agent/code-mode.cordis.yml',
]],
['acp', ['--import', 'tsx', 'packages/examples/acp-demo/src/bin.ts', '--config', 'examples/acp-agent/code-mode.cordis.yml']],
])
const ui = process.argv[2] ?? 'tui'
const args = UIS.get(ui)
if (!args || process.argv.length > 3) {
console.error('usage: pnpm run demo:code-mode [tui|acp]')
if (process.argv.length > 2) {
console.error('usage: pnpm run demo:code-mode')
process.exit(2)
}
const child = spawn(process.execPath, args, { stdio: 'inherit' })
const child = spawn(process.execPath, [
'--import',
'tsx',
'packages/examples/acp-demo/src/bin.ts',
'--config',
'examples/acp-agent/code-mode.cordis.yml',
], { stdio: 'inherit' })
child.on('exit', (code, signal) => { process.exit(signal !== null ? 1 : code ?? 1) })
+6 -8
View File
@@ -1,21 +1,19 @@
/**
* Boot the self-referential Cordis tools under TUI, Web, or ACP, defaulting
* to TUI. This is a repository demo wrapper, not a product CLI feature.
* Boot the self-referential Cordis tools under Web or ACP, defaulting to Web. This is a repository demo wrapper, not a product CLI feature.
*/
import { spawn } from 'node:child_process'
const SURFACES = new Map([
['tui', ['--import', 'tsx', 'apps/cli/src/bin.ts', '--config', 'examples/cordis-agent/cordis.yml']],
// `dsh web` does not accept alternate configs yet. The TUI config escape
// hatch still boots this browser-only tree; the config owns port 3081.
['web', ['--import', 'tsx', 'apps/cli/src/bin.ts', '--config', 'examples/web-cordis/cordis.yml']],
// The browser surface with the cordis toolset layered on: `dsh web --config`
// applies this overlay over the shipped web composition; it owns port 3081.
['web', ['--import', 'tsx', 'apps/cli/src/bin.ts', 'web', '--config', 'examples/web-cordis/cordis.yml']],
['acp', ['--import', 'tsx', 'packages/examples/acp-demo/src/bin.ts', '--config', 'examples/acp-agent/cordis-tools.cordis.yml']],
])
const surface = process.argv[2] ?? 'tui'
const surface = process.argv[2] ?? 'web'
const args = SURFACES.get(surface)
if (args === undefined || process.argv.length > 3) {
console.error('usage: pnpm run demo:cordis [tui|web|acp]')
console.error('usage: pnpm run demo:cordis [web|acp]')
process.exit(2)
}
+2 -2
View File
@@ -1,5 +1,5 @@
{
"AGENTS.md": 1755,
"AGENTS.md": 1775,
"docs/AGENTS.md": 1150,
"docs/architecture.md": 1920,
"docs/cordis-primer.md": 600,
@@ -7,5 +7,5 @@
"docs/testing.md": 1100,
"examples/AGENTS.md": 310,
"packages/AGENTS.md": 675,
"packages/README.md": 900
"packages/README.md": 920
}
+11
View File
@@ -28,10 +28,12 @@ export const LINK_MAP: Readonly<Record<string, string>> = {
ContinuationDecision: 'core.md',
ContinuationStop: 'core.md',
GenerateOptions: 'core.md',
InboxItem: 'core.md',
InboxPlacement: 'core.md',
MessageId: 'core.md',
HookContext: 'core.md',
SettleReason: 'core.md',
AdapterRegistrationHandle: 'core.md',
LlmCallConfig: 'core.md',
LlmModelContext: 'core.md',
LlmModelReasoningInfo: 'core.md',
@@ -185,6 +187,14 @@ export const LINK_MAP: Readonly<Record<string, string>> = {
ToolRegistry: 'tools.md',
ToolRestriction: 'tools.md',
ToolSchema: 'tools.md',
SettingsNamespace: 'settings.md',
SettingsRegisterOptions: 'settings.md',
SettingsScope: 'settings.md',
SettingsDescriptor: 'settings.md',
SettingsUpdateSource: 'settings.md',
CredentialRef: 'credentials.md',
CredentialInfo: 'credentials.md',
ResolvedCredential: 'credentials.md',
AskUserQuestionAnswer: 'user-interaction.md',
AskUserQuestionRequest: 'user-interaction.md',
UserInteractionProvider: 'user-interaction.md',
@@ -215,6 +225,7 @@ export const FOUNDATION_TYPE_NAMES: ReadonlySet<string> = new Set([
/** Project types deliberately documented outside the core-data catalog. */
export const TYPE_LINK_EXEMPTIONS: Readonly<Record<string, string>> = {
AgentFactory: 'agent creation seam is owned by packages/core/agent/README.md',
z: 'schemastery schema constructor is owned by vendor/schemastery (vendored upstream)',
BeginCommandRequest: 'event-local request contract is owned by packages/client/ui-slash/src/types.ts',
InsertReferenceRequest: 'event-local request contract is owned by packages/client/ui-slash/src/types.ts',
ConsumeTokenRequest: 'event-local request contract is owned by packages/client/ui-slash/src/types.ts',
+98
View File
@@ -0,0 +1,98 @@
/**
* Tests for the event-relation collector's demand-driven call-site indexing:
* the single-file fast path and the global fallback must recover the same
* helper-parameter event names, including shapes that defeat the locality
* proof (alias escapes and global script files).
*/
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs'
import { tmpdir } from 'node:os'
import { dirname, join } from 'node:path'
import { afterAll, describe, expect, it } from 'vitest'
import { collectPackageSources, EventRelationCollector } from './gen-doc-graphs.ts'
import { TypeScriptProject } from './ts-project.ts'
const FIXTURE: Record<string, string> = {
'tsconfig.host.json': JSON.stringify({
compilerOptions: {
target: 'es2022',
module: 'esnext',
moduleResolution: 'bundler',
allowImportingTsExtensions: true,
noEmit: true,
skipLibCheck: true,
types: [],
},
include: ['vendor/**/*.ts', 'packages/**/*.ts'],
}),
'vendor/cordis/src/context.ts': 'export class Context { private brand!: void }\n',
'vendor/cordis/src/events.ts': [
'export class EventsService {',
' dispatch(type: string, args: unknown[]): unknown[] { return [type, args] }',
'}',
'',
].join('\n'),
'packages/core/agent/src/dispatch.ts':
'export interface AgentEventDispatch { emit(...args: unknown[]): void }\n',
// fireLocal: every same-file reference is a direct callee, so the locality
// proof holds and only this file is indexed. fireAliased: the exported
// const is a value-position reference, so the proof fails and the global
// fallback must find the cross-file call in pkgb.
'packages/fix/pkga/src/index.ts': [
"import { EventsService } from '../../../../vendor/cordis/src/events.ts'",
'declare const events: EventsService',
"function fireLocal(args: [string]): void { void events.dispatch('emit', args) }",
"fireLocal(['pkga/local-event'])",
"function fireAliased(args: [string]): void { void events.dispatch('emit', args) }",
'export const aliased = fireAliased',
'',
].join('\n'),
'packages/fix/pkgb/src/index.ts': [
"import { aliased } from '../../pkga/src/index.ts'",
"aliased(['pkgb/aliased-event'])",
'',
].join('\n'),
// Global script files (no import/export): scriptFire is program-visible, so
// the cross-file call in caller.ts leaves no same-file reference. Only the
// module-ness premise check routes this helper to the global index; without
// it the proof would pass and the event would silently drop.
'packages/fix/pkgc/src/globals.ts':
"declare var gEvents: import('../../../../vendor/cordis/src/events.ts').EventsService\n",
'packages/fix/pkgc/src/helper.ts':
"function scriptFire(args: [string]): void { void gEvents.dispatch('emit', args) }\n",
'packages/fix/pkgc/src/caller.ts': "scriptFire(['pkgc/script-event'])\n",
}
const root = mkdtempSync(join(tmpdir(), 'gen-doc-graphs-'))
for (const [rel, content] of Object.entries(FIXTURE)) {
mkdirSync(dirname(join(root, rel)), { recursive: true })
writeFileSync(join(root, rel), content)
}
const project = new TypeScriptProject(root)
const sources = collectPackageSources(project)
afterAll(() => {
rmSync(root, { recursive: true, force: true })
})
function dispatchersOf(pkgs: readonly string[], event: string): string[] {
const subset = sources.filter(source => pkgs.includes(source.pkg))
const relations = new EventRelationCollector(project, subset).collect()
return [...(relations.get(event)?.dispatchers.keys() ?? [])]
}
describe('event relation call-site indexing', () => {
it('recovers a proven-local helper through the single-file fast path', () => {
expect(dispatchersOf(['pkga', 'pkgb'], 'pkga/local-event')).toEqual(['pkga'])
})
it('recovers an alias-escaped helper through the global fallback', () => {
expect(dispatchersOf(['pkga', 'pkgb'], 'pkgb/aliased-event')).toEqual(['pkga'])
})
it('rejects the locality proof for global script files', () => {
// pkgc alone: the script helper is the first demand, so a wrongly passing
// proof would index helper.ts only and lose the caller.ts call site.
expect(dispatchersOf(['pkgc'], 'pkgc/script-event')).toEqual(['pkgc'])
})
})
+145 -27
View File
@@ -48,9 +48,13 @@ interface EventRelation {
listeners: Set<string>
}
interface PackageSource {
/** One scanned package source file and its owning package short name. */
export interface PackageSource {
/** Repository-relative path. */
rel: string
/** Package short name from the `packages/<group>/<pkg>/src` path. */
pkg: string
/** The bound program source file. */
sourceFile: ts.SourceFile
}
@@ -148,6 +152,24 @@ const SERVICE_ROLES: ServiceRole[] = [
consumers: ['agent-loop', 'tool-bash', 'hooks-claude', 'hooks-codex', 'session-query', 'session-query-sqlite'],
note: 'Backends persist the same SessionEvent vocabulary; apps choose a backend at composition time.',
},
{
key: 'settings',
pkg: 'settings',
title: 'User-settings seam',
mode: 'seam',
implementations: ['settings-local'],
consumers: ['llm-deepseek', 'llm-pi-ai'],
note: 'Plugins register namespace schemas and resolve layered values; providers store the raw document. The LLM adapters register their entry config as the composition base under the user section.',
},
{
key: 'credentials',
pkg: 'credentials',
title: 'Credential seam',
mode: 'seam',
implementations: ['credentials-local'],
consumers: ['llm-deepseek', 'llm-pi-ai'],
note: 'Configuration carries references to secrets; providers own the values. Consumers resolve per operation, so a rotated credential reaches the very next request.',
},
{
key: 'telemetry',
pkg: 'session-telemetry',
@@ -605,11 +627,11 @@ function stripYamlScalar(value: string): string {
const APP_EXAMPLES = [
{
id: 'tui',
rel: 'examples/tui-agent/composition.md',
rel: 'apps/cli/composition.md',
title: 'TUI Agent App Composition',
label: 'examples/tui-agent',
config: 'examples/tui-agent/cordis.yml',
summary: 'The TUI agent combines the real DeepSeek adapter, coding tools, compaction, subagents, and workflows with the full-screen terminal app package.',
label: 'apps/cli/config',
config: 'apps/cli/config/base.cordis.yml',
summary: 'The TUI surface combines the shared CLI base with its surface overlay and full-screen terminal package.',
},
{
id: 'headless',
@@ -619,14 +641,6 @@ const APP_EXAMPLES = [
config: 'examples/headless-agent/cordis.yml',
summary: 'The headless demo combines the real DeepSeek adapter and coding capabilities with the one-shot app package, format-pure stdout, and one fresh persisted top-level session.',
},
{
id: 'cordis',
rel: 'examples/cordis-agent/composition.md',
title: 'Cordis Agent App Composition',
label: 'examples/cordis-agent',
config: 'examples/cordis-agent/cordis.yml',
summary: 'The self-referential demo puts @deepseek-ai/dsh-tool-cordis on the coding spine, letting the agent inspect its current-process runtime and mount or unmount in-memory temporary Plugins.',
},
{
id: 'acp',
rel: 'examples/acp-agent/composition.md',
@@ -691,13 +705,26 @@ function renderAppComposition(example: AppExample): string {
return lines.join('\n')
}
type CallSiteIndex = Map<ts.SignatureDeclaration | ts.JSDocSignature, ts.CallExpression[]>
/**
* The only method names visitSource classifies; receiver typing runs on these
* alone. Obligation: every method name matched by a branch inside visitSource
* must appear here — the prefilter drops non-members before any branch runs,
* so a branch for an unlisted name is silently dead.
*/
const EVENT_API_METHODS = new Set(['on', 'once', 'emit', 'parallel', 'serial', 'waterfall', 'dispatch'])
/** Collect event dispatch/listener relations from real cross-file receiver types. */
class EventRelationCollector {
export class EventRelationCollector {
private readonly relations = new Map<string, EventRelation>()
private readonly callSites = new Map<ts.SignatureDeclaration | ts.JSDocSignature, ts.CallExpression[]>()
private readonly fileCallSites = new Map<ts.SourceFile, CallSiteIndex>()
private readonly localCalleeProofs = new Map<ts.FunctionDeclaration, boolean>()
private globalCallSites: CallSiteIndex | null = null
private readonly contextType: ts.Type
private readonly agentDispatchType: ts.Type
private readonly eventsServiceType: ts.Type
private readonly packageSourceFiles: ReadonlySet<ts.SourceFile>
constructor(
private readonly project: TypeScriptProject,
@@ -706,7 +733,7 @@ class EventRelationCollector {
this.contextType = this.declaredType('vendor/cordis/src/context.ts', 'Context')
this.agentDispatchType = this.declaredType('packages/core/agent/src/dispatch.ts', 'AgentEventDispatch')
this.eventsServiceType = this.declaredType('vendor/cordis/src/events.ts', 'EventsService')
this.indexCallSites()
this.packageSourceFiles = new Set(sources.map(source => source.sourceFile))
}
/** Return all event relations discovered from the Program. */
@@ -726,20 +753,88 @@ class EventRelationCollector {
return this.project.checker.getDeclaredTypeOfSymbol(symbol)
}
/** Index resolved local function calls for narrow argument-flow recovery. */
private indexCallSites(): void {
/** Index resolved function calls in the given files for narrow argument-flow recovery. */
private buildCallSiteIndex(files: Iterable<ts.SourceFile>): CallSiteIndex {
const index: CallSiteIndex = new Map()
const visit = (node: ts.Node): void => {
if (ts.isCallExpression(node)) {
const declaration = this.project.checker.getResolvedSignature(node)?.declaration
if (declaration) {
const calls = this.callSites.get(declaration) ?? []
const calls = index.get(declaration) ?? []
calls.push(node)
this.callSites.set(declaration, calls)
index.set(declaration, calls)
}
}
ts.forEachChild(node, visit)
}
for (const source of this.sources) visit(source.sourceFile)
for (const file of files) visit(file)
return index
}
/**
* Return every indexed call resolving to one local helper declaration.
* Fast path: when every same-file reference to the non-exported helper is
* provably a direct callee, module scoping confines all of its calls to that
* file, so only that file is indexed. Any other reference shape may alias
* the function value outward, so the original full package-source index
* decides instead.
*/
private callSitesFor(owner: ts.FunctionDeclaration): ts.CallExpression[] {
if (!this.globalCallSites && !this.provenLocalCallee(owner)) {
this.globalCallSites = this.buildCallSiteIndex(this.packageSourceFiles)
}
if (this.globalCallSites) return this.globalCallSites.get(owner) ?? []
const file = owner.getSourceFile()
let index = this.fileCallSites.get(file)
if (!index) {
index = this.buildCallSiteIndex([file])
this.fileCallSites.set(file, index)
}
return index.get(owner) ?? []
}
/**
* Prove every same-file reference to one helper is a direct callee. The
* proof owns its premises: an exported helper or a helper in a global
* script file (no import/export means program-wide scope, callable from
* another file with no same-file reference at all) fails immediately.
* Alias escapes (re-export statements, default exports, value reads)
* resolve back to the owner symbol at a non-callee position and fail the
* proof, as does anything the scan cannot positively classify.
*/
private provenLocalCallee(owner: ts.FunctionDeclaration): boolean {
const cached = this.localCalleeProofs.get(owner)
if (cached !== undefined) return cached
if (hasExportModifier(owner) || !ts.isExternalModule(owner.getSourceFile())) {
this.localCalleeProofs.set(owner, false)
return false
}
const name = owner.name
const ownerSymbol = name && this.project.checker.getSymbolAtLocation(name)
let proven = !!ownerSymbol
const refersToOwner = (identifier: ts.Identifier): boolean => {
// Shorthand properties resolve to the property symbol; ask for the value side.
const local = ts.isShorthandPropertyAssignment(identifier.parent)
? this.project.checker.getShorthandAssignmentValueSymbol(identifier.parent)
: this.project.checker.getSymbolAtLocation(identifier)
if (!local) return false
const symbol = local.flags & ts.SymbolFlags.Alias
? this.project.checker.getAliasedSymbol(local)
: local
return symbol === ownerSymbol
}
const visit = (node: ts.Node): void => {
if (!proven) return
if (ts.isIdentifier(node) && node !== name && node.text === name?.text
&& !isDirectCallee(node) && refersToOwner(node)) {
proven = false
return
}
ts.forEachChild(node, visit)
}
visit(owner.getSourceFile())
this.localCalleeProofs.set(owner, proven)
return proven
}
/** Walk one package source file and classify event API calls by receiver type. */
@@ -753,7 +848,7 @@ class EventRelationCollector {
this.addDispatcher(name, source.pkg, 'emitAgentEvent')
}
}
} else if (ts.isPropertyAccessExpression(node.expression)) {
} else if (ts.isPropertyAccessExpression(node.expression) && EVENT_API_METHODS.has(node.expression.name.text)) {
const receiverKind = this.receiverKind(node.expression.expression)
const method = node.expression.name.text
if (receiverKind === 'events-service' && method === 'dispatch') {
@@ -856,7 +951,7 @@ class EventRelationCollector {
const index = owner.parameters.indexOf(parameter)
if (index < 0) return new Set()
const events = new Set<string>()
for (const call of this.callSites.get(owner) ?? []) {
for (const call of this.callSitesFor(owner)) {
const argument = call.arguments[index]
if (argument) addAll(events, this.eventNamesFromArgumentList(argument, new Set(seen)))
}
@@ -903,6 +998,21 @@ class EventRelationCollector {
}
}
/** Return whether an identifier is the callee of a call, seen through value-preserving wrappers. */
function isDirectCallee(identifier: ts.Identifier): boolean {
let current: ts.Node = identifier
while (
ts.isParenthesizedExpression(current.parent)
|| ts.isAsExpression(current.parent)
|| ts.isTypeAssertionExpression(current.parent)
|| ts.isNonNullExpression(current.parent)
|| ts.isSatisfiesExpression(current.parent)
) {
current = current.parent
}
return ts.isCallExpression(current.parent) && current.parent.expression === current
}
/** Peel syntax-only wrappers that do not change an expression's runtime value. */
function unwrapExpression(expression: ts.Expression): ts.Expression {
let current = expression
@@ -958,14 +1068,22 @@ function unionSets<T>(left: ReadonlySet<T>, right: ReadonlySet<T>): Set<T> {
return out
}
function collectEventRelations(): Map<string, EventRelation> {
const project = new TypeScriptProject(root)
const sources = project.sourceFiles().flatMap((sourceFile): PackageSource[] => {
/**
* Select the package source files of one project in deterministic order.
* @param project - the loaded repository TypeScript project.
* @returns `packages/<group>/<pkg>/src` files tagged with their package name.
*/
export function collectPackageSources(project: TypeScriptProject): PackageSource[] {
return project.sourceFiles().flatMap((sourceFile): PackageSource[] => {
const rel = project.relativePath(sourceFile)
const match = /^packages\/[^/]+\/([^/]+)\/src\/.+\.ts$/.exec(rel)
return match?.[1] ? [{ rel, pkg: match[1], sourceFile }] : []
}).sort((left, right) => left.rel.localeCompare(right.rel))
return new EventRelationCollector(project, sources).collect()
}
function collectEventRelations(): Map<string, EventRelation> {
const project = new TypeScriptProject(root)
return new EventRelationCollector(project, collectPackageSources(project)).collect()
}
function relationPackages(map: Map<string, Set<string>>, pkgsByShort: Map<string, Pkg>): string {
+32 -4
View File
@@ -33,9 +33,11 @@ import * as SkillLocal from '@deepseek-ai/dsh-skill-local'
import LocalTaskService from '@deepseek-ai/dsh-tasks-local'
import * as ToolAskUser from '@deepseek-ai/dsh-tool-ask-user'
import * as ToolBash from '@deepseek-ai/dsh-tool-bash'
import * as ToolBashPersistent from '@deepseek-ai/dsh-tool-bash-persistent'
import * as ToolCordis from '@deepseek-ai/dsh-tool-cordis'
import * as ToolFs from '@deepseek-ai/dsh-tool-fs'
import * as ToolFsSearch from '@deepseek-ai/dsh-tool-fs-search'
import * as ToolStrReplaceEditor from '@deepseek-ai/dsh-tool-str-replace-editor'
import PtyService from '@deepseek-ai/dsh-pty'
import * as ToolPty from '@deepseek-ai/dsh-tool-pty'
import * as ToolGoal from '@deepseek-ai/dsh-tool-goal'
@@ -215,7 +217,33 @@ const TOOL_PACKAGES: ToolPackage[] = [
await ctx.plugin(ToolCordis)
},
note:
'Ships in examples/cordis-agent only (a deliberate opt-in — temporary Plugin code reaches the real runtime, see .agents/notes/implemented/feature/2026-07-08-self-referential-cordis-toolset.md). Plugins created by cordis_mount may register ADDITIONAL model-visible tools until unmounted or DSH restarts; a full changed request header logs those tool-set changes.',
'Not in any shipped tree (a deliberate opt-in — temporary Plugin code reaches the real runtime, see .agents/notes/implemented/feature/2026-07-08-self-referential-cordis-toolset.md). Plugins created by cordis_mount may register ADDITIONAL model-visible tools until unmounted or DSH restarts; a full changed request header logs those tool-set changes.',
},
{
pkg: '@deepseek-ai/dsh-tool-bash-persistent',
dir: 'tool-bash-persistent',
source: 'packages/pty/tool-bash-persistent/src/index.ts',
requires: ['ctx.tools', 'ctx.pty', 'an owning Agent at execution time'],
writes: ['tool/call', 'PTY shell state', 'tool/result'],
async mount(ctx) {
await ctx.plugin(PtyService)
await ctx.plugin(ToolBashPersistent)
},
note:
'One owner-isolated persistent bash tool; deployment composition supplies the PTY backend and may override the model-facing environment description.',
},
{
pkg: '@deepseek-ai/dsh-tool-str-replace-editor',
dir: 'tool-str-replace-editor',
source: 'packages/fs/tool-str-replace-editor/src/index.ts',
requires: ['ctx.tools', 'ctx.fs'],
writes: ['tool/call', 'fs/observed after successful file operations', 'tool/result'],
async mount(ctx) {
await ctx.plugin(LocalFileSystem)
await ctx.plugin(ToolStrReplaceEditor)
},
note:
'Standalone view/create/unique literal replace/line insert tool over the filesystem seam; it composes with any shell or terminal surface.',
},
{
pkg: '@deepseek-ai/dsh-tool-fs',
@@ -245,10 +273,10 @@ const TOOL_PACKAGES: ToolPackage[] = [
// never depends on the host PATH. `ctx.spillStore` is optional (read via
// ctx.get) and does not affect the schemas, so no spill backend is mounted.
await ctx.plugin(CatalogSearchBashExecutor)
await ctx.plugin(ToolFsSearch)
await ctx.plugin(ToolFsSearch, { sampleOverCapGlobResults: true })
},
note:
'glob and grep are conditional bash-backed discovery tools: they register only when ctx.bash can find `rg`, then run fixed ripgrep commands through ctx.bash as ordinary foreground calls (never background tasks). Capped results save the complete formatted list through the optional ctx.spillStore backend; returned locators are follow-up-readable/searchable when the backend exposes local paths in co-located deployments.',
'glob and grep are conditional bash-backed discovery tools: they register only when ctx.bash can find `rg`, then run fixed ripgrep commands through ctx.bash as ordinary foreground calls (never background tasks). The catalog uses `sampleOverCapGlobResults: true`; deployments must choose that behavior explicitly. Capped results save the complete formatted list through the optional ctx.spillStore backend; returned locators are follow-up-readable/searchable when the backend exposes local paths in co-located deployments.',
},
{
pkg: '@deepseek-ai/dsh-tool-pty',
@@ -349,7 +377,7 @@ const TOOL_PACKAGES: ToolPackage[] = [
await ctx.plugin(ToolSubagent, { provider: 'mock' })
},
note:
'The registered tool name is the load-time `toolName` config (default `subagent`); the schema above is that default. The shipped example agents load this package once per subagent backend, so the model additionally sees `subagent_fork` (bound to the fork backend) with an identical schema — see `examples/tui-agent/cordis.yml` and `examples/acp-agent/cordis.yml`.',
'The registered tool name is the load-time `toolName` config (default `subagent`); the schema above is that default. The shipped example agents load this package once per subagent backend, so the model additionally sees `subagent_fork` (bound to the fork backend) with an identical schema — see `apps/cli/config/base.cordis.yml` and `examples/acp-agent/cordis.yml`.',
},
{
pkg: '@deepseek-ai/dsh-tool-tasks',
+78
View File
@@ -0,0 +1,78 @@
// Regression drive for the unified hero composer (0729-0357-hero-unify):
// cold start with zero workspaces -> create a workspace -> type. Asserts the
// composer textarea is the SAME DOM node across the disabled->live flip (a
// remount drops the __heroMark marker property) — the session-maybe
// composer.bar contract.
//
// Prereqs: `pnpm run build`, then a fresh server against empty state:
// rm -rf .storages && DSH_HOME=$(mktemp -d) node --experimental-transform-types \
// --import ./scripts/tspath-loader.ts apps/cli/src/bin.ts web --port 44285 \
// --workspace-root $(mktemp -d)
// Run: node scripts/hero-composer-dom-continuity.mjs
// (BASE_URL overrides the target; screenshots land in .artifacts/.)
import { createRequire } from 'node:module'
// playwright is a devDependency of apps/web only — resolve through its tree.
const require = createRequire(new URL('../apps/web/package.json', import.meta.url))
const { chromium } = require('playwright')
const BASE = process.env.BASE_URL ?? 'http://127.0.0.1:44285'
const SHOTS = new URL('../.artifacts/screenshots/0729-0357-hero-unify/', import.meta.url).pathname
const browser = await chromium.launch()
const page = await browser.newPage({ viewport: { width: 1280, height: 800 } })
page.on('console', msg => { if (msg.type() === 'error') console.log('[console.error]', msg.text()) })
page.on('pageerror', err => { console.log('[pageerror]', err.message) })
await page.goto(BASE)
await page.waitForSelector('textarea', { timeout: 20000 })
await page.screenshot({ path: SHOTS + '01-cold-start.png' })
const initial = await page.evaluate(() => {
const boxes = [...document.querySelectorAll('textarea')]
boxes.forEach((b, i) => { b.__heroMark = 'alive-' + i })
return boxes.map(b => ({ disabled: b.disabled, placeholder: b.placeholder }))
})
console.log('cold-start textareas:', JSON.stringify(initial))
// Open the picker and create a workspace by name (typed-input flow). The name
// must be unique per registry; keystrokes go through pressSequentially so the
// dialog's React onChange enables the submit button.
await page.getByRole('button', { name: 'Choose workspace' }).click()
await page.getByText('Create a new workspace').click()
await page.screenshot({ path: SHOTS + '03-create-form.png' })
const nameBox = page.getByPlaceholder('Workspace name')
await nameBox.click()
const wsName = 'proj-' + Date.now().toString(36)
await nameBox.pressSequentially(wsName, { delay: 30 })
await page.locator('button:text-is("Create workspace")').click()
// Wait for the composer to go live (placeholder flips, textarea enabled).
await page.waitForFunction(() => {
const box = document.querySelector('textarea')
return box !== null && !box.disabled
}, { timeout: 20000 })
await page.screenshot({ path: SHOTS + '04-live.png' })
const after = await page.evaluate(() => {
const boxes = [...document.querySelectorAll('textarea')]
return boxes.map(b => ({
mark: b.__heroMark ?? 'REMOUNTED',
disabled: b.disabled,
placeholder: b.placeholder,
}))
})
console.log('post-pick textareas:', JSON.stringify(after))
// Type into the live composer.
await page.locator('textarea').first().fill('hello from acceptance run')
const typed = await page.evaluate(() => document.querySelector('textarea')?.value)
console.log('typed value:', JSON.stringify(typed))
await page.screenshot({ path: SHOTS + '05-typed.png' })
const survived = after.length === 1 && after[0].mark === 'alive-0'
console.log(survived
? 'DOM-CONTINUITY: PASS (same textarea node across cold-start -> live)'
: 'DOM-CONTINUITY: FAIL ' + JSON.stringify(after))
await browser.close()
process.exit(survived && typed === 'hello from acceptance run' ? 0 : 1)
+1 -1
View File
@@ -197,7 +197,7 @@ describe('docsPages locale routes', () => {
const translated = rootPages.filter(page => page.contentLocale === 'zh-CN')
const fallbacks = rootPages.filter(page => page.contentLocale === 'en-US')
expect(translated).toHaveLength(18)
expect(translated).toHaveLength(20)
expect(translated.every(page => page.source.endsWith('.zh.md'))).toBe(true)
expect(fallbacks.map(page => page.source).sort()).toEqual([
'docs/core-data-structures/commands.md',
+39 -8
View File
@@ -57,6 +57,7 @@ function withEnv<T>(name: string, value: string | undefined, action: () => T): T
describe('gate graph validation', () => {
it.each([
'ci-primary',
'ci-linux-primary',
'ci-static',
'ci-lint',
'ci-coverage',
@@ -134,30 +135,60 @@ describe('Oxlint gate', () => {
})
})
describe('Node 24 consumer graph', () => {
it('owns the seven-command pool and orders restored-artifact consumers', () => {
describe('Node 24 lane ownership', () => {
it('keeps the static lane source-only', () => {
const subject = withPnpmEntrypoint(() => gatesForMode('ci-static'))
expect(subject.map(item => item.id)).not.toContain('build')
expect(subject.map(item => item.id)).not.toContain('doc-typecheck')
})
it('owns the build and orders its artifact consumers', () => {
const subject = withPnpmEntrypoint(() => gatesForMode('ci-consumers'))
expect(defaultConcurrency('ci-consumers', subject.length, 4)).toEqual({
workers: 7,
workers: 10,
source: 'ci-consumers gate count',
})
expect(subject.map(item => item.id)).toEqual([
'lint-and-duplication',
'build',
'node-compat',
'snapshot',
'publint',
'node-next-types',
'built-package-invariants',
'lint-and-duplication',
'snapshot',
'web-snapshot',
'doc-typecheck',
'node-next-types',
'built-bin-smoke',
])
expect(subject.find(item => item.id === 'publint')?.needs).toBeUndefined()
expect(subject.find(item => item.id === 'publint')?.needs).toEqual(['build'])
expect(subject.find(item => item.id === 'built-package-invariants')?.needs).toEqual(['publint'])
expect(subject.find(item => item.id === 'lint-and-duplication')?.needs).toEqual(['built-package-invariants'])
for (const id of ['snapshot', 'node-next-types', 'built-bin-smoke']) {
for (const id of ['snapshot', 'web-snapshot', 'doc-typecheck', 'node-next-types', 'built-bin-smoke']) {
expect(subject.find(item => item.id === id)?.needs).toEqual(['built-package-invariants'])
}
expect(subject.find(item => item.id === 'snapshot')?.env).toEqual({ DSH_EXAMPLE_MODE: 'lib' })
expect(subject.find(item => item.id === 'doc-typecheck')?.env).toEqual({
DSH_DOC_TYPECHECK_USE_BUILD_OUTPUT: '1',
})
expect(subject.find(item => item.id === 'web-snapshot')).toMatchObject({
displayCommand: 'DSH_SNAPSHOT=replay pnpm run test:web:built',
env: { DSH_SNAPSHOT: 'replay' },
})
})
})
describe('Linux primary graph', () => {
it('adds the same compare-only web gate after built client artifacts', () => {
const subject = withPnpmEntrypoint(() => gatesForMode('ci-linux-primary'))
const web = subject.find(item => item.id === 'web-snapshot')
expect(web).toMatchObject({
displayCommand: 'DSH_SNAPSHOT=replay pnpm run test:web:built',
env: { DSH_SNAPSHOT: 'replay' },
needs: ['built-package-invariants'],
})
})
})
+46 -19
View File
@@ -13,6 +13,7 @@ import { performance } from 'node:perf_hooks'
/** A named aggregate exposed by the gate runner. */
export type Mode =
| 'ci-primary'
| 'ci-linux-primary'
| 'ci-static'
| 'ci-lint'
| 'ci-coverage'
@@ -97,6 +98,7 @@ async function main(args: string[]): Promise<number> {
function parseMode(raw: string | undefined): Mode {
switch (raw) {
case 'ci-primary':
case 'ci-linux-primary':
case 'ci-static':
case 'ci-lint':
case 'ci-coverage':
@@ -112,7 +114,7 @@ function parseMode(raw: string | undefined): Mode {
return raw
default:
throw new Error(
`run-gates: expected mode ci-primary | ci-static | ci-lint | ci-coverage | ci-snapshot | ci-artifacts | ci-consumers | ci-windows-blocking | ci-windows-complete | ci-windows-observational | node-compat | check-all | doc-sync, got ${JSON.stringify(raw)}.`,
`run-gates: expected mode ci-primary | ci-linux-primary | ci-static | ci-lint | ci-coverage | ci-snapshot | ci-artifacts | ci-consumers | ci-windows-blocking | ci-windows-complete | ci-windows-observational | node-compat | check-all | doc-sync, got ${JSON.stringify(raw)}.`,
)
}
}
@@ -190,8 +192,10 @@ export function gatesForMode(selected: Mode): Gate[] {
switch (selected) {
case 'ci-primary':
return ciPrimaryGates()
case 'ci-linux-primary':
return [...ciPrimaryGates(), webSnapshotGate(['built-package-invariants'])]
case 'ci-static':
return ciStaticGates()
return ciStaticGates({ ownsBuild: false })
case 'ci-lint':
return [
lintGate(),
@@ -327,16 +331,21 @@ function runningNodeMajor(): number {
return major
}
function ciStaticGates(): Gate[] {
function ciStaticGates(options: { ownsBuild: boolean }): Gate[] {
return [
pnpmScript('runtime-closure', 'verify-runtime-closure', { label: 'runtime closure' }),
pnpmScript('constraints', 'constraints'),
pnpmScript('package-invariants', 'verify-package-invariants', { label: 'package invariants' }),
pnpmScript('cordis-config', 'verify-cordis-config', { label: 'Cordis config' }),
pnpmScript('build', 'build'),
...options.ownsBuild ? [pnpmScript('build', 'build')] : [],
...docSyncLeafGates({
docTypecheckNeeds: ['build'],
docTypecheckEnv: { DSH_DOC_TYPECHECK_USE_BUILD_OUTPUT: '1' },
includeDocTypecheck: options.ownsBuild,
...options.ownsBuild
? {
docTypecheckNeeds: ['build'],
docTypecheckEnv: { DSH_DOC_TYPECHECK_USE_BUILD_OUTPUT: '1' },
}
: {},
docsBuildScript: 'docs:build:mpa',
}),
pnpmScript('module-graph', 'verify-module-graph', { label: 'module graph' }),
@@ -358,25 +367,40 @@ function ciArtifactGates(): Gate[] {
}
function ciConsumerGates(): Gate[] {
const publicArtifacts = ['publint']
const restoredBuild = ['built-package-invariants']
const builtTree = ['build']
const validatedBuild = ['built-package-invariants']
return [
pnpmScript('build', 'build'),
pnpmScript('node-compat', 'check:node-compat', { label: 'Node compatibility' }),
pnpmScript('publint', 'publint', { needs: builtTree }),
builtPackageInvariantsGate(['publint']),
pnpmScript('lint-and-duplication', 'check:ci:lint', {
label: 'lint and duplication',
needs: restoredBuild,
needs: validatedBuild,
}),
snapshotGate(validatedBuild),
webSnapshotGate(validatedBuild),
pnpmScript('doc-typecheck', 'doc-typecheck', {
needs: validatedBuild,
env: { DSH_DOC_TYPECHECK_USE_BUILD_OUTPUT: '1' },
}),
pnpmScript('node-compat', 'check:node-compat', { label: 'Node compatibility' }),
snapshotGate(restoredBuild),
pnpmScript('publint', 'publint'),
pnpmScript('node-next-types', 'verify-node-next-types', {
label: 'node-next types',
needs: restoredBuild,
needs: validatedBuild,
}),
builtPackageInvariantsGate(publicArtifacts),
builtBinSmokeGate(restoredBuild),
builtBinSmokeGate(validatedBuild),
]
}
function webSnapshotGate(needs: string[]): Gate {
return pnpmScript('web-snapshot', 'test:web:built', {
label: 'web browser snapshot',
displayCommand: 'DSH_SNAPSHOT=replay pnpm run test:web:built',
env: { DSH_SNAPSHOT: 'replay' },
needs,
})
}
function ciWindowsBlockingGates(): Gate[] {
return [
pnpmScript('windows-build', 'build', { label: 'build' }),
@@ -399,7 +423,7 @@ function ciWindowsCompleteGates(): Gate[] {
function ciWindowsObservationalGates(): Gate[] {
return [
...ciStaticGates(),
...ciStaticGates({ ownsBuild: true }),
// Linux owns required lint, coverage, and snapshots; Windows omits those duplicates.
pnpmScript('duplication', 'duplication'),
pnpmScript('publint', 'publint', { needs: ['build'] }),
@@ -432,7 +456,7 @@ function coverageGate(): Gate {
// Example and package snapshots boot their bins in `lib` mode (built artifacts under plain Node,
// plugins via real exports); repository-script snapshots execute their real source entry path.
// Build-owning modes wait on `build`; a restored-artifact mode passes its validation dependency.
// Callers wait either on `build` or on a validation gate that transitively owns that build.
function snapshotGate(needs: string[] = ['build']): Gate {
return pnpmScript('snapshot', 'test:snapshot', {
env: { DSH_EXAMPLE_MODE: 'lib' },
@@ -480,6 +504,7 @@ function hygieneLeafGates(options: { artifactNeeds?: string[] } = {}): Gate[] {
}
function docSyncLeafGates(options: {
includeDocTypecheck?: boolean
docTypecheckNeeds?: string[]
docTypecheckEnv?: Record<string, string | undefined>
docsBuildScript?: 'docs:build' | 'docs:build:mpa'
@@ -488,7 +513,9 @@ function docSyncLeafGates(options: {
if (options.docTypecheckNeeds !== undefined) docTypecheckOptions.needs = options.docTypecheckNeeds
if (options.docTypecheckEnv !== undefined) docTypecheckOptions.env = options.docTypecheckEnv
return [
pnpmScript('doc-typecheck', 'doc-typecheck', docTypecheckOptions),
...options.includeDocTypecheck === false
? []
: [pnpmScript('doc-typecheck', 'doc-typecheck', docTypecheckOptions)],
pnpmScript('cordis-catalog', 'verify-cordis-catalog', { label: 'cordis catalog' }),
pnpmScript('export-jsdoc', 'verify-export-jsdoc', { label: 'export jsdoc' }),
pnpmScript('tool-catalog', 'verify-tool-catalog', { label: 'tool catalog' }),
@@ -525,7 +552,7 @@ function builtBinSmokeGate(needs: string[] = ['build']): Gate {
'--config',
'vitest.e2e.config.ts',
'examples/headless-agent/tests/keyless-smoke.e2e.ts',
'examples/tui-agent/tests/tui-keyless-smoke.e2e.ts',
'apps/cli/tests/tui-keyless-smoke.e2e.ts',
'packages/examples/cli-demo/tests/built-bin.e2e.ts',
'packages/examples/acp-demo/tests/built-bin.e2e.ts',
'packages/ui/jsonrpc/tests/built-scope-carrier.e2e.ts',
+158 -7
View File
@@ -25,6 +25,14 @@ CODE_PROMPT = "Use run_code to compute the packaged worker smoke value."
CODE_WORKER_TEXT = "code worker smoke ok"
WORKFLOW_PROMPT = "Use workflow to compute the packaged worker smoke value without agents."
WORKFLOW_WORKER_TEXT = "workflow worker smoke ok"
PERSISTENT_TOOLS_PROMPT = "Exercise the packaged persistent Bash and string-replacement editor."
PERSISTENT_TOOLS_TEXT = "persistent tools smoke ok"
PERSISTENT_EDITOR_PATH_PREFIX = "Editor path: "
PERSISTENT_BASH_COMMAND = (
"counter=$(( ${counter:-0} + 1 )); export counter; "
"printf 'COUNT=%s CWD=%s\\n' \"$counter\" \"$PWD\"; "
"if [ \"$counter\" -eq 1 ]; then cd /tmp; fi"
)
SNAPSHOT_PROMPT = "Run the advanced packaged-runtime snapshot scenario."
SNAPSHOT_SESSION_ID = "advanced-executable"
SNAPSHOT_DIRECT_CHILD_PROMPT = "Reply with exactly DIRECT_CHILD_OK and nothing else."
@@ -64,6 +72,9 @@ CUSTOM_CORDIS = """\
name: '@deepseek-ai/dsh-agent-spine-demo'
config:
workspaceContext: false
skills:
enabled: false
toolBash: false
tools:
mode: both
- id: sessions
@@ -71,10 +82,6 @@ CUSTOM_CORDIS = """\
config:
root: !!js process.env.DSH_SESSION_ROOT
compression: 'none'
- id: bash
name: '@deepseek-ai/dsh-bash-local'
config:
cwd: !!js process.env.DSH_CWD
- id: code-runtime
name: '@deepseek-ai/dsh-code-runtime-worker'
- id: subagents
@@ -96,6 +103,49 @@ CUSTOM_CORDIS = """\
- id: cordis-tool
name: '@deepseek-ai/dsh-tool-cordis'
"""
PERSISTENT_TOOLS_CORDIS = """\
- id: jsonrpc
name: '@deepseek-ai/dsh-jsonrpc'
- id: llm
name: '@deepseek-ai/dsh-llm-deepseek'
config:
apiKey: !!js process.env.DEEPSEEK_API_KEY
baseURL: !!js process.env.DEEPSEEK_BASE_URL
- id: sandbox
name: '@deepseek-ai/dsh-sandbox-local'
- id: sandbox-policy
name: '@deepseek-ai/dsh-sandbox-policy'
config:
mode: danger-full-access
workspaceRoot: !!js process.env.DSH_CWD
- id: pty
name: '@deepseek-ai/dsh-pty'
- id: pty-local
name: '@deepseek-ai/dsh-pty-local'
- id: fs
name: '@deepseek-ai/dsh-fs-local'
config:
cwd: !!js process.env.DSH_CWD
- id: agent-core
name: '@deepseek-ai/dsh-agent-spine-demo'
config:
includeHarnessIdentity: false
persona: 'You are a helpful software engineer assistant.'
workspaceContext: false
skills:
enabled: false
toolBash: false
toolTasks: false
- id: sessions
name: '@deepseek-ai/dsh-session-persistence-jsonl'
config:
root: !!js process.env.DSH_SESSION_ROOT
compression: 'none'
- id: persistent-bash
name: '@deepseek-ai/dsh-tool-bash-persistent'
- id: str-replace-editor
name: '@deepseek-ai/dsh-tool-str-replace-editor'
"""
class MockModelHandler(BaseHTTPRequestHandler):
@@ -132,6 +182,9 @@ def completion_chunks(body: dict[str, object]) -> list[dict[str, object]]:
if latest.get("role") == "tool":
call_id, tool_name = latest_tool_call(messages)
tool_text = message_text(latest.get("content"))
persistent = persistent_tool_followup(body, call_id, tool_name, tool_text)
if persistent is not None:
return persistent
advanced = advanced_tool_followup(body, call_id, tool_name, tool_text)
if advanced is not None:
return advanced
@@ -144,6 +197,15 @@ def completion_chunks(body: dict[str, object]) -> list[dict[str, object]]:
raise AssertionError(f"unexpected tool follow-up: {tool_name}")
prompt = message_text(latest.get("content"))
if prompt.startswith(f"{PERSISTENT_TOOLS_PROMPT}\n{PERSISTENT_EDITOR_PATH_PREFIX}"):
names = advertised_tool_names(body)
if names != {"bash", "str_replace_editor"}:
raise AssertionError(f"persistent tools smoke advertised unexpected tools: {names}")
return tool_call_chunks(
"persistent-bash-1",
"bash",
{"command": PERSISTENT_BASH_COMMAND},
)
if prompt == SNAPSHOT_DIRECT_CHILD_PROMPT:
return text_chunks("DIRECT_CHILD_OK")
if prompt == SNAPSHOT_WORKFLOW_CHILD_PROMPT:
@@ -178,6 +240,57 @@ def completion_chunks(body: dict[str, object]) -> list[dict[str, object]]:
return text_chunks(EXPECTED_TEXT)
def persistent_tool_followup(
body: dict[str, object],
call_id: str,
tool_name: str,
tool_text: str,
) -> list[dict[str, object]] | None:
"""Verify packaged PTY persistence, then invoke the packaged editor."""
if not call_id.startswith("persistent-"):
return None
if call_id == "persistent-bash-1" and tool_name == "bash":
if "COUNT=1" not in tool_text:
raise AssertionError(f"first persistent bash call lost its output: {tool_text}")
return tool_call_chunks(
"persistent-bash-2",
"bash",
{"command": PERSISTENT_BASH_COMMAND},
)
if call_id == "persistent-bash-2" and tool_name == "bash":
if "COUNT=2 CWD=/tmp" not in tool_text:
raise AssertionError(f"persistent bash did not retain state: {tool_text}")
messages = body.get("messages")
if not isinstance(messages, list):
raise AssertionError("persistent editor smoke request has no messages")
editor_path = next(
(
text.split(PERSISTENT_EDITOR_PATH_PREFIX, 1)[1].strip()
for message in messages
if isinstance(message, dict) and message.get("role") == "user"
for text in [message_text(message.get("content"))]
if PERSISTENT_EDITOR_PATH_PREFIX in text
),
None,
)
if editor_path is None:
raise AssertionError("persistent editor smoke prompt has no editor path")
return tool_call_chunks(
"persistent-editor",
"str_replace_editor",
{
"command": "create",
"path": editor_path,
"file_text": "created by packaged editor\n",
},
)
if call_id == "persistent-editor" and tool_name == "str_replace_editor":
if "New file created successfully" not in tool_text:
raise AssertionError(f"packaged editor did not create its file: {tool_text}")
return text_chunks(PERSISTENT_TOOLS_TEXT)
raise AssertionError(f"unexpected persistent-tools follow-up: {call_id} {tool_name}: {tool_text}")
def advanced_tool_followup(
body: dict[str, object],
call_id: str,
@@ -357,14 +470,14 @@ def main() -> None:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument(
"--scenario",
choices=("all", "sdk-default", "sdk-custom", "sdk-snapshot", "direct"),
choices=("all", "sdk-default", "sdk-custom", "sdk-persistent", "sdk-snapshot", "direct"),
default="all",
)
parser.add_argument("--exe", type=Path)
parser.add_argument("--update-snapshots", action="store_true")
args = parser.parse_args()
if args.scenario in {"all", "sdk-custom", "sdk-snapshot", "direct"} and args.exe is None:
parser.error("--exe is required for custom, snapshot, and direct scenarios")
if args.scenario in {"all", "sdk-custom", "sdk-persistent", "sdk-snapshot", "direct"} and args.exe is None:
parser.error("--exe is required for custom, persistent, snapshot, and direct scenarios")
if args.update_snapshots and args.scenario not in {"all", "sdk-snapshot"}:
parser.error("--update-snapshots requires --scenario sdk-snapshot or all")
if args.exe is not None and not args.exe.is_file():
@@ -376,6 +489,9 @@ def main() -> None:
if args.scenario in {"all", "sdk-custom"}:
assert args.exe is not None
smoke_sdk_custom(model.url, args.exe.resolve())
if args.scenario in {"all", "sdk-persistent"}:
assert args.exe is not None
smoke_sdk_persistent_tools(model.url, args.exe.resolve())
if args.scenario in {"all", "sdk-snapshot"}:
assert args.exe is not None
smoke_sdk_snapshot(model.url, args.exe.resolve(), args.update_snapshots)
@@ -439,6 +555,39 @@ def smoke_sdk_custom(base_url: str, executable: Path) -> None:
assert_session_log(sessions, root, EXPECTED_TEXT, CODE_WORKER_TEXT, WORKFLOW_WORKER_TEXT)
def smoke_sdk_persistent_tools(base_url: str, executable: Path) -> None:
"""Exercise native PTY state and the editor through the packaged executable."""
from deepseek_harness import DeepSeekHarness
with tempfile.TemporaryDirectory(prefix="dsh-sdk-persistent-tools-") as temporary:
root = Path(temporary).resolve()
editor_path = root / "created.txt"
prompt = f"{PERSISTENT_TOOLS_PROMPT}\n{PERSISTENT_EDITOR_PATH_PREFIX}{editor_path}"
sessions = root / "sessions"
cordis = root / "cordis.yml"
cordis.write_text(PERSISTENT_TOOLS_CORDIS)
with DeepSeekHarness(
provider="deepseek",
model="smoke-model",
cwd=str(root),
session_root=str(sessions),
cordis=str(cordis),
runtime_bin=str(executable),
api_key="sk-keyless-smoke",
base_url=base_url,
request_timeout_seconds=60,
) as harness:
result = harness.run(prompt, session_id="persistent-tools-smoke")
assert result.status == "ok", result
event_text = json.dumps(result.events)
if PERSISTENT_TOOLS_TEXT not in event_text:
raise AssertionError(f"packaged tools run emitted no final response: {result.events}")
if editor_path.read_text() != "created by packaged editor\n":
raise AssertionError(f"packaged editor wrote unexpected content: {editor_path.read_text()!r}")
assert_session_log(sessions, root, PERSISTENT_TOOLS_TEXT, "COUNT=1", "COUNT=2 CWD=/tmp")
def smoke_sdk_snapshot(base_url: str, executable: Path, update_snapshots: bool) -> None:
"""Drive and compare the advanced SDK/executable behavioral snapshot."""
from deepseek_harness import DeepSeekHarness
@@ -724,6 +873,8 @@ def normalize_snapshot_value(
normalized["createdAt"] = 0
if "seq" in normalized and "time" in normalized:
normalized["time"] = 0
if isinstance(normalized.get("id"), str) and normalized.get("role") in ("assistant", "user"):
normalized["id"] = "{{messageId}}"
scrub_snapshot_header(normalized)
return normalized
@@ -30,7 +30,9 @@
],
"source": {
"kind": "user"
}
},
"role": "user",
"id": "{{messageId}}"
},
"surfaceOp": "append"
},
@@ -70,20 +72,15 @@
},
"system": "{{system}}",
"tools": [
"bash",
"cordis_inspect",
"cordis_mount",
"cordis_unmount",
"run_code",
"skill",
"subagent",
"task_kill",
"task_list",
"task_output",
"workflow"
],
"messagePrefix": [
"{{messagePrefix}}"
]
},
"reason": "initial"
@@ -176,17 +173,22 @@
"data": {
"turn": 1,
"step": 1,
"content": [
{
"type": "tool-call",
"id": "advanced-mount",
"name": "cordis_mount",
"arguments": "{\"code\": \"return (ctx) => {\\n harness.registerTool(ctx, harness.defineTool({\\n name: 'snapshot_double',\\n description: 'Double a number for executable snapshot verification.',\\n parameters: { value: { type: 'number', required: true } },\\n output: {\\n schema: { type: 'number' },\\n render(_args, value) {\\n return [{ type: 'text', text: String(value) }]\\n }\\n },\\n async execute(args) {\\n return args.value * 2\\n }\\n }))\\n}\\n\"}"
}
],
"provenance": {
"provider": "deepseek",
"model": "smoke-model"
"message": {
"role": "assistant",
"content": [
{
"type": "tool-call",
"id": "advanced-mount",
"name": "cordis_mount",
"arguments": "{\"code\": \"return (ctx) => {\\n harness.registerTool(ctx, harness.defineTool({\\n name: 'snapshot_double',\\n description: 'Double a number for executable snapshot verification.',\\n parameters: { value: { type: 'number', required: true } },\\n output: {\\n schema: { type: 'number' },\\n render(_args, value) {\\n return [{ type: 'text', text: String(value) }]\\n }\\n },\\n async execute(args) {\\n return args.value * 2\\n }\\n }))\\n}\\n\"}"
}
],
"source": {
"kind": "model",
"provider": "deepseek",
"model": "smoke-model"
},
"id": "{{messageId}}"
},
"usage": {
"inputTokens": 3,
@@ -221,14 +223,27 @@
"data": {
"turn": 1,
"step": 1,
"callId": "advanced-mount",
"content": [
{
"type": "text",
"text": "Temporary Plugin dyn-1 is running (plugin \"<anonymous>\"; available until unmounted or DSH restarts)."
}
],
"isError": false
"message": {
"source": {
"kind": "tool",
"callId": "advanced-mount"
},
"content": [
{
"type": "tool-result",
"toolCallId": "advanced-mount",
"content": [
{
"type": "text",
"text": "Temporary Plugin dyn-1 is running (plugin \"<anonymous>\"; available until unmounted or DSH restarts)."
}
],
"isError": false
}
],
"role": "user",
"id": "{{messageId}}"
}
},
"sourceEventSeqs": [
11
@@ -266,21 +281,16 @@
},
"system": "{{system}}",
"tools": [
"bash",
"cordis_inspect",
"cordis_mount",
"cordis_unmount",
"run_code",
"skill",
"snapshot_double",
"subagent",
"task_kill",
"task_list",
"task_output",
"workflow"
],
"messagePrefix": [
"{{messagePrefix}}"
]
},
"reason": "change"
@@ -373,17 +383,22 @@
"data": {
"turn": 1,
"step": 2,
"content": [
{
"type": "tool-call",
"id": "advanced-code",
"name": "run_code",
"arguments": "{\"code\": \"return await tools.snapshot_double({ value: 21 })\", \"description\": \"Run the temporary Plugin tool\"}"
}
],
"provenance": {
"provider": "deepseek",
"model": "smoke-model"
"message": {
"role": "assistant",
"content": [
{
"type": "tool-call",
"id": "advanced-code",
"name": "run_code",
"arguments": "{\"code\": \"return await tools.snapshot_double({ value: 21 })\", \"description\": \"Run the temporary Plugin tool\"}"
}
],
"source": {
"kind": "model",
"provider": "deepseek",
"model": "smoke-model"
},
"id": "{{messageId}}"
},
"usage": {
"inputTokens": 3,
@@ -451,14 +466,27 @@
"data": {
"turn": 1,
"step": 2,
"callId": "advanced-code",
"content": [
{
"type": "text",
"text": "42"
}
],
"isError": false
"message": {
"source": {
"kind": "tool",
"callId": "advanced-code"
},
"content": [
{
"type": "tool-result",
"toolCallId": "advanced-code",
"content": [
{
"type": "text",
"text": "42"
}
],
"isError": false
}
],
"role": "user",
"id": "{{messageId}}"
}
},
"sourceEventSeqs": [
22
@@ -570,17 +598,22 @@
"data": {
"turn": 1,
"step": 3,
"content": [
{
"type": "tool-call",
"id": "advanced-direct-child",
"name": "subagent",
"arguments": "{\"description\": \"Check direct child\", \"prompt\": \"Reply with exactly DIRECT_CHILD_OK and nothing else.\"}"
}
],
"provenance": {
"provider": "deepseek",
"model": "smoke-model"
"message": {
"role": "assistant",
"content": [
{
"type": "tool-call",
"id": "advanced-direct-child",
"name": "subagent",
"arguments": "{\"description\": \"Check direct child\", \"prompt\": \"Reply with exactly DIRECT_CHILD_OK and nothing else.\"}"
}
],
"source": {
"kind": "model",
"provider": "deepseek",
"model": "smoke-model"
},
"id": "{{messageId}}"
},
"usage": {
"inputTokens": 3,
@@ -615,14 +648,27 @@
"data": {
"turn": 1,
"step": 3,
"callId": "advanced-direct-child",
"content": [
{
"type": "text",
"text": "DIRECT_CHILD_OK"
}
],
"isError": false
"message": {
"source": {
"kind": "tool",
"callId": "advanced-direct-child"
},
"content": [
{
"type": "tool-result",
"toolCallId": "advanced-direct-child",
"content": [
{
"type": "text",
"text": "DIRECT_CHILD_OK"
}
],
"isError": false
}
],
"role": "user",
"id": "{{messageId}}"
}
},
"sourceEventSeqs": [
34
@@ -734,17 +780,22 @@
"data": {
"turn": 1,
"step": 4,
"content": [
{
"type": "tool-call",
"id": "advanced-workflow",
"name": "workflow",
"arguments": "{\"script\": \"phase('Delegate')\\nconst reply = await agent('Reply with exactly WORKFLOW_CHILD_OK and nothing else.', { label: 'workflow-child' })\\nreturn { reply }\", \"meta\": {\"name\": \"advanced-exe-snapshot\", \"description\": \"exercise one packaged workflow child\"}}"
}
],
"provenance": {
"provider": "deepseek",
"model": "smoke-model"
"message": {
"role": "assistant",
"content": [
{
"type": "tool-call",
"id": "advanced-workflow",
"name": "workflow",
"arguments": "{\"script\": \"phase('Delegate')\\nconst reply = await agent('Reply with exactly WORKFLOW_CHILD_OK and nothing else.', { label: 'workflow-child' })\\nreturn { reply }\", \"meta\": {\"name\": \"advanced-exe-snapshot\", \"description\": \"exercise one packaged workflow child\"}}"
}
],
"source": {
"kind": "model",
"provider": "deepseek",
"model": "smoke-model"
},
"id": "{{messageId}}"
},
"usage": {
"inputTokens": 3,
@@ -779,14 +830,27 @@
"data": {
"turn": 1,
"step": 4,
"callId": "advanced-workflow",
"content": [
{
"type": "text",
"text": "workflow \"advanced-exe-snapshot\" completed (1 agent).\nReturn value:\n{\n \"reply\": \"WORKFLOW_CHILD_OK\"\n}"
}
],
"isError": false
"message": {
"source": {
"kind": "tool",
"callId": "advanced-workflow"
},
"content": [
{
"type": "tool-result",
"toolCallId": "advanced-workflow",
"content": [
{
"type": "text",
"text": "workflow \"advanced-exe-snapshot\" completed (1 agent).\nReturn value:\n{\n \"reply\": \"WORKFLOW_CHILD_OK\"\n}"
}
],
"isError": false
}
],
"role": "user",
"id": "{{messageId}}"
}
},
"sourceEventSeqs": [
44
@@ -898,17 +962,22 @@
"data": {
"turn": 1,
"step": 5,
"content": [
{
"type": "tool-call",
"id": "advanced-unmount",
"name": "cordis_unmount",
"arguments": "{\"id\": \"dyn-1\"}"
}
],
"provenance": {
"provider": "deepseek",
"model": "smoke-model"
"message": {
"role": "assistant",
"content": [
{
"type": "tool-call",
"id": "advanced-unmount",
"name": "cordis_unmount",
"arguments": "{\"id\": \"dyn-1\"}"
}
],
"source": {
"kind": "model",
"provider": "deepseek",
"model": "smoke-model"
},
"id": "{{messageId}}"
},
"usage": {
"inputTokens": 3,
@@ -943,14 +1012,27 @@
"data": {
"turn": 1,
"step": 5,
"callId": "advanced-unmount",
"content": [
{
"type": "text",
"text": "Temporary Plugin dyn-1 was unmounted and removed."
}
],
"isError": false
"message": {
"source": {
"kind": "tool",
"callId": "advanced-unmount"
},
"content": [
{
"type": "tool-result",
"toolCallId": "advanced-unmount",
"content": [
{
"type": "text",
"text": "Temporary Plugin dyn-1 was unmounted and removed."
}
],
"isError": false
}
],
"role": "user",
"id": "{{messageId}}"
}
},
"sourceEventSeqs": [
54
@@ -988,20 +1070,15 @@
},
"system": "{{system}}",
"tools": [
"bash",
"cordis_inspect",
"cordis_mount",
"cordis_unmount",
"run_code",
"skill",
"subagent",
"task_kill",
"task_list",
"task_output",
"workflow"
],
"messagePrefix": [
"{{messagePrefix}}"
]
},
"reason": "change"
@@ -1090,15 +1167,20 @@
"data": {
"turn": 1,
"step": 6,
"content": [
{
"type": "text",
"text": "ADVANCED_EXECUTABLE_OK"
}
],
"provenance": {
"provider": "deepseek",
"model": "smoke-model"
"message": {
"role": "assistant",
"content": [
{
"type": "text",
"text": "ADVANCED_EXECUTABLE_OK"
}
],
"source": {
"kind": "model",
"provider": "deepseek",
"model": "smoke-model"
},
"id": "{{messageId}}"
},
"usage": {
"inputTokens": 3,
@@ -1173,7 +1255,9 @@
],
"source": {
"kind": "user"
}
},
"role": "user",
"id": "{{messageId}}"
},
"surfaceOp": "append"
}
@@ -1231,20 +1315,15 @@
},
"system": "{{system}}",
"tools": [
"bash",
"cordis_inspect",
"cordis_mount",
"cordis_unmount",
"run_code",
"skill",
"subagent",
"task_kill",
"task_list",
"task_output",
"workflow"
],
"messagePrefix": [
"{{messagePrefix}}"
]
},
"reason": "initial"
@@ -1373,17 +1452,22 @@
"data": {
"turn": 1,
"step": 1,
"content": [
{
"type": "tool-call",
"id": "advanced-mount",
"name": "cordis_mount",
"arguments": "{\"code\": \"return (ctx) => {\\n harness.registerTool(ctx, harness.defineTool({\\n name: 'snapshot_double',\\n description: 'Double a number for executable snapshot verification.',\\n parameters: { value: { type: 'number', required: true } },\\n output: {\\n schema: { type: 'number' },\\n render(_args, value) {\\n return [{ type: 'text', text: String(value) }]\\n }\\n },\\n async execute(args) {\\n return args.value * 2\\n }\\n }))\\n}\\n\"}"
}
],
"provenance": {
"provider": "deepseek",
"model": "smoke-model"
"message": {
"role": "assistant",
"content": [
{
"type": "tool-call",
"id": "advanced-mount",
"name": "cordis_mount",
"arguments": "{\"code\": \"return (ctx) => {\\n harness.registerTool(ctx, harness.defineTool({\\n name: 'snapshot_double',\\n description: 'Double a number for executable snapshot verification.',\\n parameters: { value: { type: 'number', required: true } },\\n output: {\\n schema: { type: 'number' },\\n render(_args, value) {\\n return [{ type: 'text', text: String(value) }]\\n }\\n },\\n async execute(args) {\\n return args.value * 2\\n }\\n }))\\n}\\n\"}"
}
],
"source": {
"kind": "model",
"provider": "deepseek",
"model": "smoke-model"
},
"id": "{{messageId}}"
},
"usage": {
"inputTokens": 3,
@@ -1430,14 +1514,27 @@
"data": {
"turn": 1,
"step": 1,
"callId": "advanced-mount",
"content": [
{
"type": "text",
"text": "Temporary Plugin dyn-1 is running (plugin \"<anonymous>\"; available until unmounted or DSH restarts)."
}
],
"isError": false
"message": {
"source": {
"kind": "tool",
"callId": "advanced-mount"
},
"content": [
{
"type": "tool-result",
"toolCallId": "advanced-mount",
"content": [
{
"type": "text",
"text": "Temporary Plugin dyn-1 is running (plugin \"<anonymous>\"; available until unmounted or DSH restarts)."
}
],
"isError": false
}
],
"role": "user",
"id": "{{messageId}}"
}
},
"sourceEventSeqs": [
11
@@ -1493,21 +1590,16 @@
},
"system": "{{system}}",
"tools": [
"bash",
"cordis_inspect",
"cordis_mount",
"cordis_unmount",
"run_code",
"skill",
"snapshot_double",
"subagent",
"task_kill",
"task_list",
"task_output",
"workflow"
],
"messagePrefix": [
"{{messagePrefix}}"
]
},
"reason": "change"
@@ -1636,17 +1728,22 @@
"data": {
"turn": 1,
"step": 2,
"content": [
{
"type": "tool-call",
"id": "advanced-code",
"name": "run_code",
"arguments": "{\"code\": \"return await tools.snapshot_double({ value: 21 })\", \"description\": \"Run the temporary Plugin tool\"}"
}
],
"provenance": {
"provider": "deepseek",
"model": "smoke-model"
"message": {
"role": "assistant",
"content": [
{
"type": "tool-call",
"id": "advanced-code",
"name": "run_code",
"arguments": "{\"code\": \"return await tools.snapshot_double({ value: 21 })\", \"description\": \"Run the temporary Plugin tool\"}"
}
],
"source": {
"kind": "model",
"provider": "deepseek",
"model": "smoke-model"
},
"id": "{{messageId}}"
},
"usage": {
"inputTokens": 3,
@@ -1738,14 +1835,27 @@
"data": {
"turn": 1,
"step": 2,
"callId": "advanced-code",
"content": [
{
"type": "text",
"text": "42"
}
],
"isError": false
"message": {
"source": {
"kind": "tool",
"callId": "advanced-code"
},
"content": [
{
"type": "tool-result",
"toolCallId": "advanced-code",
"content": [
{
"type": "text",
"text": "42"
}
],
"isError": false
}
],
"role": "user",
"id": "{{messageId}}"
}
},
"sourceEventSeqs": [
22
@@ -1905,17 +2015,22 @@
"data": {
"turn": 1,
"step": 3,
"content": [
{
"type": "tool-call",
"id": "advanced-direct-child",
"name": "subagent",
"arguments": "{\"description\": \"Check direct child\", \"prompt\": \"Reply with exactly DIRECT_CHILD_OK and nothing else.\"}"
}
],
"provenance": {
"provider": "deepseek",
"model": "smoke-model"
"message": {
"role": "assistant",
"content": [
{
"type": "tool-call",
"id": "advanced-direct-child",
"name": "subagent",
"arguments": "{\"description\": \"Check direct child\", \"prompt\": \"Reply with exactly DIRECT_CHILD_OK and nothing else.\"}"
}
],
"source": {
"kind": "model",
"provider": "deepseek",
"model": "smoke-model"
},
"id": "{{messageId}}"
},
"usage": {
"inputTokens": 3,
@@ -1995,7 +2110,9 @@
],
"source": {
"kind": "user"
}
},
"role": "user",
"id": "{{messageId}}"
},
"surfaceOp": "append"
}
@@ -2053,21 +2170,16 @@
},
"system": "{{system}}",
"tools": [
"bash",
"cordis_inspect",
"cordis_mount",
"cordis_unmount",
"run_code",
"skill",
"snapshot_double",
"subagent",
"task_kill",
"task_list",
"task_output",
"workflow"
],
"messagePrefix": [
"{{messagePrefix}}"
]
},
"reason": "initial"
@@ -2192,15 +2304,20 @@
"data": {
"turn": 1,
"step": 1,
"content": [
{
"type": "text",
"text": "DIRECT_CHILD_OK"
}
],
"provenance": {
"provider": "deepseek",
"model": "smoke-model"
"message": {
"role": "assistant",
"content": [
{
"type": "text",
"text": "DIRECT_CHILD_OK"
}
],
"source": {
"kind": "model",
"provider": "deepseek",
"model": "smoke-model"
},
"id": "{{messageId}}"
},
"usage": {
"inputTokens": 3,
@@ -2278,14 +2395,27 @@
"data": {
"turn": 1,
"step": 3,
"callId": "advanced-direct-child",
"content": [
{
"type": "text",
"text": "DIRECT_CHILD_OK"
}
],
"isError": false
"message": {
"source": {
"kind": "tool",
"callId": "advanced-direct-child"
},
"content": [
{
"type": "tool-result",
"toolCallId": "advanced-direct-child",
"content": [
{
"type": "text",
"text": "DIRECT_CHILD_OK"
}
],
"isError": false
}
],
"role": "user",
"id": "{{messageId}}"
}
},
"sourceEventSeqs": [
34
@@ -2445,17 +2575,22 @@
"data": {
"turn": 1,
"step": 4,
"content": [
{
"type": "tool-call",
"id": "advanced-workflow",
"name": "workflow",
"arguments": "{\"script\": \"phase('Delegate')\\nconst reply = await agent('Reply with exactly WORKFLOW_CHILD_OK and nothing else.', { label: 'workflow-child' })\\nreturn { reply }\", \"meta\": {\"name\": \"advanced-exe-snapshot\", \"description\": \"exercise one packaged workflow child\"}}"
}
],
"provenance": {
"provider": "deepseek",
"model": "smoke-model"
"message": {
"role": "assistant",
"content": [
{
"type": "tool-call",
"id": "advanced-workflow",
"name": "workflow",
"arguments": "{\"script\": \"phase('Delegate')\\nconst reply = await agent('Reply with exactly WORKFLOW_CHILD_OK and nothing else.', { label: 'workflow-child' })\\nreturn { reply }\", \"meta\": {\"name\": \"advanced-exe-snapshot\", \"description\": \"exercise one packaged workflow child\"}}"
}
],
"source": {
"kind": "model",
"provider": "deepseek",
"model": "smoke-model"
},
"id": "{{messageId}}"
},
"usage": {
"inputTokens": 3,
@@ -2535,7 +2670,9 @@
],
"source": {
"kind": "user"
}
},
"role": "user",
"id": "{{messageId}}"
},
"surfaceOp": "append"
}
@@ -2593,21 +2730,16 @@
},
"system": "{{system}}",
"tools": [
"bash",
"cordis_inspect",
"cordis_mount",
"cordis_unmount",
"run_code",
"skill",
"snapshot_double",
"subagent",
"task_kill",
"task_list",
"task_output",
"workflow"
],
"messagePrefix": [
"{{messagePrefix}}"
]
},
"reason": "initial"
@@ -2732,15 +2864,20 @@
"data": {
"turn": 1,
"step": 1,
"content": [
{
"type": "text",
"text": "WORKFLOW_CHILD_OK"
}
],
"provenance": {
"provider": "deepseek",
"model": "smoke-model"
"message": {
"role": "assistant",
"content": [
{
"type": "text",
"text": "WORKFLOW_CHILD_OK"
}
],
"source": {
"kind": "model",
"provider": "deepseek",
"model": "smoke-model"
},
"id": "{{messageId}}"
},
"usage": {
"inputTokens": 3,
@@ -2818,14 +2955,27 @@
"data": {
"turn": 1,
"step": 4,
"callId": "advanced-workflow",
"content": [
{
"type": "text",
"text": "workflow \"advanced-exe-snapshot\" completed (1 agent).\nReturn value:\n{\n \"reply\": \"WORKFLOW_CHILD_OK\"\n}"
}
],
"isError": false
"message": {
"source": {
"kind": "tool",
"callId": "advanced-workflow"
},
"content": [
{
"type": "tool-result",
"toolCallId": "advanced-workflow",
"content": [
{
"type": "text",
"text": "workflow \"advanced-exe-snapshot\" completed (1 agent).\nReturn value:\n{\n \"reply\": \"WORKFLOW_CHILD_OK\"\n}"
}
],
"isError": false
}
],
"role": "user",
"id": "{{messageId}}"
}
},
"sourceEventSeqs": [
44
@@ -2985,17 +3135,22 @@
"data": {
"turn": 1,
"step": 5,
"content": [
{
"type": "tool-call",
"id": "advanced-unmount",
"name": "cordis_unmount",
"arguments": "{\"id\": \"dyn-1\"}"
}
],
"provenance": {
"provider": "deepseek",
"model": "smoke-model"
"message": {
"role": "assistant",
"content": [
{
"type": "tool-call",
"id": "advanced-unmount",
"name": "cordis_unmount",
"arguments": "{\"id\": \"dyn-1\"}"
}
],
"source": {
"kind": "model",
"provider": "deepseek",
"model": "smoke-model"
},
"id": "{{messageId}}"
},
"usage": {
"inputTokens": 3,
@@ -3042,14 +3197,27 @@
"data": {
"turn": 1,
"step": 5,
"callId": "advanced-unmount",
"content": [
{
"type": "text",
"text": "Temporary Plugin dyn-1 was unmounted and removed."
}
],
"isError": false
"message": {
"source": {
"kind": "tool",
"callId": "advanced-unmount"
},
"content": [
{
"type": "tool-result",
"toolCallId": "advanced-unmount",
"content": [
{
"type": "text",
"text": "Temporary Plugin dyn-1 was unmounted and removed."
}
],
"isError": false
}
],
"role": "user",
"id": "{{messageId}}"
}
},
"sourceEventSeqs": [
54
@@ -3105,20 +3273,15 @@
},
"system": "{{system}}",
"tools": [
"bash",
"cordis_inspect",
"cordis_mount",
"cordis_unmount",
"run_code",
"skill",
"subagent",
"task_kill",
"task_list",
"task_output",
"workflow"
],
"messagePrefix": [
"{{messagePrefix}}"
]
},
"reason": "change"
@@ -3243,15 +3406,20 @@
"data": {
"turn": 1,
"step": 6,
"content": [
{
"type": "text",
"text": "ADVANCED_EXECUTABLE_OK"
}
],
"provenance": {
"provider": "deepseek",
"model": "smoke-model"
"message": {
"role": "assistant",
"content": [
{
"type": "text",
"text": "ADVANCED_EXECUTABLE_OK"
}
],
"source": {
"kind": "model",
"provider": "deepseek",
"model": "smoke-model"
},
"id": "{{messageId}}"
},
"usage": {
"inputTokens": 3,
@@ -1,14 +1,14 @@
{"type":"session","version":0,"id":"{{child-1}}","createdAt":0,"cwd":"{{cwd}}","parentSession":"{{parent}}","delegationDepth":1}
{"type":"turn/start","seq":0,"time":0,"data":{"turn":1,"trigger":{"kind":"message","source":{"kind":"user"}}}}
{"type":"user/message","seq":1,"time":0,"data":{"content":[{"type":"text","text":"Reply with exactly DIRECT_CHILD_OK and nothing else."}],"source":{"kind":"user"}},"surfaceOp":"append"}
{"type":"user/message","seq":1,"time":0,"data":{"content":[{"type":"text","text":"Reply with exactly DIRECT_CHILD_OK and nothing else."}],"source":{"kind":"user"},"role":"user","id":"{{messageId}}"},"surfaceOp":"append"}
{"type":"session/title","seq":2,"time":0,"data":{"title":"Reply with exactly DIRECT_CHILD_OK and","messageSeqs":[1],"source":{"kind":"fallback"}}}
{"type":"step/start","seq":3,"time":0,"data":{"turn":1,"step":1}}
{"type":"request/header","seq":4,"time":0,"data":{"header":{"config":{"provider":"deepseek","model":"smoke-model","reasoningEffort":"high"},"system":"{{system}}","tools":["bash","cordis_inspect","cordis_mount","cordis_unmount","run_code","skill","snapshot_double","subagent","task_kill","task_list","task_output","workflow"],"messagePrefix":["{{messagePrefix}}"]},"reason":"initial"}}
{"type":"request/header","seq":4,"time":0,"data":{"header":{"config":{"provider":"deepseek","model":"smoke-model","reasoningEffort":"high"},"system":"{{system}}","tools":["cordis_inspect","cordis_mount","cordis_unmount","run_code","snapshot_double","subagent","task_kill","task_list","task_output","workflow"]},"reason":"initial"}}
{"type":"assistant/chunk","seq":5,"time":0,"data":{"turn":1,"step":1,"chunk":{"type":"block-start","index":0,"blockType":"text"}}}
{"type":"assistant/chunk","seq":6,"time":0,"data":{"turn":1,"step":1,"chunk":{"type":"text-delta","index":0,"text":"DIRECT_CHILD_OK"}}}
{"type":"assistant/chunk","seq":7,"time":0,"data":{"turn":1,"step":1,"chunk":{"type":"block-end","index":0,"block":{"type":"text","text":"DIRECT_CHILD_OK"}}}}
{"type":"assistant/chunk","seq":8,"time":0,"data":{"turn":1,"step":1,"chunk":{"type":"usage","usage":{"inputTokens":3,"outputTokens":3}}}}
{"type":"assistant/chunk","seq":9,"time":0,"data":{"turn":1,"step":1,"chunk":{"type":"finish","reason":{"kind":"stop"}}}}
{"type":"assistant/message","seq":10,"time":0,"data":{"turn":1,"step":1,"content":[{"type":"text","text":"DIRECT_CHILD_OK"}],"provenance":{"provider":"deepseek","model":"smoke-model"},"usage":{"inputTokens":3,"outputTokens":3}},"sourceEventSeqs":[5,6,7,8,9],"surfaceOp":"append"}
{"type":"assistant/message","seq":10,"time":0,"data":{"turn":1,"step":1,"message":{"role":"assistant","content":[{"type":"text","text":"DIRECT_CHILD_OK"}],"source":{"kind":"model","provider":"deepseek","model":"smoke-model"},"id":"{{messageId}}"},"usage":{"inputTokens":3,"outputTokens":3}},"sourceEventSeqs":[5,6,7,8,9],"surfaceOp":"append"}
{"type":"step/end","seq":11,"time":0,"data":{"turn":1,"step":1}}
{"type":"turn/end","seq":12,"time":0,"data":{"turn":1,"reason":{"kind":"completed"}}}
@@ -1,14 +1,14 @@
{"type":"session","version":0,"id":"{{child-2}}","createdAt":0,"cwd":"{{cwd}}","parentSession":"{{parent}}","delegationDepth":1}
{"type":"turn/start","seq":0,"time":0,"data":{"turn":1,"trigger":{"kind":"message","source":{"kind":"user"}}}}
{"type":"user/message","seq":1,"time":0,"data":{"content":[{"type":"text","text":"Reply with exactly WORKFLOW_CHILD_OK and nothing else."}],"source":{"kind":"user"}},"surfaceOp":"append"}
{"type":"user/message","seq":1,"time":0,"data":{"content":[{"type":"text","text":"Reply with exactly WORKFLOW_CHILD_OK and nothing else."}],"source":{"kind":"user"},"role":"user","id":"{{messageId}}"},"surfaceOp":"append"}
{"type":"session/title","seq":2,"time":0,"data":{"title":"Reply with exactly WORKFLOW_CHILD_OK and","messageSeqs":[1],"source":{"kind":"fallback"}}}
{"type":"step/start","seq":3,"time":0,"data":{"turn":1,"step":1}}
{"type":"request/header","seq":4,"time":0,"data":{"header":{"config":{"provider":"deepseek","model":"smoke-model","reasoningEffort":"high"},"system":"{{system}}","tools":["bash","cordis_inspect","cordis_mount","cordis_unmount","run_code","skill","snapshot_double","subagent","task_kill","task_list","task_output","workflow"],"messagePrefix":["{{messagePrefix}}"]},"reason":"initial"}}
{"type":"request/header","seq":4,"time":0,"data":{"header":{"config":{"provider":"deepseek","model":"smoke-model","reasoningEffort":"high"},"system":"{{system}}","tools":["cordis_inspect","cordis_mount","cordis_unmount","run_code","snapshot_double","subagent","task_kill","task_list","task_output","workflow"]},"reason":"initial"}}
{"type":"assistant/chunk","seq":5,"time":0,"data":{"turn":1,"step":1,"chunk":{"type":"block-start","index":0,"blockType":"text"}}}
{"type":"assistant/chunk","seq":6,"time":0,"data":{"turn":1,"step":1,"chunk":{"type":"text-delta","index":0,"text":"WORKFLOW_CHILD_OK"}}}
{"type":"assistant/chunk","seq":7,"time":0,"data":{"turn":1,"step":1,"chunk":{"type":"block-end","index":0,"block":{"type":"text","text":"WORKFLOW_CHILD_OK"}}}}
{"type":"assistant/chunk","seq":8,"time":0,"data":{"turn":1,"step":1,"chunk":{"type":"usage","usage":{"inputTokens":3,"outputTokens":3}}}}
{"type":"assistant/chunk","seq":9,"time":0,"data":{"turn":1,"step":1,"chunk":{"type":"finish","reason":{"kind":"stop"}}}}
{"type":"assistant/message","seq":10,"time":0,"data":{"turn":1,"step":1,"content":[{"type":"text","text":"WORKFLOW_CHILD_OK"}],"provenance":{"provider":"deepseek","model":"smoke-model"},"usage":{"inputTokens":3,"outputTokens":3}},"sourceEventSeqs":[5,6,7,8,9],"surfaceOp":"append"}
{"type":"assistant/message","seq":10,"time":0,"data":{"turn":1,"step":1,"message":{"role":"assistant","content":[{"type":"text","text":"WORKFLOW_CHILD_OK"}],"source":{"kind":"model","provider":"deepseek","model":"smoke-model"},"id":"{{messageId}}"},"usage":{"inputTokens":3,"outputTokens":3}},"sourceEventSeqs":[5,6,7,8,9],"surfaceOp":"append"}
{"type":"step/end","seq":11,"time":0,"data":{"turn":1,"step":1}}
{"type":"turn/end","seq":12,"time":0,"data":{"turn":1,"reason":{"kind":"completed"}}}
@@ -1,30 +1,30 @@
{"type":"session","version":0,"id":"{{parent}}","createdAt":0,"cwd":"{{cwd}}","delegationDepth":0}
{"type":"turn/start","seq":0,"time":0,"data":{"turn":1,"trigger":{"kind":"message","source":{"kind":"user"}}}}
{"type":"user/message","seq":1,"time":0,"data":{"content":[{"type":"text","text":"Run the advanced packaged-runtime snapshot scenario."}],"source":{"kind":"user"}},"surfaceOp":"append"}
{"type":"user/message","seq":1,"time":0,"data":{"content":[{"type":"text","text":"Run the advanced packaged-runtime snapshot scenario."}],"source":{"kind":"user"},"role":"user","id":"{{messageId}}"},"surfaceOp":"append"}
{"type":"session/title","seq":2,"time":0,"data":{"title":"Run the advanced packaged-runtime snapsh","messageSeqs":[1],"source":{"kind":"fallback"}}}
{"type":"step/start","seq":3,"time":0,"data":{"turn":1,"step":1}}
{"type":"request/header","seq":4,"time":0,"data":{"header":{"config":{"provider":"deepseek","model":"smoke-model","reasoningEffort":"high"},"system":"{{system}}","tools":["bash","cordis_inspect","cordis_mount","cordis_unmount","run_code","skill","subagent","task_kill","task_list","task_output","workflow"],"messagePrefix":["{{messagePrefix}}"]},"reason":"initial"}}
{"type":"request/header","seq":4,"time":0,"data":{"header":{"config":{"provider":"deepseek","model":"smoke-model","reasoningEffort":"high"},"system":"{{system}}","tools":["cordis_inspect","cordis_mount","cordis_unmount","run_code","subagent","task_kill","task_list","task_output","workflow"]},"reason":"initial"}}
{"type":"assistant/chunk","seq":5,"time":0,"data":{"turn":1,"step":1,"chunk":{"type":"block-start","index":0,"blockType":"tool-call"}}}
{"type":"assistant/chunk","seq":6,"time":0,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":0,"id":"advanced-mount","name":"cordis_mount","argumentsDelta":"{\"code\": \"return (ctx) => {\\n harness.registerTool(ctx, harness.defineTool({\\n name: 'snapshot_double',\\n description: 'Double a number for executable snapshot verification.',\\n parameters: { value: { type: 'number', required: true } },\\n output: {\\n schema: { type: 'number' },\\n render(_args, value) {\\n return [{ type: 'text', text: String(value) }]\\n }\\n },\\n async execute(args) {\\n return args.value * 2\\n }\\n }))\\n}\\n\"}"}}}
{"type":"assistant/chunk","seq":7,"time":0,"data":{"turn":1,"step":1,"chunk":{"type":"block-end","index":0,"block":{"type":"tool-call","id":"advanced-mount","name":"cordis_mount","arguments":"{\"code\": \"return (ctx) => {\\n harness.registerTool(ctx, harness.defineTool({\\n name: 'snapshot_double',\\n description: 'Double a number for executable snapshot verification.',\\n parameters: { value: { type: 'number', required: true } },\\n output: {\\n schema: { type: 'number' },\\n render(_args, value) {\\n return [{ type: 'text', text: String(value) }]\\n }\\n },\\n async execute(args) {\\n return args.value * 2\\n }\\n }))\\n}\\n\"}"}}}}
{"type":"assistant/chunk","seq":8,"time":0,"data":{"turn":1,"step":1,"chunk":{"type":"usage","usage":{"inputTokens":3,"outputTokens":3}}}}
{"type":"assistant/chunk","seq":9,"time":0,"data":{"turn":1,"step":1,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}}
{"type":"assistant/message","seq":10,"time":0,"data":{"turn":1,"step":1,"content":[{"type":"tool-call","id":"advanced-mount","name":"cordis_mount","arguments":"{\"code\": \"return (ctx) => {\\n harness.registerTool(ctx, harness.defineTool({\\n name: 'snapshot_double',\\n description: 'Double a number for executable snapshot verification.',\\n parameters: { value: { type: 'number', required: true } },\\n output: {\\n schema: { type: 'number' },\\n render(_args, value) {\\n return [{ type: 'text', text: String(value) }]\\n }\\n },\\n async execute(args) {\\n return args.value * 2\\n }\\n }))\\n}\\n\"}"}],"provenance":{"provider":"deepseek","model":"smoke-model"},"usage":{"inputTokens":3,"outputTokens":3}},"sourceEventSeqs":[5,6,7,8,9],"surfaceOp":"append"}
{"type":"assistant/message","seq":10,"time":0,"data":{"turn":1,"step":1,"message":{"role":"assistant","content":[{"type":"tool-call","id":"advanced-mount","name":"cordis_mount","arguments":"{\"code\": \"return (ctx) => {\\n harness.registerTool(ctx, harness.defineTool({\\n name: 'snapshot_double',\\n description: 'Double a number for executable snapshot verification.',\\n parameters: { value: { type: 'number', required: true } },\\n output: {\\n schema: { type: 'number' },\\n render(_args, value) {\\n return [{ type: 'text', text: String(value) }]\\n }\\n },\\n async execute(args) {\\n return args.value * 2\\n }\\n }))\\n}\\n\"}"}],"source":{"kind":"model","provider":"deepseek","model":"smoke-model"},"id":"{{messageId}}"},"usage":{"inputTokens":3,"outputTokens":3}},"sourceEventSeqs":[5,6,7,8,9],"surfaceOp":"append"}
{"type":"tool/call","seq":11,"time":0,"data":{"turn":1,"step":1,"callId":"advanced-mount","name":"cordis_mount","arguments":"{\"code\": \"return (ctx) => {\\n harness.registerTool(ctx, harness.defineTool({\\n name: 'snapshot_double',\\n description: 'Double a number for executable snapshot verification.',\\n parameters: { value: { type: 'number', required: true } },\\n output: {\\n schema: { type: 'number' },\\n render(_args, value) {\\n return [{ type: 'text', text: String(value) }]\\n }\\n },\\n async execute(args) {\\n return args.value * 2\\n }\\n }))\\n}\\n\"}"}}
{"type":"tool/result","seq":12,"time":0,"data":{"turn":1,"step":1,"callId":"advanced-mount","content":[{"type":"text","text":"Temporary Plugin dyn-1 is running (plugin \"<anonymous>\"; available until unmounted or DSH restarts)."}],"isError":false},"sourceEventSeqs":[11],"surfaceOp":"append"}
{"type":"tool/result","seq":12,"time":0,"data":{"turn":1,"step":1,"message":{"source":{"kind":"tool","callId":"advanced-mount"},"content":[{"type":"tool-result","toolCallId":"advanced-mount","content":[{"type":"text","text":"Temporary Plugin dyn-1 is running (plugin \"<anonymous>\"; available until unmounted or DSH restarts)."}],"isError":false}],"role":"user","id":"{{messageId}}"}},"sourceEventSeqs":[11],"surfaceOp":"append"}
{"type":"step/end","seq":13,"time":0,"data":{"turn":1,"step":1}}
{"type":"step/start","seq":14,"time":0,"data":{"turn":1,"step":2}}
{"type":"request/header","seq":15,"time":0,"data":{"header":{"config":{"provider":"deepseek","model":"smoke-model","reasoningEffort":"high"},"system":"{{system}}","tools":["bash","cordis_inspect","cordis_mount","cordis_unmount","run_code","skill","snapshot_double","subagent","task_kill","task_list","task_output","workflow"],"messagePrefix":["{{messagePrefix}}"]},"reason":"change"}}
{"type":"request/header","seq":15,"time":0,"data":{"header":{"config":{"provider":"deepseek","model":"smoke-model","reasoningEffort":"high"},"system":"{{system}}","tools":["cordis_inspect","cordis_mount","cordis_unmount","run_code","snapshot_double","subagent","task_kill","task_list","task_output","workflow"]},"reason":"change"}}
{"type":"assistant/chunk","seq":16,"time":0,"data":{"turn":1,"step":2,"chunk":{"type":"block-start","index":0,"blockType":"tool-call"}}}
{"type":"assistant/chunk","seq":17,"time":0,"data":{"turn":1,"step":2,"chunk":{"type":"tool-call-delta","index":0,"id":"advanced-code","name":"run_code","argumentsDelta":"{\"code\": \"return await tools.snapshot_double({ value: 21 })\", \"description\": \"Run the temporary Plugin tool\"}"}}}
{"type":"assistant/chunk","seq":18,"time":0,"data":{"turn":1,"step":2,"chunk":{"type":"block-end","index":0,"block":{"type":"tool-call","id":"advanced-code","name":"run_code","arguments":"{\"code\": \"return await tools.snapshot_double({ value: 21 })\", \"description\": \"Run the temporary Plugin tool\"}"}}}}
{"type":"assistant/chunk","seq":19,"time":0,"data":{"turn":1,"step":2,"chunk":{"type":"usage","usage":{"inputTokens":3,"outputTokens":3}}}}
{"type":"assistant/chunk","seq":20,"time":0,"data":{"turn":1,"step":2,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}}
{"type":"assistant/message","seq":21,"time":0,"data":{"turn":1,"step":2,"content":[{"type":"tool-call","id":"advanced-code","name":"run_code","arguments":"{\"code\": \"return await tools.snapshot_double({ value: 21 })\", \"description\": \"Run the temporary Plugin tool\"}"}],"provenance":{"provider":"deepseek","model":"smoke-model"},"usage":{"inputTokens":3,"outputTokens":3}},"sourceEventSeqs":[16,17,18,19,20],"surfaceOp":"append"}
{"type":"assistant/message","seq":21,"time":0,"data":{"turn":1,"step":2,"message":{"role":"assistant","content":[{"type":"tool-call","id":"advanced-code","name":"run_code","arguments":"{\"code\": \"return await tools.snapshot_double({ value: 21 })\", \"description\": \"Run the temporary Plugin tool\"}"}],"source":{"kind":"model","provider":"deepseek","model":"smoke-model"},"id":"{{messageId}}"},"usage":{"inputTokens":3,"outputTokens":3}},"sourceEventSeqs":[16,17,18,19,20],"surfaceOp":"append"}
{"type":"tool/call","seq":22,"time":0,"data":{"turn":1,"step":2,"callId":"advanced-code","name":"run_code","arguments":"{\"code\": \"return await tools.snapshot_double({ value: 21 })\", \"description\": \"Run the temporary Plugin tool\"}"}}
{"type":"tool/code-dispatch-start","seq":23,"time":0,"data":{"parentCallId":"advanced-code","subCallId":"advanced-code:code:1","name":"snapshot_double","arguments":{"value":21}}}
{"type":"tool/code-dispatch","seq":24,"time":0,"data":{"parentCallId":"advanced-code","subCallId":"advanced-code:code:1","name":"snapshot_double","arguments":{"value":21},"isError":false,"content":[{"type":"text","text":"42"}]}}
{"type":"tool/result","seq":25,"time":0,"data":{"turn":1,"step":2,"callId":"advanced-code","content":[{"type":"text","text":"42"}],"isError":false},"sourceEventSeqs":[22],"surfaceOp":"append"}
{"type":"tool/result","seq":25,"time":0,"data":{"turn":1,"step":2,"message":{"source":{"kind":"tool","callId":"advanced-code"},"content":[{"type":"tool-result","toolCallId":"advanced-code","content":[{"type":"text","text":"42"}],"isError":false}],"role":"user","id":"{{messageId}}"}},"sourceEventSeqs":[22],"surfaceOp":"append"}
{"type":"step/end","seq":26,"time":0,"data":{"turn":1,"step":2}}
{"type":"step/start","seq":27,"time":0,"data":{"turn":1,"step":3}}
{"type":"assistant/chunk","seq":28,"time":0,"data":{"turn":1,"step":3,"chunk":{"type":"block-start","index":0,"blockType":"tool-call"}}}
@@ -32,9 +32,9 @@
{"type":"assistant/chunk","seq":30,"time":0,"data":{"turn":1,"step":3,"chunk":{"type":"block-end","index":0,"block":{"type":"tool-call","id":"advanced-direct-child","name":"subagent","arguments":"{\"description\": \"Check direct child\", \"prompt\": \"Reply with exactly DIRECT_CHILD_OK and nothing else.\"}"}}}}
{"type":"assistant/chunk","seq":31,"time":0,"data":{"turn":1,"step":3,"chunk":{"type":"usage","usage":{"inputTokens":3,"outputTokens":3}}}}
{"type":"assistant/chunk","seq":32,"time":0,"data":{"turn":1,"step":3,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}}
{"type":"assistant/message","seq":33,"time":0,"data":{"turn":1,"step":3,"content":[{"type":"tool-call","id":"advanced-direct-child","name":"subagent","arguments":"{\"description\": \"Check direct child\", \"prompt\": \"Reply with exactly DIRECT_CHILD_OK and nothing else.\"}"}],"provenance":{"provider":"deepseek","model":"smoke-model"},"usage":{"inputTokens":3,"outputTokens":3}},"sourceEventSeqs":[28,29,30,31,32],"surfaceOp":"append"}
{"type":"assistant/message","seq":33,"time":0,"data":{"turn":1,"step":3,"message":{"role":"assistant","content":[{"type":"tool-call","id":"advanced-direct-child","name":"subagent","arguments":"{\"description\": \"Check direct child\", \"prompt\": \"Reply with exactly DIRECT_CHILD_OK and nothing else.\"}"}],"source":{"kind":"model","provider":"deepseek","model":"smoke-model"},"id":"{{messageId}}"},"usage":{"inputTokens":3,"outputTokens":3}},"sourceEventSeqs":[28,29,30,31,32],"surfaceOp":"append"}
{"type":"tool/call","seq":34,"time":0,"data":{"turn":1,"step":3,"callId":"advanced-direct-child","name":"subagent","arguments":"{\"description\": \"Check direct child\", \"prompt\": \"Reply with exactly DIRECT_CHILD_OK and nothing else.\"}"}}
{"type":"tool/result","seq":35,"time":0,"data":{"turn":1,"step":3,"callId":"advanced-direct-child","content":[{"type":"text","text":"DIRECT_CHILD_OK"}],"isError":false},"sourceEventSeqs":[34],"surfaceOp":"append"}
{"type":"tool/result","seq":35,"time":0,"data":{"turn":1,"step":3,"message":{"source":{"kind":"tool","callId":"advanced-direct-child"},"content":[{"type":"tool-result","toolCallId":"advanced-direct-child","content":[{"type":"text","text":"DIRECT_CHILD_OK"}],"isError":false}],"role":"user","id":"{{messageId}}"}},"sourceEventSeqs":[34],"surfaceOp":"append"}
{"type":"step/end","seq":36,"time":0,"data":{"turn":1,"step":3}}
{"type":"step/start","seq":37,"time":0,"data":{"turn":1,"step":4}}
{"type":"assistant/chunk","seq":38,"time":0,"data":{"turn":1,"step":4,"chunk":{"type":"block-start","index":0,"blockType":"tool-call"}}}
@@ -42,9 +42,9 @@
{"type":"assistant/chunk","seq":40,"time":0,"data":{"turn":1,"step":4,"chunk":{"type":"block-end","index":0,"block":{"type":"tool-call","id":"advanced-workflow","name":"workflow","arguments":"{\"script\": \"phase('Delegate')\\nconst reply = await agent('Reply with exactly WORKFLOW_CHILD_OK and nothing else.', { label: 'workflow-child' })\\nreturn { reply }\", \"meta\": {\"name\": \"advanced-exe-snapshot\", \"description\": \"exercise one packaged workflow child\"}}"}}}}
{"type":"assistant/chunk","seq":41,"time":0,"data":{"turn":1,"step":4,"chunk":{"type":"usage","usage":{"inputTokens":3,"outputTokens":3}}}}
{"type":"assistant/chunk","seq":42,"time":0,"data":{"turn":1,"step":4,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}}
{"type":"assistant/message","seq":43,"time":0,"data":{"turn":1,"step":4,"content":[{"type":"tool-call","id":"advanced-workflow","name":"workflow","arguments":"{\"script\": \"phase('Delegate')\\nconst reply = await agent('Reply with exactly WORKFLOW_CHILD_OK and nothing else.', { label: 'workflow-child' })\\nreturn { reply }\", \"meta\": {\"name\": \"advanced-exe-snapshot\", \"description\": \"exercise one packaged workflow child\"}}"}],"provenance":{"provider":"deepseek","model":"smoke-model"},"usage":{"inputTokens":3,"outputTokens":3}},"sourceEventSeqs":[38,39,40,41,42],"surfaceOp":"append"}
{"type":"assistant/message","seq":43,"time":0,"data":{"turn":1,"step":4,"message":{"role":"assistant","content":[{"type":"tool-call","id":"advanced-workflow","name":"workflow","arguments":"{\"script\": \"phase('Delegate')\\nconst reply = await agent('Reply with exactly WORKFLOW_CHILD_OK and nothing else.', { label: 'workflow-child' })\\nreturn { reply }\", \"meta\": {\"name\": \"advanced-exe-snapshot\", \"description\": \"exercise one packaged workflow child\"}}"}],"source":{"kind":"model","provider":"deepseek","model":"smoke-model"},"id":"{{messageId}}"},"usage":{"inputTokens":3,"outputTokens":3}},"sourceEventSeqs":[38,39,40,41,42],"surfaceOp":"append"}
{"type":"tool/call","seq":44,"time":0,"data":{"turn":1,"step":4,"callId":"advanced-workflow","name":"workflow","arguments":"{\"script\": \"phase('Delegate')\\nconst reply = await agent('Reply with exactly WORKFLOW_CHILD_OK and nothing else.', { label: 'workflow-child' })\\nreturn { reply }\", \"meta\": {\"name\": \"advanced-exe-snapshot\", \"description\": \"exercise one packaged workflow child\"}}"}}
{"type":"tool/result","seq":45,"time":0,"data":{"turn":1,"step":4,"callId":"advanced-workflow","content":[{"type":"text","text":"workflow \"advanced-exe-snapshot\" completed (1 agent).\nReturn value:\n{\n \"reply\": \"WORKFLOW_CHILD_OK\"\n}"}],"isError":false},"sourceEventSeqs":[44],"surfaceOp":"append"}
{"type":"tool/result","seq":45,"time":0,"data":{"turn":1,"step":4,"message":{"source":{"kind":"tool","callId":"advanced-workflow"},"content":[{"type":"tool-result","toolCallId":"advanced-workflow","content":[{"type":"text","text":"workflow \"advanced-exe-snapshot\" completed (1 agent).\nReturn value:\n{\n \"reply\": \"WORKFLOW_CHILD_OK\"\n}"}],"isError":false}],"role":"user","id":"{{messageId}}"}},"sourceEventSeqs":[44],"surfaceOp":"append"}
{"type":"step/end","seq":46,"time":0,"data":{"turn":1,"step":4}}
{"type":"step/start","seq":47,"time":0,"data":{"turn":1,"step":5}}
{"type":"assistant/chunk","seq":48,"time":0,"data":{"turn":1,"step":5,"chunk":{"type":"block-start","index":0,"blockType":"tool-call"}}}
@@ -52,17 +52,17 @@
{"type":"assistant/chunk","seq":50,"time":0,"data":{"turn":1,"step":5,"chunk":{"type":"block-end","index":0,"block":{"type":"tool-call","id":"advanced-unmount","name":"cordis_unmount","arguments":"{\"id\": \"dyn-1\"}"}}}}
{"type":"assistant/chunk","seq":51,"time":0,"data":{"turn":1,"step":5,"chunk":{"type":"usage","usage":{"inputTokens":3,"outputTokens":3}}}}
{"type":"assistant/chunk","seq":52,"time":0,"data":{"turn":1,"step":5,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}}
{"type":"assistant/message","seq":53,"time":0,"data":{"turn":1,"step":5,"content":[{"type":"tool-call","id":"advanced-unmount","name":"cordis_unmount","arguments":"{\"id\": \"dyn-1\"}"}],"provenance":{"provider":"deepseek","model":"smoke-model"},"usage":{"inputTokens":3,"outputTokens":3}},"sourceEventSeqs":[48,49,50,51,52],"surfaceOp":"append"}
{"type":"assistant/message","seq":53,"time":0,"data":{"turn":1,"step":5,"message":{"role":"assistant","content":[{"type":"tool-call","id":"advanced-unmount","name":"cordis_unmount","arguments":"{\"id\": \"dyn-1\"}"}],"source":{"kind":"model","provider":"deepseek","model":"smoke-model"},"id":"{{messageId}}"},"usage":{"inputTokens":3,"outputTokens":3}},"sourceEventSeqs":[48,49,50,51,52],"surfaceOp":"append"}
{"type":"tool/call","seq":54,"time":0,"data":{"turn":1,"step":5,"callId":"advanced-unmount","name":"cordis_unmount","arguments":"{\"id\": \"dyn-1\"}"}}
{"type":"tool/result","seq":55,"time":0,"data":{"turn":1,"step":5,"callId":"advanced-unmount","content":[{"type":"text","text":"Temporary Plugin dyn-1 was unmounted and removed."}],"isError":false},"sourceEventSeqs":[54],"surfaceOp":"append"}
{"type":"tool/result","seq":55,"time":0,"data":{"turn":1,"step":5,"message":{"source":{"kind":"tool","callId":"advanced-unmount"},"content":[{"type":"tool-result","toolCallId":"advanced-unmount","content":[{"type":"text","text":"Temporary Plugin dyn-1 was unmounted and removed."}],"isError":false}],"role":"user","id":"{{messageId}}"}},"sourceEventSeqs":[54],"surfaceOp":"append"}
{"type":"step/end","seq":56,"time":0,"data":{"turn":1,"step":5}}
{"type":"step/start","seq":57,"time":0,"data":{"turn":1,"step":6}}
{"type":"request/header","seq":58,"time":0,"data":{"header":{"config":{"provider":"deepseek","model":"smoke-model","reasoningEffort":"high"},"system":"{{system}}","tools":["bash","cordis_inspect","cordis_mount","cordis_unmount","run_code","skill","subagent","task_kill","task_list","task_output","workflow"],"messagePrefix":["{{messagePrefix}}"]},"reason":"change"}}
{"type":"request/header","seq":58,"time":0,"data":{"header":{"config":{"provider":"deepseek","model":"smoke-model","reasoningEffort":"high"},"system":"{{system}}","tools":["cordis_inspect","cordis_mount","cordis_unmount","run_code","subagent","task_kill","task_list","task_output","workflow"]},"reason":"change"}}
{"type":"assistant/chunk","seq":59,"time":0,"data":{"turn":1,"step":6,"chunk":{"type":"block-start","index":0,"blockType":"text"}}}
{"type":"assistant/chunk","seq":60,"time":0,"data":{"turn":1,"step":6,"chunk":{"type":"text-delta","index":0,"text":"ADVANCED_EXECUTABLE_OK"}}}
{"type":"assistant/chunk","seq":61,"time":0,"data":{"turn":1,"step":6,"chunk":{"type":"block-end","index":0,"block":{"type":"text","text":"ADVANCED_EXECUTABLE_OK"}}}}
{"type":"assistant/chunk","seq":62,"time":0,"data":{"turn":1,"step":6,"chunk":{"type":"usage","usage":{"inputTokens":3,"outputTokens":3}}}}
{"type":"assistant/chunk","seq":63,"time":0,"data":{"turn":1,"step":6,"chunk":{"type":"finish","reason":{"kind":"stop"}}}}
{"type":"assistant/message","seq":64,"time":0,"data":{"turn":1,"step":6,"content":[{"type":"text","text":"ADVANCED_EXECUTABLE_OK"}],"provenance":{"provider":"deepseek","model":"smoke-model"},"usage":{"inputTokens":3,"outputTokens":3}},"sourceEventSeqs":[59,60,61,62,63],"surfaceOp":"append"}
{"type":"assistant/message","seq":64,"time":0,"data":{"turn":1,"step":6,"message":{"role":"assistant","content":[{"type":"text","text":"ADVANCED_EXECUTABLE_OK"}],"source":{"kind":"model","provider":"deepseek","model":"smoke-model"},"id":"{{messageId}}"},"usage":{"inputTokens":3,"outputTokens":3}},"sourceEventSeqs":[59,60,61,62,63],"surfaceOp":"append"}
{"type":"step/end","seq":65,"time":0,"data":{"turn":1,"step":6}}
{"type":"turn/end","seq":66,"time":0,"data":{"turn":1,"reason":{"kind":"completed"}}}
@@ -16,11 +16,11 @@
},
{
"role": "user",
"content": "# Development guide\n\nEnglish | [中文](development.zh.md)\n\nThis onboarding guide helps project contributors get started with the local environment, daily workflow, and CI flow; see the Agent Notes for design rationale and technical trade-offs.\n\n## Prerequisites\n\n- Node.js supports 22.19+ and 24+. CI covers 22.19, 24, and 26; see the [Node engine floor Agent Note](../.agents/notes/implemented/process/2026-07-06-node-engine-floor.md).\n- Corepack-enabled pnpm. The repo pins `pnpm@11.7.0` in `package.json`; run `corepack enable` if `pnpm --version` does not resolve through Corepack.\n- Git 2.26 or newer; hook setup enables Git's worktree-specific configuration extension.\n- Optional: a DeepSeek API key for the TUI, headless, and ACP automation demos and real-API e2e tests.\n\n## First-time setup\n\nInstall dependencies from the repo root:\n\n```sh\npnpm install\n```\n\nThe install also runs the root `postinstall` script, which installs lefthook from the repo dev dependency through `scripts/install-lefthook.mjs`. With `CI=true` or `GITHUB_ACTIONS=true`, the wrapper returns before Git discovery because automated jobs do not consume contributor hooks. Otherwise, it requires Git 2.26 or newer and gives the current worktree an explicit hook directory under its own Git directory; linked worktrees therefore use their own lefthook binary and configuration instead of rewriting common hooks. The first install enables Git's worktree-specific configuration extension and repository format 1; see the [worktree-local hooks Agent Note](../.agents/notes/implemented/process/2026-07-27-worktree-local-lefthook.md).\n\nIf hooks are missing because dependencies were restored from cache or `postinstall` was skipped, install them manually:\n\n```sh\nnode scripts/install-lefthook.mjs\n```\n\nThe wrapper refuses user-owned `core.hooksPath` values. An inherited system, global, or common-repository path requires `DSH_LEFTHOOK_ALLOW_HOOKS_PATH_OVERRIDE=1`. When Git seeds a new worktree with another registered worktree's marker-backed hook path, the wrapper replaces that copied value with the new worktree's own path; command-scoped and other worktree-scoped paths must be integrated or removed explicitly.\n\nBefore enabling worktree config, migrate direct `extensions.*` in a format-0 common config, direct `core.worktree` or `core.bare=true`, and any non-empty dormant `config.worktree`. The common config and every worktree config must be regular files, while the owned hook directory may contain only unaliased regular files.\n\nAfter moving a checkout, rerun the wrapper to relocate its owned path and regenerate hooks. For a stale or invalid installer lock, first confirm no installer is running, then remove the reported lock and retry. If installation and hook-path rollback both fail, inspect the reported worktree config before retrying. The [worktree-local hooks Agent Note](../.agents/notes/implemented/process/2026-07-27-worktree-local-lefthook.md) owns the full safety contract.\n\nRun typecheck once after a fresh clone:\n\n```sh\npnpm run typecheck\n```\n\nThat first typecheck runs the whole-repo `tsc -b` graph: it emits every package/vendor `lib/types` and checks examples, tests, and scripts through the two no-emit aggregates described below.\n\n## TypeScript project layout\n\nThe repository's TypeScript configuration has exactly three roles; every tsconfig file plays one of them.\n\n| File | Role | Forms a program? |\n|---|---|---|\n| `tsconfig.json` | Solution root: `extends` base, `files: []`, references to the two aggregates. The whole-repo `tsc -b tsconfig.json` graph, the tsserver discovery entry, and — through the inherited `paths` — the resolution config for tsx running `examples/` and `scripts/` (their nearest tsconfig is this file). | No |\n| `tsconfig.host.json` | Host aggregate: host-side packages (via references), examples, tests, scripts, website. Excludes `packages/client`. | Yes |\n| `tsconfig.client.json` | Client aggregate: `packages/client/*` packages and their tests, `apps/web`. | Yes |\n| `tsconfig.base.json` | Shared compilerOptions and the source `paths` map. Also the resolution facade the vitest configs point vite-tsconfig-paths at: it has no `include`, so its `paths` apply to every importer. | No |\n| `tsconfig.base.client.json` | Browser compiler shape (`jsx`, DOM libs, `types: []`) extended by the client aggregate and every `packages/client/*` package. | No |\n\nHost and client stay two aggregate programs because both sides declaration-merge the cordis `Context` interface under the same keys with different services; one program seeing both merges reports a collision. The collision exists only inside a `ts.Program` — module resolution never triggers it — which is why the solution may reference both aggregates and one paths facade may span both sides. Two disciplines follow:\n\n- `tsconfig.base.json` never gains `include` or `files`: they would leak into every extending package Line truncated
"content": "# Development guide\n\nEnglish | [中文](development.zh.md)\n\nThis onboarding guide helps project contributors get started with the local environment, daily workflow, and CI flow; see the Agent Notes for design rationale and technical trade-offs.\n\n## Prerequisites\n\n- Node.js supports 22.19+ and 24+. CI covers 22.19, 24, and 26; see the [Node engine floor Agent Note](../.agents/notes/implemented/process/2026-07-06-node-engine-floor.md).\n- Corepack-enabled pnpm. The repo pins `pnpm@11.7.0` in `package.json`; run `corepack enable` if `pnpm --version` does not resolve through Corepack.\n- Git 2.26 or newer; hook setup enables Git's worktree-specific configuration extension.\n- Optional: a DeepSeek API key for the TUI, headless, and ACP automation demos and real-API e2e tests.\n\n## First-time setup\n\nInstall dependencies from the repo root:\n\n```sh\npnpm install\n```\n\nThe install also runs the root `postinstall` script, which installs lefthook from the repo dev dependency through `scripts/install-lefthook.mjs`. With `CI=true` or `GITHUB_ACTIONS=true`, the wrapper returns before Git discovery because automated jobs do not consume contributor hooks. Otherwise, it requires Git 2.26 or newer and gives the current worktree an explicit hook directory under its own Git directory; linked worktrees therefore use their own lefthook binary and configuration instead of rewriting common hooks. The first install enables Git's worktree-specific configuration extension and repository format 1; see the [worktree-local hooks Agent Note](../.agents/notes/implemented/process/2026-07-27-worktree-local-lefthook.md).\n\nIf hooks are missing because dependencies were restored from cache or `postinstall` was skipped, install them manually:\n\n```sh\nnode scripts/install-lefthook.mjs\n```\n\nThe wrapper refuses user-owned `core.hooksPath` values. An inherited system, global, or common-repository path requires `DSH_LEFTHOOK_ALLOW_HOOKS_PATH_OVERRIDE=1`. When Git seeds a new worktree with another registered worktree's marker-backed hook path, the wrapper replaces that copied value with the new worktree's own path; command-scoped and other worktree-scoped paths must be integrated or removed explicitly.\n\nBefore enabling worktree config, migrate direct `extensions.*` in a format-0 common config, direct `core.worktree` or `core.bare=true`, and any non-empty dormant `config.worktree`. The common config and every worktree config must be regular files, while the owned hook directory may contain only unaliased regular files.\n\nAfter moving a checkout, rerun the wrapper to relocate its owned path and regenerate hooks. For a stale or invalid installer lock, first confirm no installer is running, then remove the reported lock and retry. If installation and hook-path rollback both fail, inspect the reported worktree config before retrying. The [worktree-local hooks Agent Note](../.agents/notes/implemented/process/2026-07-27-worktree-local-lefthook.md) owns the full safety contract.\n\nRun typecheck once after a fresh clone:\n\n```sh\npnpm run typecheck\n```\n\nThat first typecheck runs the whole-repo `tsc -b` graph: it emits every package/vendor `lib/types` and checks examples, tests, and scripts through the two no-emit aggregates described below.\n\n## TypeScript project layout\n\nThe repository's TypeScript configuration has exactly three roles; every tsconfig file plays one of them.\n\n| File | Role | Forms a program? |\n|---|---|---|\n| `tsconfig.json` | Solution root: `extends` base, `files: []`, references to the two aggregates. The whole-repo `tsc -b tsconfig.json` graph, the tsserver discovery entry, and — through the inherited `paths` — the resolution config for tsx running `examples/` and `scripts/` (their nearest tsconfig is this file). | No |\n| `tsconfig.host.json` | Host aggregate: host-side packages (via references), examples, tests, scripts, website. Excludes `packages/client`. | Yes |\n| `tsconfig.client.json` | Client aggregate: `packages/client/*` packages and their tests, `apps/web`. | Yes |\n| `tsconfig.base.json` | Shared compilerOptions and the source `paths` map. Also the resolution facade the vitest configs point vite-tsconfig-paths at: it has no `include`, so its `paths` apply to every importer. | No |\n| `tsconfig.base.client.json` | Browser compiler shape (`jsx`, DOM libs, `types: []`) extended by the client aggregate and every `packages/client/*` package. | No |\n\nHost and client stay two aggregate programs because both sides declaration-merge the cordis `Context` interface under the same keys with different services; one program seeing both merges reports a collision. The collision exists only inside a `ts.Program` — module resolution never triggers it — which is why the solution may reference both aggregates and one paths facade may span both sides. Two disciplines follow:\n\n- `tsconfig.base.json` never gains `include` or `files`: they would leak into every extending package Line truncated
},
{
"role": "assistant",
"content": "# 开发指南\n\n[English](development.md) | 中文\n\n本指南覆盖参与 DeepSeek Harness 开发所需的本地环境搭建、日常工作流与 CI 流程;设计动机与技术权衡请查阅相应 Agent Note。\n\n## 前置条件\n\n- Node.js 支持 22.19+ 与 24+。CI 覆盖 22.19、24 和 26;见 [Node 引擎下限 Agent Note](../.agents/notes/implemented/process/2026-07-06-node-engine-floor.md)。\n- 启用了 Corepack 的 pnpm。仓库在 `package.json` 中固定使用 `pnpm@11.7.0`;如果 `pnpm --version` 无法通过 Corepack 解析,请先运行 `corepack enable`。\n- Git 2.26 或更高版本;钩子设置会启用 Git 的 worktree 专属配置扩展。\n- 可选:一个 DeepSeek API key,用于 TUI、headless 和 ACP(Agent Client Protocol)自动化 agent(智能体)演示以及真实 API 的 e2e 测试。\n\n## 首次搭建\n\n在仓库根目录安装依赖:\n\n```sh\npnpm install\n```\n\n安装过程同时会运行根目录的 `postinstall` 脚本,该脚本通过 `scripts/install-lefthook.mjs` 从仓库 dev 依赖安装 lefthook。当 `CI=true` 或 `GITHUB_ACTIONS=true` 时,该脚本会在探测 Git 前返回,因为自动化任务不会使用贡献者钩子。否则,包装脚本要求使用 Git 2.26 或更高版本,并会为当前 worktree 在其自身的 Git 目录下设置显式钩子目录;因此,关联 worktree 会使用各自的 lefthook 二进制文件和配置,而不会改写共用钩子。首次安装会启用 Git 的 worktree 专属配置扩展和仓库格式 1;见 [worktree 本地钩子 Agent Note](../.agents/notes/implemented/process/2026-07-27-worktree-local-lefthook.md)。\n\n如果依赖是从缓存恢复或 `postinstall` 被跳过而导致缺少钩子,请手动安装:\n\n```sh\nnode scripts/install-lefthook.mjs\n```\n\n包装层会拒绝用户自有的 `core.hooksPath` 值。继承自系统、全局或共用仓库配置的路径必须设置 `DSH_LEFTHOOK_ALLOW_HOOKS_PATH_OVERRIDE=1`。当 Git 使用另一个已注册 worktree 中由所有权标记佐证的钩子路径初始化新 worktree 时,包装层会将这个复制值替换为新 worktree 自有的路径;命令作用域和其他 worktree 作用域的路径必须显式集成或移除。\n\n启用 worktree 配置之前,请迁移格式 0 共用配置中直接设置的 `extensions.*`,并迁移直接设置的 `core.worktree` 或 `core.bare=true`,以及任何非空且尚未生效的 `config.worktree`。共用配置和每个 worktree 配置都必须是常规文件,而自有钩子目录只能包含不带别名的常规文件。\n\n检出目录移动后,请重新运行包装层,使其重新定位自有路径并重新生成钩子。对于陈旧或无效的安装程序锁,请先确认没有安装程序正在运行,再移除报告的锁并重试。若安装和钩子路径回滚都失败,请在重试前检查报告的 worktree 配置。完整安全契约由 [worktree 本地钩子 Agent Note](../.agents/notes/implemented/process/2026-07-27-worktree-local-lefthook.md) 统一定义。\n\n新克隆后请先运行一次类型检查:\n\n```sh\npnpm run typecheck\n```\n\n首次类型检查会执行全仓 `tsc -b tsconfig.json` 图:发射每个 package/vendor 的 `lib/types`,并通过下述两个 no-emit 聚合检查示例、测试和脚本。\n\n## TypeScript 项目布局\n\n仓库的 TypeScript 配置只有三种角色;每个 tsconfig 文件恰好扮演其中一种。\n\n| 文件 | 角色 | 是否构成 program? |\n|---|---|---|\n| `tsconfig.json` | solution 根:`extends` base、`files: []`、引用两个聚合。全仓 `tsc -b tsconfig.json` 图、tsserver 发现入口,并经继承的 `paths` 充当 tsx 运行 `examples/` 与 `scripts/` 时的解析配置(它们最近的 tsconfig 就是此文件)。 | 否 |\n| `tsconfig.host.json` | host 聚合:host 侧各包(经 references)、示例、测试、脚本、website。排除 `packages/client`。 | 是 |\n| `tsconfig.client.json` | client 聚合:`packages/client/*` 各包及其测试、`apps/web`。 | 是 |\n| `tsconfig.base.json` | 共享 compilerOptions 与源码 `paths` 映射。同时是各 vitest 配置让 vite-tsconfig-paths 指向的解析门面:它没有 `include`,因此其 `paths` 适用于任何 importer。 | 否 |\n| `tsconfig.base.client.json` | 浏览器编译形状(`jsx`、DOM lib、`types: []`),由 client 聚合和每个 `packages/client/*` 包 extends。 | 否 |\n\nhost 与 client 保持两个聚合 program,是因为两侧在相同键下以不同服务对 cordis `Context` 接口做声明合并;单一 program 同时看到两份合并会报冲突。这种冲突只存在于 `ts.Program` 内部——模块解析永远不会触发它——所以 solution 可以同时引用两个聚合,一个 paths 门面也可以横跨两侧。由此推出两条纪律:\n\n- `tsconfig.base.json` 永不添加 `include` 或 `files`:它们会泄漏进每个 extends 它的包项Line truncated
"content": "# 开发指南\n\n[English](development.md) | 中文\n\n本指南覆盖参与 DeepSeek Harness 开发所需的本地环境搭建、日常工作流与 CI 流程;设计动机与技术权衡请查阅相应 Agent Note。\n\n## 前置条件\n\n- Node.js 支持 22.19+ 与 24+。CI 覆盖 22.19、24 和 26;见 [Node 引擎下限 Agent Note](../.agents/notes/implemented/process/2026-07-06-node-engine-floor.md)。\n- 启用了 Corepack 的 pnpm。仓库在 `package.json` 中固定使用 `pnpm@11.7.0`;如果 `pnpm --version` 无法通过 Corepack 解析,请先运行 `corepack enable`。\n- Git 2.26 或更高版本;钩子设置会启用 Git 的 worktree 专属配置扩展。\n- 可选:一个 DeepSeek API key,用于 TUI、headless 和 ACP(Agent Client Protocol)自动化 agent(智能体)演示以及真实 API 的 e2e 测试。\n\n## 首次搭建\n\n在仓库根目录安装依赖:\n\n```sh\npnpm install\n```\n\n安装过程同时会运行根目录的 `postinstall` 脚本,该脚本通过 `scripts/install-lefthook.mjs` 从仓库 dev 依赖安装 lefthook。当 `CI=true` 或 `GITHUB_ACTIONS=true` 时,该脚本会在探测 Git 前返回,因为自动化任务不会使用贡献者钩子。否则,包装脚本要求使用 Git 2.26 或更高版本,并会为当前 worktree 在其自身的 Git 目录下设置显式钩子目录;因此,关联 worktree 会使用各自的 lefthook 二进制文件和配置,而不会改写共用钩子。首次安装会启用 Git 的 worktree 专属配置扩展和仓库格式 1;见 [worktree 本地钩子 Agent Note](../.agents/notes/implemented/process/2026-07-27-worktree-local-lefthook.md)。\n\n如果依赖是从缓存恢复或 `postinstall` 被跳过而导致缺少钩子,请手动安装:\n\n```sh\nnode scripts/install-lefthook.mjs\n```\n\n包装层会拒绝用户自有的 `core.hooksPath` 值。继承自系统、全局或共用仓库配置的路径必须设置 `DSH_LEFTHOOK_ALLOW_HOOKS_PATH_OVERRIDE=1`。当 Git 使用另一个已注册 worktree 中由所有权标记佐证的钩子路径初始化新 worktree 时,包装层会将这个复制值替换为新 worktree 自有的路径;命令作用域和其他 worktree 作用域的路径必须显式集成或移除。\n\n启用 worktree 配置之前,请迁移格式 0 共用配置中直接设置的 `extensions.*`,并迁移直接设置的 `core.worktree` 或 `core.bare=true`,以及任何非空且尚未生效的 `config.worktree`。共用配置和每个 worktree 配置都必须是常规文件,而自有钩子目录只能包含不带别名的常规文件。\n\n检出目录移动后,请重新运行包装层,使其重新定位自有路径并重新生成钩子。对于陈旧或无效的安装程序锁,请先确认没有安装程序正在运行,再移除报告的锁并重试。若安装和钩子路径回滚都失败,请在重试前检查报告的 worktree 配置。完整安全契约由 [worktree 本地钩子 Agent Note](../.agents/notes/implemented/process/2026-07-27-worktree-local-lefthook.md) 统一定义。\n\n新克隆后请先运行一次类型检查:\n\n```sh\npnpm run typecheck\n```\n\n首次类型检查会执行全仓 `tsc -b tsconfig.json` 图:发射每个 package/vendor 的 `lib/types`,并通过下述两个 no-emit 聚合检查示例、测试和脚本。\n\n## TypeScript 项目布局\n\n仓库的 TypeScript 配置只有三种角色;每个 tsconfig 文件恰好扮演其中一种。\n\n| 文件 | 角色 | 是否构成 program? |\n|---|---|---|\n| `tsconfig.json` | solution 根:`extends` base、`files: []`、引用两个聚合。全仓 `tsc -b tsconfig.json` 图、tsserver 发现入口,并经继承的 `paths` 充当 tsx 运行 `examples/` 与 `scripts/` 时的解析配置(它们最近的 tsconfig 就是此文件)。 | 否 |\n| `tsconfig.host.json` | host 聚合:host 侧各包(经 references)、示例、测试、脚本、website。排除 `packages/client`。 | 是 |\n| `tsconfig.client.json` | client 聚合:`packages/client/*` 各包及其测试、`apps/web`。 | 是 |\n| `tsconfig.base.json` | 共享 compilerOptions 与源码 `paths` 映射。同时是各 vitest 配置让 vite-tsconfig-paths 指向的解析门面:它没有 `include`,因此其 `paths` 适用于任何 importer。 | 否 |\n| `tsconfig.base.client.json` | 浏览器编译形状(`jsx`、DOM lib、`types: []`),由 client 聚合和每个 `packages/client/*` 包 extends。 | 否 |\n\nhost 与 client 保持两个聚合 program,是因为两侧在相同键下以不同服务对 cordis `Context` 接口做声明合并;单一 program 同时看到两份合并会报冲突。这种冲突只存在于 `ts.Program` 内部——模块解析永远不会触发它——所以 solution 可以同时引用两个聚合,一个 paths 门面也可以横跨两侧。由此推出两条纪律:\n\n- `tsconfig.base.json` 永不添加 `include` 或 `files`:它们会泄漏进每个 extends 它的包项Line truncated
},
{
"role": "user",
+2 -1
View File
@@ -61,7 +61,8 @@ describe('global test invariant host', () => {
return () => {}
})
const fakeContext = { invariants: { register } } as unknown as Context
for (const [rawPath, companion] of Object.entries(testInvariantCompanions)) {
for (const [rawPath, load] of Object.entries(testInvariantCompanions)) {
const companion = await load()
const path = rawPath.replace(/^\.\.\//, '')
expect(companion.default, path).toBeUndefined()
const unwrapped = loader.unwrapExports(companion) as typeof companion
+36 -30
View File
@@ -12,8 +12,8 @@ import InvariantService from '@deepseek-ai/dsh-invariants'
declare global {
interface ImportMeta {
/** Eager Vite module-glob expansion used by the Vitest setup file. */
glob<TModule>(pattern: string, options: { eager: true }): Record<string, TModule>
/** Lazy Vite module-glob expansion used by the Vitest setup file. */
glob<TModule>(pattern: string): Record<string, () => Promise<TModule>>
}
}
@@ -25,9 +25,15 @@ export interface TestInvariantCompanion {
apply(ctx: Context): Promise<() => void>
}
/** Every package companion, discovered eagerly so coverage observes each registration. */
export const testInvariantCompanions: Readonly<Record<string, TestInvariantCompanion>> =
import.meta.glob<TestInvariantCompanion>('../packages/*/*/src/invariant.ts', { eager: true })
/**
* Every package companion as a lazy loader keyed by glob path. Ordinary tests
* load only their owner's module; the exhaustive topology test loads and
* executes all of them, so aggregated coverage still observes every
* registration while per-file setup stops importing 168 companions and their
* transitive package sources.
*/
export const testInvariantCompanions: Readonly<Record<string, () => Promise<TestInvariantCompanion>>> =
import.meta.glob<TestInvariantCompanion>('../packages/*/*/src/invariant.ts')
/** Manual-topology suites whose names cannot follow the focused invariant convention. */
const MANUAL_INVARIANT_TEST_EXCEPTIONS = [
@@ -36,7 +42,6 @@ const MANUAL_INVARIANT_TEST_EXCEPTIONS = [
] as const
interface InvariantHost {
readonly fibers: readonly PluginFiber[]
readonly byCallback: ReadonlyMap<unknown, PluginFiber>
readonly ready: Promise<void>
}
@@ -102,39 +107,40 @@ export function testInvariantCompanionPaths(testPath: string): string[] {
}
function startInvariantHost(root: Context): InvariantHost {
const fibers: PluginFiber[] = []
const byCallback = new Map<unknown, PluginFiber>()
const mount = (plugin: Plugin, config?: unknown): void => {
const mount = (plugin: Plugin, config?: unknown): PluginFiber => {
const fiber = originalPlugin.call(root.registry, plugin, config)
const callback = root.registry.resolve(plugin)
if (callback === undefined) throw new Error('test invariants: companion is not a valid Cordis plugin')
fibers.push(fiber)
byCallback.set(callback, fiber)
return fiber
}
mount(InvariantService, { enabled: true })
// The service mounts synchronously so the intercepted registration that
// started this host immediately finds its own fiber in byCallback.
// Companions load and mount inside the ready chain (after the service is
// active, so their startup is directly joinable); every joined root plugin
// awaits ready, so none starts ahead of its package checks. Tests plugging
// a companion directly must await an earlier root plugin first — the
// duplicate-mount failure otherwise is loud (owner name already reserved).
const serviceFiber = mount(InvariantService, { enabled: true })
const testPath = expect.getState().testPath ?? ''
const companionPaths = testInvariantCompanionPaths(testPath)
for (const path of companionPaths) {
const companion = testInvariantCompanions[path]
if (companion === undefined) {
throw new Error(`test invariants: selected companion vanished at ${path}`)
}
if (!companion.inject.includes('invariants')) {
throw new Error(`test invariants: ${path} must inject the invariant service`)
}
mount(companion)
}
const [serviceFiber, ...companionFibers] = fibers
if (serviceFiber === undefined) throw new Error('test invariants: service fiber was not mounted')
// A companion is initially PENDING on the invariant service, and Cordis
// Fiber.await() only joins work already in flight. Wait for the service to
// activate its dependants before joining their startup and failures.
const ready = serviceFiber.await()
.then(() => Promise.all(companionFibers.map(fiber => fiber.await())))
.then(() => undefined)
const host = { fibers, byCallback, ready }
const ready = serviceFiber.await().then(async () => {
const companionFibers = await Promise.all(companionPaths.map(async (path) => {
const load = testInvariantCompanions[path]
if (load === undefined) {
throw new Error(`test invariants: selected companion vanished at ${path}`)
}
const companion = await load()
if (!companion.inject.includes('invariants')) {
throw new Error(`test invariants: ${path} must inject the invariant service`)
}
return mount(companion)
}))
await Promise.all(companionFibers.map(fiber => fiber.await()))
})
const host = { byCallback, ready }
hosts.set(root, host)
return host
}
+70
View File
@@ -31,6 +31,11 @@
"symbol": "FinishReasonMap",
"source": "packages/llm/llm/src/types.ts"
},
{
"doc": "docs/core-data-structures/core.md",
"symbol": "AdapterRegistrationHandle",
"source": "packages/llm/llm/src/index.ts"
},
{
"doc": "docs/core-data-structures/core.md",
"symbol": "LlmProviderInfo",
@@ -96,6 +101,21 @@
"symbol": "InboxPlacement",
"source": "packages/core/agent/src/types.ts"
},
{
"doc": "docs/core-data-structures/core.md",
"symbol": "InboxItem",
"source": "packages/core/agent/src/types.ts"
},
{
"doc": "docs/core-data-structures/core.md",
"symbol": "InboxAction",
"source": "packages/core/agent/src/types.ts"
},
{
"doc": "docs/core-data-structures/core.md",
"symbol": "InboxActionResult",
"source": "packages/core/agent/src/types.ts"
},
{
"doc": "docs/core-data-structures/core.md",
"symbol": "SendOptions",
@@ -679,6 +699,11 @@
"symbol": "AskUserQuestionOption",
"source": "packages/ui/user-interaction/src/types.ts"
},
{
"doc": "docs/core-data-structures/user-interaction.md",
"symbol": "AskUserQuestionIntent",
"source": "packages/ui/user-interaction/src/types.ts"
},
{
"doc": "docs/core-data-structures/user-interaction.md",
"symbol": "AskUserQuestionItem",
@@ -1318,6 +1343,51 @@
"doc": "docs/core-data-structures/subprocess.md",
"symbol": "SubprocessCollectedOutputs",
"source": "packages/subprocess/subprocess/src/types.ts"
},
{
"doc": "docs/core-data-structures/settings.md",
"symbol": "SettingsNamespace",
"source": "packages/settings/settings/src/index.ts"
},
{
"doc": "docs/core-data-structures/settings.md",
"symbol": "SettingsRegisterOptions",
"source": "packages/settings/settings/src/index.ts"
},
{
"doc": "docs/core-data-structures/settings.md",
"symbol": "SettingsApplies",
"source": "packages/settings/settings/src/index.ts"
},
{
"doc": "docs/core-data-structures/settings.md",
"symbol": "SettingsScope",
"source": "packages/settings/settings/src/index.ts"
},
{
"doc": "docs/core-data-structures/settings.md",
"symbol": "SettingsDescriptor",
"source": "packages/settings/settings/src/index.ts"
},
{
"doc": "docs/core-data-structures/settings.md",
"symbol": "SettingsUpdateSource",
"source": "packages/settings/settings/src/index.ts"
},
{
"doc": "docs/core-data-structures/credentials.md",
"symbol": "CredentialRef",
"source": "packages/credentials/credentials/src/index.ts"
},
{
"doc": "docs/core-data-structures/credentials.md",
"symbol": "ResolvedCredential",
"source": "packages/credentials/credentials/src/index.ts"
},
{
"doc": "docs/core-data-structures/credentials.md",
"symbol": "CredentialInfo",
"source": "packages/credentials/credentials/src/index.ts"
}
]
}
+87 -5
View File
@@ -29,7 +29,24 @@ interface PluginReference {
}
const root = resolve(import.meta.dirname, '..')
// These example files are overlays consumed by the built dsh app, so their bare
// specifiers resolve from apps/cli rather than the examples workspace.
const appOverlayFiles = new Set(['examples/web-cordis/cordis.yml'])
const metadataFields = ['id', 'name', 'group', 'disabled', 'inject', 'intercept', 'isolate'] as const
/** The adaptive directory-picker chooser package (mounts a backend row at boot). */
const CHOOSER_PACKAGE = '@deepseek-ai/dsh-host-directory-picker-auto'
/**
* The backends the chooser mounts by runtime string (mirror of its exported
* `BACKEND_PACKAGES`), invisible to yml-row scanning: a composition mounting
* the chooser must resolve both, or keyless Linux CI (which only ever
* resolves `browse`) hides a dropped `-native` dependency until a macOS boot.
*/
const CHOOSER_BACKEND_PACKAGES = [
'@deepseek-ai/dsh-host-directory-picker-native',
'@deepseek-ai/dsh-host-directory-picker-browse',
]
const jsExprType = new yaml.Type('tag:yaml.org,2002:js', {
kind: 'scalar',
resolve: data => typeof data === 'string',
@@ -57,6 +74,7 @@ for (const file of files) {
errors.push(...validateExampleResolution())
errors.push(...validateAppResolution())
errors.push(...validateSourcePlaneResolution())
if (errors.length > 0) {
console.error('verify-cordis-config: invalid Loader metadata or plugin package resolution:')
@@ -78,6 +96,11 @@ function validateEntry(value: unknown, file: string, path: string): void {
validateEntry(value.config[index], file, `${path}.config[${index}]`)
}
}
if (isUnknownArray(value.insert)) {
for (let index = 0; index < value.insert.length; index++) {
validateEntry(value.insert[index], file, `${path}.insert[${index}]`)
}
}
if (value.name !== '@cordisjs/plugin-include') return
const config = value.config
if (!isRecord(config) || !isUnknownArray(config.patches)) return
@@ -104,7 +127,7 @@ function validateExampleResolution(): string[] {
const dependencies = exampleManifest.dependencies ?? {}
const localPackages = localPackageDirectories()
const rootReferences = rootProjectReferences()
const exampleReferences = pluginReferences.filter(reference => reference.file.startsWith('examples/'))
const exampleReferences = pluginReferences.filter(reference => reference.file.startsWith('examples/') && !appOverlayFiles.has(reference.file))
violations.push(...missingPluginDependencies(exampleReferences, dependencies, 'examples/package.json'))
const requiredPackages = new Set(exampleReferences.map(reference => packageNameFromSpecifier(reference.name)))
@@ -124,22 +147,81 @@ function validateExampleResolution(): string[] {
function validateAppResolution(): string[] {
const dependencies = readManifest('apps/cli/package.json').dependencies ?? {}
const references = pluginReferences.filter(reference => reference.file === 'apps/cli/cordis.yml')
const shipped = new Set(globSync('*.cordis.yml', { cwd: resolve(root, 'apps/cli/config') })
.map(file => `apps/cli/config/${file}`))
const references = pluginReferences.filter(reference => shipped.has(reference.file) || appOverlayFiles.has(reference.file))
return missingPluginDependencies(references, dependencies, 'apps/cli/package.json')
}
/**
* Every configured specifier of a local workspace package must resolve through
* the tsconfig `paths` facade to a `.ts`/`.tsx` source file. The `dsh` source
* launch (tsx) and vitest resolve in the source plane; without a `paths` match
* they fall back to package `exports`, which reach built `lib/` — present on a
* built dev tree, absent on a clean one — so a missing mapping boots locally
* yet breaks every clean checkout. Anything but a `.ts`/`.tsx` hit (a `.d.ts`
* or `.js` under built `lib/`) is that artifact-plane fallback, not source.
*/
function validateSourcePlaneResolution(): string[] {
const violations: string[] = []
const localPackages = localPackageDirectories()
const config = ts.readConfigFile(resolve(root, 'tsconfig.base.json'), path => ts.sys.readFile(path))
if (config.error !== undefined) {
throw new Error(ts.flattenDiagnosticMessageText(config.error.messageText, '\n'))
}
const { options, errors: optionErrors } = ts.convertCompilerOptionsFromJson(
(config.config as { compilerOptions?: unknown }).compilerOptions,
root,
'tsconfig.base.json',
)
if (optionErrors.length > 0) {
throw new Error(optionErrors.map(error => ts.flattenDiagnosticMessageText(error.messageText, '\n')).join('\n'))
}
// convertCompilerOptionsFromJson leaves `pathsBasePath` unset, so relative
// `paths` targets resolve against the host's current directory; anchor it to
// the repository root to keep the gate cwd-independent.
const host: ts.ModuleResolutionHost = {
fileExists: path => ts.sys.fileExists(path),
readFile: path => ts.sys.readFile(path),
directoryExists: path => ts.sys.directoryExists(path),
getCurrentDirectory: () => root,
}
const sourceExtensions = new Set<string>([ts.Extension.Ts, ts.Extension.Tsx])
const containingFile = resolve(root, 'scripts/verify-cordis-config.ts')
const locationsBySpecifier = new Map<string, Set<string>>()
for (const reference of pluginReferences) {
const packageName = packageNameFromSpecifier(reference.name)
if (packageName === undefined || !localPackages.has(packageName)) continue
const locations = locationsBySpecifier.get(reference.name) ?? new Set<string>()
locations.add(reference.file)
locationsBySpecifier.set(reference.name, locations)
}
for (const [specifier, locations] of locationsBySpecifier) {
const resolved = ts.resolveModuleName(specifier, containingFile, options, host).resolvedModule
if (resolved !== undefined && sourceExtensions.has(resolved.extension)) continue
violations.push(`${[...locations].join(', ')}: ${specifier} does not resolve to workspace source through tsconfig.base.json paths (add a mapping so the tsx source launch does not depend on built lib/)`)
}
return violations
}
function missingPluginDependencies(
references: readonly PluginReference[],
dependencies: Readonly<Record<string, string>>,
manifestPath: string,
): string[] {
const requiredPackages = new Map<string, Set<string>>()
const require = (packageName: string, file: string): void => {
const locations = requiredPackages.get(packageName) ?? new Set<string>()
locations.add(file)
requiredPackages.set(packageName, locations)
}
for (const reference of references) {
const packageName = packageNameFromSpecifier(reference.name)
if (packageName === undefined) continue
const locations = requiredPackages.get(packageName) ?? new Set<string>()
locations.add(reference.file)
requiredPackages.set(packageName, locations)
require(packageName, reference.file)
if (packageName === CHOOSER_PACKAGE) {
for (const backend of CHOOSER_BACKEND_PACKAGES) require(backend, reference.file)
}
}
return [...requiredPackages].flatMap(([packageName, locations]) => packageName in dependencies
? []
@@ -80,6 +80,7 @@ const SENTENCE_MODEL_EXPERIENCE: Readonly<Record<string, SentenceContract>> = {
'packages/hooks/hook-protocol': { kind: 'indirect', reason: 'Only the hook bridge plugins render decoded hook output to a model.' },
'packages/host/apiproxy': { kind: 'none', reason: 'The wire contract and fetch carriers move already-composed messages and register no model surface.' },
'packages/host/directory-picker': { kind: 'none', reason: 'The GUI-host picking seam registers no model surface.' },
'packages/host/directory-picker-auto': { kind: 'none', reason: 'The GUI-host picking chooser only mounts a backend row; registers no model surface.' },
'packages/host/directory-picker-browse': { kind: 'none', reason: 'The GUI-host picking backend registers no model surface.' },
'packages/host/directory-picker-native': { kind: 'none', reason: 'The GUI-host picking backend registers no model surface.' },
'packages/host/webserver': { kind: 'none', reason: 'The HTTP carrier bridges browser and API handler and registers no model surface.' },
@@ -101,6 +102,11 @@ const SENTENCE_MODEL_EXPERIENCE: Readonly<Record<string, SentenceContract>> = {
'packages/session-projection/session-projection-cache': { kind: 'none', reason: 'The persisted cache accelerates host-side cold reads of projection state and registers no model surface.' },
'packages/session-query/session-query': { kind: 'none', reason: 'The trusted query service exposes cloned records only to callers and registers no model surface.' },
'packages/session-query/session-query-sqlite': { kind: 'none', reason: 'The search backend returns hits only to callers and registers no model surface.' },
'packages/settings/settings': { kind: 'indirect', reason: 'The seam stores and resolves user settings; consumer plugins own any model surface a value feeds.' },
'packages/settings/settings-local': { kind: 'indirect', reason: 'The file provider stores and publishes namespace sections; consumers of ctx.settings own any model surface.' },
'packages/credentials/credentials': { kind: 'indirect', reason: 'The seam resolves credential references; the consuming adapter owns every model surface a value authorizes.' },
'packages/credentials/credentials-local': { kind: 'indirect', reason: 'The file/environment provider stores credential values; consumers of ctx.credentials own any model surface.' },
'packages/util/atomic-write': { kind: 'none', reason: 'Pure filesystem write primitive; registers no model surface.' },
'packages/telemetry/session-telemetry': { kind: 'none', reason: 'The seam observes the session stream and hands redacted copies outward; it registers no model surface.' },
'packages/telemetry/session-telemetry-otel': { kind: 'none', reason: 'The backend forwards seam records into the OTel SDK pipeline and registers no model surface.' },
'packages/skill/skill': { kind: 'indirect', reason: 'The provider registry delegates model rendering to dsh-tool-skill.' },