Merge commit '8be1a8e63a48f357807d52070ea54db61912794e' into feat/schedule-clear-ended

# Conflicts:
#	packages/client/ui-schedule/src/client/index.ts
This commit is contained in:
Chinesezjc committed 2026-10-22 14:53:21 +08:00
commit a6f92bfd79
1355 files changed
+29552 -12045

No files matched your search

@@ -328,6 +328,24 @@ describe('package payload constraints', () => {
])
})
it('publishes tools entry points and shared runtime chunks', () => {
const dir = 'packages/core/tools'
const manifest = JSON.parse(readFileSync(new URL(`../${dir}/package.json`, import.meta.url), 'utf8')) as WorkspaceManifest['manifest']
expect(checkWorkspaceManifest({ dir, manifest })).toEqual([])
for (const omitted of ['lib/pipeline.js', 'lib/shared-*.js']) {
expect(checkWorkspaceManifest({ dir, manifest: {
...manifest, files: manifest.files!.filter(file => file !== omitted),
} })).toEqual([expect.stringContaining('package.json files must be')])
}
expect(checkWorkspaceManifest({ dir, manifest: {
...manifest,
exports: {
...manifest.exports,
'./pipeline': { types: './lib/types/pipeline.d.ts', default: './lib/index.js' },
},
} })).toContainEqual(expect.stringContaining('exports["./pipeline"] must expose'))
})
it('publishes the directory of each locale detail document after the locale resources', () => {
const dir = mkdtempSync(join(tmpdir(), 'dsh-detail-files-'))
onTestFinished(() => { rmSync(dir, { recursive: true, force: true }) })
+8 -1
View File
@@ -20,7 +20,8 @@ import {
import { hasTypertRemoteNavigation, isForbiddenPublicationFile } from './publication-payload.ts'
import type { DshBundleManifest } from '../packages/util/package-manifest/src/types.ts'
import { detailPath } from '../packages/util/plugin-detail/src/document.ts'
import { OPTIONAL_BUNDLES, bundlePatchFiles } from '../packages/boot/app-boot/src/profile.ts'
import { bundlePatchFiles } from '../packages/boot/app-boot/src/profile.ts'
import { OPTIONAL_BUNDLES } from '../packages/boot/official-bundles/src/bundles.ts'
import {
PACKAGE_ASSET_EXPORT,
PACKAGE_ASSET_EXPORT_TARGET,
@@ -184,6 +185,8 @@ export function readWorkspaceManifests(repositoryRoot: string): WorkspaceManifes
}
const packageFileExtras: Readonly<Record<string, readonly string[]>> = {
// Tools entry points share error classes and invocation stages through runtime chunks.
'@deepseek-ai/dsh-tools': ['lib/pipeline.js', 'lib/shared-*.js'],
// The embedded OAR implementation retains its upstream license.
'@deepseek-ai/dsh-experimental-subagent-oar': ['LICENSE-OAR'],
// CUA publishes its client API and attributed native source for local macOS builds.
@@ -577,6 +580,10 @@ export function checkWorkspaceManifest({ dir, manifest }: WorkspaceManifest): st
if (rootEntry?.default !== './lib/index.js') {
errors.push(`${label}: package.json exports["."].default must be "./lib/index.js"`)
}
if (manifest.name === '@deepseek-ai/dsh-tools'
&& !hasExportPair(manifest, './pipeline', './lib/types/pipeline.d.ts', './lib/pipeline.js')) {
errors.push(`${label}: exports["./pipeline"] must expose lib/types/pipeline.d.ts and lib/pipeline.js`)
}
const expectedFiles = expectedDshPackageFiles(manifest, detailDocumentFiles(join(root, dir)))
if (!sameStringList(manifest.files, expectedFiles)) {
errors.push(`${label}: package.json files must be ${JSON.stringify(expectedFiles)}`)
+6 -2
View File
@@ -25,7 +25,11 @@ interface CompatibilityJob {
}
const workflow = yaml.load(readFileSync(resolve(import.meta.dirname, '../.github/workflows/ci.yml'), 'utf8')) as {
jobs: { 'node-compat': CompatibilityJob; 'python-sdk': { 'runs-on': string } }
jobs: { 'node-compat': CompatibilityJob }
}
// Python keeps its own required verdict in ci-python.yml.
const pythonWorkflow = yaml.load(readFileSync(resolve(import.meta.dirname, '../.github/workflows/ci-python.yml'), 'utf8')) as {
jobs: { 'python-sdk': { 'runs-on': string } }
}
const job = workflow.jobs['node-compat']
const labels = ['self-hosted', 'linux', 'x64', 'vm-backup']
@@ -86,7 +90,7 @@ describe('Node compatibility self-hosted routing', () => {
expect(job.env.DSH_GATE_CONCURRENCY).toBe('${{ matrix.gate_concurrency }}')
expect(job.steps.map(step => step.run)).toContain('pnpm run check:node-compat')
expect(job.steps.map(step => step.run)).toContain('pnpm exec vitest run packages/boot/app-boot/tests/loader-shape.compat.spec.ts')
expect(workflow.jobs['python-sdk']['runs-on']).toBe('ubuntu-latest')
expect(pythonWorkflow.jobs['python-sdk']['runs-on']).toBe('ubuntu-latest')
})
it('isolates version installs and enables hosted package caching only on hosted runners', () => {
+120 -46
View File
@@ -14,12 +14,18 @@ const runnerPrivatePnpmDestination = /^\$\{\{ runner\.temp \}\}\/setup-pnpm-\$\{
const nativeWindowsPnpmDestination = '${{ runner.temp }}/setup-pnpm-js-${{ github.run_id }}-${{ github.run_attempt }}-${{ github.job }}'
describe('CI workflow', () => {
it('keeps at most ten worker jobs active while runtime targets follow the SDK suite', () => {
it('keeps at most ten worker jobs active across PR workflows while Python runtime targets follow the SDK suite', () => {
const ci = loadWorkflow('.github/workflows/ci.yml')
const ciWindows = loadWorkflow('.github/workflows/ci-windows.yml')
const ciPython = loadWorkflow('.github/workflows/ci-python.yml')
const compatibility = workflowJob(ci, 'node-compat')
const runtime = workflowJob(ci, 'python-runtime')
if (!isRecord(ci.jobs) || !isRecord(compatibility.strategy) || !isRecord(runtime.with)) {
throw new TypeError('CI must define jobs, compatibility scheduling, and runtime inputs')
const runtime = workflowJob(ciPython, 'python-runtime')
if (!isRecord(ci.jobs)
|| !isRecord(ciWindows.jobs)
|| !isRecord(ciPython.jobs)
|| !isRecord(compatibility.strategy)
|| !isRecord(runtime.with)) {
throw new TypeError('CI, CI Windows, and CI Python must define jobs, compatibility scheduling, and runtime inputs')
}
const compatibilityLimit = compatibility.strategy['max-parallel']
const targets = runtime.with.targets
@@ -34,17 +40,31 @@ describe('CI workflow', () => {
})
expect(workflowJob(ci, 'all-checks-passed').needs).toContain('node-compat')
const ordinaryJobs = Object.entries(ci.jobs)
.filter(([name]) => !['node-compat', 'python-runtime', 'all-checks-passed'].includes(name))
.filter(([name]) => !['node-compat', 'all-checks-passed'].includes(name))
for (const [name, job] of ordinaryJobs) {
if (!isRecord(job)) throw new TypeError(`${name} must define a job`)
expect(job).not.toHaveProperty('strategy')
expect(job).not.toHaveProperty('uses')
}
expect(ordinaryJobs.map(([name]) => name)).toContain('python-sdk')
// Python reports its own required verdict, so neither its jobs nor a
// reusable workflow caller remain in the Linux or Windows workflow.
expect(Object.keys(ci.jobs).filter(name => name.startsWith('python'))).toEqual([])
expect(Object.keys(ciWindows.jobs)).not.toContain('python-runtime')
const pythonSdk = workflowJob(ciPython, 'python-sdk')
expect(pythonSdk).not.toHaveProperty('strategy')
expect(pythonSdk).not.toHaveProperty('uses')
expect(runtime.needs).toBe('python-sdk')
expect(runtime.strategy).toBeUndefined()
expect(runtime.with).toMatchObject({ targets: 'node24-linux-x64,node24-win-x64' })
expect(runtime.uses).toBe('./.github/workflows/build-exe-for-python-sdk.yml')
const windowsJobs = Object.entries(ciWindows.jobs)
.filter(([name]) => name !== 'windows-checks-passed')
for (const [name, job] of windowsJobs) {
if (!isRecord(job)) throw new TypeError(`${name} must define a job`)
expect(job).not.toHaveProperty('strategy')
expect(job).not.toHaveProperty('uses')
}
const builder = loadWorkflow('.github/workflows/build-exe-for-python-sdk.yml')
if (!isRecord(builder.jobs)) throw new TypeError('Python runtime builder must define jobs')
@@ -63,19 +83,54 @@ describe('CI workflow', () => {
if (!isRecord(build.strategy)) throw new TypeError('Python runtime build must define its target matrix')
expect(build.strategy.matrix).toEqual({ include: '${{ fromJSON(needs.plan.outputs.matrix) }}' })
const beforeSdk = ordinaryJobs.length + compatibilityLimit
const afterSdk = ordinaryJobs.length - 1 + compatibilityLimit + Math.max(1, targets.split(',').length)
// Python's runtime targets start only after the SDK suite; Windows starts
// immediately. Plan and wheel stages precede the two-target matrix.
const windowsActive = windowsJobs.length
const beforeSdk = ordinaryJobs.length + compatibilityLimit + windowsActive + 1
const afterSdk = ordinaryJobs.length + compatibilityLimit + windowsActive + Math.max(1, targets.split(',').length)
expect(beforeSdk).toBeLessThanOrEqual(10)
expect(afterSdk).toBeLessThanOrEqual(10)
})
it('keeps the union of required PR verdicts equal to the pre-split blocking set', () => {
const verdicts = [
['ci.yml', 'all-checks-passed'],
['ci-windows.yml', 'windows-checks-passed'],
['ci-python.yml', 'python-checks-passed'],
] as const
const blocking: string[] = []
for (const [file, name] of verdicts) {
const workflow = loadWorkflow('.github/workflows/' + file)
const verdict = workflowJob(workflow, name)
if (!isRecord(workflow.jobs) || !Array.isArray(verdict.needs)) {
throw new TypeError(`${file} must define jobs and the ${name} verdict needs`)
}
const jobs = workflow.jobs
expect(verdict.needs.every(id => typeof id === 'string' && id in jobs)).toBe(true)
blocking.push(...(verdict.needs as string[]))
}
// Splitting the workflows only moves jobs between verdicts; the blocking
// set itself must stay identical, or branch protection would accept a
// failure it rejected before the split.
expect(blocking.sort()).toEqual([
'node-24',
'node-24-bench',
'node-24-consumers',
'node-24-coverage',
'node-compat',
'python-runtime',
'python-sdk',
'windows-build',
'windows-native-tests',
])
})
it('retains aggregate diagnostics in required builds and protected publication', () => {
const ci = loadWorkflow('.github/workflows/ci.yml')
for (const [name, command] of [
['node-24-consumers', 'pnpm run check:ci:consumers'],
['windows-build', 'pnpm run check:ci:windows-blocking'],
for (const [file, name, command] of [
['ci.yml', 'node-24-consumers', 'pnpm run check:ci:consumers'],
['ci-windows.yml', 'windows-build', 'pnpm run check:ci:windows-blocking'],
] as const) {
const job = workflowJob(ci, name)
const job = workflowJob(loadWorkflow('.github/workflows/' + file), name)
expect(job.steps).toContainEqual(expect.objectContaining({ run: command }))
}
for (const [file, command] of [
@@ -182,7 +237,7 @@ describe('CI workflow', () => {
})
it('skips coverage-history uploads on cancellation but retains Wine cleanup', () => {
const coverage = workflowJob(loadWorkflow('.github/workflows/ci.yml'), 'windows-coverage')
const coverage = workflowJob(loadWorkflow('.github/workflows/ci-windows.yml'), 'windows-coverage')
const wine = workflowJob(loadWorkflow('.github/workflows/ci-master.yml'), 'windows')
expect(coverage.steps).toContainEqual(expect.objectContaining({
name: 'Save coverage duration history', if: '${{ !cancelled() }}',
@@ -191,10 +246,10 @@ describe('CI workflow', () => {
})
it.each([
['node-24-coverage', 'Linux', 'LINUX', 'pnpm run check:ci:coverage'],
['windows-coverage', 'Windows', 'WINDOWS', 'pnpm run check:ci:coverage'],
])('reuses %s coverage timings only within its platform and runner pool', (jobName, platform, variable, command) => {
const job = workflowJob(loadWorkflow('.github/workflows/ci.yml'), jobName)
['node-24-coverage', 'ci.yml', 'Linux', 'LINUX', 'pnpm run check:ci:coverage'],
['windows-coverage', 'ci-windows.yml', 'Windows', 'WINDOWS', 'pnpm run check:ci:coverage'],
])('reuses %s coverage timings only within its platform and runner pool', (jobName, file, platform, variable, command) => {
const job = workflowJob(loadWorkflow('.github/workflows/' + file), jobName)
if (!Array.isArray(job.steps)) throw new TypeError(`${jobName} must define steps`)
const steps = job.steps.filter(isRecord)
const checkout = steps.findIndex(step => step.uses === 'actions/checkout@v7.0.1')
@@ -219,7 +274,7 @@ describe('CI workflow', () => {
})
it('isolates every pnpm action setup destination per runner', () => {
const files = ['.github/workflows/ci.yml', '.github/workflows/ci-master.yml']
const files = ['.github/workflows/ci.yml', '.github/workflows/ci-windows.yml', '.github/workflows/ci-master.yml']
const setups: Array<{ jobName: string; step: unknown }> = []
for (const file of files) {
const workflow: unknown = yaml.load(readFileSync(resolve(root, file), 'utf8'))
@@ -304,13 +359,16 @@ describe('CI workflow', () => {
}
})
it('keeps split native Windows PR jobs with failover, plus a master-only standby', () => {
it('keeps split native Windows PR jobs with failover in their own workflow, plus a master-only standby', () => {
const workflow = loadWorkflow('.github/workflows/ci.yml')
const windowsWorkflow = loadWorkflow('.github/workflows/ci-windows.yml')
const masterWorkflow = loadWorkflow('.github/workflows/ci-master.yml')
if (!isRecord(workflow.jobs)
|| !isRecord(workflow.jobs['windows-build'])
|| !isRecord(workflow.jobs['windows-coverage'])
|| !isRecord(workflow.jobs['windows-native-tests'])
|| !isRecord(windowsWorkflow.jobs)
|| !isRecord(windowsWorkflow.jobs['windows-build'])
|| !isRecord(windowsWorkflow.jobs['windows-coverage'])
|| !isRecord(windowsWorkflow.jobs['windows-native-tests'])
|| !isRecord(windowsWorkflow.jobs['windows-checks-passed'])
|| !isRecord(workflow.jobs['node-24'])
|| !isRecord(workflow.jobs['node-24-coverage'])
|| !isRecord(workflow.jobs['node-24-bench'])
@@ -319,12 +377,13 @@ describe('CI workflow', () => {
|| !isRecord(workflow.jobs['all-checks-passed'])
|| !isRecord(masterWorkflow.jobs)
|| !isRecord(masterWorkflow.jobs['serial-windows'])) {
throw new TypeError('CI workflow must define windows-build, windows-coverage, windows-native-tests, node-24, node-24-coverage, node-24-bench, node-24-consumers, node-compat, and all-checks-passed; ci-master must define serial-windows')
throw new TypeError('CI workflow must define node-24, node-24-coverage, node-24-bench, node-24-consumers, node-compat, and all-checks-passed; CI Windows must define windows-build, windows-coverage, windows-native-tests, and windows-checks-passed; ci-master must define serial-windows')
}
const windowsBuild = workflow.jobs['windows-build']
const windowsCoverage = workflow.jobs['windows-coverage']
const windowsNativeTests = workflow.jobs['windows-native-tests']
const windowsBuild = windowsWorkflow.jobs['windows-build']
const windowsCoverage = windowsWorkflow.jobs['windows-coverage']
const windowsNativeTests = windowsWorkflow.jobs['windows-native-tests']
const windowsAggregate = windowsWorkflow.jobs['windows-checks-passed']
const serialWindows = masterWorkflow.jobs['serial-windows']
const node24 = workflow.jobs['node-24']
const node24Coverage = workflow.jobs['node-24-coverage']
@@ -332,9 +391,10 @@ describe('CI workflow', () => {
const node24Consumers = workflow.jobs['node-24-consumers']
const nodeCompat = workflow.jobs['node-compat']
const aggregate = workflow.jobs['all-checks-passed']
if (!Array.isArray(aggregate.needs)) {
throw new TypeError('CI aggregate must define needs')
if (!Array.isArray(aggregate.needs) || !Array.isArray(windowsAggregate.needs)) {
throw new TypeError('CI and CI Windows aggregates must define needs')
}
expect(Object.keys(workflow.jobs).filter(name => name.startsWith('windows'))).toEqual([])
// The split native jobs all resolve their pool through the Windows switch.
for (const [jobName, job] of [['windows-build', windowsBuild], ['windows-coverage', windowsCoverage], ['windows-native-tests', windowsNativeTests]] as const) {
expect(typeof job['runs-on']).toBe('string')
@@ -414,7 +474,7 @@ describe('CI workflow', () => {
expect(nativeTestCommand).toContain('tool-pwsh/tests/loader.spec.ts')
expect(nativeTestCommand).toContain('workflow-ptc.spec.ts')
expect(workflow.jobs['windows-observational']).toBeUndefined()
expect(windowsWorkflow.jobs['windows-observational']).toBeUndefined()
expect(windowsBuild['continue-on-error']).not.toBe(true)
const observational = buildCommands.find(step => step.id === 'observational')
expect(observational).toMatchObject({
@@ -473,10 +533,13 @@ describe('CI workflow', () => {
expect(serialGate).toBeDefined()
expect(serialGate!.env).toMatchObject({ DSH_COVERAGE_TEST_TIMEOUT_MS: '90000' })
expect(aggregate.needs).not.toContain('windows')
// windows-coverage is temporarily non-blocking while Windows ACP
// half-close tests are stabilized; observational stays out too.
expect(aggregate.needs).not.toContain('windows')
expect(aggregate.needs).toContain('windows-build')
expect(windowsAggregate.needs).toEqual(['windows-build', 'windows-native-tests'])
expect(windowsAggregate.name).toBe('windows checks passed')
expect(windowsAggregate['runs-on']).toBe(aggregate['runs-on'])
expect(windowsAggregate.if).toBe(aggregate.if)
// The benchmark lane is a required verdict input and runs alone so its
// wall-clock budgets never share a runner with a concurrent aggregate.
expect(aggregate.needs).toContain('node-24-bench')
@@ -492,10 +555,7 @@ describe('CI workflow', () => {
env: { DSH_GATE_VERBOSE: '1' },
run: 'pnpm run check:ci:bench',
})
expect(aggregate.needs).not.toContain('windows-coverage')
expect(aggregate.needs).toContain('windows-native-tests')
expect(aggregate.needs).not.toContain('windows-observational')
expect(aggregate.needs).not.toContain('serial-windows')
expect(windowsAggregate.needs).not.toContain('serial-windows')
// Linux failover is a separate switch: the three enterprise Linux workers
// and the verdict job resolve their pool through DSH_CI_FAILOVER_LINUX,
@@ -712,6 +772,18 @@ describe('CI workflow', () => {
}
expect(Object.keys(workflow.on).sort()).toEqual(['push', 'workflow_dispatch'])
expect(Object.keys(prWorkflow.on)).toEqual(['pull_request'])
const windowsWorkflow = loadWorkflow('.github/workflows/ci-windows.yml')
if (!isRecord(windowsWorkflow.on)) throw new TypeError('CI Windows workflow must define on')
expect(Object.keys(windowsWorkflow.on)).toEqual(['pull_request'])
expect(windowsWorkflow.concurrency).toEqual(workflow.concurrency)
// Duplicated env must not drift: a Node bump on one lane only would run
// Linux and Windows against different versions.
expect(windowsWorkflow.env).toEqual(prWorkflow.env)
const pythonWorkflow = loadWorkflow('.github/workflows/ci-python.yml')
if (!isRecord(pythonWorkflow.on)) throw new TypeError('CI Python workflow must define on')
expect(Object.keys(pythonWorkflow.on)).toEqual(['pull_request'])
expect(pythonWorkflow.concurrency).toEqual(workflow.concurrency)
expect(pythonWorkflow.env).toEqual(prWorkflow.env)
// Drills share the parent run’s supersession policy.
for (const name of ['serial-linux-selfhosted', 'serial-windows']) {
@@ -789,27 +861,29 @@ describe('CI workflow', () => {
expect(config).not.toContain('packages/lsp/lsp-stdio/src/instance.ts')
})
it('requires release-shaped Python runtime validation on Linux and Windows x64', () => {
const workflow = loadWorkflow('.github/workflows/ci.yml')
it('requires release-shaped Python runtime validation on Linux and Windows x64 in CI Python', () => {
const workflow = loadWorkflow('.github/workflows/ci-python.yml')
const pythonRuntime = workflowJob(workflow, 'python-runtime')
const aggregate = workflowJob(workflow, 'all-checks-passed')
const aggregate = workflowJob(workflow, 'python-checks-passed')
if (!Array.isArray(aggregate.needs)) {
throw new TypeError('CI aggregate must define required job dependencies')
throw new TypeError('CI Python aggregate must define required job dependencies')
}
expect(pythonRuntime).toMatchObject({
if: "github.event_name == 'pull_request'",
name: 'python runtime / release-shaped matrix',
uses: './.github/workflows/build-exe-for-python-sdk.yml',
with: {
targets: 'node24-linux-x64,node24-win-x64',
ci: true,
},
with: { targets: 'node24-linux-x64,node24-win-x64', ci: true },
secrets: {
DEEPSEEK_API_KEY_EXTERNAL: '${{ secrets.DEEPSEEK_API_KEY_EXTERNAL }}',
},
})
expect(aggregate.needs).toContain('python-runtime')
expect(aggregate.needs).toEqual(['python-sdk', 'python-runtime'])
for (const file of ['ci.yml', 'ci-windows.yml']) {
const other = loadWorkflow('.github/workflows/' + file)
if (!isRecord(other.jobs)) throw new TypeError(`${file} must define jobs`)
expect(other.jobs).not.toHaveProperty('python-runtime')
}
})
it('builds SSH executables only for explicit manual or release requests', () => {
@@ -1308,7 +1382,7 @@ describe('Weighted approval workflow', () => {
if: "failure() && steps.revoke.outputs.active == 'true'",
run: 'node .github/review-ownership/check-approval.mjs error',
})
const pythonJob = workflowJob(loadWorkflow('.github/workflows/ci.yml'), 'python-sdk')
const pythonJob = workflowJob(loadWorkflow('.github/workflows/ci-python.yml'), 'python-sdk')
expect(pythonJob.steps).toContainEqual({
name: 'Test production blame scoring',
run: "uv run --python 3.10 --with-requirements .github/review-ownership/requirements.txt python -m unittest discover -s .github/review-ownership -p 'test_*.py'",
@@ -29,6 +29,8 @@ const roots: string[] = []
const dshBuildWorkflows = [
'build-exe-for-python-sdk.yml',
'ci.yml',
'ci-python.yml',
'ci-windows.yml',
'e2e.yml',
'release.yml',
'release-publish.yml',
+8
View File
@@ -234,6 +234,14 @@ describe('client bundle purity gate', () => {
expect(() => resolveId('@deepseek-ai/dsh-spill-policy/notice/internal')).toThrow(/purity/)
})
it('inlines the stateless file-drag entry without admitting its subpaths or UI plugin', () => {
expect(resolveId('@deepseek-ai/dsh-util-file-drag')).toBeNull()
expect(() => resolveId('@deepseek-ai/dsh-util-file-drag/internal')).toThrow(/purity/)
expect(() => resolveId('@deepseek-ai/dsh-client-ui-file-drag')).toThrow(/purity/)
const deps = clientConfigs()[0]?.deps as { neverBundle: (specifier: string) => boolean }
expect(deps.neverBundle('@deepseek-ai/dsh-util-file-drag')).toBe(false)
})
it('inlines the browser-safe Timer entry while sharing Cordis through the module table', () => {
const timer = '@deepseek-ai/cordis-plugin-timer'
expect(resolveId(timer)).toBeNull()
+93
View File
@@ -0,0 +1,93 @@
/** Raw retention and candidate evaluation preserve the running plugin's config identity. */
import { Context, type Fiber } from '@deepseek-ai/cordis'
import Loader from '@deepseek-ai/cordis-plugin-loader'
import z from '@deepseek-ai/schemastery'
import { describe, expect, it, onTestFinished, vi } from 'vitest'
function root(): Context {
const ctx = new Context()
onTestFinished(() => ctx.fiber.dispose())
return ctx
}
describe('Fiber configuration access', () => {
it('keeps raw and running references unchanged while resolving valid or invalid candidates', async () => {
const ctx = root()
await ctx.plugin(Loader)
const Config = z.object({ value: z.number().min(0).volatile() })
const applied = vi.fn()
ctx.loader.builtins.consumer = { Config, apply: applied }
const raw = { value: { __jsExpr: '1 + 1' } }
const id = await ctx.loader.create({ name: 'cordis:consumer', config: raw })
const fiber = ctx.loader.resolve(id).fiber!
await fiber.await()
const running = applied.mock.calls[0]![1] as ReturnType<typeof Config>
expect(fiber.rawConfig).toBe(raw)
expect(fiber.effectiveConfig).toBe(running)
const candidate = fiber.resolveConfig({ value: { __jsExpr: '2 + 2' } }) as ReturnType<typeof Config>
expect(candidate.value.get()).toBe(4)
expect(candidate.value).not.toBe(running.value)
expect(() => fiber.resolveConfig({ value: -1 })).toThrow()
expect(fiber.rawConfig).toBe(raw)
expect(fiber.effectiveConfig).toBe(running)
expect(running.value.get()).toBe(2)
expect(applied).toHaveBeenCalledOnce()
})
it('uses the canonical fiber through registry wrappers and retains raw input without updating', async () => {
const ctx = root()
const initial = { value: 1 }
const apply = vi.fn()
const wrapper = ctx.plugin(apply, initial)
await wrapper
const fiber = wrapper.ctx.fiber
const hook = vi.fn(function (this: Fiber, _config: unknown, next: () => unknown) {
expect(this).toBe(fiber)
return next()
})
fiber.ctx.on('internal/config', hook)
const raw = { value: 2 }
wrapper.rawConfig = raw
expect(fiber.rawConfig).toBe(raw)
expect(wrapper.effectiveConfig).toBe(initial)
expect(wrapper.resolveConfig(raw)).toBe(raw)
expect(hook).toHaveBeenCalledOnce()
expect(fiber.effectiveConfig).toBe(initial)
expect(apply).toHaveBeenCalledOnce()
await wrapper.restart()
expect(fiber.effectiveConfig).toBe(raw)
expect(apply).toHaveBeenCalledTimes(2)
})
it('publishes updates through registry wrappers to the canonical fiber', async () => {
const ctx = root()
const initial = { value: 1 }
const apply = vi.fn()
const wrapper = ctx.plugin(apply, initial)
await wrapper
const raw = { value: 2 }
wrapper.update(raw)
await wrapper.await()
expect(apply).toHaveBeenLastCalledWith(wrapper.ctx, raw)
expect(wrapper.rawConfig).toBe(raw)
expect(wrapper.effectiveConfig).toBe(raw)
expect(wrapper.ctx.fiber.rawConfig).toBe(raw)
expect(wrapper.ctx.fiber.effectiveConfig).toBe(raw)
expect(apply).toHaveBeenCalledTimes(2)
})
it('retains a pending raw update until its injection is available', async () => {
const ctx = root()
ctx.provide('configAccessTest')
const apply = vi.fn()
const wrapper = ctx.plugin({ inject: ['configAccessTest'], apply }, { value: 1 })
const raw = { value: 2 }
wrapper.update(raw)
expect(wrapper.rawConfig).toBe(raw)
expect(wrapper.effectiveConfig).toBeUndefined()
ctx.set('configAccessTest', {})
await wrapper
expect(wrapper.effectiveConfig).toBe(raw)
expect(apply).toHaveBeenCalledOnce()
})
})
+2 -1
View File
@@ -10,9 +10,10 @@ const child = spawn(process.execPath, [
'apps/cli/src/bin.ts',
'--profile',
'headless',
'--patch',
'apps/cli/config/examples/ptc.yml',
task,
], {
stdio: 'inherit',
env: { ...process.env, DSH_TOOLS_MODE: 'ptc' },
})
child.on('exit', (code, signal) => { process.exit(signal !== null ? 1 : code ?? 1) })
+3
View File
@@ -55,9 +55,11 @@ const KIND_TEMPLATES: Readonly<Record<string, string>> = {
* library; the check re-derives the entry shape so a stale entry fails loud.
*/
const PACKAGE_LIBRARIES: Readonly<Record<string, string>> = {
'packages/util/file-drag': 'File naming and native transfer declarations without a plugin entry.',
'packages/experimental/browser-use-runtime': 'Provider-owned browser resource management and MCP integration helpers; no plugin entry.',
'packages/boot/app-boot': 'Boot library the app bins import; plain helper exports.',
'packages/boot/cmdline': 'Command-line library the app bins import; plain module exports.',
'packages/boot/official-bundles': 'Bundle-list data library the launcher and plugin manager import; plain constant exports.',
'packages/client/store': 'Browser-side state primitives; plain function/type exports.',
'packages/client/ui-primitives': 'Browser-side UI component library; plain component exports.',
'packages/client/ui-slots': 'Browser-side slot-map declarations; plain type exports.',
@@ -81,6 +83,7 @@ const PACKAGE_LIBRARIES: Readonly<Record<string, string>> = {
'packages/subprocess/win32-process': 'Low-level Win32 process and Job Object primitives.',
'packages/test-support/session-snapshot': 'Test infrastructure; mounts nothing into a product composition.',
'packages/test-support/agent-loop-testkit': 'Test helper library; mounts nothing into a product composition.',
'packages/test-support/tool-testkit': 'Tool fixture library; exports helpers without a plugin entry.',
'packages/test-support/client-runtime': 'Browser-side test infrastructure.',
'packages/test-support/llm-mock-server': 'Test server library; substitutes provider wire behavior.',
'packages/test-support/loader-smoke': 'Test harness library; mounts nothing into a product composition.',
+14 -14
View File
@@ -1,27 +1,27 @@
/** Add a native tool after a file read and unregister it after its first execution. */
/** Activate a native tool after a file read and disable it after its first execution. */
export const name = 'python-sdk-dynamic-tools'
export const inject = ['tools']
/** @param {import('@deepseek-ai/cordis').Context} ctx - Scenario-owned tool registry. */
export function apply(ctx) {
let removeTool
let active = false
ctx.tools.register({
name: 'snapshot_ping',
description: 'Return pong once.',
parameters: { type: 'object', properties: {}, additionalProperties: false },
output: {
schema: { type: 'string' },
render(_args, value) { return [{ type: 'text', text: value }] },
},
async execute() { return 'pong' },
}, { when: async ({ modelSelection }) => active && modelSelection.provider === 'deepseek-official' })
ctx.on('tools/post-execute', async (exec, result, next) => {
const downstream = await next()
if (result.isError) return downstream
if (exec.name === 'read' && exec.arguments.file_path === 'dynamic-tool-task.txt') {
removeTool = ctx.effect(() => ctx.tools.register({
name: 'snapshot_ping',
description: 'Return pong once.',
parameters: { type: 'object', properties: {}, additionalProperties: false },
output: {
schema: { type: 'string' },
render(_args, value) { return [{ type: 'text', text: value }] },
},
async execute() { return 'pong' },
}))
active = true
} else if (exec.name === 'snapshot_ping') {
removeTool?.()
removeTool = undefined
active = false
}
return downstream
})
+1 -1
View File
@@ -5,7 +5,7 @@ import { existsSync, mkdtempSync, readFileSync, rmSync, statSync, writeFileSync
import { tmpdir } from 'node:os'
import { join, resolve } from 'node:path'
import { promisify } from 'node:util'
import { PROFILE_TEMPLATES } from '@deepseek-ai/dsh-app-boot'
import { PROFILE_TEMPLATES } from '@deepseek-ai/dsh-official-bundles'
import {
computeTranslationPairingRecord,
renderTranslationPairingRecord,
+15 -1
View File
@@ -193,6 +193,7 @@ export const SERVICE_WALK_EXEMPTIONS: Record<string, string> = {
shortcuts: 'client-side interface-typed keyboard service — packages/client/shortcuts/README.md owns the API',
userQuestionPanels: 'client-side slot-contract accessor (UserQuestionPanels) — packages/client/ui-tool/README.md owns the API',
commandUi: 'client-side interface-typed browser service — packages/client/ui-commands/README.md owns the API',
fileDrag: 'client-side file preparation and transfer service — packages/client/ui-file-drag/README.md owns the API',
feedbackUi: 'client-side feedback dialog service — packages/client/ui-message-feedback/README.md owns the API',
conversation: 'client-side interface-typed browser service — packages/client/ui-conversation/README.md owns the API',
layout: 'client-side interface-typed browser service — packages/client/ui-layout/README.md owns the API',
@@ -466,6 +467,8 @@ export const LINK_MAP: Readonly<Record<string, string>> = {
PromptFileBinding: 'attachment.md',
FileUploadReceiptId: 'attachment.md',
FileUploadValue: 'attachment.md',
ImageUploadValue: 'attachment.md',
StagedAttachment: 'attachment.md',
SessionStartSource: 'core.md',
ToolCallId: 'core.md',
SessionLogSnapshot: 'session-query.md',
@@ -748,9 +751,18 @@ export const LINK_MAP: Readonly<Record<string, string>> = {
ToolExecutionResult: 'tools.md',
ToolExecutionToken: 'tools.md',
ToolGuard: 'tools.md',
ToolPresentationMode: 'tools.md',
ToolRuntime: 'tools.md',
ToolsService: 'tools.md',
ToolExecutor: 'tools.md',
ToolModelInput: 'tools.md',
ToolPromptSection: 'tools.md',
ToolBatchInput: 'tools.md',
ToolCallResult: 'tools.md',
ToolBatchResult: 'tools.md',
ToolRestriction: 'tools.md',
ToolActivationContext: 'tools.md',
ToolRegistrationOptions: 'tools.md',
ResolvedToolSet: 'tools.md',
ToolSchema: 'tools.md',
SettingsNamespace: 'settings.md',
SettingsDescriptor: 'settings.md',
@@ -807,6 +819,7 @@ export const LINK_MAP: Readonly<Record<string, string>> = {
PresetOption: 'permission-presets.md',
PresetSpec: 'permission-presets.md',
WebRoute: 'web-server.md',
WebResourceRoute: 'web-server.md',
IndexInjection: 'web-server.md',
StorageBackend: 'storage.md',
StorageForms: 'storage.md',
@@ -979,6 +992,7 @@ export const TYPE_LINK_EXEMPTIONS: Readonly<Record<string, string>> = {
JsonValue: 'JSON value union is owned by packages/core/session/src/json.ts',
KnobState: 'projection unit state fields are owned by packages/interaction/permission-presets/README.md',
PromptAssembly: 'assembly result is owned by packages/core/system-prompt/README.md',
PromptPresentation: 'presentation inputs are owned by packages/core/system-prompt/README.md',
RequestRunId: 'dynamic-package payload contract is owned by packages/extensions/cordis-host-runner/src/types.ts',
RpcReceipt: 'carrier-layer receipt is owned by packages/client/connection/src/rpc.ts',
SessionForkSource: 'service-local fork input is owned by packages/core/session/src/index.ts',
+12 -9
View File
@@ -574,10 +574,10 @@ const SERVICE_ROLES: ServiceRole[] = [
{
key: 'tools',
pkg: 'tools',
title: 'Tool registry and guarded execution pipeline',
title: 'Scoped tool registry and executor selection',
mode: 'core',
consumers: ['agent-loop', 'tool-ask-user', 'tool-bash', 'tool-cordis', 'tool-fs', 'tool-terminal', 'tool-skill', 'tool-subagent', 'tool-todo', 'tool-web'],
note: 'Registers capabilities, owns PTC mode transport, and routes calls through pre-policy, monotonic guards, around dispatch, post-policy, and final-result observation.',
note: 'Registers capabilities and scoped executors. Native and PTC providers own descriptions and complete raw call batches; their optional shared pipeline handles tool policy and dispatch.',
},
{
key: 'userQuestions',
@@ -755,8 +755,8 @@ const SERVICE_ROLES: ServiceRole[] = [
title: 'PTC execution seam',
mode: 'seam',
implementations: ['ptc-runtime-node', 'experimental-ptc-runtime-python'],
consumers: ['tools', 'workflow-ptc'],
note: 'Runs programs against host-provided async bindings; tools owns PTC presentation and workflow-ptc owns workflow orchestration.',
consumers: ['tool-executor-ptc', 'workflow-ptc'],
note: 'Runs programs against host-provided async bindings; tool-executor-ptc owns PTC presentation and workflow-ptc owns workflow orchestration.',
},
{
key: 'fs',
@@ -1577,7 +1577,8 @@ function renderLifecycle(): string {
' Note over Agent,Driver: claim pending next-step input plus one queued prompt',
` Driver-->>SDK: ${mermaidCode('agent/inbox/spliced')} pure deletion`,
` Driver-->>SDK: ${mermaidCode('agent/inbox/claimed')} { message, turn } per message`,
` Driver->>Prompt: ${mermaidCode('system-prompt/assemble')} waterfall`,
' Driver->>Tools: resolve({ agent, modelSelection, signal }) once per proposed step',
` Driver->>Prompt: ${mermaidCode('system-prompt/assemble')} waterfall with the step tool set`,
` Driver->>Hooks: ${mermaidCode('agent/pre-step')} waterfall`,
' Hooks-->>Driver: authoritative reject or enter(messages)',
' alt proposed step rejected, first batch empty, or pre-step failed',
@@ -1617,6 +1618,7 @@ function renderLifecycle(): string {
' end',
' end',
` Driver->>Session: ${mermaidCode('step/end')}`,
' Driver->>Tools: dispose the step tool set',
' opt natural stop and next-step inbox empty',
` Driver->>Hooks: ${mermaidCode('agent/turn-stopping')} serial terminal checkpoint`,
' end',
@@ -1648,7 +1650,7 @@ function renderToolPipeline(): string {
const maintenance = 'curated Mermaid flow; exact tool schemas and event signatures live in generated catalogs'
return [
...generatedHeader('Tool Execution Pipeline'),
'This graph shows where policy, hooks, sandboxing, filesystem guards, result rewriting, final-outcome observation, and UI rendering run without changing the loop. The `tools/pre-execute` waterfall runs first, monotonic guards run next, and the `tools/execute` and `tools/post-execute` waterfalls follow; the three waterfalls may transform a call. Definition-owned `projectContent` installs prepared content before post-execute; `finalizeContent` and `tools/result` run afterward.',
'Built-in executors own raw batch parsing, scheduling, cancellation, and Session call/result logging. This graph shows where their shared invocation pipeline applies policy, hooks, sandboxing, filesystem guards, result rewriting, final-outcome observation, and UI rendering without changing the loop. The `tools/pre-execute` waterfall runs first, monotonic guards run next, and the `tools/execute` and `tools/post-execute` waterfalls follow; the three waterfalls may transform a call. Definition-owned `projectContent` installs prepared content before post-execute; `finalizeContent` and `tools/result` run afterward.',
'',
'```mermaid',
'flowchart TD',
@@ -1665,14 +1667,15 @@ function renderToolPipeline(): string {
` owned["Tool-owned session events<br/>${mermaidCode('todo/write')}, ${mermaidCode('fs/observed')}, ${mermaidCode('hook/invoked')}, ${mermaidCode('hook/result')}, ${mermaidCode('tool/ptc-dispatch')}"]`,
' project["ToolDefinition.projectContent<br/>execution-prepared text and images"]',
` post["${mermaidCode('tools/post-execute')} waterfall<br/>accept, block, replace, add context"]`,
' normalized["Registry outer normalization<br/>pipeline/result snapshot throws become isError"]',
' normalized["Invocation pipeline normalization<br/>pipeline/result snapshot throws become isError"]',
' finalize["ToolDefinition.finalizeContent<br/>last content-only invariant"]',
` final["${mermaidCode('tools/result')} synchronous notification<br/>frozen authoritative outcome"]`,
' context["Active-batch additionalContexts FIFO<br/>injected user/message after recorded tool results"]',
` toolResult["Session event: ${mermaidCode('tool/result')}<br/>single model-facing outcome"]`,
' allResults["Tool batch settled<br/>recorded tool/result events complete"]',
' presentResult["UI completed card<br/>presentResult(args, result)"]',
' model --> toolCall',
' executor["Selected ToolExecutor.execute<br/>raw batch, Agent, optional logging step"]',
' model --> executor --> toolCall',
' toolCall --> presentCall',
' toolCall --> pre',
' pre -->|allow| guards',
@@ -1705,7 +1708,7 @@ function renderToolPipeline(): string {
' allResults --> context',
'```',
'',
'Filesystem read-before-edit checks stay below `tool-fs` on `fs/*` events. Generic pre/post waterfalls host hooks and approval policy; `ctx.approval` resolves asks before monotonic guards, and owner policy that must not be reordered remains a registered guard. Around-dispatch concerns such as timeouts wrap `tools/execute`. The registry losslessly snapshots the candidate result and normalizes a snapshot failure before the visible definition\'s snapshotted `finalizeContent` callback enforces its synchronous content-only invariant. `tools/result` then observes the immutable, lossless-JSON outcome. This lets hooks span tool families without coupling the tools to one policy service. PTC mode sends both the reserved `run_code` transport and its serialized sub-calls through the pipeline; sub-calls carry the parent token, log `tool/ptc-dispatch`, return denials as binding rejections, and omit `additionalContexts` to preserve call/result adjacency.',
'Filesystem read-before-edit checks stay below `tool-fs` on `fs/*` events. Generic pre/post waterfalls host hooks and approval policy; `ctx.approval` resolves asks before monotonic guards, and owner policy that must not be reordered remains a registered guard. Around-dispatch concerns such as timeouts wrap `tools/execute`. The pipeline losslessly snapshots the candidate result and normalizes a snapshot failure before the visible definition\'s snapshotted `finalizeContent` callback enforces its synchronous content-only invariant. `tools/result` then observes the immutable, lossless-JSON outcome. This lets hooks span tool families without coupling the tools to one policy service. PTC mode sends both the reserved `run_code` transport and its ordered sub-calls through the pipeline; sub-calls carry the parent token, log `tool/ptc-dispatch`, return denials as binding rejections, and omit `additionalContexts` to preserve call/result adjacency.',
'',
...maintenanceFooter(maintenance),
].join('\n')
+2 -2
View File
@@ -4,11 +4,11 @@ import { existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, statSync, uti
import { tmpdir } from 'node:os'
import { dirname, join } from 'node:path'
import { afterEach, describe, expect, it } from 'vitest'
import { ON_DEMAND_BUNDLES } from '../packages/boot/app-boot/src/official-bundles.ts'
import { ON_DEMAND_BUNDLES } from '../packages/boot/official-bundles/src/index.ts'
import { generateOfficialBundleCatalog, renderOfficialBundleCatalog, syncOfficialBundleCatalog } from './gen-official-bundle-catalog.ts'
const roots: string[] = []
const output = 'packages/boot/app-boot/src/official-bundles.generated.ts'
const output = 'packages/boot/official-bundles/src/catalog.generated.ts'
function write(root: string, path: string, content: unknown): void {
mkdirSync(dirname(join(root, path)), { recursive: true })
+5 -5
View File
@@ -5,11 +5,11 @@ import { resolve } from 'node:path'
import { fileURLToPath, pathToFileURL } from 'node:url'
import ts from 'typescript'
import type { LocalizedText } from '../packages/util/package-manifest/src/types.ts'
import { ON_DEMAND_BUNDLES } from '../packages/boot/app-boot/src/official-bundle-packages.ts'
import type { OfficialBundleCatalogEntry } from '../packages/boot/app-boot/src/official-bundles.ts'
import { ON_DEMAND_BUNDLES } from '../packages/boot/official-bundles/src/bundles.ts'
import type { OfficialBundleCatalogEntry } from '../packages/boot/official-bundles/src/index.ts'
import { readPluginMeta, resolvePluginResource } from '../packages/boot/app-boot/src/package-meta.ts'
const OUTPUT = 'packages/boot/app-boot/src/official-bundles.generated.ts'
const OUTPUT = 'packages/boot/official-bundles/src/catalog.generated.ts'
/**
* Read complete metadata without importing or activating any provider code.
@@ -76,11 +76,11 @@ export function generateOfficialBundleCatalog(root: string, names: readonly stri
/**
* Render a source-plane catalog with no package imports or pinned runtime version.
* @param entries - Validated metadata in catalog order.
* @returns Deterministic TypeScript source for app-boot's generated module.
* @returns Deterministic TypeScript source for the official-bundles generated module.
*/
export function renderOfficialBundleCatalog(entries: readonly OfficialBundleCatalogEntry[]): string {
return '/** Generated offline Official bundle metadata. Run pnpm gen-official-bundle-catalog; do not edit. */\n\n'
+ "import type { OfficialBundleCatalogEntry } from './official-bundles.ts'\n\n"
+ "import type { OfficialBundleCatalogEntry } from './index.ts'\n\n"
+ '/** Official on-demand entries, independent of installed packages and registry availability. */\n'
+ 'export const OFFICIAL_ON_DEMAND_CATALOG: readonly OfficialBundleCatalogEntry[] = [\n'
+ entries.map(entry => ' {\n'
+33 -23
View File
@@ -7,7 +7,7 @@
*/
import { globSync, readFileSync, writeFileSync } from 'node:fs'
import { basename, resolve } from 'node:path'
import { basename, dirname, resolve } from 'node:path'
import { Context } from '@deepseek-ai/cordis'
import LlmRuntime from '@deepseek-ai/dsh-llm'
import type { ToolSchema } from '@deepseek-ai/dsh-llm'
@@ -19,7 +19,11 @@ import SessionProjectionRegistry from '@deepseek-ai/dsh-session-projection'
import SqliteSessionQueryEngine from '@deepseek-ai/dsh-session-query-sqlite'
import GoalService from '@deepseek-ai/dsh-goal'
import SystemPrompt from '@deepseek-ai/dsh-system-prompt'
import ToolRuntime, { type Config as ToolsConfig } from '@deepseek-ai/dsh-tools'
import ToolRuntime from '@deepseek-ai/dsh-tools'
import { NativeToolExecutor } from '@deepseek-ai/dsh-tool-executor-native'
import { PtcExecutor } from '@deepseek-ai/dsh-tool-executor-ptc'
import NodePtcRuntime from '@deepseek-ai/dsh-ptc-runtime-node'
import LocalSandboxProvider from '@deepseek-ai/dsh-sandbox-local'
import LocalBashExecutor from '@deepseek-ai/dsh-bash-local'
import * as BashEnvPlugin from '@deepseek-ai/dsh-shell-env'
import { PwshLocalExecutor } from '@deepseek-ai/dsh-pwsh-local'
@@ -165,13 +169,8 @@ export interface ToolPackage {
mount: (ctx: Context) => Promise<void>
/** Agent-like scope key whose tool view is catalogued instead of the global view. */
scope?: (ctx: Context) => Agent
/**
* Config for the caller's `ToolRuntime` mount. The registry itself ships a
* model-facing tool (`run_code`, registered under a non-native `mode`), so
* ITS catalog entry boots the registry in the mode that exposes it;
* every other entry uses the default (native) registry.
*/
toolsConfig?: ToolsConfig
/** Select the program transport when harvesting the PTC package. */
executor?: 'ptc'
/**
* A deployment note rendered after the package's tools, for a fact that
* booting the package alone cannot show. The registered tool NAME can be a
@@ -254,18 +253,20 @@ const TOOL_PACKAGES: ToolPackage[] = [
'ask_user_question keeps the original blocking behavior by default; set `mode: timed` to opt into a foreground timeout and pending result while the question remains answerable. In timed mode, `timeout: -1` keeps that call blocking indefinitely.',
},
{
pkg: '@deepseek-ai/dsh-tools',
dir: 'tools',
source: 'packages/core/tools/src/ptc.ts',
requires: ['ctx.workingDirectory (Agent executions)', 'ctx.tools', 'ctx.ptcRuntime (execution time)', 'ctx.systemPrompt'],
pkg: '@deepseek-ai/dsh-tool-executor-ptc',
dir: 'tool-executor-ptc',
source: 'packages/core/tool-executor-ptc/src/ptc.ts',
requires: ['ctx.workingDirectory (Agent executions)', 'ctx.tools', 'ctx.ptcRuntime', 'ctx.systemPrompt'],
writes: ['tool/call', 'one tool/ptc-dispatch-start + tool/ptc-dispatch pair per bridged sub-call', 'tool/result'],
// The registry's OWN tool: run_code exists only under a non-native mode
// (the registry registers it in its constructor; the PTC runtime is read
// at assembly/execution time, so the schema harvest needs none mounted).
toolsConfig: { mode: 'ptc' },
async mount() {},
executor: 'ptc',
async mount(ctx) {
await ctx.plugin(LocalSubprocessRuntime)
await ctx.plugin(LocalSandboxProvider)
await ctx.plugin(SandboxPolicy)
await ctx.plugin(NodePtcRuntime)
},
note:
'Owned by the tool registry as a reserved transport outside filterable capability layers under `mode: ptc` (see the PTC mode Agent Note). Under `ptc` it is the registry\'s only wire contribution; the other visible capabilities are declared in a generated SDK section in the loaded runtime\'s language, and a program calls them through bindings scheduled under the native concurrency contract (submission-ordered starts and policy; concurrency-safe bodies overlap up to `maxParallelSubCalls`) that re-enter the complete guarded tool pipeline and link each nested execution to this outer result.',
'The PTC executor exposes only run_code to the model and declares other visible capabilities in a generated SDK in the runtime’s language. Program bindings enter the shared guarded tool pipeline in submission order; concurrency-safe bodies overlap up to maxParallelSubCalls, and nested results remain linked to the outer program.',
},
{
pkg: '@deepseek-ai/dsh-plan-mode',
@@ -721,7 +722,7 @@ export type ToolCatalog = CatalogPackage[]
/**
* Assert the boot manifest covers every shipped tool package on disk (a
* `tool-*` leaf under `packages/`).
* `tool-*` leaf under `packages/`, excluding test support).
* Booting has no source declaration to enumerate, so this glob restores the
* "a new tool cannot be silently undocumented" guarantee: an unlisted package
* fails the generator (and the freshness gate) until it is added to
@@ -730,7 +731,11 @@ export type ToolCatalog = CatalogPackage[]
* `scanRoot` defaults to the repo root; a test may point it at a fixture tree.
*/
export function assertManifestComplete(packages: ToolPackage[] = TOOL_PACKAGES, scanRoot: string = root): void {
const onDisk = globSync('packages/*/tool-*', { cwd: scanRoot }).map(p => basename(p)).sort()
// Native execution owns no tool definitions; PTC contributes run_code.
const onDisk = globSync('packages/*/tool-*', { cwd: scanRoot })
.filter(path => basename(dirname(path)) !== 'test-support')
.map(p => basename(p))
.filter(dir => dir !== 'tool-executor-native').sort()
const listed = new Set(packages.map(p => p.dir))
const missing = onDisk.filter(dir => !listed.has(dir))
if (missing.length > 0) {
@@ -783,9 +788,14 @@ export async function collectToolCatalog(packages: ToolPackage[] = TOOL_PACKAGES
await ctx.plugin(SystemPrompt)
await ctx.plugin(LocalFileSystem)
await ctx.plugin(WorkingDirectory)
await ctx.plugin(ToolRuntime, entry.toolsConfig ?? {})
await ctx.plugin(ToolRuntime)
const executor = entry.executor === 'ptc' ? new PtcExecutor(ctx) : new NativeToolExecutor(ctx)
ctx.tools.setExecutor(executor)
await entry.mount(ctx)
const schemas = ctx.tools.schemas(entry.scope?.(ctx)).sort((a, b) => a.name.localeCompare(b.name))
const scope = entry.scope?.(ctx)
const described = executor.describe(ctx.tools.definitions(scope))
const schemas = [...new Map([...ctx.tools.schemas(scope), ...described.schemas].map(schema => [schema.name, schema])).values()]
.sort((a, b) => a.name.localeCompare(b.name))
assertToolsHarvested(entry, schemas.length)
catalog.push({
pkg: entry.pkg,
+35 -35
View File
@@ -47,9 +47,9 @@ describe('volatile Loader updates', () => {
const fiber = await mountEntry(ctx, { Config, apply(owner: Context) {
owner.on('loader/volatile-update', events)
} }, { title: 'first' })
const reference = fiber.config as ReturnType<typeof Config>
const reference = fiber.effectiveConfig as ReturnType<typeof Config>
await update(fiber, { title: 'second' })
expect(fiber.config).toBe(reference)
expect(fiber.effectiveConfig).toBe(reference)
expect(reference.get()).toEqual({ title: 'second' })
expect(events).toHaveBeenCalledExactlyOnceWith([[]])
})
@@ -70,7 +70,7 @@ describe('volatile Loader updates', () => {
await update(first, { title: 'next', nested: { count: 2 } })
await first.await()
expect(mount).toHaveBeenCalledTimes(2)
expect(first.config).toBe(initial)
expect(first.effectiveConfig).toBe(initial)
expect(initial.title).toBe(title)
expect(configs[1]!.title.get()).toBe('hello')
expect(observed).toEqual([{ paths: [['title'], ['nested', 'count']], title: 'next', count: 2 }])
@@ -117,9 +117,9 @@ describe('volatile Loader updates', () => {
hooks.push(config.title.get())
})
} }, {})
const initial = fiber.config as ReturnType<typeof Config>
const initial = fiber.effectiveConfig as ReturnType<typeof Config>
await update(fiber, { title: 'accepted' })
expect(fiber.config).toBe(initial)
expect(fiber.effectiveConfig).toBe(initial)
expect(initial.title.get()).toBe('accepted')
await update(fiber, { title: 'accepted' })
expect(hooks).toEqual([])
@@ -180,11 +180,11 @@ describe('volatile Loader updates', () => {
const oldUrl = 'https://a.example/'
const newUrl = 'https://b.example/'
const fiber = await mountEntry(ctx, { Config: schema, apply }, { live: 'a', url: oldUrl })
const before = fiber.config as ReturnType<typeof schema>
const before = fiber.effectiveConfig as ReturnType<typeof schema>
await update(fiber, { live: 'b', url: newUrl })
await fiber.await()
expect(apply).toHaveBeenCalledTimes(2)
expect((fiber.config as ReturnType<typeof schema>).url.href).toBe(newUrl)
expect((fiber.effectiveConfig as ReturnType<typeof schema>).url.href).toBe(newUrl)
expect(before.live.get()).toBe('a')
await update(fiber, { live: 'c', url: newUrl, data: 'first' })
await update(fiber, { live: 'd', url: newUrl, data: 'second' })
@@ -199,7 +199,7 @@ describe('volatile Loader updates', () => {
owner.on('loader/volatile-update', events)
} }, { values: [undefined] })
await update(fiber, { values: [null] })
expect((fiber.config as ReturnType<typeof schema>).values.get()).toEqual([null])
expect((fiber.effectiveConfig as ReturnType<typeof schema>).values.get()).toEqual([null])
expect(events).toHaveBeenCalledExactlyOnceWith([['values']])
})
})
@@ -308,7 +308,7 @@ it('updates volatile children through Group and Include custom updates and saves
expect(includeEntry.fiber).toBe(originalInclude)
expect(group.fiber).toBe(originalGroup)
expect(consumer.fiber).toBe(originalConsumer)
expect(consumer.fiber!.config).toBe(initial)
expect(consumer.fiber!.effectiveConfig).toBe(initial)
expect(configs).toHaveLength(1)
expect(notifications).toHaveBeenCalledTimes(2)
expect(childUpdates).not.toHaveBeenCalled()
@@ -318,7 +318,7 @@ it('updates volatile children through Group and Include custom updates and saves
entry.fiber!.update({ title: 'custom-update', fixed: 'ordinary' })
expect(childUpdates).toHaveBeenCalledOnce()
expect(entry.options.config).toEqual({ title: 'custom-update', fixed: 'ordinary' })
expect((entry.fiber!.config as ReturnType<typeof Config>).title.get()).toBe('hello')
expect((entry.fiber!.effectiveConfig as ReturnType<typeof Config>).title.get()).toBe('hello')
entry.fiber!.update({ title: 'ephemeral', fixed: 'ordinary' }, true)
expect(childUpdates).toHaveBeenCalledTimes(2)
expect(entry.options.config).toEqual({ title: 'custom-update', fixed: 'ordinary' })
@@ -332,11 +332,11 @@ it('commits volatile edits in place and retains direct Fiber updates', async ()
const notifications = vi.fn()
const apply = vi.fn(function (owner: Context) { owner.on('loader/volatile-update', notifications) })
const fiber = await mountEntry(ctx, { Config, apply }, {})
const original = fiber.config as ReturnType<typeof Config>
const original = fiber.effectiveConfig as ReturnType<typeof Config>
await ctx.loader.create({ name: 'cordis:disabled', disabled: true })
await update(fiber, { title: 'enabled' })
expect(apply).toHaveBeenCalledOnce()
expect(fiber.config).toBe(original)
expect(fiber.effectiveConfig).toBe(original)
expect(original.title.get()).toBe('enabled')
expect(notifications).toHaveBeenCalledExactlyOnceWith([['title']])
await update(fiber, { title: 'invalid', nested: { count: -1 } })
@@ -344,7 +344,7 @@ it('commits volatile edits in place and retains direct Fiber updates', async ()
await update(fiber, { title: 'mixed', fixed: 'changed' })
expect(apply).toHaveBeenCalledTimes(2)
expect(original.title.get()).toBe('enabled')
const current = fiber.config as ReturnType<typeof Config>
const current = fiber.effectiveConfig as ReturnType<typeof Config>
expect(current.title.get()).toBe('mixed')
const hook = vi.fn((_config: unknown, _noSave: boolean, next: () => void) => { next() })
@@ -365,7 +365,7 @@ it('parses a volatile-only update through the config hook without patching the e
const fiber = await mountEntry(ctx, { Config, apply(owner: Context) {
owner.on('loader/volatile-update', () => { trace.push('notify') })
} }, { block: { title: 'initial' } })
const current = fiber.config as ReturnType<typeof Config>
const current = fiber.effectiveConfig as ReturnType<typeof Config>
const resolveHook = vi.fn((_raw: unknown, next: () => unknown) => next())
fiber.ctx.on('internal/config', resolveHook)
const contextPatch = vi.fn((_entry: unknown, next: () => void) => { next() })
@@ -379,7 +379,7 @@ it('parses a volatile-only update through the config hook without patching the e
expect(resolveHook).toHaveBeenCalledOnce()
expect(trace).toEqual(['validate', 'notify'])
expect(current.block.get()).toEqual({ title: 'changed' })
expect(fiber._config).toBe(raw)
expect(fiber.rawConfig).toBe(raw)
})
it.each([true, false])('keeps old references untouched when isolation replaces a dependency (available: %s)', async (available) => {
@@ -452,7 +452,7 @@ it.each(['group', 'include'] as const)('applies profile patches to a %s subtree
await reconcileProfilePatches(ctx, patch('second'), 'volatile-test')
expect(parent.fiber).toBe(parentFiber)
expect(entry.fiber).toBe(childFiber)
expect(entry.fiber!.config).toBe(original)
expect(entry.fiber!.effectiveConfig).toBe(original)
expect(original.title).toBe(reference)
expect(reference.get()).toBe('second')
expect(configs).toHaveLength(1)
@@ -481,9 +481,9 @@ it('logs an invalid candidate and keeps its references while other entries commi
await invalid.entry!.update({ config: { title: 'invalid', nested: { count: -1 } } })
await healthy.entry!.update({ config: { title: 'healthy' } })
expect(warning).toHaveBeenCalledTimes(2)
expect((invalid.config as ReturnType<typeof Config>).title.get()).toBe('hello')
expect(invalid._config).toEqual({ title: 'invalid', nested: { count: -1 } })
expect((healthy.config as ReturnType<typeof Config>).title.get()).toBe('healthy')
expect((invalid.effectiveConfig as ReturnType<typeof Config>).title.get()).toBe('hello')
expect(invalid.rawConfig).toEqual({ title: 'invalid', nested: { count: -1 } })
expect((healthy.effectiveConfig as ReturnType<typeof Config>).title.get()).toBe('healthy')
expect(events).toHaveBeenCalledExactlyOnceWith([['title']])
})
@@ -500,7 +500,7 @@ it.each([false, true])('retains raw expression edits for dependency restoration
await fiber.entry!.update({ config: raw })
await fiber.await()
expect(values).toEqual(volatile ? ['same'] : ['same', 'same'])
expect(fiber._config).toBe(raw)
expect(fiber.rawConfig).toBe(raw)
await provider.dispose()
await provide('old-route', 'new-route')
await fiber.await()
@@ -540,10 +540,10 @@ it('compares ordinary URL transforms by value and remounts when an expression re
const fixed = { __jsExpr: "ctx.get('volatile-transform-source').value" }
const raw = { url: 'https://example.com/', fixed, live: 'old' }
const fiber = await mountEntry(ctx, { Config: schema, apply: mount }, raw)
const current = fiber.config as ReturnType<typeof schema>
const current = fiber.effectiveConfig as ReturnType<typeof schema>
await update(fiber, { ...raw, live: 'new' })
expect(mount).toHaveBeenCalledOnce()
expect(fiber.config).toBe(current)
expect(fiber.effectiveConfig).toBe(current)
expect(current.url.href).toBe(raw.url)
expect(current.fixed).toBe('initial')
expect(current.live.get()).toBe('new')
@@ -554,7 +554,7 @@ it('compares ordinary URL transforms by value and remounts when an expression re
expect(current.fixed).toBe('initial')
expect(current.live.get()).toBe('new')
expect(events).toHaveBeenCalledOnce()
const replaced = fiber.config as ReturnType<typeof schema>
const replaced = fiber.effectiveConfig as ReturnType<typeof schema>
expect(replaced.fixed).toBe('changed')
expect(replaced.live.get()).toBe('remounted')
})
@@ -568,13 +568,13 @@ it('remounts when an ordinary parent expression removes a live reference path',
const mount = vi.fn(function (owner: Context) { owner.on('loader/volatile-update', events) })
const raw = { block: { __jsExpr: "ctx.get('volatile-path-source').block" }, live: 'old' }
const fiber = await mountEntry(ctx, { Config: schema, apply: mount }, raw)
const current = fiber.config as ReturnType<typeof schema>
const current = fiber.effectiveConfig as ReturnType<typeof schema>
source.block = undefined
await update(fiber, { ...raw, live: 'new' })
expect(mount).toHaveBeenCalledTimes(2)
expect(current.block.value.get()).toBe('old')
expect(events).not.toHaveBeenCalled()
expect((fiber.config as ReturnType<typeof schema>).block).toBeUndefined()
expect((fiber.effectiveConfig as ReturnType<typeof schema>).block).toBeUndefined()
})
it('keeps an ordinary plugin mounted when an absent object becomes its explicit default', async () => {
@@ -582,13 +582,13 @@ it('keeps an ordinary plugin mounted when an absent object becomes its explicit
const Config = z.object({ nested: z.object({ fixed: z.string() }).default({ fixed: 'default' }) })
const mount = vi.fn()
const fiber = await mountEntry(ctx, { Config, apply: mount }, {})
const config: unknown = fiber.config
const config: unknown = fiber.effectiveConfig
await update(fiber, { nested: { fixed: 'default' } })
expect(fiber.config).toBe(config)
expect(fiber.effectiveConfig).toBe(config)
expect(mount).toHaveBeenCalledTimes(1)
await update(fiber, { nested: { fixed: 'changed' } })
expect(mount).toHaveBeenCalledTimes(2)
expect(fiber.config).toEqual({ nested: { fixed: 'changed' } })
expect(fiber.effectiveConfig).toEqual({ nested: { fixed: 'changed' } })
})
it('keeps a noSave volatile value as the pending raw config across a no-op entry update', async () => {
@@ -596,11 +596,11 @@ it('keeps a noSave volatile value as the pending raw config across a no-op entry
const fiber = await mountEntry(ctx, { Config, apply() {} }, { title: 'saved' })
fiber.update({ title: 'ephemeral' }, true)
await fiber.await()
expect((fiber.config as ReturnType<typeof Config>).title.get()).toBe('ephemeral')
expect((fiber.effectiveConfig as ReturnType<typeof Config>).title.get()).toBe('ephemeral')
expect(fiber.entry!.options.config).toEqual({ title: 'saved' })
await fiber.entry!.update({ config: { title: 'saved' } })
expect(fiber._config).toEqual({ title: 'ephemeral' })
expect((fiber.config as ReturnType<typeof Config>).title.get()).toBe('ephemeral')
expect(fiber.rawConfig).toEqual({ title: 'ephemeral' })
expect((fiber.effectiveConfig as ReturnType<typeof Config>).title.get()).toBe('ephemeral')
})
it('isolates a failing notification listener and dispatches with a Context receiver', async () => {
@@ -613,11 +613,11 @@ it('isolates a failing notification listener and dispatches with a Context recei
const warning = vi.spyOn(ctx.logger, 'warn').mockImplementation(() => {})
onTestFinished(() => { warning.mockRestore() })
await update(fiber, { title: 'committed' })
expect((fiber.config as ReturnType<typeof Config>).title.get()).toBe('committed')
expect((fiber.effectiveConfig as ReturnType<typeof Config>).title.get()).toBe('committed')
expect(receivers).toEqual([fiber])
expect(warning).toHaveBeenCalledWith(expect.objectContaining({ message: 'listener boom' }))
await update(other, { title: 'unaffected' })
expect((other.config as ReturnType<typeof Config>).title.get()).toBe('unaffected')
expect((other.effectiveConfig as ReturnType<typeof Config>).title.get()).toBe('unaffected')
})
it('remounts instead of committing when an ordinary field parses into a fresh class instance', async () => {
@@ -626,9 +626,9 @@ it('remounts instead of committing when an ordinary field parses into a fresh cl
const schema = z.object({ handle: z.transform(z.string(), value => new Handle(value)), live: z.string().volatile() })
const mount = vi.fn()
const fiber = await mountEntry(ctx, { Config: schema, apply: mount }, { handle: 'h', live: 'old' })
const before = fiber.config as ReturnType<typeof schema>
const before = fiber.effectiveConfig as ReturnType<typeof schema>
await update(fiber, { handle: 'h', live: 'new' })
expect(mount).toHaveBeenCalledTimes(2)
expect(before.live.get()).toBe('old')
expect((fiber.config as ReturnType<typeof schema>).live.get()).toBe('new')
expect((fiber.effectiveConfig as ReturnType<typeof schema>).live.get()).toBe('new')
})
+1 -1
View File
@@ -8,7 +8,7 @@ import { join } from 'node:path'
import { fileURLToPath } from 'node:url'
import { execa } from 'execa'
import { describe, expect, it, onTestFinished } from 'vitest'
import { ON_DEMAND_BUNDLES } from '../packages/boot/app-boot/src/official-bundle-packages.ts'
import { ON_DEMAND_BUNDLES } from '../packages/boot/official-bundles/src/bundles.ts'
import { generateOfficialBundleCatalog } from './gen-official-bundle-catalog.ts'
import {
createRegistryManifest,
+19 -42
View File
@@ -64,7 +64,6 @@
"187ad4724635a2d845592799c3a2600437c0d2fe17e0d1c3018018d5e8b0b863": 1
},
"apps/desktop/tests/update-dialog.spec.ts": {
"3226b936a41e00f2cfd8b9563d3bfcc8eaef7cf04996abe74fb981169a07b91b": 1,
"4000017f1f3dabd4bdf8eb91120cf4ff0dd09bad89d4683151f4785974d204a5": 1
},
"apps/desktop/tests/update-http-executor.spec.ts": {
@@ -406,7 +405,6 @@
"packages/client/modules/tests/node-half.client.spec.ts": {
"621628e4e895b24171e82d1b3fa3ecb12970e8eb0dfccab8bacc8c5784188a28": 2,
"6c4ace8d135ab45685d0152eefb0f8156bbf95f66cefedd9a1a8da081d3cb180": 1,
"6d4d3e6035bfeaac7068a832f0d5c89f1112961164709c0eeb18dd8937d74cac": 1,
"8eb8ea3592cbd4e828a33b2cbe2876089d3923738996fb2259ded563811a1a85": 1,
"c4f0b7f87a16d26a1e4e7ef7a07991e789f0bbcfdbe2240a97fcd879668b5b3f": 1,
"f549d88ac2b1db67fb33f18a024c116051ff83d3f8ad0433ba4ceedc679e9044": 2
@@ -427,9 +425,6 @@
"packages/client/ui-attachment/tests/attachment-rail.client.spec.tsx": {
"b42ab24a43e0f7542bf1deb8f7ac3a76698d29e098f8dc575af6c08774987c32": 1
},
"packages/client/ui-attachment/tests/composer-attachments.client.spec.tsx": {
"ab22ebe23f59b5aa76d42502a0421f3953d1d170e8a4f900a0c442daf6f1397d": 1
},
"packages/client/ui-chat/src/client/conversation-nodes/turn-process.ts": {
"a7d8cc8d42e37ad5cc4882241492ac572ed8163c7fb8663180e893c7178e906f": 1
},
@@ -751,9 +746,6 @@
"packages/client/ui-workflow-run/tests/workflow-run.client.spec.tsx": {
"9f5585ad6cebba08252165f2dcc63c1e16101e0306fab754afb641b417e7789d": 1
},
"packages/client/ui-workspace/tests/animated-rows.client.spec.tsx": {
"a898fd26a24bbfa72e00b9b825779a95c1d1c2bd447ebf8f21e8b2d38c3fb08c": 1
},
"packages/client/ui-workspace/tests/apply.client.spec.ts": {
"b467270b0c2a95d6b29939f6baf4d5c8e5031e99a1d15437a3060f35735d8d37": 1
},
@@ -913,16 +905,29 @@
"packages/core/system-prompt/src/index.ts": {
"7fe58280287ff63d40e979e72329f975e11015a95e9a8eec207a5a78748d3c89": 1
},
"packages/core/tools/src/index.ts": {
"6061de0a1e01547eb1abf9d4e1b5fa79fda112fa35dda980775491123413dcce": 1
"packages/core/tool-executor-ptc/tests/ptc.spec.ts": {
"52faf4558fcd91bd27dbdfa7e497843e8eeb75ea5f68ee231335018d5b3f2053": 1
},
"packages/core/tool-executor-ptc/tests/py-types.spec.ts": {
"0d733b2c7c458f6a7c4d287ed741c321842339cbe5908a8720660883423474c0": 9,
"23ebbf16be06365be256c1ceb77869576a026644f3514e1737030c4556849f4a": 1,
"3ec1a0855f2cf4a5b3646725ea95c07addd33d5957ba6af530b944cd26f1ea8f": 1,
"43155833aa226b066cf374829e013176403ddbd6acd313ccf90f6cfb92ceec06": 1,
"5d1224b16a64bb138a708eb3e3e130df2ddf1f8f656617e3f1b3f2583250cf63": 1,
"6aecd519658d0d2fb040f0289d6db823e3f6bbb26fcb2b118dc7f7f2c1d3eb06": 1,
"9b2b04bd2ecde0c56b00ae050b721d56b3c29a1c834b982c9c2f6e62dbcd0a04": 1,
"d334b0b55263b6cb2125317ce96b766155b11455d40d600590f5302f5e0543cc": 1,
"d6650a0b52b5f7311afff190373b4a1cbd31ba44f9fa0dff09395f6f2901e32d": 1
},
"packages/core/tool-executor-ptc/tests/ts-types.spec.ts": {
"054c87ca977d98749baf70bb5dcad1bb8d9e725d08a480b7804a181434633873": 1,
"0d733b2c7c458f6a7c4d287ed741c321842339cbe5908a8720660883423474c0": 1,
"84a5877f154b369cc0528281904d1dc18e20fd3ad74a26c939301ba0db20ae02": 1,
"d80a3368311f07e812a76ff265125b072175f58b6003d9f96a37d4f3e70e3dec": 1
},
"packages/core/tools/src/schema.ts": {
"62870121f1e1644b67c62606e94a375bb40f880cc88edf7cd6baab0249e9da46": 1
},
"packages/core/tools/src/testing.ts": {
"5be3dd49608bd752ae1371aae8c8d748a07158f7a597e2506af79a621f74f0b3": 1,
"6fe9024d30e0d4757a348b83d246925b3f5aeaf2e79a0eff4bf12f3c9fb5826b": 1
},
"packages/core/tools/tests/execution-mode.spec.ts": {
"33dec0693f265044c830377981b51852b773e1a8a765e0b3398a4542c646537b": 1,
"f9c9afc4ff66b96c59303ffa5475e26719ef101dc45504fee4b56145ff4be02c": 1
@@ -937,20 +942,6 @@
"c21022b1c0c54a213857769a5d72bc249dce83dd63c9d6f2370139bbea3288c9": 1,
"d1250fc465aac30c8474c1ee43ecbafc0a8c072d5e607961da1295acc710a257": 1
},
"packages/core/tools/tests/ptc.spec.ts": {
"52faf4558fcd91bd27dbdfa7e497843e8eeb75ea5f68ee231335018d5b3f2053": 1
},
"packages/core/tools/tests/py-types.spec.ts": {
"0d733b2c7c458f6a7c4d287ed741c321842339cbe5908a8720660883423474c0": 9,
"23ebbf16be06365be256c1ceb77869576a026644f3514e1737030c4556849f4a": 1,
"3ec1a0855f2cf4a5b3646725ea95c07addd33d5957ba6af530b944cd26f1ea8f": 1,
"43155833aa226b066cf374829e013176403ddbd6acd313ccf90f6cfb92ceec06": 1,
"5d1224b16a64bb138a708eb3e3e130df2ddf1f8f656617e3f1b3f2583250cf63": 1,
"6aecd519658d0d2fb040f0289d6db823e3f6bbb26fcb2b118dc7f7f2c1d3eb06": 1,
"9b2b04bd2ecde0c56b00ae050b721d56b3c29a1c834b982c9c2f6e62dbcd0a04": 1,
"d334b0b55263b6cb2125317ce96b766155b11455d40d600590f5302f5e0543cc": 1,
"d6650a0b52b5f7311afff190373b4a1cbd31ba44f9fa0dff09395f6f2901e32d": 1
},
"packages/core/tools/tests/schema.spec.ts": {
"2994ddac72096564a65b1d11cfe50c5b5f21de0cca775ed7b758d031b9a4df99": 1,
"7724a969f2b794fc521b1a246d444c75616cf27e6ce073b00e8c8956b626634d": 1,
@@ -974,12 +965,6 @@
"d3478c9f3a88906a17d5daa6d19c3694a33618cfc000c7c0ee8f3e16f2697322": 1,
"f9c9afc4ff66b96c59303ffa5475e26719ef101dc45504fee4b56145ff4be02c": 2
},
"packages/core/tools/tests/ts-types.spec.ts": {
"054c87ca977d98749baf70bb5dcad1bb8d9e725d08a480b7804a181434633873": 1,
"0d733b2c7c458f6a7c4d287ed741c321842339cbe5908a8720660883423474c0": 1,
"84a5877f154b369cc0528281904d1dc18e20fd3ad74a26c939301ba0db20ae02": 1,
"d80a3368311f07e812a76ff265125b072175f58b6003d9f96a37d4f3e70e3dec": 1
},
"packages/credentials/credentials-local/tests/drain.spec.ts": {
"a96f4a3497a1c661f76e47ef5fda3c32d2759a7a5f9c519527fa46801a91bf79": 1
},
@@ -1505,10 +1490,6 @@
"cb524acd0159b942e7974d76b581b1f419b8653d1734946a3f154dda8e8f4762": 1,
"f10c6a0a3a762be54c2de87ab5d787e37a21a89c9d468bf81d0658c2bf15d581": 1
},
"packages/session-query/session-log-export/tests/client-apply.client.spec.tsx": {
"04984a2ce1486250236e638e0197da0a1a840855ed3975c59130a85358c7e5fe": 1,
"eee35b4d8641824c6eaacfe03ed60d353d81d5257d1111132f34d46a7b13ef35": 1
},
"packages/session-query/session-log-export/tests/controller.client.spec.ts": {
"746ebc5aade01a50cc9a74fc07b1023addf021aef80b62c069a437ac140c9e64": 1,
"c920d88f6eb309ef5991608642a1ab0fd972690538b33c34d86c68d07ddd6edf": 1
@@ -1516,10 +1497,6 @@
"packages/session-query/session-log-export/tests/dialog.client.spec.tsx": {
"a9b1969d76db0b05f87c862956f8ff45c52f1f4a5b32a206339de0c9fee99a77": 1
},
"packages/session-query/session-log-export/tests/header-action.client.spec.tsx": {
"238a292bbe3a61acbc4f2d96fff474df5fba1577357219a88c914813786b56a7": 1,
"47d3a14a42ef7142ea22071677b42265f02fba5a2a0cf4210f4f9c7c5a876d10": 1
},
"packages/session-query/session-log-export/tests/loader-composition.host.spec.ts": {
"59c63f734e8bba862325f776e3fcbebd78a84cfb4161f8df41c216950d9bf351": 1,
"e12d49327a6708ae3ace371e28ed0deb471dc8895c584ba94a86af344c15cec3": 1
+2 -1
View File
@@ -8,7 +8,8 @@ import { describe, expect, it, onTestFinished } from 'vitest'
import type { EntryOptions } from '@deepseek-ai/cordis-plugin-loader'
import { loadOverlayPatches } from '../packages/boot/app-boot/src/index.ts'
import { readPluginMeta } from '../packages/boot/app-boot/src/package-meta.ts'
import { OPTIONAL_BUNDLES, PROFILE_TEMPLATES, bundlePatchPaths, composeEntries, initProfile, readProfileManifest } from '../packages/boot/app-boot/src/profile.ts'
import { bundlePatchPaths, composeEntries, initProfile, readProfileManifest } from '../packages/boot/app-boot/src/profile.ts'
import { OPTIONAL_BUNDLES, PROFILE_TEMPLATES } from '../packages/boot/official-bundles/src/bundles.ts'
import type { DshBundleManifest } from '../packages/util/package-manifest/src/types.ts'
import { createPluginProfile } from '../apps/desktop/src/project-manager.ts'
+2
View File
@@ -16,6 +16,7 @@ export interface ProductPackagePolicy {
* Optional integrations may be reached by an optional bundle without becoming default capabilities.
*/
export const PRODUCT_PACKAGE_POLICY: Readonly<Record<string, ProductPackagePolicy>> = {
'packages/core/agent-tool-presentation': { category: 'optional', reason: 'Deprecated selector retained for existing executor configurations.' },
'packages/browser-use/browser-use': { category: 'optional', reason: 'Service definition used by explicitly selected browser providers.' },
'packages/computer-use/computer-use': { category: 'optional', reason: 'Service definition used by explicitly selected computer providers.' },
'packages/lsp/lsp': { category: 'optional', reason: 'Service definition for deployment-configured language servers.' },
@@ -38,6 +39,7 @@ export const PRODUCT_PACKAGE_POLICY: Readonly<Record<string, ProductPackagePolic
'packages/sdk/client': { category: 'sdk', reason: 'Published TypeScript embedding client runs in the consumer process.' },
'packages/typert/generator': { category: 'build', reason: 'Build-time generation of type metadata.' },
'packages/test-support/agent-loop-testkit': { category: 'test', reason: 'Published agent-loop test harness.' },
'packages/test-support/tool-testkit': { category: 'test', reason: 'Shared tool definition and execution fixtures for tests and benchmarks.' },
'packages/test-support/client-runtime': { category: 'test', reason: 'Published browser plugin test harness.' },
'packages/test-support/llm-mock-server': { category: 'test', reason: 'Published model-provider test server.' },
'packages/test-support/llm-replay': { category: 'test', reason: 'Published recorded-model replay harness.' },
+1 -1
View File
@@ -5,7 +5,7 @@ import { tmpdir } from 'node:os'
import { dirname, join, resolve } from 'node:path'
import { afterEach, describe, expect, it, vi } from 'vitest'
import { officialClientBuildEnvironment, writeClientBuildRecord } from '../client-build-environment.ts'
import { ON_DEMAND_BUNDLES } from '../../packages/boot/app-boot/src/official-bundles.ts'
import { ON_DEMAND_BUNDLES } from '../../packages/boot/official-bundles/src/bundles.ts'
import { releaseFamily, type ReleaseMember } from './families.ts'
import { compareVersions, nextVendorVersion, planShared, reachesPayload } from './bump.ts'
+1 -1
View File
@@ -16,7 +16,7 @@ import {
readClientBuildRecord,
} from '../client-build-environment.ts'
import { validateTarballPayload } from '../publication-payload.ts'
import { ON_DEMAND_BUNDLES } from '../../packages/boot/app-boot/src/official-bundle-packages.ts'
import { ON_DEMAND_BUNDLES } from '../../packages/boot/official-bundles/src/bundles.ts'
/**
* Dependency sections a consumer must publish after, because npm resolves them
@@ -4,7 +4,7 @@ import { existsSync, readFileSync, realpathSync } from 'node:fs'
import { createRequire } from 'node:module'
import { join } from 'node:path'
import { hasExperimentalPackageReference } from '../experimental-package-policy.ts'
import { OPTIONAL_BUNDLES } from '../../packages/boot/app-boot/src/profile.ts'
import { OPTIONAL_BUNDLES } from '../../packages/boot/official-bundles/src/bundles.ts'
interface InstalledManifest {
name: string
+146 -1
View File
@@ -1,11 +1,18 @@
/** Recorded npm evidence stays intact while authored files and exact edits remain checked. */
import { readFileSync } from 'node:fs'
import { describe, expect, it } from 'vitest'
import { exactEditState, isRescopeExcluded } from './rescope-vendor.ts'
import { exactEditState, isRescopeExcluded, rescopeText } from './rescope-vendor.ts'
const ANCHOR = '\n## Sync procedure'
const INSERTED = `\n15. **rescope**: one log entry.\n${ANCHOR}`
function expectRoundTrip(file: string, text: string, scoped: string, lines: number): void {
expect(rescopeText(text, file)).toEqual({ text: scoped, lines })
expect(rescopeText(scoped, file)).toEqual({ text: scoped, lines: 0 })
expect(rescopeText(scoped, file, true)).toEqual({ text, lines })
}
describe('rescope file selection', () => {
it('preserves the recorded npm resolution', () => {
expect(isRescopeExcluded('scripts/dependency-catalog/package-lock.json')).toBe(true)
@@ -51,3 +58,141 @@ describe('exactEditState', () => {
expect(exactEditState('x\n', 'a = 1', 'b = 2', 1)).toBe('invalid')
})
})
describe('product identifiers', () => {
it.each([
'apps/cli/tests/optional-bundle-transitions.e2e.ts',
'apps/web/tests/agent-preset-selection.e2e.ts',
'apps/web/tests/developer-tools-settings.e2e.ts',
'docs/subsystems/schedule.md',
'docs/subsystems/schedule.zh.md',
'docs/upgrade-guide/v0.2.0-rc.2/schedule-bundle-retired/guide.md',
'docs/upgrade-guide/v0.2.0-rc.2/schedule-bundle-retired/guide.zh.md',
'docs/upgrade-guide/v0.2.1-alpha.1/subagent-preset-composition/guide.md',
'docs/upgrade-guide/v0.2.1-alpha.1/subagent-preset-composition/guide.zh.md',
'docs/user/guide/schedule.md',
'docs/user/guide/schedule.zh.md',
'packages/bundle/web-app/cordis.patch.yml',
'packages/client/ui-agent-preset/src/client/CreatePluginMenuItem.tsx',
'packages/client/ui-agent-preset/src/client/analytics.ts',
'packages/client/ui-agent-preset/tests/analytics.client.spec.ts',
'packages/client/ui-agent-preset/tests/components.client.spec.tsx',
'packages/client/ui-agent-preset/tests/create-plugin-menu-item.client.spec.tsx',
'packages/client/ui-agent-preset/tests/section-store.client.spec.ts',
'packages/client/ui-conversation/src/client/input/submission-analytics.ts',
'packages/client/ui-conversation/tests/service-orchestration.client.spec.ts',
'packages/client/ui-entry-customization/src/client/index.ts',
'packages/client/ui-entry-customization/tests/menu.client.spec.tsx',
'packages/client/ui-workspace/tests/workspaces-service.client.spec.ts',
'packages/extensions/cordis-host-runner/tests/inspect-registry.spec.ts',
'snapshots/session/cordis-inspect-liveness/client-fixture.mjs',
'snapshots/session/cordis-inspect-timeout/client-fixture.mjs',
])('preserves the product values in %s', (file) => {
const text = readFileSync(new URL(`../${file}`, import.meta.url), 'utf8')
expect(text).toMatch(/(['"`])cordis(?:\/inspect-query(?:-resolved)?)?\1/)
expect(isRescopeExcluded(file)).toBe(false)
expect(rescopeText(text, file)).toEqual({ text, lines: 0 })
})
it('still renames other libraries in a preset consumer and reverses them', () => {
const file = 'packages/client/ui-agent-preset/src/client/CreatePluginMenuItem.tsx'
const text = "const preset = 'cordis'\nimport utils from 'cosmokit'\nimport Schema from 'schemastery'\n"
const scoped = "const preset = 'cordis'\nimport utils from '@deepseek-ai/cosmokit'\nimport Schema from '@deepseek-ai/schemastery'\n"
expect(rescopeText(text, file)).toEqual({ text: scoped, lines: 2 })
expect(rescopeText(scoped, file)).toEqual({ text: scoped, lines: 0 })
expect(rescopeText(scoped, file, true)).toEqual({ text, lines: 2 })
})
it('keeps unlisted package references subject to residue detection', () => {
const file = 'packages/example/src/index.ts'
const text = "import { Context } from 'cordis'\nimport type Schema from 'schemastery'\n"
const scoped = "import { Context } from '@deepseek-ai/cordis'\nimport type Schema from '@deepseek-ai/schemastery'\n"
expect(rescopeText(text, file)).toEqual({ text: scoped, lines: 2 })
expect(rescopeText(scoped, file)).toEqual({ text: scoped, lines: 0 })
expect(rescopeText(scoped, file, true)).toEqual({ text, lines: 2 })
expect(rescopeText("ctx.emit('cordis/inspect-query')", file).text).toBe("ctx.emit('@deepseek-ai/cordis/inspect-query')")
})
it.each([
['import', "import { Context } from 'cordis'", "import { Context } from '@deepseek-ai/cordis'"],
['multiline import', "import {\n Context,\n} from 'cordis'", "import {\n Context,\n} from '@deepseek-ai/cordis'"],
['side-effect import', "import 'cordis'", "import '@deepseek-ai/cordis'"],
['export', "export { Context } from 'cordis/subpath'", "export { Context } from '@deepseek-ai/cordis/subpath'"],
['import equals', "import Cordis = require('cordis')", "import Cordis = require('@deepseek-ai/cordis')"],
['dynamic import', 'const cordis = import(`cordis`)', 'const cordis = import(`@deepseek-ai/cordis`)'],
['require', 'const cordis = require("cordis/subpath")', 'const cordis = require("@deepseek-ai/cordis/subpath")'],
['import type', "type Context = import('cordis').Context", "type Context = import('@deepseek-ai/cordis').Context"],
['module declaration', "declare module 'cordis' {}", "declare module '@deepseek-ai/cordis' {}"],
['JSDoc type', "/** @param {import('cordis').Context} ctx */\nfunction apply(ctx) {}", "/** @param {import('@deepseek-ai/cordis').Context} ctx */\nfunction apply(ctx) {}"],
['JSDoc import', "/** @import { Context } from 'cordis' */\nfunction apply(ctx) {}", "/** @import { Context } from '@deepseek-ai/cordis' */\nfunction apply(ctx) {}"],
['same-line imports', "import 'cordis'; import 'cordis/subpath'", "import '@deepseek-ai/cordis'; import '@deepseek-ai/cordis/subpath'"],
])('rewrites %s without changing same-file product data', (_label, source, target) => {
const file = 'packages/client/ui-agent-preset/src/client/CreatePluginMenuItem.tsx'
const product = "const preset = 'cordis'\nctx.emit('cordis/inspect-query')\nconst fixture = \"import 'cordis'\"\n"
const text = `${product}${source}\n`
const scoped = `${product}${target}\n`
expectRoundTrip(file, text, scoped, 1)
})
it.each([
['apps/cli/tests/optional-bundle-transitions.e2e.ts', 1],
['packages/client/ui-conversation/src/client/input/submission-analytics.ts', 1],
['packages/client/ui-conversation/tests/service-orchestration.client.spec.ts', 1],
['packages/client/ui-entry-customization/src/client/index.ts', 1],
['packages/client/ui-entry-customization/tests/menu.client.spec.tsx', 1],
['packages/client/ui-workspace/tests/workspaces-service.client.spec.ts', 1],
['packages/extensions/cordis-host-runner/tests/inspect-registry.spec.ts', 2],
['snapshots/session/cordis-inspect-liveness/client-fixture.mjs', 1],
['snapshots/session/cordis-inspect-timeout/client-fixture.mjs', 2],
] as const)('reverses real package references in %s and restores them', (file, lines) => {
const scoped = readFileSync(new URL(`../${file}`, import.meta.url), 'utf8')
const text = scoped.replaceAll("'@deepseek-ai/cordis'", "'cordis'")
expect(text).not.toBe(scoped)
expectRoundTrip(file, text, scoped, lines)
expect(rescopeText(text, file, true)).toEqual({ text, lines: 0 })
})
it('rewrites multiline fenced imports while preserving prose and fenced product data', () => {
const file = 'docs/subsystems/schedule.md'
const text = "The `cordis` preset.\n```ts\nconst preset = 'cordis'\nimport {\n Context,\n} from 'cordis'\n```\n"
const scoped = "The `cordis` preset.\n```ts\nconst preset = 'cordis'\nimport {\n Context,\n} from '@deepseek-ai/cordis'\n```\n"
expectRoundTrip(file, text, scoped, 1)
})
it.each(['cordis', "'cordis'", '"cordis/subpath"'])('rewrites the YAML name %s without changing product comments', (name) => {
const text = `# The 'cordis' preset.\n- name: ${name} # 'cordis' stays product data\n`
const scoped = `# The 'cordis' preset.\n- name: ${name.replace('cordis', '@deepseek-ai/cordis')} # 'cordis' stays product data\n`
for (const [file, wrap] of [
['packages/bundle/web-app/cordis.patch.yml', (value: string) => value],
['docs/subsystems/schedule.md', (value: string) => `The \`cordis\` preset.\n\`\`\`yaml\n${value}\`\`\`\n`],
] as const) {
expectRoundTrip(file, wrap(text), wrap(scoped), 1)
}
})
it.each(['scripts/rescope-vendor.spec.ts', 'scripts/gen-third-party-notices.spec.ts'])('keeps both package spellings in the fixture owner %s', (file) => {
const text = "import 'cordis'\nimport '@deepseek-ai/cordis'\nconst fixture = \"import 'cosmokit'\"\n"
expect(rescopeText(text, file)).toEqual({ text, lines: 0 })
expect(rescopeText(text, file, true)).toEqual({ text, lines: 0 })
})
it('keeps schema runtime identifiers while rewriting their same-file import', () => {
const file = 'vendor/loader/src/config/diff.ts'
const text = "const key = Symbol.for('schemastery')\nconst metadata = { vendor: 'schemastery' }\nimport Schema from 'schemastery'\n"
const scoped = "const key = Symbol.for('schemastery')\nconst metadata = { vendor: 'schemastery' }\nimport Schema from '@deepseek-ai/schemastery'\n"
expectRoundTrip(file, text, scoped, 1)
})
it('rewrites declarations in an older event-name exception', () => {
const file = 'packages/extensions/cordis-host-runner/src/index.ts'
const text = "ctx.emit('cordis/inspect-query')\nimport { Context } from 'cordis'\ndeclare module 'cordis' {}\n"
const scoped = "ctx.emit('cordis/inspect-query')\nimport { Context } from '@deepseek-ai/cordis'\ndeclare module '@deepseek-ai/cordis' {}\n"
expectRoundTrip(file, text, scoped, 2)
})
it.each(['```ts\n```\n', "```ts\nimport 'cordis'\n"])('keeps fence line counts without requiring a closing fence', (text) => {
const file = 'docs/subsystems/schedule.md'
const scoped = text.replace("import 'cordis'", "import '@deepseek-ai/cordis'")
expectRoundTrip(file, text, scoped, scoped === text ? 0 : 1)
})
})
+129 -33
View File
@@ -12,9 +12,9 @@
* `cordis.yml`, the Loader's `cordis:` builtin prefix, `cordis-config-entry`,
* `@deepseek-ai/dsh-tool-cordis`, and `cordiverse/cordis`, and makes the
* rewrite idempotent because the scoped name's `cordis` is preceded by `/`.
* Markdown follows the rename inside every fence, and in `docs/` prose too:
* a tutorial that teaches an unresolvable name is wrong, while prose elsewhere
* records what was true when it was written.
* Markdown follows the token rule in fences and `docs/` prose. Audited product
* names stay intact; their source module references and YAML plugin names still
* follow the rename. Prose elsewhere records what was true when it was written.
*
* Sites the token rule cannot express (dot-notation access, unquoted object
* keys, regex literals, the vendored-manifest table) are listed in
@@ -31,6 +31,7 @@ import { execFileSync } from 'node:child_process'
import { existsSync, readFileSync, realpathSync, writeFileSync } from 'node:fs'
import { resolve } from 'node:path'
import { fileURLToPath } from 'node:url'
import ts from 'typescript'
const root = resolve(import.meta.dirname, '..')
@@ -66,13 +67,14 @@ interface ExactEdit {
}
/**
* A file where an upstream name also appears as a vendor DIRECTORY name or an
* upstream runtime identifier: the generic pass is disabled for the listed
* names and {@link EXACT_EDITS} renames the real package-name occurrences.
* A file where an upstream name also identifies runtime metadata, directories,
* presets, events or locale keys. Its module references and YAML plugin names
* still change; fixture owners retain both package spellings throughout.
*/
interface GenericSkip {
readonly file: string
readonly upstream: readonly string[]
readonly fixture?: true
}
const GENERIC_SKIPS: readonly GenericSkip[] = [
@@ -83,8 +85,10 @@ const GENERIC_SKIPS: readonly GenericSkip[] = [
// Native schema detection and its fixture share the vendored runtime's Symbol.for identifier.
{ file: 'packages/boot/app-boot/src/config-schema/native.ts', upstream: ['schemastery'] },
{ file: 'packages/boot/app-boot/tests/config-schema.spec.ts', upstream: ['schemastery'] },
// The codemod's positive and negative fixtures contain both package spellings.
{ file: 'scripts/rescope-vendor.spec.ts', upstream: RENAMES.map(rename => rename.upstream), fixture: true },
// Asserts the vendored-manifest table, which gains an upstream-name column.
{ file: 'scripts/gen-third-party-notices.spec.ts', upstream: RENAMES.map(rename => rename.upstream) },
{ file: 'scripts/gen-third-party-notices.spec.ts', upstream: RENAMES.map(rename => rename.upstream), fixture: true },
// `cordis` is also an agent-preset id, so in these files the bare name is
// product data, not a package reference. Renaming it changed which preset
// the creator flow stages and which id the roster reports.
@@ -98,6 +102,30 @@ const GENERIC_SKIPS: readonly GenericSkip[] = [
{ file: 'apps/cli/tests/profiles/web/tests/fixtures/creator-plugin-manager.mjs', upstream: ['cordis'] },
{ file: 'apps/web/tests/agent-preset-authoring.e2e.ts', upstream: ['cordis'] },
{ file: 'packages/preset/agent-preset-registry/tests/session.spec.ts', upstream: ['cordis'] },
// Preset selection, reminder policy and UI copy use the same product id.
{ file: 'apps/cli/tests/optional-bundle-transitions.e2e.ts', upstream: ['cordis'] },
{ file: 'apps/web/tests/agent-preset-selection.e2e.ts', upstream: ['cordis'] },
{ file: 'apps/web/tests/developer-tools-settings.e2e.ts', upstream: ['cordis'] },
{ file: 'docs/subsystems/schedule.md', upstream: ['cordis'] },
{ file: 'docs/subsystems/schedule.zh.md', upstream: ['cordis'] },
{ file: 'docs/upgrade-guide/v0.2.0-rc.2/schedule-bundle-retired/guide.md', upstream: ['cordis'] },
{ file: 'docs/upgrade-guide/v0.2.0-rc.2/schedule-bundle-retired/guide.zh.md', upstream: ['cordis'] },
{ file: 'docs/upgrade-guide/v0.2.1-alpha.1/subagent-preset-composition/guide.md', upstream: ['cordis'] },
{ file: 'docs/upgrade-guide/v0.2.1-alpha.1/subagent-preset-composition/guide.zh.md', upstream: ['cordis'] },
{ file: 'docs/user/guide/schedule.md', upstream: ['cordis'] },
{ file: 'docs/user/guide/schedule.zh.md', upstream: ['cordis'] },
{ file: 'packages/bundle/web-app/cordis.patch.yml', upstream: ['cordis'] },
{ file: 'packages/client/ui-agent-preset/src/client/CreatePluginMenuItem.tsx', upstream: ['cordis'] },
{ file: 'packages/client/ui-agent-preset/src/client/analytics.ts', upstream: ['cordis'] },
{ file: 'packages/client/ui-agent-preset/tests/analytics.client.spec.ts', upstream: ['cordis'] },
{ file: 'packages/client/ui-agent-preset/tests/components.client.spec.tsx', upstream: ['cordis'] },
{ file: 'packages/client/ui-agent-preset/tests/create-plugin-menu-item.client.spec.tsx', upstream: ['cordis'] },
{ file: 'packages/client/ui-agent-preset/tests/section-store.client.spec.ts', upstream: ['cordis'] },
{ file: 'packages/client/ui-conversation/src/client/input/submission-analytics.ts', upstream: ['cordis'] },
{ file: 'packages/client/ui-conversation/tests/service-orchestration.client.spec.ts', upstream: ['cordis'] },
{ file: 'packages/client/ui-entry-customization/src/client/index.ts', upstream: ['cordis'] },
{ file: 'packages/client/ui-entry-customization/tests/menu.client.spec.tsx', upstream: ['cordis'] },
{ file: 'packages/client/ui-workspace/tests/workspaces-service.client.spec.ts', upstream: ['cordis'] },
// The preset-roster loop names the `cordis` preset id, not a package.
{ file: 'apps/cli/tests/windows-shell.spec.ts', upstream: ['cordis'] },
// GROUP_ORDER holds `packages/<group>/` directory names, not package names.
@@ -124,6 +152,10 @@ const GENERIC_SKIPS: readonly GenericSkip[] = [
{ file: 'packages/extensions/tool-cordis/src/providers.ts', upstream: ['cordis'] },
{ file: 'packages/extensions/ui-cordis/src/client/index.ts', upstream: ['cordis'] },
{ file: 'packages/extensions/ui-cordis/src/client/inventory.ts', upstream: ['cordis'] },
// Inspector fixtures use the same extension event domain.
{ file: 'packages/extensions/cordis-host-runner/tests/inspect-registry.spec.ts', upstream: ['cordis'] },
{ file: 'snapshots/session/cordis-inspect-liveness/client-fixture.mjs', upstream: ['cordis'] },
{ file: 'snapshots/session/cordis-inspect-timeout/client-fixture.mjs', upstream: ['cordis'] },
// `cordis/tree` is an Inspector observation topic, not a package subpath.
{ file: 'packages/experimental/inspector/src/shared/bridge/messages/cordis.ts', upstream: ['cordis'] },
{ file: 'packages/experimental/inspector/tests/cordis-query.host.spec.ts', upstream: ['cordis'] },
@@ -470,7 +502,7 @@ function patterns(reverse: boolean): Pattern[] {
.map(rename => ({
...rename,
token: new RegExp(`(['"\`])${escapeRegExp(rename.from)}((?:/[^'"\`\\s]*)?)\\1`, 'g'),
yamlName: new RegExp(`^(\\s*(?:-\\s*)?name:[ \\t]+)${escapeRegExp(rename.from)}([ \\t]*(?:#.*)?)$`, 'gm'),
yamlName: new RegExp(`^(\\s*(?:-\\s*)?name:[ \\t]+)(['"]?)${escapeRegExp(rename.from)}((?:/[^\\s'"]*)?)\\2([ \\t]*(?:#.*)?)$`, 'gm'),
}))
}
@@ -478,45 +510,109 @@ function skipped(file: string, pattern: Pattern): boolean {
return GENERIC_SKIPS.some(skip => skip.file === file && skip.upstream.includes(pattern.upstream))
}
function rewriteLine(line: string, file: string, all: readonly Pattern[]): string {
function rewriteLine(line: string, file: string, all: readonly Pattern[], yaml = false): string {
let out = line
for (const pattern of all) {
if (skipped(file, pattern)) continue
out = out.replace(pattern.token, (_match, quote: string, subpath: string) => `${quote}${pattern.to}${subpath}${quote}`)
out = out.replace(pattern.yamlName, (_match, prefix: string, suffix: string) => `${prefix}${pattern.to}${suffix}`)
const preserve = skipped(file, pattern)
if (!preserve) out = out.replace(pattern.token, (_match, quote: string, subpath: string) => `${quote}${pattern.to}${subpath}${quote}`)
if (!preserve || yaml) {
out = out.replace(pattern.yamlName, (_match, prefix: string, quote: string, subpath: string, suffix: string) => `${prefix}${quote}${pattern.to}${subpath}${quote}${suffix}`)
}
}
return out
}
/** Rewrite only module literals whose other same-file uses are product data. */
function rewriteModules(text: string, file: string, all: readonly Pattern[], sourceName = file): string {
const skip = GENERIC_SKIPS.find(entry => entry.file === file)
if (!skip || skip.fixture) return text
const active = all.filter(pattern => skip.upstream.includes(pattern.upstream))
const source = ts.createSourceFile(sourceName, text, ts.ScriptTarget.Latest, true)
const edits = new Map<number, { end: number; value: string }>()
const visited = new Set<ts.Node>()
const visit = (node: ts.Node): void => {
if (visited.has(node)) return
visited.add(node)
let literal: ts.Node | undefined
if (ts.isImportDeclaration(node) || ts.isExportDeclaration(node) || ts.isJSDocImportTag(node)) {
literal = node.moduleSpecifier
} else if (ts.isImportEqualsDeclaration(node) && ts.isExternalModuleReference(node.moduleReference)) {
literal = node.moduleReference.expression
} else if (ts.isImportTypeNode(node) && ts.isLiteralTypeNode(node.argument)) {
literal = node.argument.literal
} else if (ts.isCallExpression(node) && (node.expression.kind === ts.SyntaxKind.ImportKeyword || (ts.isIdentifier(node.expression) && node.expression.text === 'require'))) {
literal = node.arguments[0]
} else if (ts.isModuleDeclaration(node)) {
literal = node.name
}
if (literal && ts.isStringLiteralLike(literal)) {
const start = literal.getStart(source) + 1
const end = literal.getEnd() - 1
const value = text.slice(start, end)
const pattern = active.find(entry => value === entry.from || value.startsWith(`${entry.from}/`))
if (pattern) edits.set(start, { end, value: pattern.to + value.slice(pattern.from.length) })
}
ts.forEachChild(node, visit)
for (const doc of ts.getJSDocCommentsAndTags(node)) visit(doc)
}
visit(source)
let out = text
for (const [start, edit] of [...edits].sort(([left], [right]) => right - left)) {
out = out.slice(0, start) + edit.value + out.slice(edit.end)
}
return out
}
/**
* Rewrite a file's eligible lines.
*
* Markdown splits in two. Every fence is code a reader copies or a
* configuration they mount, so every fence follows the rename regardless of its
* info string. Prose follows it only under `docs/`, where a sentence quoting
* `` `cordis` `` teaches an unresolved package name; elsewhere
* prose is a record of what was true when it was written, and the same spelling
* can mean something else entirely — the Python SDK's `cordis` option, or the
* unvendored `@cordisjs/plugin-http`.
* Rewrite eligible source, fences and docs prose. Audited names in source and
* TypeScript/JavaScript fences change only in module syntax; YAML names remain
* eligible too. Other names retain the token rule. Prose outside docs is kept.
*/
function rewrite(text: string, file: string, all: readonly Pattern[]): { text: string; lines: number } {
const markdown = file.endsWith('.md')
const prose = markdown && file.startsWith('docs/')
let insideFence = false
let lines = 0
const out = text.split('\n').map((line) => {
const original = text.split('\n')
let source = text
if (!markdown && /\.(ts|tsx|js|mjs|cjs|tpl)$/.test(file)) source = rewriteModules(text, file, all)
let fenceStart: number | undefined
let language = ''
const out = source.split('\n')
const finishFence = (end: number): void => {
if (fenceStart === undefined || fenceStart === end || !/^(?:[cm]?[jt]sx?|typescript|javascript)?$/.test(language)) return
const extension = language === 'jsx' || language === 'tsx' ? 'tsx' : language === 'js' || language === 'javascript' || language === 'mjs' || language === 'cjs' ? 'js' : 'ts'
const body = out.slice(fenceStart, end).join('\n')
out.splice(fenceStart, end - fenceStart, ...rewriteModules(body, file, all, `fence.${extension}`).split('\n'))
}
for (const [index, line] of out.entries()) {
if (markdown) {
if (/^\s*```/.test(line)) {
insideFence = !insideFence
return line
const fence = /^\s*```(\S*)/.exec(line)
if (fence) {
if (fenceStart === undefined) {
fenceStart = index + 1
language = fence[1] ?? ''
} else {
finishFence(index)
fenceStart = undefined
}
continue
}
if (!insideFence && !prose) return line
if (fenceStart === undefined && !prose) continue
}
const next = rewriteLine(line, file, all)
if (next !== line) lines += 1
return next
})
return { text: out.join('\n'), lines }
out[index] = rewriteLine(line, file, all, /\.ya?ml$/.test(file) || (fenceStart !== undefined && (language === 'yaml' || language === 'yml')))
}
if (fenceStart !== undefined) finishFence(out.length)
return { text: out.join('\n'), lines: out.filter((line, index) => line !== original[index]).length }
}
/**
* Apply the CLI's generic package rename with the audited name exceptions.
* @param text - Complete source text.
* @param file - Repository-relative path with forward slash separators.
* @param reverse - Restore upstream package names when true.
* @returns Rewritten text and the number of changed lines.
*/
export function rescopeText(text: string, file: string, reverse = false): { text: string; lines: number } {
return rewrite(text, file, patterns(reverse))
}
function classify(file: string): string {
+2 -1
View File
@@ -258,7 +258,8 @@ def write_advanced_profile_patch(
) -> Path:
"""Write the shared custom, snapshot, and restart profile patch."""
return write_profile_patch(root, name, sessions, [
{"id": "tools", "config": {"mode": mode}},
{"id": "tool-executor-native", "disabled": mode != "native"},
{"id": "tool-executor-ptc", "disabled": mode != "ptc"},
{
"id": "system-prompt",
"config": {
@@ -2580,6 +2580,70 @@
"childSessionId": "{{child-1}}"
}
},
{
"method": "session.event",
"payload": {
"sessionId": "{{child-1}}",
"event": {
"type": "subagent/descriptor",
"seq": 0,
"time": 0,
"data": {
"version": 3,
"mode": "continuable",
"provider": "spawn",
"label": "Check direct child",
"agentProvider": "deepseek-official",
"agentModel": "smoke-model",
"agentReasoningEffort": "high"
}
}
}
},
{
"method": "session.event",
"payload": {
"sessionId": "{{child-1}}",
"event": {
"type": "sandbox/mode",
"seq": 1,
"time": 0,
"data": {
"mode": "danger-full-access",
"source": "delegation"
}
}
}
},
{
"method": "session.event",
"payload": {
"sessionId": "{{child-1}}",
"event": {
"type": "approval/policy",
"seq": 2,
"time": 0,
"data": {
"policy": "never",
"source": "delegation"
}
}
}
},
{
"method": "session.event",
"payload": {
"sessionId": "{{child-1}}",
"event": {
"type": "permission/preset",
"seq": 3,
"time": 0,
"data": {
"preset": "danger-full-access"
}
}
}
},
{
"method": "session.event",
"payload": {
@@ -3405,6 +3469,70 @@
"childSessionId": "{{child-2}}"
}
},
{
"method": "session.event",
"payload": {
"sessionId": "{{child-2}}",
"event": {
"type": "subagent/descriptor",
"seq": 0,
"time": 0,
"data": {
"version": 3,
"mode": "continuable",
"provider": "spawn",
"label": "advanced-exe-snapshot child 1",
"agentProvider": "deepseek-official",
"agentModel": "smoke-model",
"agentReasoningEffort": "high"
}
}
}
},
{
"method": "session.event",
"payload": {
"sessionId": "{{child-2}}",
"event": {
"type": "sandbox/mode",
"seq": 1,
"time": 0,
"data": {
"mode": "danger-full-access",
"source": "delegation"
}
}
}
},
{
"method": "session.event",
"payload": {
"sessionId": "{{child-2}}",
"event": {
"type": "approval/policy",
"seq": 2,
"time": 0,
"data": {
"policy": "never",
"source": "delegation"
}
}
}
},
{
"method": "session.event",
"payload": {
"sessionId": "{{child-2}}",
"event": {
"type": "permission/preset",
"seq": 3,
"time": 0,
"data": {
"preset": "danger-full-access"
}
}
}
},
{
"method": "session.event",
"payload": {
@@ -48,7 +48,6 @@
"ask_user_question",
"create_goal",
"edit",
"exit_plan_mode",
"get_goal",
"glob",
"grep",
@@ -110,7 +109,6 @@
"ask_user_question",
"create_goal",
"edit",
"exit_plan_mode",
"get_goal",
"glob",
"grep",
@@ -187,7 +185,6 @@
"ask_user_question",
"create_goal",
"edit",
"exit_plan_mode",
"get_goal",
"glob",
"grep",
+11 -6
View File
@@ -49,10 +49,12 @@ function evaluateCondition(expression: string, cancelled: boolean, results: stri
}, { timeout: 1000 }) as boolean
}
const verdicts = [['ci.yml', 'all-checks-passed'], ['ci-windows.yml', 'windows-checks-passed'], ['ci-python.yml', 'python-checks-passed']] as const
describe('master-only platform scheduling', () => {
it.each(['success', 'failure', 'skipped', 'cancelled'])(
'reports %s dependencies in active runs but never starts a cancelled-run verdict', (result) => {
const aggregate = workflow('ci.yml').jobs['all-checks-passed']!
it.each(verdicts.flatMap(([file, job]) => ['success', 'failure', 'skipped', 'cancelled'].map(result => [file, job, result] as const)))(
'%s %s reports %s dependencies in active runs but never starts a cancelled-run verdict', (file, job, result) => {
const aggregate = workflow(file).jobs[job]!
const results = aggregate.needs!.map(() => 'success')
results[0] = result
const condition = aggregate.if as string
@@ -72,8 +74,8 @@ describe('master-only platform scheduling', () => {
expect(evaluateCondition(workflow('ci.yml').jobs['all-checks-passed']!.if as string, true, ['success'])).toBe(false)
})
it('keeps only Linux and Windows x64 runtimes in required PR CI', () => {
const pr = workflow('ci.yml')
it('keeps the required Python PR verdict and its two x64 targets in CI Python', () => {
const pr = workflow('ci-python.yml')
expect(Object.keys(pr.on)).toEqual(['pull_request'])
expect(pr.jobs['python-runtime']).toMatchObject({
if: "github.event_name == 'pull_request'",
@@ -82,7 +84,7 @@ describe('master-only platform scheduling', () => {
})
expect(pr.jobs.windows).toBeUndefined()
expect(JSON.stringify(pr.jobs)).not.toMatch(/wine-windows-gates|check:windows-wine/)
const aggregate = pr.jobs['all-checks-passed']!
const aggregate = pr.jobs['python-checks-passed']!
expect(aggregate.needs).toContain('python-runtime')
expect(aggregate.needs).not.toContain('windows')
expect(aggregate.needs!.every(id => id in pr.jobs)).toBe(true)
@@ -90,6 +92,9 @@ describe('master-only platform scheduling', () => {
expect(aggregate.steps).toContainEqual(expect.objectContaining({
if: "contains(needs.*.result, 'failure') || contains(needs.*.result, 'cancelled') || contains(needs.*.result, 'skipped')",
}))
for (const file of ['ci.yml', 'ci-windows.yml']) {
expect(workflow(file).jobs['python-runtime']).toBeUndefined()
}
})
it('runs all three deferred carriers on master pushes with fail-loud API credentials', () => {
+68 -18
View File
@@ -924,12 +924,12 @@
{
"doc": "docs/subsystems/tools.md",
"symbol": "ToolOutputDefinition",
"source": "packages/core/tools/src/index.ts"
"source": "packages/core/tools/src/contracts.ts"
},
{
"doc": "docs/subsystems/tools.md",
"symbol": "ToolDefinition",
"source": "packages/core/tools/src/index.ts"
"source": "packages/core/tools/src/contracts.ts"
},
{
"doc": "docs/subsystems/tools.md",
@@ -959,82 +959,97 @@
{
"doc": "docs/subsystems/tools.md",
"symbol": "ToolExecutionToken",
"source": "packages/core/tools/src/index.ts"
"source": "packages/core/tools/src/contracts.ts"
},
{
"doc": "docs/subsystems/tools.md",
"symbol": "ToolExecutionInput",
"source": "packages/core/tools/src/index.ts"
"source": "packages/core/tools/src/contracts.ts"
},
{
"doc": "docs/subsystems/tools.md",
"symbol": "ToolExecution",
"source": "packages/core/tools/src/index.ts"
"source": "packages/core/tools/src/contracts.ts"
},
{
"doc": "docs/subsystems/tools.md",
"symbol": "ToolDispatchExecution",
"source": "packages/core/tools/src/index.ts"
"source": "packages/core/tools/src/contracts.ts"
},
{
"doc": "docs/subsystems/tools.md",
"symbol": "ToolExecutionMode",
"source": "packages/core/tools/src/index.ts"
"source": "packages/core/tools/src/contracts.ts"
},
{
"doc": "docs/subsystems/tools.md",
"symbol": "PtcDispatchLog",
"source": "packages/core/tools/src/index.ts"
"source": "packages/core/tool-executor-ptc/src/runtime-types.ts"
},
{
"doc": "docs/subsystems/tools.md",
"symbol": "ToolRunContext",
"source": "packages/core/tools/src/index.ts"
"source": "packages/core/tools/src/contracts.ts"
},
{
"doc": "docs/subsystems/tools.md",
"symbol": "ToolGuard",
"source": "packages/core/tools/src/index.ts"
"source": "packages/core/tools/src/contracts.ts"
},
{
"doc": "docs/subsystems/tools.md",
"symbol": "ToolErrorInfo",
"source": "packages/core/tools/src/index.ts"
"source": "packages/core/tools/src/contracts.ts"
},
{
"doc": "docs/subsystems/tools.md",
"symbol": "ToolRestriction",
"source": "packages/core/tools/src/index.ts"
"source": "packages/core/tools/src/contracts.ts"
},
{
"doc": "docs/subsystems/tools.md",
"symbol": "ToolActivationContext",
"source": "packages/core/tools/src/contracts.ts"
},
{
"doc": "docs/subsystems/tools.md",
"symbol": "ToolRegistrationOptions",
"source": "packages/core/tools/src/contracts.ts"
},
{
"doc": "docs/subsystems/tools.md",
"symbol": "ResolvedToolSet",
"source": "packages/core/tools/src/contracts.ts"
},
{
"doc": "docs/subsystems/tools.md",
"symbol": "ToolFailure",
"source": "packages/core/tools/src/index.ts"
"source": "packages/core/tools/src/contracts.ts"
},
{
"doc": "docs/subsystems/tools.md",
"symbol": "ToolExecutionSuccess",
"source": "packages/core/tools/src/index.ts"
"source": "packages/core/tools/src/contracts.ts"
},
{
"doc": "docs/subsystems/tools.md",
"symbol": "ToolExecutionFailure",
"source": "packages/core/tools/src/index.ts"
"source": "packages/core/tools/src/contracts.ts"
},
{
"doc": "docs/subsystems/tools.md",
"symbol": "ToolExecutionResult",
"source": "packages/core/tools/src/index.ts"
"source": "packages/core/tools/src/contracts.ts"
},
{
"doc": "docs/subsystems/tools.md",
"symbol": "PreToolDecision",
"source": "packages/core/tools/src/index.ts"
"source": "packages/core/tools/src/contracts.ts"
},
{
"doc": "docs/subsystems/tools.md",
"symbol": "PostToolDecision",
"source": "packages/core/tools/src/index.ts"
"source": "packages/core/tools/src/contracts.ts"
},
{
"doc": "docs/subsystems/tools.md",
@@ -1941,6 +1956,16 @@
"symbol": "WebRoute",
"source": "packages/host/webserver/src/index.ts"
},
{
"doc": "docs/subsystems/web-server.md",
"symbol": "WebResource",
"source": "packages/host/webserver/src/index.ts"
},
{
"doc": "docs/subsystems/web-server.md",
"symbol": "WebResourceRoute",
"source": "packages/host/webserver/src/index.ts"
},
{
"doc": "docs/subsystems/web-server.md",
"symbol": "Config",
@@ -2497,6 +2522,31 @@
"symbol": "PendingModule",
"source": "packages/boot/hmr/src/index.ts"
},
{
"doc": "docs/subsystems/tools.md",
"symbol": "ToolModelInput",
"source": "packages/core/tools/src/executor.ts"
},
{
"doc": "docs/subsystems/tools.md",
"symbol": "ToolBatchInput",
"source": "packages/core/tools/src/executor.ts"
},
{
"doc": "docs/subsystems/tools.md",
"symbol": "ToolCallResult",
"source": "packages/core/tools/src/executor.ts"
},
{
"doc": "docs/subsystems/tools.md",
"symbol": "ToolBatchResult",
"source": "packages/core/tools/src/executor.ts"
},
{
"doc": "docs/subsystems/tools.md",
"symbol": "ToolExecutor",
"source": "packages/core/tools/src/executor.ts"
},
{
"doc": "docs/subsystems/core.md",
"symbol": "ModelSelection",
+12
View File
@@ -11,6 +11,7 @@ import { describe, expect, it } from 'vitest'
import {
bundleManifestPaths,
bundlePluginDependencyErrors,
exclusivePlaneRowIds,
metadataExpressionErrors,
packageTestFixtureDependencyErrors,
packageTestPluginDependencyErrors,
@@ -48,6 +49,17 @@ describe('verify-cordis-config metadata expressions', () => {
})
describe('workspace Bundle discovery and product dependency closures', () => {
it('allows scoped executors on both planes while retaining other plugin rows', () => {
expect([...exclusivePlaneRowIds([{ insert: [
{ id: 'native', name: '@deepseek-ai/dsh-tool-executor-native' },
{ id: 'ptc', name: '@deepseek-ai/dsh-tool-executor-ptc' },
{ id: 'tools', name: '@deepseek-ai/dsh-tools' },
{ id: 'group', name: 'cordis:group', group: true, config: [
{ id: 'tool-executor-ptc', name: '@deepseek-ai/dsh-tool-fs' },
] },
] }])]).toEqual(['tools', 'group', 'tool-executor-ptc'])
})
it('discovers a Bundle outside packages/bundle from its manifest declaration', () => {
const fixture = mkdtempSync(join(tmpdir(), 'dsh-bundle-discovery-'))
try {
+13 -5
View File
@@ -119,7 +119,8 @@ function validateClientHalvesDeclared(): string[] {
}
/**
* No shipped agent preset may repeat a row the host composition still runs.
* No shipped agent preset may repeat an unscoped row the host composition still runs.
* Tool executors select through the mounting scope and may appear on both planes.
*
* A preset contributes what ONE session adds to the host's registries. A row
* active on both planes is therefore mounted twice — once per process and once
@@ -157,7 +158,7 @@ function validatePresetPlaneSeparation(): string[] {
const active = new Set([...rowIds(hostFile), ...overlayRows].filter(id => !disabled.has(id)))
for (const file of overlayFiles) {
for (const definition of presetDefinitions(loadEntries(file))) {
for (const id of collectRowIds(definition.plugins)) {
for (const id of exclusivePlaneRowIds(definition.plugins)) {
if (active.has(id)) problems.push(`${file}#${definition.id}: row "${id}" is also active in the host composition; a row belongs to exactly one plane`)
}
}
@@ -178,10 +179,15 @@ function loadEntries(file: string): unknown[] {
* @returns the declared ids.
*/
function rowIds(file: string): Set<string> {
return collectRowIds(loadEntries(file))
return exclusivePlaneRowIds(loadEntries(file))
}
function collectRowIds(rows: unknown): Set<string> {
/**
* Collect rows that cannot be shared between the host and a preset.
* @param rows - parsed Loader entries, including inserts and groups.
* @returns ids excluding the executor plugins whose registrations are scope-owned.
*/
export function exclusivePlaneRowIds(rows: unknown): Set<string> {
const ids = new Set<string>()
const walk = (value: unknown): void => {
if (isUnknownArray(value)) {
@@ -189,7 +195,9 @@ function collectRowIds(rows: unknown): Set<string> {
return
}
if (!isRecord(value)) return
if (typeof value.id === 'string' && typeof value.name === 'string') ids.add(value.id)
if (typeof value.id === 'string' && typeof value.name === 'string'
&& value.name !== '@deepseek-ai/dsh-tool-executor-native'
&& value.name !== '@deepseek-ai/dsh-tool-executor-ptc') ids.add(value.id)
walk(value.insert)
if (isCordisGroupEntry(value)) walk(value.config)
}
@@ -13,7 +13,7 @@ const roots: string[] = []
const experimental = '@deepseek-ai/dsh-experimental-prototype'
const core = '@deepseek-ai/dsh-core'
const base = '@deepseek-ai/dsh-base'
const profile = 'packages/boot/app-boot/src/profile.ts'
const profile = 'packages/boot/official-bundles/src/bundles.ts'
const preset = 'packages/bundle/web-app/presets/standard.patch.yml'
const patch = 'packages/bundle/base/cordis.patch.yml'
@@ -488,6 +488,6 @@ describe('default product isolation', () => {
])('rejects absent, empty, or uninspectable default profile definitions', (source) => {
const root = fixture()
write(root, profile, source)
expect(() => verifyDefaultProductIsolation(root)).toThrow('profile')
expect(() => verifyDefaultProductIsolation(root)).toThrow(`${profile}: `)
})
})
+1 -1
View File
@@ -23,7 +23,7 @@ import {
import { isExperimentalPackageName } from './experimental-package-policy.ts'
// The independently published entry package owns platform-engine dependencies.
const EXTERNAL_KIT_PACKAGES = new Set(['@deepseek-ai/libreoffice-kit'])
const PROFILE_SOURCE = 'packages/boot/app-boot/src/profile.ts'
const PROFILE_SOURCE = 'packages/boot/official-bundles/src/bundles.ts'
const PRESET_PATTERN = 'packages/bundle/web-app/presets/*.patch.yml'
const RUNTIME_SECTIONS = ['dependencies', 'optionalDependencies', 'peerDependencies'] as const
@@ -30,12 +30,14 @@ interface SentenceContract {
* so an absent section cannot be mistaken for forgotten documentation.
*/
const NO_MODEL_EXPERIENCE_SECTION: Readonly<Record<string, string>> = {
'packages/util/file-drag': 'The package resolves export names and declares native file operations; it does not construct model input.',
'packages/core/scope': 'The package is a model-agnostic registration and lifecycle primitive; model-facing consumers own any context selection.',
'packages/util/brand': 'The package only constructs plain string values and registers nothing model-facing.',
'packages/util/home-paths': 'The package only resolves harness-owned host paths; model-facing consumers own any rendered use.',
'packages/util/launch-environment': 'The package only resolves host environment values; model-facing consumers own any rendered use.',
'packages/util/workspace-path': 'The package only formats Workspace paths for browser UI; it never constructs model input.',
'packages/util/values': 'The package only validates, snapshots, compares, freezes, or rejects caller-owned values; consumers own every model-facing use.',
'packages/boot/official-bundles': 'The package only lists bundle package names and their display metadata; the loaded plugin tree owns model context.',
'packages/util/code-language': 'The package only maps a filename suffix to a syntax-highlighting language id; the read consumer owns the persisted hint.',
}
@@ -111,6 +113,7 @@ const SENTENCE_MODEL_EXPERIENCE: Readonly<Record<string, SentenceContract>> = {
'packages/experimental/inspector-profile': { kind: 'none', reason: 'The bundle mounts developer inspection plugins without contributing model context.' },
'packages/experimental/session-inspector': { kind: 'none', reason: 'The conversation view entries add no model-facing input.' },
'packages/client/ui-slots': { kind: 'none', reason: 'Browser-side UI plugin layer; registers nothing model-facing.' },
'packages/client/ui-file-drag': { kind: 'none', reason: 'Browser-side file transfers and transient feedback; registers nothing model-facing.' },
'packages/client/ui-attachment': { kind: 'none', reason: 'Browser-side UI plugin layer; registers nothing model-facing.' },
'packages/client/ui-primitives': { kind: 'none', reason: 'Browser-side UI plugin layer; registers nothing model-facing.' },
'packages/client/store': { kind: 'none', reason: 'Browser-side state primitives; register nothing model-facing.' },
@@ -224,6 +227,7 @@ const SENTENCE_MODEL_EXPERIENCE: Readonly<Record<string, SentenceContract>> = {
'packages/spill/spill-local': { kind: 'indirect', reason: 'The storage backend delegates model rendering to spill consumers.' },
'packages/test-support/session-snapshot': { kind: 'none', reason: 'The test harness observes and normalizes transcripts without changing live requests.' },
'packages/test-support/agent-loop-testkit': { kind: 'none', reason: 'The test helper mounts services but neither drives nor modifies model requests.' },
'packages/test-support/tool-testkit': { kind: 'none', reason: 'Fixture helpers define and execute test tools without making model requests.' },
'packages/test-support/loader-smoke': { kind: 'none', reason: 'The test harness submits an ordinary user task but delegates prompt and tool composition to the loaded tree.' },
'packages/test-support/llm-mock-server': { kind: 'none', reason: 'The test server substitutes provider wire behavior without invoking a real model.' },
'packages/test-support/llm-replay': { kind: 'none', reason: 'The keyless adapter invokes no provider model.' },
+6 -7
View File
@@ -13,8 +13,7 @@ const base = '@deepseek-ai/dsh-base'
const core = '@deepseek-ai/dsh-core'
const candidate = '@deepseek-ai/dsh-candidate'
const candidateDir = 'packages/core/candidate'
const profile = 'packages/boot/app-boot/src/profile.ts'
const catalog = 'packages/boot/app-boot/src/official-bundle-packages.ts'
const profile = 'packages/boot/official-bundles/src/bundles.ts'
const patch = 'packages/bundle/base/cordis.patch.yml'
function pkg(root: string, directory: string, name: string, fields: Record<string, unknown> = {}): void {
@@ -29,10 +28,10 @@ function pkg(root: string, directory: string, name: string, fields: Record<strin
}
function profiles(root: string, optional: string[] = [], onDemand: string[] = []): void {
write(root, catalog, `export const ON_DEMAND_BUNDLES = ${JSON.stringify(onDemand)}\n`)
write(root, profile, `export const PROFILE_TEMPLATES = ${JSON.stringify(Object.fromEntries(
['web', 'acp', 'headless', 'sdk', 'sdk-minimal'].map(name => [name, { bundles: [base] }]),
))}\nexport const DEFAULT_PROFILE_BUNDLES = ['${base}']\nexport const OPTIONAL_BUNDLES = ${JSON.stringify(optional)}\n`)
))}\nexport const DEFAULT_PROFILE_BUNDLES = ['${base}']\nexport const OPTIONAL_BUNDLES = ${JSON.stringify(optional)}\n`
+ `export const ON_DEMAND_BUNDLES = ${JSON.stringify(onDemand)}\n`)
}
function fixture(): string {
@@ -142,7 +141,7 @@ describe('product package use', () => {
const root = fixture()
pkg(root, candidateDir, candidate)
const children = [{ name: candidate }]
if (!initial) write(root, 'packages/boot/app-boot/src/included.yml', children)
if (!initial) write(root, 'packages/boot/official-bundles/src/included.yml', children)
write(root, patch, [{ insert: [{ name: core }, {
name: 'cordis:include', disabled: true, config: { path: './included.yml', ...(initial ? { initial: children } : {}) },
}] }])
@@ -162,12 +161,12 @@ describe('product package use', () => {
it('applies Include patches before collecting runtime mounts and uses files ahead of initial entries', () => {
const root = fixture()
pkg(root, candidateDir, candidate)
write(root, 'packages/boot/app-boot/src/included.yml', [{ id: 'child', name: candidate }])
write(root, 'packages/boot/official-bundles/src/included.yml', [{ id: 'child', name: candidate }])
write(root, patch, [{ insert: [{ name: core }, { name: 'cordis:include', config: {
path: './included.yml', initial: [{ name: candidate }], patches: [{ id: 'child', disabled: true }],
} }] }])
expect(failures(root)).toContain('no product runtime use')
write(root, 'packages/boot/app-boot/src/included.yml', [])
write(root, 'packages/boot/official-bundles/src/included.yml', [])
expect(failures(root)).toContain('no product runtime use')
})
+2 -5
View File
@@ -14,8 +14,7 @@ import { isAgentPresetEntry, isCordisGroupEntry, loadCordisYaml } from './cordis
import { PRODUCT_PACKAGE_POLICY, type ProductPackagePolicy } from './product-package-policy.ts'
import { collectRuntimeLocalSourceSpecifiers, collectRuntimeSourceSpecifiers } from './verify-client-packages.ts'
const PROFILE_SOURCE = 'packages/boot/app-boot/src/profile.ts'
const ON_DEMAND_SOURCE = 'packages/boot/app-boot/src/official-bundle-packages.ts'
const PROFILE_SOURCE = 'packages/boot/official-bundles/src/bundles.ts'
const PROFILES = ['acp', 'web', 'headless', 'sdk', 'sdk-minimal']
const PICKER_SOURCE = 'packages/host/directory-picker-auto/src/index.ts'
const DYNAMIC_MOUNTS: Readonly<Record<string, readonly string[]>> = {
@@ -188,11 +187,9 @@ export function verifyProductUse(
const profileFile = ts.createSourceFile(profilePath, readFileSync(profilePath, 'utf8'), ts.ScriptTarget.Latest, true)
const templates = literalDeclaration(profileFile, 'PROFILE_TEMPLATES')
const fallback = stringList(literalDeclaration(profileFile, 'DEFAULT_PROFILE_BUNDLES'), PROFILE_SOURCE)
const catalogPath = resolve(root, ON_DEMAND_SOURCE)
const catalogFile = ts.createSourceFile(catalogPath, readFileSync(catalogPath, 'utf8'), ts.ScriptTarget.Latest, true)
const optionals = [
...stringList(literalDeclaration(profileFile, 'OPTIONAL_BUNDLES'), PROFILE_SOURCE),
...stringList(literalDeclaration(catalogFile, 'ON_DEMAND_BUNDLES'), ON_DEMAND_SOURCE),
...stringList(literalDeclaration(profileFile, 'ON_DEMAND_BUNDLES'), PROFILE_SOURCE),
]
if (!isRecord(templates)) throw new Error(`${PROFILE_SOURCE}: PROFILE_TEMPLATES must be a literal object`)
for (const name of PROFILES) if (!isRecord(templates[name])) failures.push(`${PROFILE_SOURCE}: missing shipped ${name} profile`)