Merge remote-tracking branch 'origin/master' into codex/disable-telemetry-default

Resolve three conflicts:

- packages/bundle/base/tests/base.spec.ts: keep both assertion sets — this
  branch's telemetry-otel mode expression and master's subagent rows plus
  manifest dependencies.
- packages/session/session-telemetry-otel/tests/otel.spec.ts: keep master's
  per-mode sharing disclosure test, but select FULL explicitly instead of
  relying on an omitted mode, because the default is now DISABLED; the
  default-construction test keeps asserting disabled delivery and gains a
  case pinning that an omitted mode reports 'disabled' sharing.
- scripts/snapshots/translation-prompt-v4/request-response.expected.json:
  re-recorded from the merged READMEs rather than hand-merging the embedded
  document text.
This commit is contained in:
Chinesezjc
2026-08-11 15:57:02 +08:00
2789 changed files with 42941 additions and 15643 deletions

View File

@@ -21,22 +21,28 @@ function exitCode(argv: string[]): number {
afterEach(() => { vi.restoreAllMocks() })
describe('parseDshArgs', () => {
it('routes profile boots, one-shot runs, and the web alias', () => {
expect(parse(['--profile', 'tui'])).toEqual({ mode: 'profile', profile: 'tui', patches: [] })
it('routes profile boots and the web alias, handing the rest to the app', () => {
expect(parse(['--profile', 'tui'])).toEqual({ mode: 'profile', profile: 'tui', patches: [], args: [] })
expect(parse(['--profile', 'tui', '--patch', 'a.yml', '--patch', 'b.yml']))
.toEqual({ mode: 'profile', profile: 'tui', patches: ['a.yml', 'b.yml'] })
expect(parse(['run', 'run', 'the', 'tests']))
.toEqual({ mode: 'run', profile: 'headless', patches: [], task: 'run the tests' })
expect(parse(['run', '--profile', 'custom', '--patch', 'a.yml', '--patch', 'b.yml', 'run', 'the', 'tests']))
.toEqual({ mode: 'run', profile: 'custom', patches: ['a.yml', 'b.yml'], task: 'run the tests' })
expect(parse(['run', '--', '--profile', 'is', 'task', 'text']))
.toEqual({ mode: 'run', profile: 'headless', patches: [], task: '--profile is task text' })
expect(parse(['web'])).toEqual({ mode: 'web', dev: false, patches: [] })
expect(parse(['web', '--patch', 'web.yml'])).toEqual({ mode: 'web', dev: false, patches: ['web.yml'] })
.toEqual({ mode: 'profile', profile: 'tui', patches: ['a.yml', 'b.yml'], args: [] })
expect(parse(['web'])).toEqual({ mode: 'profile', profile: 'web', patches: [], args: [] })
expect(parse(['web', '--patch', 'web.yml']))
.toEqual({ mode: 'profile', profile: 'web', patches: ['web.yml'], args: [] })
})
it('ends the launcher flags at the first token it does not own', () => {
// App flags, including its -h, and positionals reach the app verbatim.
expect(parse(['--profile', 'tui', '--resume', 'abc']))
.toEqual({ mode: 'profile', profile: 'tui', patches: [], args: ['--resume', 'abc'] })
expect(parse(['--profile', 'web', '-h']))
.toEqual({ mode: 'profile', profile: 'web', patches: [], args: ['-h'] })
expect(parse(['web', '--host', '0.0.0.0', '--port', '8080', '--dev']))
.toEqual({ mode: 'web', host: '0.0.0.0', port: 8080, dev: true, patches: [] })
expect(parse(['web', '--trusted-host', 'harness.internal:3080', 'lab.internal', '--trusted-host', '10.0.0.9']))
.toEqual({ mode: 'web', dev: false, patches: [], trustedHosts: ['harness.internal:3080', 'lab.internal', '10.0.0.9'] })
.toEqual({ mode: 'profile', profile: 'web', patches: [], args: ['--host', '0.0.0.0', '--port', '8080', '--dev'] })
expect(parse(['--profile', 'headless', 'run', 'the', 'tests']))
.toEqual({ mode: 'profile', profile: 'headless', patches: [], args: ['run', 'the', 'tests'] })
// Launcher flags placed after that boundary belong to the app too.
expect(parse(['--profile', 'tui', '--patch', 'a.yml', '--resume', 'b', '--patch', 'late.yml']))
.toEqual({ mode: 'profile', profile: 'tui', patches: ['a.yml'], args: ['--resume', 'b', '--patch', 'late.yml'] })
})
it('routes the plugin pnpm forwarder', () => {
@@ -44,8 +50,8 @@ describe('parseDshArgs', () => {
.toEqual({ mode: 'plugin', profile: 'tui', args: ['add', 'turtle-ui'] })
expect(parse(['plugin', '--profile', 'tui', 'remove', 'turtle-ui']))
.toEqual({ mode: 'plugin', profile: 'tui', args: ['remove', 'turtle-ui'] })
expect(parse(['plugin', '--profile', 'tui', 'why', 'cordis']))
.toEqual({ mode: 'plugin', profile: 'tui', args: ['why', 'cordis'] })
expect(parse(['plugin', '--profile', 'tui', 'why', '@deepseek-ai/cordis']))
.toEqual({ mode: 'plugin', profile: 'tui', args: ['why', '@deepseek-ai/cordis'] })
// Unknown pnpm flags forward verbatim.
expect(parse(['plugin', '--profile', 'tui', 'add', '--save-dev', 'x']))
.toEqual({ mode: 'plugin', profile: 'tui', args: ['add', '--save-dev', 'x'] })
@@ -64,18 +70,12 @@ describe('parseDshArgs', () => {
.toEqual({ mode: 'dump-config', profile: 'web', defaultOnly: true, patches: [] })
})
it('rejects missing profile, flags outside the current grammar, and contradictory inputs', () => {
it('rejects missing profile, removed flags, and contradictory inputs', () => {
expect(exitCode([])).toBe(1)
expect(exitCode(['tui'])).toBe(1) // a bare word is a task without --profile
expect(exitCode(['--config', 'c.yml'])).toBe(1) // outside the current grammar
expect(exitCode(['-p', 'task'])).toBe(1) // outside the current grammar
expect(exitCode(['--profile', 'headless', 'task'])).toBe(1) // tasks belong to `run`
expect(exitCode(['run'])).toBe(1)
expect(exitCode(['run', ''])).toBe(1)
expect(exitCode(['run', '--profile', '', 'task'])).toBe(1)
expect(exitCode(['run', '--patch=', 'task'])).toBe(1)
expect(exitCode(['--profile', 'headless', 'run', 'task'])).toBe(1)
expect(exitCode(['--patch', 'parent.yml', 'run', 'task'])).toBe(1)
expect(exitCode(['tui'])).toBe(1) // an app argument without --profile has no app to reach
expect(exitCode(['--config', 'c.yml'])).toBe(1) // removed
expect(exitCode(['-p', 'task'])).toBe(1) // removed
expect(exitCode(['run', 'task'])).toBe(1) // app-owned task replaced the launcher subcommand
expect(exitCode(['--profile', ''])).toBe(1)
expect(exitCode(['--profile', 'x', '--patch='])).toBe(1)
expect(exitCode(['--dump-config'])).toBe(1)
@@ -87,21 +87,20 @@ describe('parseDshArgs', () => {
expect(exitCode(['web', '--dump-config', '--dump-default-config'])).toBe(1)
expect(exitCode(['web', '--dump-default-config', '--patch', 'w.yml'])).toBe(1)
expect(exitCode(['web', '--patch='])).toBe(1)
// Boot-free dumps derive no flag patches; silently dropping the flags
// would print a tree that differs from the same invocation's boot.
// A dump never runs app command-line providers, so it cannot show what
// those flags would decide; printing a tree that differs from the same
// invocation's boot would mislead.
expect(exitCode(['web', '--dump-config', '--port', '8080'])).toBe(1)
expect(exitCode(['web', '--dump-config', '--dev'])).toBe(1)
// A non-numeric port fails at the flag, not deep in the webserver schema.
expect(exitCode(['web', '--port', 'abc'])).toBe(1)
expect(exitCode(['--profile', 'web', '--dump-config', '-h'])).toBe(1)
expect(exitCode(['plugin', 'add', 'x'])).toBe(1) // --profile required
expect(exitCode(['plugin', '--profile', 'tui'])).toBe(1) // nothing to forward
expect(exitCode(['plugin', '--profile', ''])).toBe(1)
expect(exitCode(['--profile', 'x', 'plugin', 'add', 'y'])).toBe(1)
})
it('exits 0 for help and version', () => {
it('keeps its own help for an invocation with no app to hand it to', () => {
expect(exitCode(['--help'])).toBe(0)
expect(exitCode(['run', '--help'])).toBe(0)
expect(exitCode(['-h'])).toBe(0)
expect(exitCode(['--version'])).toBe(0)
})
})

View File

@@ -8,8 +8,10 @@ import { afterEach, beforeEach, describe, expect, it } from 'vitest'
/** Published-entry acceptance for argument errors, profile lifecycle, and boot-free config dumps. */
const repoRoot = fileURLToPath(new URL('../../../', import.meta.url))
// The release version, including a prerelease such as 0.0.1-rc.1: `--version`
// prints what this manifest carries, so no test may pin it to a literal.
const cliVersion = (JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf8')) as { version: string }).version
const dshBin = join(repoRoot, 'apps/cli/lib/bin.js')
const coreWebOverlay = fileURLToPath(new URL('../config/core-web.cordis.yml', import.meta.url))
const invalidProvider = fileURLToPath(new URL('./fixtures/invalid-provider.cordis.yml', import.meta.url))
async function runBuiltBin(
@@ -129,8 +131,8 @@ function createProfileLifecycleFixture(): ProfileLifecycleFixture {
return { home, ready, settled, disposed, interrupt }
}
function startProfileLifecycle(fixture: ProfileLifecycleFixture) {
return execa(process.execPath, [dshBin, '--profile', 'lifecycle'], {
function startProfileLifecycle(fixture: ProfileLifecycleFixture, args: readonly string[] = []) {
return execa(process.execPath, [dshBin, '--profile', 'lifecycle', ...args], {
cwd: fixture.home,
input: '',
reject: false,
@@ -145,8 +147,8 @@ function startProfileLifecycle(fixture: ProfileLifecycleFixture) {
}
function requestProfileShutdown(
child: ReturnType<typeof startProfileLifecycle>,
fixture: ProfileLifecycleFixture,
child: Pick<ReturnType<typeof startProfileLifecycle>, 'kill'>,
fixture: Pick<ProfileLifecycleFixture, 'interrupt'>,
): void {
if (process.platform === 'win32') {
writeFileSync(fixture.interrupt, 'interrupt')
@@ -194,8 +196,121 @@ function createEnvironmentProbeProfile(home: string, project: string): void {
].join('\n'))
}
interface StartupFixture {
home: string
ready: string
echo: string
interrupt: string
/** An always-running row's echo, used to observe that a user patch reload landed. */
witness: string
}
/**
* A custom profile whose ordinary provider plugin injects `cmdlineArgs`, plus
* a row that reads its app-owned service through a `!!js` config expression.
* Both plugin modules resolve
* `@deepseek-ai/dsh-cmdline` and `commander` through the profile module
* fallback, exactly as an installed out-of-tree bundle does.
*/
function createStartupFixture(): StartupFixture {
const home = mkdtempSync(join(tmpdir(), 'dsh-profile-startup-'))
const profileDir = join(home, 'profiles', 'startup')
// Written straight into the installed location: a row module resolves its
// own imports from where it is installed, and only inside the profile does
// Node's parent walk reach the installation fallback these plugins need.
const bundleDir = join(profileDir, 'node_modules', 'dsh-startup-bundle')
mkdirSync(bundleDir, { recursive: true })
writeFileSync(join(bundleDir, 'startup.mjs'), [
"import { Command } from 'commander'",
"import { parseCmdline } from '@deepseek-ai/dsh-cmdline'",
"export const name = 'fixture-startup'",
"export const inject = ['cmdlineArgs']",
'export function apply(ctx) {',
" const program = new Command().name('fixture').option('--generation <value>', 'echoed generation')",
' const values = parseCmdline(ctx, program, parsed => ({ generation: parsed.opts().generation }))',
' if (values !== undefined) ctx.provide(\'fixtureStartup\', values)',
'}',
'',
].join('\n'))
writeFileSync(join(bundleDir, 'waiting.mjs'), [
"import { existsSync, writeFileSync } from 'node:fs'",
"import { join } from 'node:path'",
"export const name = 'startup-fixture'",
'export function apply(ctx, config = {}) {',
' let interrupted = false',
' const heartbeat = setInterval(() => {',
' if (interrupted || !existsSync(process.env.RAW_INTERRUPT_FILE)) return',
' interrupted = true',
" process.emit('SIGTERM')",
' }, 20)',
" writeFileSync(join(process.env.DSH_HOME, 'config-echo'), String(config.generation ?? 'bundle-default'))",
" writeFileSync(process.env.RAW_READY_FILE, 'ready')",
' ctx.effect(() => () => { clearInterval(heartbeat) })',
'}',
'',
].join('\n'))
writeFileSync(join(bundleDir, 'witness.mjs'), [
"import { writeFileSync } from 'node:fs'",
"import { join } from 'node:path'",
"export const name = 'reload-witness'",
'export function apply(ctx, config = {}) {',
" writeFileSync(join(process.env.DSH_HOME, 'witness'), String(config.generation ?? 'bundle-default'))",
'}',
'',
].join('\n'))
writeFileSync(join(bundleDir, 'cordis.patch.yml'), [
'- insert:',
' - id: startup-fixture',
` name: ${pathToFileURL(join(bundleDir, 'waiting.mjs')).href}`,
' inject: [fixtureStartup]',
' config:',
// Lazy interpolation runs only after the provider's service is injected.
" generation: !!js ctx.fixtureStartup.generation ?? 'bundle-default'",
' - id: fixture-startup',
` name: ${pathToFileURL(join(bundleDir, 'startup.mjs')).href}`,
' - id: reload-witness',
` name: ${pathToFileURL(join(bundleDir, 'witness.mjs')).href}`,
'',
].join('\n'))
writeFileSync(join(bundleDir, 'package.json'), JSON.stringify({
name: 'dsh-startup-bundle',
version: '0.0.0',
type: 'module',
dsh: { bundle: { patch: './cordis.patch.yml' } },
}, undefined, 2))
writeFileSync(join(profileDir, 'package.json'), JSON.stringify({
name: 'dsh-profile-startup',
private: true,
dependencies: {},
dsh: { profile: { bundles: ['dsh-startup-bundle'] } },
}, undefined, 2))
writeFileSync(join(profileDir, 'cordis.patch.yml'), '[]\n')
return {
home,
ready: join(home, 'ready'),
echo: join(home, 'config-echo'),
interrupt: join(home, 'interrupt'),
witness: join(home, 'witness'),
}
}
function startStartupProfile(fixture: StartupFixture, args: readonly string[]) {
return execa(process.execPath, [dshBin, '--profile', 'startup', ...args], {
cwd: fixture.home,
input: '',
reject: false,
timeout: 25_000,
killSignal: 'SIGKILL',
env: {
DSH_HOME: fixture.home,
RAW_READY_FILE: fixture.ready,
RAW_INTERRUPT_FILE: fixture.interrupt,
},
})
}
describe.skipIf(!existsSync(dshBin))('dsh BUILT bin (node lib/bin.js, no tsx)', () => {
it('requires --profile and rejects inputs outside the current grammar', async () => {
it('requires --profile and rejects removed commands', async () => {
const bare = await runBuiltBin()
expect(bare.code).toBe(1)
expect(bare.stdout).toBe('')
@@ -203,46 +318,63 @@ describe.skipIf(!existsSync(dshBin))('dsh BUILT bin (node lib/bin.js, no tsx)',
const help = await runBuiltBin(['--help'])
expect(help.code).toBe(0)
expect(help.stdout).toContain('dsh --profile web')
expect(help.stdout).toContain('dsh run "run the tests"')
expect(help.stdout).toContain('dsh plugin --profile')
expect(help.stdout).not.toMatch(/^\s+(?:tui|meta|upgrade)\b/mu)
for (const outsideGrammar of [['tui'], ['--config', 'x.yml'], ['-p', 'task'], ['--profile', 'headless', 'task']]) {
const result = await runBuiltBin(outsideGrammar)
for (const removed of [['tui'], ['--config', 'x.yml'], ['-p', 'task'], ['run', 'task']]) {
const result = await runBuiltBin(removed)
expect(result.code).toBe(1)
}
}, 30_000)
it('prints run help without initializing the selected profile', async () => {
const parent = mkdtempSync(join(tmpdir(), 'dsh-run-help-'))
const home = join(parent, 'not-created')
it('routes help and usage errors without activating startup-dependent rows', async () => {
const home = mkdtempSync(join(tmpdir(), 'dsh-app-help-'))
try {
const result = await runBuiltBin(['run', '--help'], { DSH_HOME: home })
expect(result.code).toBe(0)
expect(result.stderr).toBe('')
expect(result.stdout).toContain('Usage: dsh run [options] <task...>')
expect(existsSync(home)).toBe(false)
} finally {
rmSync(parent, { recursive: true, force: true })
}
})
const web = await runBuiltBin(['--profile', 'web', '--help'], {
DSH_HOME: home,
DSH_TELEMETRY_DISABLED: '1',
})
expect(web.code).toBe(0)
expect(web.stderr).toBe('')
expect(web.stdout).toContain('Usage: dsh --profile web')
expect(web.stdout).toContain('--port <port>')
expect(web.stdout).not.toContain('dsh web: http://')
it('runs the default headless profile through the published run command', async () => {
const apiKey = 'built-dsh-run-key'
const headlessHelp = await runBuiltBin(['--profile', 'headless', '--help'], {
DSH_HOME: home,
DSH_TELEMETRY_DISABLED: '1',
})
expect(headlessHelp.code).toBe(0)
expect(headlessHelp.stderr).toBe('')
expect(headlessHelp.stdout).toContain('Usage: dsh --profile headless')
const missingTask = await runBuiltBin(['--profile', 'headless'], {
DSH_HOME: home,
DSH_TELEMETRY_DISABLED: '1',
})
expect(missingTask.code).toBe(1)
expect(missingTask.stderr).toContain('a task is required')
} finally {
rmSync(home, { recursive: true, force: true })
}
}, 30_000)
it('runs the headless profile through its app-owned task positional', async () => {
const apiKey = 'built-dsh-headless-key'
const server = await startMockLlmServer({
sequence: ['success'],
apiKey,
successText: 'published dsh run reached the mock',
successText: 'published headless profile reached the mock',
})
const home = mkdtempSync(join(tmpdir(), 'dsh-built-run-'))
const home = mkdtempSync(join(tmpdir(), 'dsh-built-headless-'))
try {
const result = await runBuiltBin(['run', 'answer', 'from', 'the', 'published', 'entry'], {
const result = await runBuiltBin(['--profile', 'headless', 'answer', 'from', 'the', 'published', 'entry'], {
DSH_HOME: home,
DSH_TELEMETRY_DISABLED: '1',
DEEPSEEK_API_KEY: apiKey,
DEEPSEEK_BASE_URL: server.baseURL,
})
expect(result.code, result.stderr).toBe(0)
expect(result.stdout).toBe('published dsh run reached the mock')
expect(result.stdout).toBe('published headless profile reached the mock')
expect(result.stderr).toBe('')
expect(server.requests.length).toBeGreaterThan(0)
expect(server.requests.every(request => request.path === '/chat/completions')).toBe(true)
@@ -258,7 +390,7 @@ describe.skipIf(!existsSync(dshBin))('dsh BUILT bin (node lib/bin.js, no tsx)',
writeFileSync(join(project, '.env'), 'PATH=/project-only-path\n')
try {
const result = await runBuiltBin(['--version'], {}, project)
expect(result).toEqual({ code: 0, stdout: '0.0.1', stderr: '' })
expect(result).toEqual({ code: 0, stdout: cliVersion, stderr: '' })
} finally {
rmSync(project, { recursive: true, force: true })
}
@@ -318,9 +450,9 @@ describe.skipIf(!existsSync(dshBin))('dsh BUILT bin (node lib/bin.js, no tsx)',
}, 30_000)
it('reports a patch-overlay boot failure without hanging', async () => {
// An HMR main-watcher initial scan that refreshes the include
// mid-initial-apply deadlocks the failing apply's rollback against the
// refresh drain: dsh exits 13 with no diagnostic instead of settling
// The HMR main watcher's initial scan once refreshed the include
// mid-initial-apply, deadlocking the failing apply's rollback against the
// refresh drain: dsh exited 13 with no diagnostic instead of settling
// ([Agent Note](../../../.agents/notes/implemented/bug-fix/2026-08-03-hmr-initial-scan-boot-deadlock.md)).
const home = mkdtempSync(join(tmpdir(), 'dsh-invalid-patch-'))
try {
@@ -337,9 +469,9 @@ describe.skipIf(!existsSync(dshBin))('dsh BUILT bin (node lib/bin.js, no tsx)',
}
}, 30_000)
it('applies a custom profile bundle and disposes it on a startup-time signal', async () => {
it('lets a profile without a parser ignore app arguments and dispose on a startup-time signal', async () => {
const fixture = createProfileLifecycleFixture()
const child = startProfileLifecycle(fixture)
const child = startProfileLifecycle(fixture, ['--unclaimed'])
try {
await waitForFile(fixture.ready)
requestProfileShutdown(child, fixture)
@@ -405,6 +537,83 @@ describe.skipIf(!existsSync(dshBin))('dsh BUILT bin (node lib/bin.js, no tsx)',
}
}, 30_000)
it('hands the app arguments to the profile, which applies them before its rows start', async () => {
const fixture = createStartupFixture()
const child = startStartupProfile(fixture, ['--generation', 'flagged'])
try {
await waitForFile(fixture.ready)
// The consumer started once, already carrying the flag value: the
// launcher never saw --generation, and the app provider resolved it first.
expect(readFileSync(fixture.echo, 'utf8')).toBe('flagged')
requestProfileShutdown(child, fixture)
expect((await child).exitCode).toBe(0)
} finally {
child.kill('SIGKILL')
rmSync(fixture.home, { recursive: true, force: true })
}
}, 30_000)
it('starts a consumer on its composed value when the invocation carries no app arguments', async () => {
const fixture = createStartupFixture()
const child = startStartupProfile(fixture, [])
try {
await waitForFile(fixture.ready)
expect(readFileSync(fixture.echo, 'utf8')).toBe('bundle-default')
requestProfileShutdown(child, fixture)
expect((await child).exitCode).toBe(0)
} finally {
child.kill('SIGKILL')
rmSync(fixture.home, { recursive: true, force: true })
}
}, 30_000)
it('keeps the app arguments across a user patch reload', async () => {
// A live edit recomposes every row while the provider service remains
// active, so each config expression reads the same invocation value (a
// served port does not move back to its composed fallback).
const fixture = createStartupFixture()
const profilePatch = join(fixture.home, 'profiles', 'startup', 'cordis.patch.yml')
const child = startStartupProfile(fixture, ['--generation', 'flagged'])
try {
// Both rows: the waiting one carries the flag value, and the witness is
// what a reload will re-mount. They start independently, so neither
// marker implies the other.
await waitForFile(fixture.ready)
await waitForFile(fixture.witness)
expect(readFileSync(fixture.echo, 'utf8')).toBe('flagged')
// An edit to an unrelated row: the witness re-mounts, which is how this
// test knows the whole tree was recomposed.
rmSync(fixture.witness)
writeFileSync(profilePatch, [
'- id: reload-witness',
' config:',
' generation: reloaded',
'',
].join('\n'))
await waitForFile(fixture.witness)
expect(readFileSync(fixture.witness, 'utf8')).toBe('reloaded')
expect(readFileSync(fixture.echo, 'utf8')).toBe('flagged')
requestProfileShutdown(child, fixture)
expect((await child).exitCode).toBe(0)
} finally {
child.kill('SIGKILL')
rmSync(fixture.home, { recursive: true, force: true })
}
}, 30_000)
it("prints the app's own help, starts none of its rows, and exits", async () => {
const fixture = createStartupFixture()
try {
const result = await startStartupProfile(fixture, ['--help'])
expect(result.exitCode).toBe(0)
expect(result.stdout).toContain('Usage: fixture')
expect(result.stdout).toContain('--generation')
expect(existsSync(fixture.ready)).toBe(false)
} finally {
rmSync(fixture.home, { recursive: true, force: true })
}
}, 30_000)
it('anchors a relative add spec to the invoking directory, not the profile', async () => {
// `dsh plugin --profile x add .` from a plugin checkout must install THAT
// checkout — pnpm's cwd is the profile directory, so an un-anchored `.`
@@ -491,18 +700,17 @@ describe.skipIf(!existsSync(dshBin))('dsh BUILT bin (node lib/bin.js, no tsx)',
expect(stdout).toContain("name: '@deepseek-ai/dsh-host-webserver'")
}, 30_000)
it('prints a headless profile with no Host, HTTP, or browser rows', async () => {
it('prints the headless profile without Host or browser layers', async () => {
const { stdout, code, stderr } = await runBuiltBin(
['--profile', 'headless', '--dump-default-config'],
{ DSH_HOME: home },
)
expect(code).toBe(0)
expect(stderr).toBe('')
expect(stdout).toContain("name: '@deepseek-ai/dsh-agent-default-model'")
expect(stdout).toContain("name: '@deepseek-ai/dsh-headless'")
expect(stdout).not.toContain("name: '@deepseek-ai/dsh-host-")
expect(stdout).not.toMatch(/name: '@deepseek-ai\/dsh-host-/)
expect(stdout).not.toContain("name: '@deepseek-ai/dsh-web-app'")
expect(stdout).not.toContain("name: '@deepseek-ai/dsh-client-")
expect(stdout).not.toMatch(/name: '@deepseek-ai\/dsh-client-/)
}, 30_000)
it('composes the profile user layer and a --patch overlay in order', async () => {
@@ -543,16 +751,5 @@ describe.skipIf(!existsSync(dshBin))('dsh BUILT bin (node lib/bin.js, no tsx)',
expect(stdout).toContain(`patched by ${profilePatch}, ${overlay}`)
expect(stderr).toContain('patch: entry "absent-row" not found')
}, 30_000)
it('shows the RL Web patch disabling runtime surface context', async () => {
const { stdout, code, stderr } = await runBuiltBin(
['web', '--patch', coreWebOverlay, '--dump-config'],
{ DSH_HOME: home },
)
expect(code).toBe(0)
expect(stderr).toBe('')
expect(stdout).toContain("name: '@deepseek-ai/dsh-web-app'")
expect(stdout).toContain('surfaceContext: false')
}, 30_000)
})
})

View File

@@ -85,14 +85,14 @@ describe('dsh badge assembled snapshot', () => {
- Local PNG: [\`dsh-badge.png\`](dsh-badge.png), 726×120 source image; render at 121×20
- Shields.io image URL: \`https://img.shields.io/badge/powered_by-dsh-4D6BFE?style=flat-square&logo=deepseek&logoColor=white\`
- Project URL: \`https://github.com/deepseek-ai/deepseek-harness-sdk\`
- Project URL: \`https://github.com/deepseek-ai/deepseek-harness\`
## Markdown
Use this linked badge in Markdown:
\`\`\`markdown
[![](https://img.shields.io/badge/powered_by-dsh-4D6BFE?style=flat-square&logo=deepseek&logoColor=white)](https://github.com/deepseek-ai/deepseek-harness-sdk)
[![](https://img.shields.io/badge/powered_by-dsh-4D6BFE?style=flat-square&logo=deepseek&logoColor=white)](https://github.com/deepseek-ai/deepseek-harness)
\`\`\`
If attribution should not be linked, use:
@@ -124,14 +124,14 @@ describe('dsh badge assembled snapshot', () => {
- Local PNG: [\`dsh-badge.png\`](dsh-badge.png), 726×120 source image; render at 121×20
- Shields.io image URL: \`https://img.shields.io/badge/powered_by-dsh-4D6BFE?style=flat-square&logo=deepseek&logoColor=white\`
- Project URL: \`https://github.com/deepseek-ai/deepseek-harness-sdk\`
- Project URL: \`https://github.com/deepseek-ai/deepseek-harness\`
## Markdown
Use this linked badge in Markdown:
\`\`\`markdown
[![](https://img.shields.io/badge/powered_by-dsh-4D6BFE?style=flat-square&logo=deepseek&logoColor=white)](https://github.com/deepseek-ai/deepseek-harness-sdk)
[![](https://img.shields.io/badge/powered_by-dsh-4D6BFE?style=flat-square&logo=deepseek&logoColor=white)](https://github.com/deepseek-ai/deepseek-harness)
\`\`\`
If attribution should not be linked, use:

View File

@@ -1,5 +1,5 @@
import { fileURLToPath } from 'node:url'
import { Context } from 'cordis'
import { Context } from '@deepseek-ai/cordis'
import { agentEvents, Inbox, type Agent } from '@deepseek-ai/dsh-agent'
import { CallId } from '@deepseek-ai/dsh-llm'
import { boot, loadOverlayPatches } from '@deepseek-ai/dsh-app-boot'

View File

@@ -4,7 +4,7 @@ import { existsSync } from 'node:fs'
/**
* Register a disposer that keeps process shutdown pending until it is forced.
* @param {import('cordis').Context} ctx - loader-mounted test plugin context.
* @param {import('@deepseek-ai/cordis').Context} ctx - loader-mounted test plugin context.
*/
export function apply(ctx) {
const keepAlive = setInterval(() => {}, 60_000)

View File

@@ -83,7 +83,7 @@ async function runHeadlessPtySmoke(): Promise<string> {
].join('\n'))
const launch = resolveExampleLaunch({
srcBin: dshBinScript,
configArgs: ['run', 'never complete'],
configArgs: ['--profile', 'headless', 'never complete'],
tsconfigPath,
env: {
DSH_HOME: home,

View File

@@ -1,140 +0,0 @@
import { chmodSync, copyFileSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs'
import { mkdtemp, rm } from 'node:fs/promises'
import { tmpdir } from 'node:os'
import { join } from 'node:path'
import { fileURLToPath } from 'node:url'
import { execa } from 'execa'
import { afterEach, describe, expect, it } from 'vitest'
const installer = fileURLToPath(new URL('../../../scripts/install.sh', import.meta.url))
const fixtures: string[] = []
const PTY_DRIVER = String.raw`
import errno, json, os, pty, select, signal, sys, time
script, cwd, env_json, actions_json = sys.argv[1:]
env = os.environ.copy()
env.update(json.loads(env_json))
actions = json.loads(actions_json)
pid, fd = pty.fork()
if pid == 0:
os.chdir(cwd)
os.execvpe("sh", ["sh", script], env)
output = bytearray()
action_index = 0
deadline = time.monotonic() + 15
status = None
while time.monotonic() < deadline:
ready, _, _ = select.select([fd], [], [], 0.05)
if ready:
try:
chunk = os.read(fd, 65536)
except OSError as error:
if error.errno != errno.EIO:
raise
chunk = b""
output.extend(chunk)
while action_index < len(actions) and actions[action_index]["waitFor"].encode() in output:
os.write(fd, actions[action_index]["send"].encode())
action_index += 1
waited, candidate = os.waitpid(pid, os.WNOHANG)
if waited == pid:
status = candidate
break
if status is None:
os.kill(pid, signal.SIGKILL)
_, status = os.waitpid(pid, 0)
sys.stdout.buffer.write(output)
if action_index != len(actions):
sys.stderr.write(f"completed {action_index}/{len(actions)} PTY actions\n")
sys.exit(124)
sys.exit(os.waitstatus_to_exitcode(status))
`
interface Action {
readonly waitFor: string
readonly send: string
}
interface Fixture {
readonly binDirectory: string
readonly launchLog: string
readonly pnpmLog: string
readonly root: string
readonly script: string
}
afterEach(async () => {
await Promise.all(fixtures.splice(0).map(async (fixture) => { await rm(fixture, { force: true, recursive: true }) }))
})
function executable(path: string, content: string): void {
writeFileSync(path, content)
chmodSync(path, 0o755)
}
async function createFixture(): Promise<Fixture> {
const root = await mkdtemp(join(tmpdir(), 'dsh-install-'))
fixtures.push(root)
const checkoutDirectory = join(root, 'checkout')
const scriptsDirectory = join(checkoutDirectory, 'scripts')
const sourceBinDirectory = join(checkoutDirectory, 'bin')
const fakeBinDirectory = join(root, 'fake-bin')
const binDirectory = join(root, 'path-bin')
for (const directory of [scriptsDirectory, sourceBinDirectory, fakeBinDirectory, binDirectory, join(root, 'home/.dsh')]) {
mkdirSync(directory, { recursive: true })
}
const script = join(scriptsDirectory, 'install.sh')
copyFileSync(installer, script)
const launchLog = join(root, 'launch.log')
const pnpmLog = join(root, 'pnpm.log')
executable(join(sourceBinDirectory, 'dsh'), '#!/bin/sh\nprintf \'%s\\n\' "$*" >"$DSH_TEST_LAUNCH_LOG"\n')
executable(join(fakeBinDirectory, 'pnpm'), `#!/bin/sh
if [ "\${1:-}" = --version ]; then printf '11.7.0\\n'; exit 0; fi
printf '%s\\n' "$*" >>"$DSH_TEST_PNPM_LOG"
`)
await execa('git', ['init', '-q'], { cwd: checkoutDirectory })
await execa('git', ['add', 'bin/dsh', 'scripts/install.sh'], { cwd: checkoutDirectory })
await execa('git', [
'-c', 'user.name=dsh-test',
'-c', 'user.email=dsh-test@example.invalid',
'commit', '-qm', 'fixture',
], { cwd: checkoutDirectory })
writeFileSync(join(root, 'home/.dsh/.env'), 'DEEPSEEK_API_KEY=test\n')
return { binDirectory, launchLog, pnpmLog, root, script }
}
async function runInstaller(fixture: Fixture, actions: readonly Action[]): Promise<string> {
const result = await execa('python3', [
'-c',
PTY_DRIVER,
fixture.script,
fixture.root,
JSON.stringify({
DSH_BIN_DIR: fixture.binDirectory,
DSH_HOME: join(fixture.root, 'home/.dsh'),
DSH_TEST_LAUNCH_LOG: fixture.launchLog,
DSH_TEST_PNPM_LOG: fixture.pnpmLog,
HOME: join(fixture.root, 'home'),
PATH: `${join(fixture.root, 'fake-bin')}:${fixture.binDirectory}:${process.env.PATH ?? ''}`,
}),
JSON.stringify(actions),
], { reject: false, stripFinalNewline: false, timeout: 20_000 })
expect(result.exitCode, result.stderr).toBe(0)
return result.stdout
}
describe.runIf(process.platform !== 'win32')('one-line installer launch', { timeout: 25_000 }, () => {
it('builds and launches the Web UI', async () => {
const fixture = await createFixture()
const output = await runInstaller(fixture, [
{ waitFor: 'Replace it?', send: '\n' },
])
expect(output).toContain('launching Web UI')
expect(readFileSync(fixture.pnpmLog, 'utf8')).toBe('install\nrun build\n')
expect(readFileSync(fixture.launchLog, 'utf8')).toBe('web\n')
})
})

View File

@@ -8,8 +8,8 @@
import { readFileSync } from 'node:fs'
import { resolve } from 'node:path'
import { afterEach, describe, expect, it } from 'vitest'
import type { Context } from 'cordis'
import type { PatchOptions } from '@cordisjs/plugin-include'
import type { Context } from '@deepseek-ai/cordis'
import type { PatchOptions } from '@deepseek-ai/cordis-plugin-include'
import { boot, loadOverlayPatches } from '@deepseek-ai/dsh-app-boot'
import SystemPrompt from '@deepseek-ai/dsh-system-prompt'
import ToolRegistry from '@deepseek-ai/dsh-tools'

View File

@@ -1,11 +1,12 @@
import { readFile } from 'node:fs/promises'
import { fileURLToPath } from 'node:url'
import { execa } from 'execa'
import { describe, expect, it } from 'vitest'
/**
* Keyless smoke for the SOURCE `dsh` launcher: run `apps/cli/src/bin.ts`
* with the exact production launch vector (`node --import tsx/esm`, the same
* executable and arguments as `bin/dsh` and the root `dsh`/`demo:web` scripts) and assert the
* Keyless smoke for SOURCE `dsh` execution: run `apps/cli/src/bin.ts`
* with the exact production runtime vector (`node --import tsx/esm`, the
* vector the root `dsh` script invokes after building) and assert the
* required-config diagnostic. The Node compatibility matrix runs this
* WHOLE file, so a Node release changing module hooks or TypeScript handling
* breaks this gate instead of every developer's `pnpm dsh`; the built-bin
@@ -16,6 +17,13 @@ const repoRoot = fileURLToPath(new URL('../../../', import.meta.url))
const dshSourceBin = 'apps/cli/src/bin.ts'
describe('dsh SOURCE launcher (node --import tsx/esm)', () => {
it('builds before launching the source CLI', async () => {
const rootPackage = JSON.parse(await readFile(new URL('../../../package.json', import.meta.url), 'utf8')) as {
readonly scripts?: Record<string, string>
}
expect(rootPackage.scripts?.dsh).toBe('pnpm run build && node --import tsx/esm apps/cli/src/bin.ts')
})
it('boots the source entry and requires a profile', async () => {
const result = await execa(process.execPath, ['--import', 'tsx/esm', dshSourceBin], {
cwd: repoRoot,

View File

@@ -1,45 +0,0 @@
/** Single-sample LAN-trust resolution for the /api browser-trust fence (`resolveLanTrust`). */
import { describe, expect, it, vi } from 'vitest'
import { resolveLanTrust, webSurfaceContextEnabled } from '../src/web.ts'
vi.mock('node:os', () => ({
networkInterfaces: () => ({
lo0: [
{ family: 'IPv4', internal: true, address: '127.0.0.1' },
],
en0: [
{ family: 'IPv6', internal: false, address: 'fe80::1' },
{ family: 'IPv4', internal: false, address: '192.168.1.5' },
],
en1: [
{ family: 'IPv4', internal: false, address: '10.0.0.7' },
],
utun0: undefined,
}),
}))
describe('resolveLanTrust', () => {
it('samples non-internal IPv4 addresses once for an all-interfaces bind: trust and display share them', () => {
const { lanAddresses, trustedHosts } = resolveLanTrust('0.0.0.0', ['harness.internal:3080'])
expect(lanAddresses).toEqual(['192.168.1.5', '10.0.0.7'])
expect(trustedHosts).toEqual(['192.168.1.5', '10.0.0.7', 'harness.internal:3080'])
})
it('derives nothing for a loopback or unresolved bind — extras alone stand, no LAN URL to print', () => {
expect(resolveLanTrust('127.0.0.1', [])).toEqual({ lanAddresses: [], trustedHosts: [] })
expect(resolveLanTrust(undefined, ['lab.internal'])).toEqual({ lanAddresses: [], trustedHosts: ['lab.internal'] })
})
})
describe('webSurfaceContextEnabled', () => {
it('defaults to enabled and honors an explicit complete-prompt disable', () => {
expect(webSurfaceContextEnabled(new Map())).toBe(true)
expect(webSurfaceContextEnabled(new Map([
['web-runtime', { config: { mode: 'production' } }],
]))).toBe(true)
expect(webSurfaceContextEnabled(new Map([
['web-runtime', { config: { surfaceContext: false } }],
]))).toBe(false)
})
})

View File

@@ -3,18 +3,23 @@ import { mkdir, mkdtemp, readFile, stat, writeFile } from 'node:fs/promises'
import { tmpdir } from 'node:os'
import { fileURLToPath } from 'node:url'
import { dirname, join } from 'node:path'
import { Context } from 'cordis'
import { Context } from '@deepseek-ai/cordis'
import { boot, healProfilesModuleFallback, loadOverlayPatches } from '@deepseek-ai/dsh-app-boot'
import { provideCmdline } from '@deepseek-ai/dsh-cmdline'
import { SessionId } from '@deepseek-ai/dsh-session'
import type { Agent } from '@deepseek-ai/dsh-agent'
import type { PatchOptions } from '@cordisjs/plugin-include'
import { beforeAll, describe, expect, it } from 'vitest'
import type { PatchOptions } from '@deepseek-ai/cordis-plugin-include'
import { afterAll, beforeAll, describe, expect, it } from 'vitest'
import { settingsNamespace } from '@deepseek-ai/dsh-settings'
import { resolveSessionPreset, SETTINGS_NAMESPACE } from '@deepseek-ai/dsh-agent-presets'
import { applyChildComposition, childSessionMeta } from '@deepseek-ai/dsh-subagent'
import { CallId } from '@deepseek-ai/dsh-llm'
import type { BasicCompactService } from '@deepseek-ai/dsh-compact-basic'
import type {} from '@deepseek-ai/dsh-skill'
import type {} from '@deepseek-ai/dsh-tools'
// Type-only: resolves `ctx.get('sessionProjections')` and `ctx.get('tokenMeter')`.
import type {} from '@deepseek-ai/dsh-session-projection'
import type {} from '@deepseek-ai/dsh-token-meter'
const CONFIG_DIR = fileURLToPath(new URL('../config/', import.meta.url))
const REPO_ROOT = fileURLToPath(new URL('../../..', import.meta.url))
@@ -23,6 +28,15 @@ const BASE_PATCH = join(REPO_ROOT, 'packages/bundle/base/cordis.patch.yml')
const WEB_PATCH = join(REPO_ROOT, 'packages/bundle/web-app/cordis.patch.yml')
/** The installation anchor whose dependency surface the preset module fallback mirrors. */
const INSTALL_ANCHOR = join(REPO_ROOT, 'apps/cli/package.json')
const MINIMAL_PROMPT = 'You are a helpful software engineer assistant.'
const MINIMAL_BASH_DESCRIPTION = `Run commands in a bash shell
* When invoking this tool, the contents of the "command" parameter does NOT need to be XML-escaped.
* You don't have access to the internet via this tool.
* You do have access to a mirror of common linux and python packages via apt and pip.
* State is persistent across command calls and discussions with the user.
* To inspect a particular line range of a file, e.g. lines 10-25, try 'sed -n 10,25p /path/to/the/file'.
* Please avoid commands that may produce a very large amount of output.
* Please run long lived commands in the background, e.g. 'sleep 10 &' or start a server in the background.`
/**
* Boot the shipped Web composition, minus the rows that would bind a port,
@@ -90,12 +104,26 @@ async function bootWeb(settingsFile: string, extra: PatchOptions[] = []): Promis
await mkdir(profileDir, { recursive: true })
const rootConfig = join(profileDir, 'cordis.yml')
await writeFile(rootConfig, '[]\n')
return await boot('dsh-test', rootConfig, patches)
return await boot('dsh-test', rootConfig, patches, (bootCtx) => {
provideCmdline(bootCtx, { args: [], exit: () => {} })
})
}
const toolNames = (ctx: Context, agent?: Agent): string[] =>
ctx.tools.schemas(agent).map(schema => schema.name).sort()
function enablePresetTool(composition: string, id: string): string {
const row = ` - id: ${id}\n`
const start = composition.indexOf(row)
if (start < 0) throw new Error(`missing preset row ${id}`)
const end = composition.indexOf('\n - id:', start + row.length)
const disabled = composition.indexOf(' disabled: true\n', start)
if (disabled < 0 || (end >= 0 && disabled > end)) {
throw new Error(`preset row ${id} is not disabled`)
}
return composition.slice(0, disabled) + composition.slice(disabled + ' disabled: true\n'.length)
}
let ctx: Context
beforeAll(async () => {
const settingsFile = join(await mkdtemp(join(tmpdir(), 'dsh-web-presets-')), 'settings.yaml')
@@ -113,6 +141,33 @@ describe('the shipped Web composition', () => {
expect(toolNames(ctx)).toEqual([])
})
it('keeps the token meter and its context-meter projections on the host plane', async () => {
// Read before any preset in this file mounts, which is what makes this an
// ownership assertion rather than a mount-order coincidence: a preset-side
// meter sits behind an `isolate` realm and is invisible to `ctx.get`.
//
// The projection registry is process-wide rather than scope-layered, so a
// preset-side meter would also make the browser's context meter appear for
// a `minimal` session the moment some OTHER session mounted a preset that
// carries one, and vanish entirely in a process that only ever ran
// `minimal`. Host ownership is what makes the meter a per-session fact.
expect(ctx.get('tokenMeter')).toBeDefined()
const projections = ctx.get('sessionProjections')
if (projections === undefined) throw new Error('the Web composition must compose a projection registry')
const handle = await ctx.agents.create({
sessionId: SessionId('preset-minimal-meter'),
setup: agentCtx => ctx.agentPresets.mount(agentCtx, 'minimal').then(() => undefined),
})
try {
// A subset assertion: `tasks`, `goal`, and the rest register into the
// same process-wide table, and this is about the meter's three units.
expect(Object.keys(projections.snapshot(handle.agent.session).values))
.toEqual(expect.arrayContaining(['contextBreakdown', 'contextPressure', 'tokenUsage']))
} finally {
await handle.dispose()
}
})
it('supplies both shipped presets, and only those, from the system root', async () => {
const listed = await ctx.agentPresets.list()
@@ -134,8 +189,8 @@ describe('the shipped Web composition', () => {
// depend on ripgrep being present on the machine.
expect(toolNames(ctx, handle.agent).filter(name => name !== 'glob' && name !== 'grep')).toEqual([
'ask_user_question', 'bash', 'create_goal', 'edit', 'exit_plan_mode',
'get_goal', 'interrupt_agent', 'list_agents', 'ralph', 'read', 'send_message', 'skill',
'str_replace_editor', 'subagent', 'subagent_fork', 'task_kill',
'get_goal', 'interrupt_agent', 'list_agents', 'ralph', 'read', 'read_image', 'send_message', 'skill',
'subagent', 'subagent_fork', 'task_kill',
'task_list', 'task_output', 'todo_write', 'update_goal', 'web_search',
'workflow', 'write',
])
@@ -144,14 +199,30 @@ describe('the shipped Web composition', () => {
}
})
it('composes exactly two tools from `minimal`', async () => {
it('composes the exact RL prompt and two tools from `minimal`', async () => {
const handle = await ctx.agents.create({
sessionId: SessionId('preset-minimal'),
setup: agentCtx => ctx.agentPresets.mount(agentCtx, 'minimal').then(() => undefined),
})
try {
// Exactly what the preset lists — nothing arrives from the host.
expect(toolNames(ctx, handle.agent)).toEqual(['bash', 'str_replace_editor'])
const assembly = await ctx.systemPrompt.assemble({ scope: handle.agent })
expect(assembly.sections).toEqual([
{ name: 'deployment:persona', text: MINIMAL_PROMPT },
])
expect(assembly.tools.map(tool => tool.name)).toEqual(['bash', 'str_replace_editor'])
expect(assembly.tools.find(tool => tool.name === 'bash')?.description).toBe(MINIMAL_BASH_DESCRIPTION)
expect(JSON.stringify(assembly.tools.find(tool => tool.name === 'str_replace_editor')?.parameters))
.toContain('Absolute path')
const compact = ctx.agentPresets.serviceFor(handle.agent, 'compact')
expect(compact).toBeDefined()
expect((compact as BasicCompactService).config).toMatchObject({
thresholdRatio: 0.8,
retainTokens: 20480,
summarizationProvider: '',
summarizationModel: '',
maxTokens: 8192,
compactionRetries: 1,
})
} finally {
await handle.dispose()
}
@@ -191,6 +262,7 @@ describe('the shipped Web composition', () => {
expect(tools).toEqual(expect.arrayContaining(['cordis_inspect', 'cordis_mount', 'cordis_unmount']))
// And it keeps the standard agent's own tools rather than replacing them.
expect(tools).toEqual(expect.arrayContaining(['bash', 'read', 'edit', 'skill']))
expect(tools).not.toContain('str_replace_editor')
// The preset's own authoring skill registers into ITS layer of the host
// registry: the cordis agent's view carries it, the global view does not.
@@ -217,9 +289,9 @@ describe('the shipped Web composition', () => {
// the capabilities — so the assembly is what carries the claim.
const assembly = await ctx.systemPrompt.assemble({ scope: coded.agent })
expect(assembly.tools.map(tool => tool.name)).toEqual(['run_code'])
expect(toolNames(ctx, coded.agent)).toContain('str_replace_editor')
expect(toolNames(ctx, coded.agent)).not.toContain('str_replace_editor')
const sdk = assembly.sections.find(section => section.name === 'tools:sdk')?.text ?? ''
expect(sdk).toContain('str_replace_editor')
expect(sdk).not.toContain('str_replace_editor')
expect(sdk).toContain('web_search')
// The presentation is this agent's alone: the deployment default is
@@ -340,18 +412,96 @@ describe('the shipped Web composition', () => {
expect(await readFile(path, 'utf8')).toBe(before)
})
})
it('gives each session its own persona', async () => {
const handle = await ctx.agents.create({
sessionId: SessionId('preset-persona'),
setup: agentCtx => ctx.agentPresets.mount(agentCtx, 'minimal').then(() => undefined),
describe('product subagent rows in user presets', () => {
let productCtx: Context
const ids = ['products-none', 'products-codex', 'products-claude', 'products-both'] as const
beforeAll(async () => {
const root = await mkdtemp(join(tmpdir(), 'dsh-product-presets-'))
const userRoot = join(root, 'presets')
const settingsFile = join(root, 'settings.yaml')
const standard = await readFile(join(CONFIG_DIR, 'agent-presets', 'standard', 'agent.cordis.yml'), 'utf8')
await writeFile(settingsFile, '{}\n')
for (const id of ids) {
let composition = standard
if (id === 'products-codex' || id === 'products-both') {
composition = enablePresetTool(composition, 'tool-subagent-codex')
}
if (id === 'products-claude' || id === 'products-both') {
composition = enablePresetTool(composition, 'tool-subagent-claude-code')
}
const directory = join(userRoot, id)
await mkdir(directory, { recursive: true })
await writeFile(join(directory, 'agent.cordis.yml'), composition)
}
productCtx = await bootWeb(settingsFile, [{
id: 'agent-presets',
config: {
default: 'standard',
roots: [
{ path: join(CONFIG_DIR, 'agent-presets'), trust: 'system' },
{ path: userRoot, trust: 'user' },
],
},
}])
}, 120_000)
afterAll(async () => {
await productCtx.fiber.dispose()
})
it('composes none, either product, or both without changing the shared host registry', async () => {
const expected = new Map<string, string[]>([
['products-none', []],
['products-codex', ['subagent_codex']],
['products-claude', ['subagent_claude_code']],
['products-both', ['subagent_claude_code', 'subagent_codex']],
])
expect(productCtx.subagents.list()).toEqual(expect.arrayContaining([
'spawn', 'fork', 'codex', 'claude-code',
]))
for (const [id, productTools] of expected) {
const handle = await productCtx.agents.create({
sessionId: SessionId(`preset-${id}`),
setup: agentCtx => productCtx.agentPresets.mount(agentCtx, id).then(() => undefined),
})
try {
const tools = toolNames(productCtx, handle.agent)
expect(tools.filter(name => name === 'subagent_codex' || name === 'subagent_claude_code'))
.toEqual(productTools)
} finally {
await handle.dispose()
}
}
})
it('applies a product-row edit only to later sessions on the preset', async () => {
const preset = await productCtx.agentPresets.resolve('products-none')
const original = await readFile(preset.path, 'utf8')
const existing = await productCtx.agents.create({
sessionId: SessionId('preset-product-generation-existing'),
setup: agentCtx => productCtx.agentPresets.mount(agentCtx, 'products-none').then(() => undefined),
})
try {
const assembly = await ctx.systemPrompt.assemble({ scope: handle.agent })
expect(assembly.sections.find(section => section.name === 'deployment:persona')?.text)
.toContain('You are a coding agent powered by')
expect(toolNames(productCtx, existing.agent)).not.toContain('subagent_codex')
await writeFile(preset.path, enablePresetTool(original, 'tool-subagent-codex'))
const later = await productCtx.agents.create({
sessionId: SessionId('preset-product-generation-later'),
setup: agentCtx => productCtx.agentPresets.mount(agentCtx, 'products-none').then(() => undefined),
})
try {
expect(toolNames(productCtx, existing.agent)).not.toContain('subagent_codex')
expect(toolNames(productCtx, later.agent)).toContain('subagent_codex')
} finally {
await later.dispose()
}
} finally {
await handle.dispose()
await existing.dispose()
await writeFile(preset.path, original)
}
})
})