Merge remote-tracking branch 'origin/fix/web-transcript-projection' into feat/manual-compact-single-lock

# Conflicts:
#	apps/cli/config/base.cordis.yml
#	apps/cli/cordis.yml
#	docs/event-producer-consumer.md
#	examples/package.json
#	examples/tui-agent/README.i18n.yaml
#	examples/tui-agent/README.md
#	examples/tui-agent/README.zh.md
#	pnpm-lock.yaml
This commit is contained in:
Hypatia May
2026-07-30 20:09:23 +08:00
271 changed files with 4393 additions and 3153 deletions

View File

@@ -1,28 +1,16 @@
/**
* Boot the TUI or ACP Code Mode overlay, defaulting to TUI. Each overlay
* includes its base example, selects Code Mode, and adds the worker runtime.
* All require a DeepSeek API key; unsupported arguments fail with usage.
*/
/** Boot the ACP Code Mode overlay. Requires a DeepSeek API key. */
import { spawn } from 'node:child_process'
// Each UI's node invocation matches its base demo script plus the overlay config.
const UIS = new Map([
['tui', [
'--import',
'tsx/esm',
'apps/cli/src/bin.ts',
'--config',
'examples/tui-agent/code-mode.cordis.yml',
]],
['acp', ['--import', 'tsx', 'packages/examples/acp-demo/src/bin.ts', '--config', 'examples/acp-agent/code-mode.cordis.yml']],
])
const ui = process.argv[2] ?? 'tui'
const args = UIS.get(ui)
if (!args || process.argv.length > 3) {
console.error('usage: pnpm run demo:code-mode [tui|acp]')
if (process.argv.length > 2) {
console.error('usage: pnpm run demo:code-mode')
process.exit(2)
}
const child = spawn(process.execPath, args, { stdio: 'inherit' })
const child = spawn(process.execPath, [
'--import',
'tsx',
'packages/examples/acp-demo/src/bin.ts',
'--config',
'examples/acp-agent/code-mode.cordis.yml',
], { stdio: 'inherit' })
child.on('exit', (code, signal) => { process.exit(signal !== null ? 1 : code ?? 1) })

View File

@@ -1,21 +1,19 @@
/**
* Boot the self-referential Cordis tools under TUI, Web, or ACP, defaulting
* to TUI. This is a repository demo wrapper, not a product CLI feature.
* Boot the self-referential Cordis tools under Web or ACP, defaulting to Web. This is a repository demo wrapper, not a product CLI feature.
*/
import { spawn } from 'node:child_process'
const SURFACES = new Map([
['tui', ['--import', 'tsx', 'apps/cli/src/bin.ts', '--config', 'examples/cordis-agent/cordis.yml']],
// `dsh web` does not accept alternate configs yet. The TUI config escape
// hatch still boots this browser-only tree; the config owns port 3081.
['web', ['--import', 'tsx', 'apps/cli/src/bin.ts', '--config', 'examples/web-cordis/cordis.yml']],
// The browser surface with the cordis toolset layered on: `dsh web --config`
// applies this overlay over the shipped web composition; it owns port 3081.
['web', ['--import', 'tsx', 'apps/cli/src/bin.ts', 'web', '--config', 'examples/web-cordis/cordis.yml']],
['acp', ['--import', 'tsx', 'packages/examples/acp-demo/src/bin.ts', '--config', 'examples/acp-agent/cordis-tools.cordis.yml']],
])
const surface = process.argv[2] ?? 'tui'
const surface = process.argv[2] ?? 'web'
const args = SURFACES.get(surface)
if (args === undefined || process.argv.length > 3) {
console.error('usage: pnpm run demo:cordis [tui|web|acp]')
console.error('usage: pnpm run demo:cordis [web|acp]')
process.exit(2)
}

View File

@@ -605,11 +605,11 @@ function stripYamlScalar(value: string): string {
const APP_EXAMPLES = [
{
id: 'tui',
rel: 'examples/tui-agent/composition.md',
rel: 'apps/cli/composition.md',
title: 'TUI Agent App Composition',
label: 'examples/tui-agent',
config: 'examples/tui-agent/cordis.yml',
summary: 'The TUI agent combines the real DeepSeek adapter, coding tools, compaction, subagents, and workflows with the full-screen terminal app package.',
label: 'apps/cli/config',
config: 'apps/cli/config/base.cordis.yml',
summary: 'The TUI surface combines the shared CLI base with its surface overlay and full-screen terminal package.',
},
{
id: 'headless',
@@ -619,14 +619,6 @@ const APP_EXAMPLES = [
config: 'examples/headless-agent/cordis.yml',
summary: 'The headless demo combines the real DeepSeek adapter and coding capabilities with the one-shot app package, format-pure stdout, and one fresh persisted top-level session.',
},
{
id: 'cordis',
rel: 'examples/cordis-agent/composition.md',
title: 'Cordis Agent App Composition',
label: 'examples/cordis-agent',
config: 'examples/cordis-agent/cordis.yml',
summary: 'The self-referential demo puts @deepseek-ai/dsh-tool-cordis on the coding spine, letting the agent inspect its current-process runtime and mount or unmount in-memory temporary Plugins.',
},
{
id: 'acp',
rel: 'examples/acp-agent/composition.md',

View File

@@ -217,7 +217,7 @@ const TOOL_PACKAGES: ToolPackage[] = [
await ctx.plugin(ToolCordis)
},
note:
'Ships in examples/cordis-agent only (a deliberate opt-in — temporary Plugin code reaches the real runtime, see .agents/notes/implemented/feature/2026-07-08-self-referential-cordis-toolset.md). Plugins created by cordis_mount may register ADDITIONAL model-visible tools until unmounted or DSH restarts; a full changed request header logs those tool-set changes.',
'Not in any shipped tree (a deliberate opt-in — temporary Plugin code reaches the real runtime, see .agents/notes/implemented/feature/2026-07-08-self-referential-cordis-toolset.md). Plugins created by cordis_mount may register ADDITIONAL model-visible tools until unmounted or DSH restarts; a full changed request header logs those tool-set changes.',
},
{
pkg: '@deepseek-ai/dsh-tool-bash-persistent',
@@ -377,7 +377,7 @@ const TOOL_PACKAGES: ToolPackage[] = [
await ctx.plugin(ToolSubagent, { provider: 'mock' })
},
note:
'The registered tool name is the load-time `toolName` config (default `subagent`); the schema above is that default. The shipped example agents load this package once per subagent backend, so the model additionally sees `subagent_fork` (bound to the fork backend) with an identical schema — see `examples/tui-agent/cordis.yml` and `examples/acp-agent/cordis.yml`.',
'The registered tool name is the load-time `toolName` config (default `subagent`); the schema above is that default. The shipped example agents load this package once per subagent backend, so the model additionally sees `subagent_fork` (bound to the fork backend) with an identical schema — see `apps/cli/config/base.cordis.yml` and `examples/acp-agent/cordis.yml`.',
},
{
pkg: '@deepseek-ai/dsh-tool-tasks',

View File

@@ -503,7 +503,7 @@ function builtBinSmokeGate(needs: string[] = ['build']): Gate {
'--config',
'vitest.e2e.config.ts',
'examples/headless-agent/tests/keyless-smoke.e2e.ts',
'examples/tui-agent/tests/tui-keyless-smoke.e2e.ts',
'apps/cli/tests/tui-keyless-smoke.e2e.ts',
'packages/examples/cli-demo/tests/built-bin.e2e.ts',
'packages/examples/acp-demo/tests/built-bin.e2e.ts',
'packages/ui/jsonrpc/tests/built-scope-carrier.e2e.ts',

File diff suppressed because one or more lines are too long

View File

@@ -29,6 +29,9 @@ interface PluginReference {
}
const root = resolve(import.meta.dirname, '..')
// These example files are overlays consumed by the built dsh app, so their bare
// specifiers resolve from apps/cli rather than the examples workspace.
const appOverlayFiles = new Set(['examples/web-cordis/cordis.yml'])
const metadataFields = ['id', 'name', 'group', 'disabled', 'inject', 'intercept', 'isolate'] as const
const jsExprType = new yaml.Type('tag:yaml.org,2002:js', {
kind: 'scalar',
@@ -78,6 +81,11 @@ function validateEntry(value: unknown, file: string, path: string): void {
validateEntry(value.config[index], file, `${path}.config[${index}]`)
}
}
if (isUnknownArray(value.insert)) {
for (let index = 0; index < value.insert.length; index++) {
validateEntry(value.insert[index], file, `${path}.insert[${index}]`)
}
}
if (value.name !== '@cordisjs/plugin-include') return
const config = value.config
if (!isRecord(config) || !isUnknownArray(config.patches)) return
@@ -104,7 +112,7 @@ function validateExampleResolution(): string[] {
const dependencies = exampleManifest.dependencies ?? {}
const localPackages = localPackageDirectories()
const rootReferences = rootProjectReferences()
const exampleReferences = pluginReferences.filter(reference => reference.file.startsWith('examples/'))
const exampleReferences = pluginReferences.filter(reference => reference.file.startsWith('examples/') && !appOverlayFiles.has(reference.file))
violations.push(...missingPluginDependencies(exampleReferences, dependencies, 'examples/package.json'))
const requiredPackages = new Set(exampleReferences.map(reference => packageNameFromSpecifier(reference.name)))
@@ -124,7 +132,9 @@ function validateExampleResolution(): string[] {
function validateAppResolution(): string[] {
const dependencies = readManifest('apps/cli/package.json').dependencies ?? {}
const references = pluginReferences.filter(reference => reference.file === 'apps/cli/cordis.yml')
const shipped = new Set(globSync('*.cordis.yml', { cwd: resolve(root, 'apps/cli/config') })
.map(file => `apps/cli/config/${file}`))
const references = pluginReferences.filter(reference => shipped.has(reference.file) || appOverlayFiles.has(reference.file))
return missingPluginDependencies(references, dependencies, 'apps/cli/package.json')
}