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:
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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}"
|
||||
|
||||
@@ -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
@@ -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) })
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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',
|
||||
|
||||
@@ -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
@@ -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 {
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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)
|
||||
@@ -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',
|
||||
|
||||
@@ -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
@@ -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',
|
||||
|
||||
@@ -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"}}}
|
||||
File diff suppressed because one or more lines are too long
@@ -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
@@ -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
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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.' },
|
||||
|
||||
Reference in New Issue
Block a user