fix(cli): restore shipped surface capabilities

This commit is contained in:
Turtle
2026-07-29 17:34:50 +08:00
parent bee0a77aad
commit 857a4941be
13 changed files with 133 additions and 54 deletions

View File

@@ -8,8 +8,8 @@
# A patch replaces the targeted row's whole `config` rather than merging into # A patch replaces the targeted row's whole `config` rather than merging into
# it, so a row whose value differs per surface does NOT live here: it belongs to # it, so a row whose value differs per surface does NOT live here: it belongs to
# each overlay, keeping any single row down to one overlay layer plus the user's. # each overlay, keeping any single row down to one overlay layer plus the user's.
# That is why `agent-loop`, `system-prompt`, `tools`, `fs-local`, and # Rows with surface-specific values appear below only with shared plugin identity
# `llm-deepseek` thinking defaults are absent below. # and neutral defaults; each overlay restates the complete surface configuration.
# #
# Row order carries no load semantics (activation is service-availability # Row order carries no load semantics (activation is service-availability
# driven); the grouping is for readers. # driven); the grouping is for readers.
@@ -51,9 +51,20 @@
- id: llm-retry - id: llm-retry
name: '@deepseek-ai/dsh-llm-retry' name: '@deepseek-ai/dsh-llm-retry'
- id: llm-pi-ai
name: '@deepseek-ai/dsh-llm-pi-ai'
config:
providers:
- provider: openai
apiKey: !!js process.env.OPENAI_API_KEY
baseURL: !!js process.env.OPENAI_BASE_URL
- provider: anthropic
apiKey: !!js process.env.ANTHROPIC_API_KEY
baseURL: !!js process.env.ANTHROPIC_BASE_URL
# The session store root is the launcher's policy, not a plugin's: `dsh` shares # The session store root is the launcher's policy, not a plugin's: `dsh` shares
# one store under the Harness home across every cwd, so `/resume` and # one store under the Harness home across every cwd, so `/resume` spans
# `/resume` spans workspaces. Without a launcher the project-local fallback keeps # workspaces. Without a launcher the project-local fallback keeps
# an embedder's sessions beside its project. # an embedder's sessions beside its project.
- id: session-persistence-jsonl - id: session-persistence-jsonl
name: '@deepseek-ai/dsh-session-persistence-jsonl' name: '@deepseek-ai/dsh-session-persistence-jsonl'

View File

@@ -7,7 +7,7 @@ The TUI agent combines the real DeepSeek adapter, coding tools, compaction, suba
```mermaid ```mermaid
flowchart LR flowchart LR
cfg["apps/cli (dsh)<br/>cordis.yml"] cfg["apps/cli (dsh)<br/>base.cordis.yml + tui.cordis.yml"]
plugin_tui_timer["timer<br/>@cordisjs/plugin-timer"] plugin_tui_timer["timer<br/>@cordisjs/plugin-timer"]
cfg --> plugin_tui_timer cfg --> plugin_tui_timer
plugin_tui_llm["llm<br/>@deepseek-ai/dsh-llm"] plugin_tui_llm["llm<br/>@deepseek-ai/dsh-llm"]
@@ -26,6 +26,8 @@ flowchart LR
cfg --> plugin_tui_tasks cfg --> plugin_tui_tasks
plugin_tui_llm_retry["llm-retry<br/>@deepseek-ai/dsh-llm-retry"] plugin_tui_llm_retry["llm-retry<br/>@deepseek-ai/dsh-llm-retry"]
cfg --> plugin_tui_llm_retry cfg --> plugin_tui_llm_retry
plugin_tui_llm_pi_ai["llm-pi-ai<br/>@deepseek-ai/dsh-llm-pi-ai"]
cfg --> plugin_tui_llm_pi_ai
plugin_tui_session_persistence_jsonl["session-persistence-jsonl<br/>@deepseek-ai/dsh-session-persistence-jsonl"] plugin_tui_session_persistence_jsonl["session-persistence-jsonl<br/>@deepseek-ai/dsh-session-persistence-jsonl"]
cfg --> plugin_tui_session_persistence_jsonl cfg --> plugin_tui_session_persistence_jsonl
plugin_tui_subprocess["subprocess<br/>@deepseek-ai/dsh-subprocess-local"] plugin_tui_subprocess["subprocess<br/>@deepseek-ai/dsh-subprocess-local"]
@@ -94,6 +96,8 @@ flowchart LR
cfg --> plugin_tui_fs_local cfg --> plugin_tui_fs_local
plugin_tui_llm_deepseek["llm-deepseek<br/>@deepseek-ai/dsh-llm-deepseek"] plugin_tui_llm_deepseek["llm-deepseek<br/>@deepseek-ai/dsh-llm-deepseek"]
cfg --> plugin_tui_llm_deepseek cfg --> plugin_tui_llm_deepseek
plugin_tui_tools["tools<br/>@deepseek-ai/dsh-tool-ask-user"]
cfg --> plugin_tui_tools
``` ```
| Plugin id | Package / module | | Plugin id | Package / module |
@@ -107,6 +111,7 @@ flowchart LR
| `agent` | `@deepseek-ai/dsh-agent` | | `agent` | `@deepseek-ai/dsh-agent` |
| `tasks` | `@deepseek-ai/dsh-tasks-local` | | `tasks` | `@deepseek-ai/dsh-tasks-local` |
| `llm-retry` | `@deepseek-ai/dsh-llm-retry` | | `llm-retry` | `@deepseek-ai/dsh-llm-retry` |
| `llm-pi-ai` | `@deepseek-ai/dsh-llm-pi-ai` |
| `session-persistence-jsonl` | `@deepseek-ai/dsh-session-persistence-jsonl` | | `session-persistence-jsonl` | `@deepseek-ai/dsh-session-persistence-jsonl` |
| `subprocess` | `@deepseek-ai/dsh-subprocess-local` | | `subprocess` | `@deepseek-ai/dsh-subprocess-local` |
| `bash-local` | `@deepseek-ai/dsh-bash-local` | | `bash-local` | `@deepseek-ai/dsh-bash-local` |
@@ -141,7 +146,8 @@ flowchart LR
| `agent-loop` | `@deepseek-ai/dsh-agent-loop` | | `agent-loop` | `@deepseek-ai/dsh-agent-loop` |
| `fs-local` | `@deepseek-ai/dsh-fs-local` | | `fs-local` | `@deepseek-ai/dsh-fs-local` |
| `llm-deepseek` | `@deepseek-ai/dsh-llm-deepseek` | | `llm-deepseek` | `@deepseek-ai/dsh-llm-deepseek` |
| `tools` | `@deepseek-ai/dsh-tool-ask-user` |
Source config: [`apps/cli/base.cordis.yml`](base.cordis.yml). Source configs: [`apps/cli/base.cordis.yml`](base.cordis.yml), [`apps/cli/tui.cordis.yml`](tui.cordis.yml).
Maintenance mode: hybrid: the leaf plugin list is parsed from its `cordis.yml`; app package expansion is curated from package source. Maintenance mode: hybrid: the leaf plugin list is parsed from its shipped config files; app package expansion is curated from package source.

View File

@@ -22,6 +22,7 @@
"@deepseek-ai/dsh-agent-loop": "workspace:^", "@deepseek-ai/dsh-agent-loop": "workspace:^",
"@deepseek-ai/dsh-app-boot": "workspace:^", "@deepseek-ai/dsh-app-boot": "workspace:^",
"@deepseek-ai/dsh-bash-local": "workspace:^", "@deepseek-ai/dsh-bash-local": "workspace:^",
"@deepseek-ai/dsh-bash-sandbox": "workspace:^",
"@deepseek-ai/dsh-client-connection": "workspace:^", "@deepseek-ai/dsh-client-connection": "workspace:^",
"@deepseek-ai/dsh-client-hmr": "workspace:^", "@deepseek-ai/dsh-client-hmr": "workspace:^",
"@deepseek-ai/dsh-client-locale": "workspace:^", "@deepseek-ai/dsh-client-locale": "workspace:^",
@@ -33,6 +34,7 @@
"@deepseek-ai/dsh-client-ui-layout": "workspace:^", "@deepseek-ai/dsh-client-ui-layout": "workspace:^",
"@deepseek-ai/dsh-client-ui-model": "workspace:^", "@deepseek-ai/dsh-client-ui-model": "workspace:^",
"@deepseek-ai/dsh-client-ui-models": "workspace:^", "@deepseek-ai/dsh-client-ui-models": "workspace:^",
"@deepseek-ai/dsh-client-ui-permission": "workspace:^",
"@deepseek-ai/dsh-client-ui-plan": "workspace:^", "@deepseek-ai/dsh-client-ui-plan": "workspace:^",
"@deepseek-ai/dsh-client-ui-question": "workspace:^", "@deepseek-ai/dsh-client-ui-question": "workspace:^",
"@deepseek-ai/dsh-client-ui-settings": "workspace:^", "@deepseek-ai/dsh-client-ui-settings": "workspace:^",
@@ -52,10 +54,11 @@
"@deepseek-ai/dsh-frontend": "workspace:^", "@deepseek-ai/dsh-frontend": "workspace:^",
"@deepseek-ai/dsh-fs-local": "workspace:^", "@deepseek-ai/dsh-fs-local": "workspace:^",
"@deepseek-ai/dsh-fs-policy": "workspace:^", "@deepseek-ai/dsh-fs-policy": "workspace:^",
"@deepseek-ai/dsh-fs-sandbox": "workspace:^",
"@deepseek-ai/dsh-goal": "workspace:^", "@deepseek-ai/dsh-goal": "workspace:^",
"@deepseek-ai/dsh-goal-session": "workspace:^", "@deepseek-ai/dsh-goal-session": "workspace:^",
"@deepseek-ai/dsh-helper": "workspace:^",
"@deepseek-ai/dsh-host-apiproxy": "workspace:^", "@deepseek-ai/dsh-host-apiproxy": "workspace:^",
"@deepseek-ai/dsh-host-directory-picker-browse": "workspace:^",
"@deepseek-ai/dsh-host-webserver": "workspace:^", "@deepseek-ai/dsh-host-webserver": "workspace:^",
"@deepseek-ai/dsh-invariants": "workspace:^", "@deepseek-ai/dsh-invariants": "workspace:^",
"@deepseek-ai/dsh-llm": "workspace:^", "@deepseek-ai/dsh-llm": "workspace:^",
@@ -63,7 +66,10 @@
"@deepseek-ai/dsh-llm-pi-ai": "workspace:^", "@deepseek-ai/dsh-llm-pi-ai": "workspace:^",
"@deepseek-ai/dsh-llm-retry": "workspace:^", "@deepseek-ai/dsh-llm-retry": "workspace:^",
"@deepseek-ai/dsh-paths": "workspace:^", "@deepseek-ai/dsh-paths": "workspace:^",
"@deepseek-ai/dsh-permission": "workspace:^",
"@deepseek-ai/dsh-plan-mode": "workspace:^", "@deepseek-ai/dsh-plan-mode": "workspace:^",
"@deepseek-ai/dsh-sandbox-local": "workspace:^",
"@deepseek-ai/dsh-sandbox-policy": "workspace:^",
"@deepseek-ai/dsh-scope": "workspace:^", "@deepseek-ai/dsh-scope": "workspace:^",
"@deepseek-ai/dsh-session": "workspace:^", "@deepseek-ai/dsh-session": "workspace:^",
"@deepseek-ai/dsh-session-checkpoint-policy": "workspace:^", "@deepseek-ai/dsh-session-checkpoint-policy": "workspace:^",
@@ -102,6 +108,7 @@
"@deepseek-ai/dsh-tool-workflow": "workspace:^", "@deepseek-ai/dsh-tool-workflow": "workspace:^",
"@deepseek-ai/dsh-tools": "workspace:^", "@deepseek-ai/dsh-tools": "workspace:^",
"@deepseek-ai/dsh-tui": "workspace:^", "@deepseek-ai/dsh-tui": "workspace:^",
"@deepseek-ai/dsh-user-approval": "workspace:^",
"@deepseek-ai/dsh-user-interaction": "workspace:^", "@deepseek-ai/dsh-user-interaction": "workspace:^",
"@deepseek-ai/dsh-workflow-workerthread": "workspace:^", "@deepseek-ai/dsh-workflow-workerthread": "workspace:^",
"@deepseek-ai/dsh-workspace": "workspace:^", "@deepseek-ai/dsh-workspace": "workspace:^",

View File

@@ -213,27 +213,12 @@ Examples:
resolved = resolveWeb(options) resolved = resolveWeb(options)
}) })
// `--resume` is NOT redeclared here: an option a subcommand shares with its
// parent parses into `program.opts()` and leaves the subcommand's own options
// empty, so redeclaring it would silently drop the id. Commander therefore
// omits it from this subcommand's option list, hence the trailing help text.
program program
.command('meta') .command('meta')
.description('work on the dsh source that runs this command, from any directory') .description('work on the dsh source that runs this command, from any directory')
.addHelpText('after', '\nAccepts --resume <id> to resume a persisted session from this checkout.\n')
.action(() => { .action(() => {
// Commander parses the parent (default-surface) options on either side of rejectParentOptions('meta')
// the subcommand into `program.opts()`. `meta` accepts only `--resume`, so resolved = { mode: 'meta' }
// a leaked `--config`/`-p` is a mistyped invocation that must fail loud
// rather than silently be dropped.
const parent = program.opts<{ config?: string; configReplace?: string; prompt?: string; resume?: string }>()
if (parent.config !== undefined || parent.configReplace !== undefined || parent.prompt !== undefined) {
program.error('error: meta takes none of --config, --config-replace, or -p/--prompt')
}
// Same reason as the default surface: an empty id would start a fresh
// session downstream instead of failing the mistyped resume.
if (parent.resume === '') program.error('error: --resume needs a session id')
resolved = { mode: 'meta', ...parent.resume !== undefined && { resume: parent.resume } }
}) })
try { try {

View File

@@ -18,7 +18,7 @@
*/ */
import { randomUUID } from 'node:crypto' import { randomUUID } from 'node:crypto'
import { join } from 'node:path' import { join, resolve } from 'node:path'
import { fileURLToPath } from 'node:url' import { fileURLToPath } from 'node:url'
import { import {
addHarnessSourceSection, addHarnessSourceSection,
@@ -155,6 +155,8 @@ export async function runTui(
// selected session may belong to another workspace, so the handoff also enters // selected session may belong to another workspace, so the handoff also enters
// that directory. The host is offered only when Node exposes `process.execve` // that directory. The host is offered only when Node exposes `process.execve`
// and knows its own entry. // and knows its own entry.
const resolvedConfig = config === undefined ? undefined : resolve(config)
const resolvedConfigReplace = configReplace === undefined ? undefined : resolve(configReplace)
const entry = process.argv[1] const entry = process.argv[1]
const execve = process.execve?.bind(process) const execve = process.execve?.bind(process)
const app: { current?: Context } = {} const app: { current?: Context } = {}
@@ -172,8 +174,8 @@ export async function runTui(
`--resume=${sessionId}`, `--resume=${sessionId}`,
// Both config flags must survive the handoff: resuming into a different // Both config flags must survive the handoff: resuming into a different
// tree than the session was created in would silently change the agent. // tree than the session was created in would silently change the agent.
...config !== undefined ? ['--config', config] : [], ...resolvedConfig !== undefined ? ['--config', resolvedConfig] : [],
...configReplace !== undefined ? ['--config-replace', configReplace] : [], ...resolvedConfigReplace !== undefined ? ['--config-replace', resolvedConfigReplace] : [],
] ]
// Mint the fresh id here rather than in the app bundle: the exit line names // Mint the fresh id here rather than in the app bundle: the exit line names
// the session to resume, so the launcher must know it before the tree boots. // the session to resume, so the launcher must know it before the tree boots.
@@ -223,11 +225,11 @@ export async function runTui(
...loadOverlayPatches(NAME, TUI_OVERLAY), ...loadOverlayPatches(NAME, TUI_OVERLAY),
...config === undefined ...config === undefined
? loadPersonalPatches(NAME) ?? [] ? loadPersonalPatches(NAME) ?? []
: loadOverlayPatches(NAME, resolveConfigPath(config, undefined)), : loadOverlayPatches(NAME, resolveConfigPath(resolve(config), undefined)),
] ]
const ctx = await boot( const ctx = await boot(
NAME, NAME,
replaceTree ? resolveConfigPath(configReplace, undefined) : BASE_CONFIG, replaceTree ? resolveConfigPath(resolve(configReplace), undefined) : BASE_CONFIG,
patches, patches,
(hostCtx) => { (hostCtx) => {
// The launcher owns session identity and the exit line: a config-mounted // The launcher owns session identity and the exit line: a config-mounted

View File

@@ -30,12 +30,7 @@ describe('parseDshArgs', () => {
expect(parse(['--config-replace', 'tree.yml'])).toEqual({ mode: 'tui', configReplace: 'tree.yml' }) expect(parse(['--config-replace', 'tree.yml'])).toEqual({ mode: 'tui', configReplace: 'tree.yml' })
expect(parse(['--resume', 'sess', '--config', 'app.yml'])).toEqual({ mode: 'tui', config: 'app.yml', resume: 'sess' }) expect(parse(['--resume', 'sess', '--config', 'app.yml'])).toEqual({ mode: 'tui', config: 'app.yml', resume: 'sess' })
expect(parse(['-p', 'do the thing'])).toEqual({ mode: 'headless', prompt: 'do the thing' }) expect(parse(['-p', 'do the thing'])).toEqual({ mode: 'headless', prompt: 'do the thing' })
// `meta` accepts `--resume` but does not redeclare it: a shared option parses
// into program.opts() on either side of the subcommand, and redeclaring it
// would leave the subcommand's own options empty and drop the id.
expect(parse(['meta'])).toEqual({ mode: 'meta' }) expect(parse(['meta'])).toEqual({ mode: 'meta' })
expect(parse(['meta', '--resume', 'sess'])).toEqual({ mode: 'meta', resume: 'sess' })
expect(parse(['--resume', 'sess', 'meta'])).toEqual({ mode: 'meta', resume: 'sess' })
// Credential setup is option-free: it writes the Harness-home .env, so // Credential setup is option-free: it writes the Harness-home .env, so
// there is nothing for a flag to select. // there is nothing for a flag to select.
// Bare `web` carries no host/port: the shipped cordis.yml owns the default. // Bare `web` carries no host/port: the shipped cordis.yml owns the default.
@@ -72,9 +67,9 @@ describe('parseDshArgs', () => {
expect(exitCode(['--config-replace', 'tree.yml', 'web'])).toBe(1) expect(exitCode(['--config-replace', 'tree.yml', 'web'])).toBe(1)
// Same rule for credential setup: it shares no option with the default // Same rule for credential setup: it shares no option with the default
// surface, so a leaked flag is a typo, not something to ignore. // surface, so a leaked flag is a typo, not something to ignore.
// `meta` fixes its own config tree and is interactive, so --config/-p are // `meta` fixes its own config tree and always starts fresh, so every
// rejected; an empty id is swallowed downstream exactly as above. // default-surface option is rejected.
expect(exitCode(['meta', '--resume='])).toBe(1) expect(exitCode(['meta', '--resume', 's'])).toBe(1)
expect(exitCode(['meta', '--config', 'c.yml'])).toBe(1) expect(exitCode(['meta', '--config', 'c.yml'])).toBe(1)
expect(exitCode(['meta', '--config-replace', 'tree.yml'])).toBe(1) expect(exitCode(['meta', '--config-replace', 'tree.yml'])).toBe(1)
expect(exitCode(['meta', '-p', 'task'])).toBe(1) expect(exitCode(['meta', '-p', 'task'])).toBe(1)

View File

@@ -94,9 +94,8 @@
- id: session-reference - id: session-reference
name: '@deepseek-ai/dsh-session-reference' name: '@deepseek-ai/dsh-session-reference'
# Refuses write/edit inside the dsh checkout this launcher runs from, on that # Compacts oversized tool results before the broader conversation compactor
# checkout's own branch, until the session loads dsh-customize. Inert # runs, preserving the model-visible result within the configured budget.
# everywhere else, so an ordinary project sees no change.
- id: tool-result-prune - id: tool-result-prune
name: '@deepseek-ai/dsh-compact-tool-result-prune' name: '@deepseek-ai/dsh-compact-tool-result-prune'

View File

@@ -31,7 +31,49 @@
apiKey: !!js process.env.DEEPSEEK_API_KEY apiKey: !!js process.env.DEEPSEEK_API_KEY
baseURL: !!js process.env.DEEPSEEK_BASE_URL baseURL: !!js process.env.DEEPSEEK_BASE_URL
# The web surface replaces the unrestricted local executors with the shared
# sandbox policy. Its default preserves the previous unrestricted behavior;
# DSH_PERMISSION_MODE and the browser permission picker can confine a session.
- insert:
- id: sandbox
name: '@deepseek-ai/dsh-sandbox-local'
- id: sandbox-policy
name: '@deepseek-ai/dsh-sandbox-policy'
config:
mode: !!js process.env.DSH_PERMISSION_MODE ?? 'danger-full-access'
workspaceRoot: !!js process.cwd()
- id: bash-sandbox
name: '@deepseek-ai/dsh-bash-sandbox'
- id: approval
name: '@deepseek-ai/dsh-user-approval'
config:
policy: !!js "(process.env.DSH_PERMISSION_MODE ?? 'danger-full-access') === 'danger-full-access' ? 'never' : 'ask'"
- id: permission
name: '@deepseek-ai/dsh-permission'
config:
presets:
read-only:
sandbox: read-only
approval: ask
workspace-write:
sandbox: workspace-write
approval: ask
danger-full-access:
sandbox: danger-full-access
approval: never
- id: fs-sandbox
name: '@deepseek-ai/dsh-fs-sandbox'
- id: bash-local
remove: true
- id: fs-local - id: fs-local
remove: true
# ── web-only host rows, the transport layer, and the browser roster ───────── # ── web-only host rows, the transport layer, and the browser roster ─────────
@@ -72,6 +114,9 @@
# The API gateway: the transport-agnostic dispatch face every client shape # The API gateway: the transport-agnostic dispatch face every client shape
# shares. provider/model are the host default routing — the profile json's # shares. provider/model are the host default routing — the profile json's
# mapping target (user config overrides these engineering defaults). # mapping target (user config overrides these engineering defaults).
- id: directory-picker
name: '@deepseek-ai/dsh-host-directory-picker-browse'
- id: api-gateway - id: api-gateway
name: '@deepseek-ai/dsh-host-apiproxy' name: '@deepseek-ai/dsh-host-apiproxy'
config: config:
@@ -156,6 +201,9 @@
- id: ui-model - id: ui-model
name: '@deepseek-ai/dsh-client-ui-model' name: '@deepseek-ai/dsh-client-ui-model'
- id: ui-permission
name: '@deepseek-ai/dsh-client-ui-permission'
# Plan control: the composer plan seat over the plan projection + /plan channel. # Plan control: the composer plan seat over the plan projection + /plan channel.
- id: ui-plan - id: ui-plan
name: '@deepseek-ai/dsh-client-ui-plan' name: '@deepseek-ai/dsh-client-ui-plan'

View File

@@ -94,4 +94,4 @@ flowchart LR
Source config: [`examples/acp-agent/cordis.yml`](cordis.yml). Source config: [`examples/acp-agent/cordis.yml`](cordis.yml).
Maintenance mode: hybrid: the leaf plugin list is parsed from its `cordis.yml`; app package expansion is curated from package source. Maintenance mode: hybrid: the leaf plugin list is parsed from its shipped config files; app package expansion is curated from package source.

View File

@@ -76,4 +76,4 @@ flowchart LR
Source config: [`examples/headless-agent/cordis.yml`](cordis.yml). Source config: [`examples/headless-agent/cordis.yml`](cordis.yml).
Maintenance mode: hybrid: the leaf plugin list is parsed from its `cordis.yml`; app package expansion is curated from package source. Maintenance mode: hybrid: the leaf plugin list is parsed from its shipped config files; app package expansion is curated from package source.

27
pnpm-lock.yaml generated
View File

@@ -134,6 +134,9 @@ importers:
'@deepseek-ai/dsh-bash-local': '@deepseek-ai/dsh-bash-local':
specifier: workspace:^ specifier: workspace:^
version: link:../../packages/bash/bash-local version: link:../../packages/bash/bash-local
'@deepseek-ai/dsh-bash-sandbox':
specifier: workspace:^
version: link:../../packages/bash/bash-sandbox
'@deepseek-ai/dsh-client-connection': '@deepseek-ai/dsh-client-connection':
specifier: workspace:^ specifier: workspace:^
version: link:../../packages/client/connection version: link:../../packages/client/connection
@@ -167,6 +170,9 @@ importers:
'@deepseek-ai/dsh-client-ui-models': '@deepseek-ai/dsh-client-ui-models':
specifier: workspace:^ specifier: workspace:^
version: link:../../packages/client/ui-models version: link:../../packages/client/ui-models
'@deepseek-ai/dsh-client-ui-permission':
specifier: workspace:^
version: link:../../packages/client/ui-permission
'@deepseek-ai/dsh-client-ui-plan': '@deepseek-ai/dsh-client-ui-plan':
specifier: workspace:^ specifier: workspace:^
version: link:../../packages/client/ui-plan version: link:../../packages/client/ui-plan
@@ -224,18 +230,21 @@ importers:
'@deepseek-ai/dsh-fs-policy': '@deepseek-ai/dsh-fs-policy':
specifier: workspace:^ specifier: workspace:^
version: link:../../packages/fs/fs-policy version: link:../../packages/fs/fs-policy
'@deepseek-ai/dsh-fs-sandbox':
specifier: workspace:^
version: link:../../packages/fs/fs-sandbox
'@deepseek-ai/dsh-goal': '@deepseek-ai/dsh-goal':
specifier: workspace:^ specifier: workspace:^
version: link:../../packages/goal/goal version: link:../../packages/goal/goal
'@deepseek-ai/dsh-goal-session': '@deepseek-ai/dsh-goal-session':
specifier: workspace:^ specifier: workspace:^
version: link:../../packages/goal/goal-session version: link:../../packages/goal/goal-session
'@deepseek-ai/dsh-helper':
specifier: workspace:^
version: link:../../packages/sdk/helper
'@deepseek-ai/dsh-host-apiproxy': '@deepseek-ai/dsh-host-apiproxy':
specifier: workspace:^ specifier: workspace:^
version: link:../../packages/host/apiproxy version: link:../../packages/host/apiproxy
'@deepseek-ai/dsh-host-directory-picker-browse':
specifier: workspace:^
version: link:../../packages/host/directory-picker-browse
'@deepseek-ai/dsh-host-webserver': '@deepseek-ai/dsh-host-webserver':
specifier: workspace:^ specifier: workspace:^
version: link:../../packages/host/webserver version: link:../../packages/host/webserver
@@ -257,9 +266,18 @@ importers:
'@deepseek-ai/dsh-paths': '@deepseek-ai/dsh-paths':
specifier: workspace:^ specifier: workspace:^
version: link:../../packages/util/paths version: link:../../packages/util/paths
'@deepseek-ai/dsh-permission':
specifier: workspace:^
version: link:../../packages/ui/permission
'@deepseek-ai/dsh-plan-mode': '@deepseek-ai/dsh-plan-mode':
specifier: workspace:^ specifier: workspace:^
version: link:../../packages/plan/plan-mode version: link:../../packages/plan/plan-mode
'@deepseek-ai/dsh-sandbox-local':
specifier: workspace:^
version: link:../../packages/sandbox/sandbox-local
'@deepseek-ai/dsh-sandbox-policy':
specifier: workspace:^
version: link:../../packages/sandbox/sandbox-policy
'@deepseek-ai/dsh-scope': '@deepseek-ai/dsh-scope':
specifier: workspace:^ specifier: workspace:^
version: link:../../packages/core/scope version: link:../../packages/core/scope
@@ -374,6 +392,9 @@ importers:
'@deepseek-ai/dsh-tui': '@deepseek-ai/dsh-tui':
specifier: workspace:^ specifier: workspace:^
version: link:../../packages/ui/tui version: link:../../packages/ui/tui
'@deepseek-ai/dsh-user-approval':
specifier: workspace:^
version: link:../../packages/ui/user-approval
'@deepseek-ai/dsh-user-interaction': '@deepseek-ai/dsh-user-interaction':
specifier: workspace:^ specifier: workspace:^
version: link:../../packages/ui/user-interaction version: link:../../packages/ui/user-interaction

View File

@@ -597,7 +597,7 @@ const APP_EXAMPLES = [
rel: 'apps/cli/composition.md', rel: 'apps/cli/composition.md',
title: 'dsh TUI Composition', title: 'dsh TUI Composition',
label: 'apps/cli (dsh)', label: 'apps/cli (dsh)',
config: 'apps/cli/base.cordis.yml', configs: ['apps/cli/base.cordis.yml', 'apps/cli/tui.cordis.yml'],
summary: 'The TUI agent combines the real DeepSeek adapter, coding tools, compaction, subagents, and workflows with the full-screen terminal app package.', summary: 'The TUI agent combines the real DeepSeek adapter, coding tools, compaction, subagents, and workflows with the full-screen terminal app package.',
}, },
{ {
@@ -605,7 +605,7 @@ const APP_EXAMPLES = [
rel: 'examples/headless-agent/composition.md', rel: 'examples/headless-agent/composition.md',
title: 'Headless Agent App Composition', title: 'Headless Agent App Composition',
label: 'examples/headless-agent', label: 'examples/headless-agent',
config: 'examples/headless-agent/cordis.yml', configs: ['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.', 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.',
}, },
{ {
@@ -613,7 +613,7 @@ const APP_EXAMPLES = [
rel: 'examples/acp-agent/composition.md', rel: 'examples/acp-agent/composition.md',
title: 'ACP Automation App Composition', title: 'ACP Automation App Composition',
label: 'examples/acp-agent', label: 'examples/acp-agent',
config: 'examples/acp-agent/cordis.yml', configs: ['examples/acp-agent/cordis.yml'],
summary: 'The ACP demo exposes fresh baseline-prompt agent sessions to programmatic clients over JSON-RPC stdio, with no stdout logger, human UI, or pre-created agent.', summary: 'The ACP demo exposes fresh baseline-prompt agent sessions to programmatic clients over JSON-RPC stdio, with no stdout logger, human UI, or pre-created agent.',
}, },
] ]
@@ -639,15 +639,15 @@ function renderAppExpansion(lines: string[], appNode: string, pluginName: string
} }
function renderAppComposition(example: AppExample): string { function renderAppComposition(example: AppExample): string {
const plugins = parseExampleCordis(example.config) const plugins = example.configs.flatMap(parseExampleCordis)
const maintenance = 'hybrid: the leaf plugin list is parsed from its `cordis.yml`; app package expansion is curated from package source' const maintenance = 'hybrid: the leaf plugin list is parsed from its shipped config files; app package expansion is curated from package source'
const lines = generatedHeader(example.title) const lines = generatedHeader(example.title)
lines.push( lines.push(
example.summary, example.summary,
'', '',
'```mermaid', '```mermaid',
'flowchart LR', 'flowchart LR',
` cfg["${escLabel(example.label)}<br/>cordis.yml"]`, ` cfg["${escLabel(example.label)}<br/>${escLabel(example.configs.map(config => config.split('/').at(-1)).join(' + '))}"]`,
) )
for (const plugin of plugins) { for (const plugin of plugins) {
const pluginNode = nodeId(`plugin_${example.id}`, plugin.id) const pluginNode = nodeId(`plugin_${example.id}`, plugin.id)
@@ -664,7 +664,7 @@ function renderAppComposition(example: AppExample): string {
'| --- | --- |', '| --- | --- |',
...plugins.map(plugin => `| \`${plugin.id}\` | \`${plugin.name}\` |`), ...plugins.map(plugin => `| \`${plugin.id}\` | \`${plugin.name}\` |`),
'', '',
`Source config: [\`${example.config}\`](${linkFromDoc(example.rel, example.config)}).`, `Source config${example.configs.length === 1 ? '' : 's'}: ${example.configs.map(config => `[\`${config}\`](${linkFromDoc(example.rel, config)})`).join(', ')}.`,
) )
lines.push('', ...maintenanceFooter(maintenance)) lines.push('', ...maintenanceFooter(maintenance))
return lines.join('\n') return lines.join('\n')

View File

@@ -78,6 +78,11 @@ function validateEntry(value: unknown, file: string, path: string): void {
validateEntry(value.config[index], file, `${path}.config[${index}]`) 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 if (value.name !== '@cordisjs/plugin-include') return
const config = value.config const config = value.config
if (!isRecord(config) || !isUnknownArray(config.patches)) return if (!isRecord(config) || !isUnknownArray(config.patches)) return
@@ -124,7 +129,7 @@ function validateExampleResolution(): string[] {
function validateAppResolution(): string[] { function validateAppResolution(): string[] {
const dependencies = readManifest('apps/cli/package.json').dependencies ?? {} const dependencies = readManifest('apps/cli/package.json').dependencies ?? {}
const references = pluginReferences.filter(reference => reference.file === 'apps/cli/base.cordis.yml') const references = pluginReferences.filter(reference => reference.file.startsWith('apps/cli/'))
return missingPluginDependencies(references, dependencies, 'apps/cli/package.json') return missingPluginDependencies(references, dependencies, 'apps/cli/package.json')
} }