跳转至

插件:连 agent 循环都是插件

pi 的扩展是挂在固定内核外面的工厂函数 (pi) => void:想收回注册的东西,得自己留着 pi.on() 返回的撤销函数,或者整体 reload1;循环、会话、工具注册表换不掉,一个进程只有一套组合。DeepSeek Harness 的底座是 Cordis 插件框架,没有特权内核,连 agent 循环都是插件,注册的东西卸载时由框架撤销。下面每节贴源码模版和用 dsh 自己的包跑出的效果,构造的插件和工具都注明了。

卸载就撤销:注册都挂在插件上

问题:插件管理器关掉一行、配置热重载、agent 结束,都要把插件装进来的工具、提示词段、监听器收干净——漏一样,模型就还看得见已经卸掉的工具。

Cordis 里每个注册都是当前插件上的一个 effect:执行时交回撤销函数,插件卸载时框架按逆序全跑一遍。监听器这样挂:

模版 · vendor/cordis/src/events.ts:245-260
  /**
   * Store a listener record as an effect on the current fiber.
   *
   * @param label — effect label shown in fiber diagnostics.
   * @param hooks — the listener list for one event.
   * @param callback — the listener to store.
   * @param options — placement and filtering options.
   * @returns a disposer that unregisters the listener.
   */
  register(label: string, hooks: Hook[], callback: any, options: EventOptions): () => void {
    const method = options.prepend ? 'unshift' : 'push'
    return this.ctx.fiber.effect(() => {
      hooks[method]({ ctx: this.ctx, callback, ...options })
      return () => this.unregister(hooks, callback)
    }, label)
  }

dsh 的工具注册表走同一条路:

模版 · packages/core/tools/src/index.ts:1091-1095
    return this.layers.effect(
      this.ctx,
      layer => layer.tools.insert(name, definition),
      { label: 'tools.register()' },
    )

构造一个插件 weather,注册工具、提示词段、监听器和一个带清理的 effect,挂到真实的注册表上再卸掉:

效果 · 挂载和卸载一个构造的插件
before mount
   tools: []
   prompt sections: ["harness:identity","deployment:persona-prefix","deployment:persona-suffix"]
   effect: open cache
after mount
   tools: ["weather"]
   prompt sections: ["harness:identity","deployment:persona-prefix","weather:guidance","deployment:persona-suffix"]
   weather plugin heard ping
   effect: close cache
after dispose
   tools: []
   prompt sections: ["harness:identity","deployment:persona-prefix","deployment:persona-suffix"]

插件没写一行反注册代码,三样注册随卸载消失。

换提供方不动消费方:依赖到齐才启动

问题:约定、实现、给模型用的接口变化节奏不同,打包在一起,换实现就牵动接口。决策记录2原话:

把本地执行器换成沙箱化执行器时,模型看到的工具 schema 也会被搅动,尽管面向模型的约定从未改变。

dsh 把可替换能力(项目叫 seam)拆成三个角色,bash 是范本。定义是占住 ctx.shell 的抽象类:

模版 · packages/shell/shell/src/index.ts:64-67
export abstract class ShellExecutor extends Service {
  constructor(ctx: Context) {
    super(ctx, 'shell')
  }

提供方继承它,沙箱版又继承本地版,多依赖两项沙箱服务:

模版 · packages/shell/bash-local/src/index.ts:97-98
export class LocalBashExecutor extends ShellExecutor {
  static inject = ['subprocess']
模版 · packages/shell/bash-sandbox/src/index.ts:38-46
/**
 * Registers as `ctx.shell` in place of the local executor and requires a
 * `ctx.sandbox` provider plus `ctx.sandboxPolicy`; the tool layer is
 * unchanged. Tool calls pass the calling session's resolved policy; direct
 * calls fall back to deployment policy. `result.sandbox` reports the mode and
 * enforcement actually used.
 */
export class SandboxBashExecutor extends LocalBashExecutor {
  static override inject = ['subprocess', 'sandbox', 'sandboxPolicy']

消费方是 bash 工具,只声明要 shell:

模版 · packages/shell/tool-bash/src/index.ts:33-34
export const name = 'tool-bash'
export const inject = ['tools', 'shell', 'systemPrompt', 'shellEnv', 'workingDirectory']

启停由 inject 决定:依赖缺一个就卸载自己,实现换了就重跑:

模版 · vendor/cordis/src/fiber.ts:611-638
  _refresh() {
    let epoch: string | boolean = false
    epoch = ''
    for (const name of Object.keys(this.inject)) {
      const impl = this._store[name]
      if (!impl) {
        epoch = INACTIVE
        break
      }
      epoch += ':' + impl.fiber.uid
    }
    this._setEpoch(epoch)
  }

  private _setEpoch(epoch: string) {
    const oldEpoch = this._runner.epoch
    if (epoch === oldEpoch) return
    this._runner.epoch = epoch
    if (this.inertia) return
    this._updateState(() => {
      if (epoch !== INACTIVE && oldEpoch === INACTIVE) {
        this.inertia = this._reload()
        return FiberState.LOADING
      } else {
        this.inertia = this._unload()
        return FiberState.UNLOADING
      }
    })

写一个只 inject shell 的消费方,依次挂、卸、换提供方。提供方一是真实的 LocalBashExecutor;DryRunBash 是构造的,照沙箱版继承本地版、只改 resolve:

效果 · 消费方不改一行,跟着提供方启停
1. mount consumer (no shell provider yet)
   run: (no consumer listening)
2. mount subprocess-local + LocalBashExecutor
consumer: activated, ctx.shell is LocalBashExecutor
   run: LocalBashExecutor -> exit 0, stdout "hello"
3. dispose LocalBashExecutor
consumer: deactivated
   run: (no consumer listening)
4. mount DryRunBash in its place
consumer: activated, ctx.shell is DryRunBash
   run: DryRunBash -> exit 0, stdout "[dry-run] echo hello"

消费方代码没动:没有提供方时连监听器都没装上,换了提供方自己重新激活。默认挂的是沙箱版,换本地版只改配置一行(下一节实演);packages/ssh/ 的一组 SSH 提供方也靠换行把文件和进程搬到远端。

拦截点是事件:守卫只能否决

问题:hook 若能改参数、靠注册顺序定结果,后注册的监听器就能把前面的拒绝翻回来,日志里记的调用也和实际跑的对不上。pi 的 tool_call 处理器能直接改 event.input;dsh 没开这个口子,改参数的方案还停在提案,要求日志、界面、执行三处同时改写。

dsh 的说法是「原生 hook 不是一个包」:hook 就是监听规范事件的普通插件。拦截用 waterfall——外层先跑,调 next() 交给里层,不调就短路:

模版 · vendor/cordis/src/events.ts:224-243
  /**
   * Compose listeners around the final `next` callback.
   *
   * The last dispatch argument is treated as the innermost `next`. Listeners
   * run outermost-first; a listener that does not call `next()` vetoes the
   * rest of the chain, including the built-in behavior.
   *
   * @param args — optional `this`, the event name, listener arguments, then `next`.
   * @returns the outermost listener's return value.
   */
  waterfall(...args: any[]) {
    const cbs = this.dispatch('waterfall', args)
    const inner = args.pop()
    const next = () => {
      const cb = cbs.shift() ?? inner
      return cb(...args)
    }
    args.push(next)
    return next()
  }

调用前的决定只有四种,没有改参数——参数已经记进日志、上了屏:

模版 · packages/core/tools/src/index.ts:599-612
/**
 * Pre-dispatch decision. `allow` runs the call; `deny` materializes its
 * model-facing reason and optional structured error identity; `cancel` selects
 * the canonical cancellation result without presenting a policy denial; `ask`
 * runs only after an approval service returns `allowed-once` and otherwise
 * denies; its `reason` is the audited approval reason and its optional
 * `displayReason` is the localized prompt text. Input rewriting is excluded because arguments are already logged and
 * presented.
 */
export type PreToolDecision =
  | { kind: 'allow' }
  | { kind: 'deny'; reason: string; info?: ToolErrorInfo }
  | { kind: 'cancel' }
  | { kind: 'ask'; reason?: string; displayReason?: { readonly en: string; readonly [locale: string]: string } }

waterfall 之后还有一层守卫,签名里没有「放行」:

模版 · packages/core/tools/src/index.ts:724-732
/**
 * A monotonic execution guard evaluated after every `tools/pre-execute`
 * listener and before the tool body. Returning a reason denies the call;
 * returning `undefined` leaves it unchanged. Because guards have no allow
 * result, listener ordering cannot turn a denial back into permission.
 * @param execution - the identity-protected call after extensible pre-execute policy completed.
 * @returns a final denial reason, or `undefined` to leave the call allowed.
 */
export type ToolGuard = (execution: Readonly<ToolExecution>) => string | undefined
模版 · packages/core/tools/src/index.ts:1512-1527
      const carrier = scopeTarget(this, exec.agent)
      const gate = await this.ctx.waterfall(
        carrier, 'tools/pre-execute', exec,
        () => Promise.resolve<PreToolDecision>({ kind: 'allow' }),
      )
      const askResolution = gate.kind === 'ask'
        ? await this.serviceAsk(exec, gate)
        : { decision: gate, approvalCancelled: false }
      const { decision } = askResolution
      if (this.callerCancelled(exec) && askResolution.approvalCancelled) {
        return await next({ kind: 'post-result', exec, result: toolAbortedBeforeDispatchResult() })
      }
      if (decision.kind === 'cancel') {
        return await next({ kind: 'post-result', exec, result: toolAbortedBeforeDispatchResult() })
      }
      const denialReason = decision.kind === 'allow' ? this.guardReason(exec) : decision.reason

在真实工具管线上构造工具 rm_cache 和三个插件:记日志的 audit、拒绝策略、插到最外层直接放行的 allow-all。拒绝先写成监听器,再写成守卫:

效果 · 同一个拒绝,两种写法
1. no plugins
   -> cache deleted
2. + audit (wraps, calls next)
   [audit] before rm_cache
   [audit] decision allow
   -> cache deleted
3. + deny as a waterfall listener
   [audit] before rm_cache
   [audit] decision deny
   -> isError: Error: rm_cache is disabled by policy
4. + allow-all prepended outermost: the listener denial is skipped
   -> cache deleted
5. same denial as a guard, allow-all still mounted
   -> isError: Error: rm_cache is disabled by policy
6. guard and allow-all unmounted
   [audit] before rm_cache
   [audit] decision allow
   -> cache deleted

第 4 步,allow-all 不调 next(),里层的拒绝连同 audit 都没跑;第 5 步写成守卫,翻不回来。硬禁令写守卫,waterfall 留给包装和可协商的策略;管线后几站和审批见工具与安全边界。

组装是一叠 patch:agent 循环也只是一行

问题:启动器把组合写死,装一个树外插件(TUI、新的模型 provider)就得改仓库,第三方包也没处贡献默认组合。

profile 是 $DSH_HOME/profiles/<名字> 目录:一张组合包(bundle)清单,加用户自己的 cordis.patch.yml。随发行版带的模板:

模版 · packages/boot/app-boot/src/profile.ts:178-195
/** The shipped profile templates auto-initialized on first use, by name. */
export const PROFILE_TEMPLATES: Record<string, ProfileTemplate> = {
  acp: {
    bundles: ['@deepseek-ai/dsh-base', '@deepseek-ai/dsh-acp-app'],
  },
  web: {
    bundles: ['@deepseek-ai/dsh-base', '@deepseek-ai/dsh-web-app'],
  },
  headless: {
    bundles: ['@deepseek-ai/dsh-base', '@deepseek-ai/dsh-headless'],
  },
  sdk: {
    bundles: ['@deepseek-ai/dsh-base', '@deepseek-ai/dsh-sdk-app'],
  },
  'sdk-minimal': {
    bundles: ['@deepseek-ai/dsh-sdk-minimal'],
  },
}

启动时从空列表起,依次叠组合包、profile patch、home patch、--patch。patch 按 id 找行、整体替换写到的字段,包名对不上就跳过:

模版 · vendor/include/src/index.ts:109-123
    const target = entryMap.get(id)
    if (!target) {
      warn('patch: entry %C not found', id)
      continue
    }

    if (name && name !== target.name) {
      warn('patch: name mismatch for %C (expected %C, got %C), skipping', id, target.name, name)
      continue
    }

    for (const [key, value] of Object.entries(overrides)) {
      if (key === 'id') continue
      target[key] = value
    }

公共底座 dsh-base 开头写明了这套语义,agent 循环在里面是普通的一行:

模版 · packages/bundle/base/cordis.patch.yml:1-13
# The dsh-base bundle patch: the shared core of each base-backed profile, applied as
# ONE insert over the empty profile root. Later bundle patches and the user's
# profile cordis.patch.yml address these rows by id, with the last write
# winning per row.
#
# A patch replaces the targeted row's whole `config` rather than merging into
# it, so a row whose value differs by mode does NOT live here: it belongs to
# each mode bundle, keeping any single row down to one bundle layer plus the
# user's. Mode-specific rows appear below only with shared plugin identity and
# neutral defaults; each mode bundle restates its complete configuration.
#
# Row order carries no load semantics (activation is service-availability
# driven); the grouping is for readers.
模版 · packages/bundle/base/cordis.patch.yml:509-514
    # Agents created at startup. The base stays empty; raw overlays may create
    # agents, while Web creates sessions on client request.
    - id: agent-loop
      name: '@deepseek-ai/dsh-agent-loop'
      config:
        agents: []

dsh --dump-config 不启动,只调同一个叠加函数,打出来的就是会挂的。在临时 DSH_HOME 里真跑 dsh --profile web --dump-config,展开是 190 行顶层插件,每段标着来自哪一层:

效果 · web profile 展开后的三行
# == @deepseek-ai/dsh-base, patched by @deepseek-ai/dsh-web-app
- id: tools
  name: '@deepseek-ai/dsh-tools'
  config:
    mode: !!js process.env.DSH_TOOLS_MODE
- id: system-prompt
  name: '@deepseek-ai/dsh-system-prompt'
  config:
    personaPrefix: You are a coding agent powered by the {{model}} model.
# == @deepseek-ai/dsh-base
- id: agent-loop
  name: '@deepseek-ai/dsh-agent-loop'
  config:
    agents: []

再放一份构造的用户 patch:沙箱版 bash 换本地版,直接改 agent-loop 的包名,换人设:

构造的用户 patch · profiles/web/cordis.patch.yml
# 构造的用户 patch,放进 $DSH_HOME/profiles/web/cordis.patch.yml
# 1. 带沙箱的 bash 关掉,插入不带沙箱的本地实现
- id: bash-sandbox
  disabled: true
- insert:
    - id: bash-local
      name: '@deepseek-ai/dsh-bash-local'
      config:
        timeoutMs: 60000
# 2. 想直接把 agent-loop 这一行换成别的包
- id: agent-loop
  name: '@example/my-agent-loop'
# 3. 换人设
- id: system-prompt
  config:
    personaPrefix: You are a careful code reviewer.
效果 · 重新 dump 的告警和前后差异
dsh: [$DSH_HOME/profiles/web/cordis.patch.yml] patch: name mismatch for "agent-loop" (expected "@deepseek-ai/dsh-agent-loop", got "@example/my-agent-loop"), skipping

137a138
> # == @deepseek-ai/dsh-base, patched by $DSH_HOME/profiles/web/cordis.patch.yml
140c141
<   disabled: !!js process.platform === 'win32'
---
>   disabled: true
142a144
> # == @deepseek-ai/dsh-base
389a392
> # == @deepseek-ai/dsh-base, patched by @deepseek-ai/dsh-web-app, $DSH_HOME/profiles/web/cordis.patch.yml
393c396
<     personaPrefix: You are a coding agent powered by the {{model}} model.
---
>     personaPrefix: You are a careful code reviewer.
1229a1233,1237
> # == $DSH_HOME/profiles/web/cordis.patch.yml
> - id: bash-local
>   name: '@deepseek-ai/dsh-bash-local'
>   config:
>     timeoutMs: 60000

被改的行标上了来源,插入的行排在最后;改包名那条被跳过——同一个 id 不能偷换插件,想换循环就 disabled 这一行、insert 自己的。

循环插件占注册表里的工厂槽位,同时只能有一个:

模版 · packages/core/agent/src/index.ts:205-206
/** Thrown when create/resume is called before an agent factory is registered. */
const NO_FACTORY_MESSAGE = 'no agent factory registered (load an agent-loop plugin)'
模版 · packages/core/agent/src/index.ts:360-369
  setFactory(factory: AgentFactory): () => void {
    const dispose = this.ctx.effect(() => {
      if (this.factory !== undefined) throw new Error('an agent factory is already registered')
      // Avoid stacking two Cordis shadow layers when a caller passes a Service
      // already read through a context. Calls are re-traced through their
      // actual owner context below.
      const target = (factory as AgentFactory & { [symbols.original]?: AgentFactory })[symbols.original] ?? factory
      this.factory = { target }
      return () => { this.factory = undefined }
    }, 'agents.setFactory()')
模版 · packages/core/agent-loop/src/index.ts:388
    ctx.effect(() => ctx.agents.setFactory(this), 'agentLoop.setFactory()')

在真实的 dsh-agent 注册表上挂、卸一个构造的循环 TinyLoop:

效果 · 工厂槽位
without a loop plugin: no agent factory registered (load an agent-loop plugin)
with TinyLoop mounted:   TinyLoop.createAgent got model=deepseek-flash (a real loop builds the agent here)
a second loop plugin:     an agent factory is already registered
after TinyLoop unmount:  no agent factory registered (load an agent-loop plugin)

能替换只在结构上成立:仓库只带 dsh-agent-loop 一个实现,目标续跑、Ralph 都做成循环之上的插件,见多 agent 与长时工作。

agent 作用域:同一进程里各 agent 看到的不一样

问题:一个进程里几个 agent 共享适配器、持久化和界面,人设、工具变体、监听器却只属于其中一个。全局一张注册表,专属工具就漏给别人;每个 agent 一整套服务,又把基础设施复制一遍。3

agent 对象本身就是作用域的 key:

模版 · packages/core/agent-loop/src/agent.ts:130-131
    this.scope = createScope(loopCtx, this)
    this.ctx = this.scope.ctx

作用域是一个空插件加一个标签,经它注册的东西只对这个 agent 可见,也随它释放:

模版 · packages/core/scope/src/index.ts:137-147
export function createScope(ctx: Context, key: ScopeKey, options?: CreateScopeOptions): Scope {
  if (options?.parent !== undefined) bindScopeParent(key, options.parent)
  const fiber = ctx.plugin(scope)
  const scoped: Context = fiber.ctx.extend({ [kScope]: key })
  let disposing: Promise<void> | undefined
  return {
    ctx: scoped,
    rawDispose: fiber.dispose,
    dispose: () => (disposing ??= quiesceFiber(fiber)),
  }
}

注册表合并全局层和本 agent 层,同名的本层胜出;带标签的监听器只收本 agent 的事件;收窄工具只能对某个 agent 做:

模版 · packages/core/tools/src/index.ts:1098-1109
  /**
   * Restrict global tools for the calling agent scope. Empty filters, unknown
   * names, scope-local names, and reserved transport names fail. Restrictions
   * intersect; scoped registrations remain visible.
   * @param filter - global-tool mask: `allow` (keep only) and/or `deny` (remove).
   * @returns the exact disposer that lifts this restriction.
   */
  restrict(filter: ToolRestriction): () => void {
    const scope = scopeOf(this.ctx)
    if (scope === undefined) {
      throw new Error('tools.restrict() requires a scoped context (agent.ctx): a context-global restriction would mask every agent — deny the tool for the intended agent instead')
    }

子 agent 就这样定制:同名段换人设,restrict 去工具,加一条委派说明:

模版 · packages/subagent/subagent/src/child-agent.ts:204-218
function applyChildOverrides(childCtx: Context, composition: ChildComposition): void {
  childCtx.systemPrompt.context({
    name: 'subagent:delegation',
    order: childCtx.systemPrompt.getContextOrder('SUBAGENT_DELEGATION'),
    text: SUBAGENT_DELEGATION_CONTEXT,
  })
  if (composition.persona !== undefined) {
    childCtx.systemPrompt.section({
      name: 'deployment:persona-prefix',
      order: childCtx.systemPrompt.getSectionOrder('DEPLOYMENT_PERSONA_PREFIX'),
      text: composition.persona,
    })
  }
  if (composition.toolFilter !== undefined) childCtx.tools.restrict(composition.toolFilter)
}

构造三个全局工具和三个 agent:child 走真实的 applyChildComposition,换人设、去 bash;other 注册自己的 read,再挂一个监听器:

效果 · 三个 agent 的视图
parent
   tools: ["read","bash","web_fetch"]
   prompt: "You are an AI agent powered by DeepSeek Harness.\n\nYou are a coding agent powered by the deepseek-flash model."
   contexts: []
child (persona + deny bash via applyChildComposition)
   tools: ["read","web_fetch"]
   prompt: "You are an AI agent powered by DeepSeek Harness.\n\nYou are a read-only reviewer subagent."
   contexts: ["subagent:delegation"]
other (own read shadows the global one)
   tools: ["read","bash","web_fetch"]
   prompt: "You are an AI agent powered by DeepSeek Harness.\n\nYou are a coding agent powered by the deepseek-flash model."
   contexts: []
parent calls read
   -> read: global implementation
other calls read
   [other's listener] saw read
   -> read: other agent's own implementation
restrict on the global ctx:
   tools.restrict() requires a scoped context (agent.ctx): a context-global restriction would mask every agent — deny the tool for the intended agent instead
child after its scope is disposed
   tools: ["read","bash","web_fetch"]
   prompt: "You are an AI agent powered by DeepSeek Harness.\n\nYou are a coding agent powered by the deepseek-flash model."
   contexts: []

child 换了人设、少了 bash,多出的委派说明走运行时上下文、不进 system prompt(见拼 prompt);other 的监听器只听见自己的调用;全局限制直接抛错;child 一释放,视图回到全局。作用域只管看得见什么,不管能做什么——决策记录明写「安全与权限是非目标」。

agent 预设:一个进程跑几套组合

问题:启动时那份组合决定了 agent 的工具、人设、提示词段。要让跑分用的精简 agent 和完整的编码 agent 并存,就得开两个进程;在进程级配置里关几行工具,又会改掉所有会话。4

预设是一份插件清单,写法同 profile 的行。Web 默认带 standard、ptc、minimal、cordis 四套,minimal 这样写:

模版 · packages/bundle/web-app/presets/minimal.patch.yml:1-31
# Agent preset minimal: one `@deepseek-ai/dsh-agent-preset` declaration inserted
# after the web patch. Edits saved from the Web editor override this row's
# `config.plugins` by id from the profile patch.
- insert:
    - id: preset-minimal
      name: '@deepseek-ai/dsh-agent-preset'
      config:
        id: minimal
        order: 3
        plugins:
          - id: persona
            name: '@deepseek-ai/dsh-persona'
            config:
              prefix: You are a helpful software engineer assistant.
              complete: true
              includeRuntimeContext: false
          - id: tool-fs
            name: '@deepseek-ai/dsh-tool-fs'
            config:
              read: false
              readImage: true
              write: false
              edit: false
          - id: compaction
            name: cordis:group
            group: true
            isolate:
              compaction: true
            config:
              - id: compaction-basic
                name: '@deepseek-ai/dsh-compaction-basic'

每套预设的每个修订版在进程里只常驻一份,有自己的作用域 key:

模版 · packages/preset/agent-preset-registry/src/index.ts:103-122
  private async activate(record: Definition): Promise<void> {
    const key = {}
    const scope = createScope(this.owner, key)
    try {
      const problem = entryListProblem(record.config.plugins)
      if (problem !== undefined) throw new Error(problem)
      const context = scope.ctx.extend({ baseUrl: record.context.baseUrl })
      const mount = await mountPreset(context, record.config.id, record.config.plugins)
      const generation: Generation = { scope, key, mount, users: 0, retired: false }
      context.effect(() => {
        this.generations.set(key, generation)
        return () => { this.generations.delete(key) }
      }, 'agent-preset.mount')
      record.generation = generation
    } catch (error) {
      record.broken = (error as Error).message
      this.owner.logger.warn(`agent preset ${record.config.id}: ${record.broken}`)
      await scope.dispose()
    }
  }

agent 用作用域父链挂到它下面——注册向下继承,事件向上放行,兄弟预设互不可见:

模版 · packages/preset/agent-preset-registry/src/index.ts:265-274
  private join(ctx: Context, key: ScopeKey, generation: Generation): void {
    const binding = { parent: bindScopeParent(key, generation.key), generation }
    generation.users++
    this.bindings.set(key, binding)
    ctx.effect(() => async () => {
      this.bindings.delete(key)
      binding.generation.users--
      await this.collect(binding.generation)
    }, 'agent-preset.binding')
  }

预设自带的服务(minimal 里的压缩、持久终端)必须放进独立的 isolate 命名空间,否则挂载被拒:

模版 · packages/preset/agent-preset-registry/src/mount.ts:204-215
export async function mountPreset(ctx: Context, id: string, plugins: PresetDefinition['plugins']): Promise<PresetMount> {
  if (scopeOf(ctx) === undefined) throw new Error('agent-preset: mounting requires a scope')
  await ctx.fiber.await()
  const tree = new PresetTree(ctx)
  ctx.effect(() => () =>{  tree.root.stop() }, 'agent-preset.tree')
  await tree.root.update(prepareProfileEntries(ctx, plugins as EntryOptions[], ctx.baseUrl))
  const audit = await auditRows(tree)
  const leaked = leakedServices(ctx, ctx.fiber)
  if (audit.failed.length > 0) throw new Error(audit.failed.join('\n'))
  if (leaked.length > 0) throw new Error(`Preset services require isolate realms: ${leaked.join(', ')}`)
  const mount = { presetId: id, fiber: ctx.fiber, tree, key: scopeOf(ctx) }
  return mount

注册表本身(加载 YAML、逐行审计)没整套跑;脚本用它底下的作用域父链和 isolate 搭两套构造的预设:standard 带 bash、web_fetch 和真实的本地 shell,minimal 只带 DryRunBash:

效果 · 两套预设、三个 agent
a1: tools ["read","bash","web_fetch"]
a2: tools ["read","bash","web_fetch"]
b1: tools ["read"]
shell inside preset standard: LocalBashExecutor
shell inside preset minimal: DryRunBash
shell at the root realm: (none)
without isolate, second shell provider: service "shell" has been registered at <LocalBashExecutor>

a1、a2 共用 standard 那一份,b1 只看得见共享的 read;两套预设各有 shell,根上没有。不 isolate,第二个 shell 就撞在根上——预设挂载拒绝的正是这种情况。

谁管什么

  • 程序:注册即 effect,卸载按逆序撤销;inject 决定谁什么时候启动、重启;patch 的叠加顺序和整行替换;作用域的可见性和事件路由;守卫只能否决
  • 模型:默认不碰这一层,只看到组装的结果——工具表和提示词段,不知道背后是哪个提供方、哪套预设。只有 cordis 预设给了它插件管理工具,每次操作都要用户审批,或者会话本来就开着完全放开的权限
  • 用户:写 profile 和 home 的 cordis.patch.yml、命令行 --patch;在插件管理器里开关一行、装树外的组合包;在 Web 里给会话选预设

实跑原件

dsh --profile web --dump-config 全文
# == @deepseek-ai/dsh-base
- id: tool-plugin-manager
  name: '@deepseek-ai/dsh-plugin-manager/tools'
  disabled: true
- id: plugin-manager
  name: '@deepseek-ai/dsh-plugin-manager'
  disabled: !!js '!ctx.get(''profileContext'')'
- id: timer
  name: '@deepseek-ai/cordis-plugin-timer'
- id: hmr
  name: '@deepseek-ai/dsh-hmr'
  disabled: !!js '!ctx.get(''profileContext'')'
  config:
    root: []
- id: llm
  name: '@deepseek-ai/dsh-llm'
- id: deepseek-llm-api-extensions
  name: '@deepseek-ai/dsh-deepseek-llm-api-extensions'
- id: session
  name: '@deepseek-ai/dsh-session'
- id: session-log-deepseek
  name: '@deepseek-ai/dsh-session-log-deepseek'
- id: typert
  name: '@deepseek-ai/dsh-typert-registry'
- id: typert-loader
  name: '@deepseek-ai/dsh-typert-loader'
- id: typert-gateway
  name: '@deepseek-ai/dsh-api-gateway'
- id: remote-access
  name: '@deepseek-ai/dsh-api-remote-access'
- id: session-title
  name: '@deepseek-ai/dsh-session-title'
  config:
    fallbackMaxWords: 5
    fallbackMaxBytes: 40
    maxTitleBytes: 80
- id: session-title-llm
  name: '@deepseek-ai/dsh-session-title-first-prompt-llm'
  config:
    targetWords: 5
    targetCjkCharacters: 10
    maxInputBytes: 4096
    maxOutputTokens: 4096
    timeoutMs: 60000
- id: user-questions
  name: '@deepseek-ai/dsh-user-questions'
- id: agent
  name: '@deepseek-ai/dsh-agent'
- id: plugin-package-inventory-deepseek
  name: '@deepseek-ai/dsh-plugin-package-inventory-deepseek'
- id: agent-default-model
  name: '@deepseek-ai/dsh-agent-default-model'
  config:
    provider: deepseek-official
    model: deepseek-flash
- id: jobs
  name: '@deepseek-ai/dsh-jobs-local'
- id: llm-retry
  name: '@deepseek-ai/dsh-llm-retry'
- id: config-editor
  name: '@deepseek-ai/dsh-config-editor'
  disabled: !!js '!ctx.get(''profileContext'')'
- id: settings
  name: '@deepseek-ai/dsh-settings'
  disabled: !!js '!ctx.get(''profileContext'')'
- id: authorization
  name: '@deepseek-ai/dsh-authorization'
- id: deepseek-account
  name: '@deepseek-ai/dsh-deepseek-account-platform'
  config:
    desktopPlatform: !!js >-
      ctx.get('profileContext')?.name === 'desktop' && ['darwin',
      'win32'].includes(process.platform) ? process.platform : null
- id: credentials
  name: '@deepseek-ai/dsh-credentials-local'
- id: llm-pi-ai
  name: '@deepseek-ai/dsh-llm-pi-ai'
- id: session-persistence-jsonl
  name: '@deepseek-ai/dsh-session-persistence-jsonl'
  config:
    root: !!js dshHomePath('sessions')
- id: attachment-local
  name: '@deepseek-ai/dsh-attachment-local'
- id: session-query-sqlite
  name: '@deepseek-ai/dsh-session-query-sqlite'
  config:
    path: ':memory:'
    openAt: never
- id: working-directory
  name: '@deepseek-ai/dsh-working-directory'
- id: tool-working-directory
  name: '@deepseek-ai/dsh-tool-working-directory'
- id: session-projection
  name: '@deepseek-ai/dsh-session-projection'
- id: storage
  name: '@deepseek-ai/dsh-storage'
- id: storage-json
  name: '@deepseek-ai/dsh-storage-json'
  config:
    root: !!js dshHomePath('storages')
- id: storage-domain
  name: '@deepseek-ai/dsh-storage-domain'
  config:
    backend: json
- id: session-projection-cache
  name: '@deepseek-ai/dsh-session-projection-cache'
  config:
    writeEveryEvents: 200
    writeIntervalMs: 5000
- id: otel
  name: '@deepseek-ai/dsh-otel'
- id: session-telemetry-otel
  name: '@deepseek-ai/dsh-session-telemetry-otel'
  config:
    mode: !!js process.env.DSH_TELEMETRY_MODE || 'FEEDBACK_ONLY'
    shutdownTimeoutMillis: 3000
    maxRequestBytes: 4000000
    exporter:
      url: !!js >-
        process.env.DSH_TELEMETRY_OTLP_URL ??
        'https://dsh-otel-collector.deepseeksvc.com/v1/logs'
      compression: gzip
      timeoutMillis: 1000
    processor:
      scheduledDelayMillis: 10000
      maxQueueSize: 2048
      maxExportBatchSize: 2048
      exportTimeoutMillis: 1500
- id: subprocess
  name: '@deepseek-ai/dsh-subprocess-local'
- 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 ?? 'workspace-write'
    workspaceRoot: !!js process.cwd()
- id: bash-sandbox
  name: '@deepseek-ai/dsh-bash-sandbox'
  disabled: !!js process.platform === 'win32'
  config:
    timeoutMs: 60000
- id: pwsh-sandbox
  name: '@deepseek-ai/dsh-pwsh-sandbox'
  disabled: !!js process.platform !== 'win32'
- id: approval
  name: '@deepseek-ai/dsh-user-approval'
  config:
    policy: !!js >-
      (process.env.DSH_PERMISSION_MODE ?? 'workspace-write') ===
      'danger-full-access' ? 'never' : 'ask'
- id: permission
  name: '@deepseek-ai/dsh-permission-presets'
  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: shell-env
  name: '@deepseek-ai/dsh-shell-env'
# == @deepseek-ai/dsh-base, patched by @deepseek-ai/dsh-web-app
- id: tool-bash
  name: '@deepseek-ai/dsh-tool-bash'
  disabled: true
- id: tool-pwsh
  name: '@deepseek-ai/dsh-tool-pwsh'
  disabled: true
- id: tool-jobs
  name: '@deepseek-ai/dsh-tool-jobs'
  disabled: true
# == @deepseek-ai/dsh-base
- id: fs-observation-policy
  name: '@deepseek-ai/dsh-fs-observation-policy'
# == @deepseek-ai/dsh-base, patched by @deepseek-ai/dsh-web-app
- id: tool-fs
  name: '@deepseek-ai/dsh-tool-fs'
  disabled: true
- id: tool-fs-search
  name: '@deepseek-ai/dsh-tool-fs-search'
  config:
    sampleOverCapGlobResults: false
  disabled: true
- id: agent-instructions
  name: '@deepseek-ai/dsh-agent-instructions'
  config:
    maxBytes: 65536
  disabled: true
# == @deepseek-ai/dsh-base
- id: skill
  name: '@deepseek-ai/dsh-skill'
# == @deepseek-ai/dsh-base, patched by @deepseek-ai/dsh-web-app
- id: skill-filesystem
  name: '@deepseek-ai/dsh-skill-filesystem'
  disabled: true
- id: tool-skill
  name: '@deepseek-ai/dsh-tool-skill'
  disabled: true
# == @deepseek-ai/dsh-base
- id: commands
  name: '@deepseek-ai/dsh-commands'
- id: command-feedback
  name: '@deepseek-ai/dsh-command-feedback'
- id: goal
  name: '@deepseek-ai/dsh-goal'
- id: goal-round-driver
  name: '@deepseek-ai/dsh-goal-round-driver'
# == @deepseek-ai/dsh-base, patched by @deepseek-ai/dsh-web-app
- id: command-goal
  name: '@deepseek-ai/dsh-command-goal'
  disabled: true
- id: plan-mode
  name: '@deepseek-ai/dsh-plan-mode'
  config:
    section: >
      You are in plan mode. Stay in plan mode until the user approves your plan
      through exit_plan_mode or switches the session mode. Imperative language
      to implement changes means plan the implementation, not execute it. A
      user's conversational agreement — including an answer confirming something
      you asked — approves nothing and does not end plan mode; fold the
      confirmed decision into the plan and submit it through exit_plan_mode.


      Explore first. Use non-mutating reads, searches, static analysis, and
      checks to ground the plan in the actual repository. Do not edit or write
      files, change configuration, run formatters or code generation that
      rewrites tracked files, commit, or otherwise carry out the plan. Prefer
      existing functions and patterns over new machinery.


      The tool catalog stays the same across modes for request-cache stability.
      These plan-mode rules override any later tool description or guidance that
      suggests using mutation tools; those tools remain listed only to keep the
      request shape stable. Do not use todo_write to track this planning phase:
      it tracks implementation after an approved plan, while the plan itself
      belongs in exit_plan_mode.


      Resolve discoverable facts by inspection. Use ask_user_question only for
      user-owned choices or material ambiguity that inspection cannot answer. Do
      not ask the user where code lives or how current behavior works when you
      can find out.


      Make the plan decision-complete: state the goal and success criteria;
      group implementation changes by subsystem; identify public API, schema,
      and data-flow changes; cover edge cases, failure modes, tests, acceptance
      criteria, and explicit assumptions. Keep it concise enough to review but
      detailed enough that another engineer can implement it without making
      design decisions.


      When ready, call exit_plan_mode with the complete plan markdown, starting
      with a # title. Make exit_plan_mode the only and final tool call in that
      assistant response: it presents the plan for approval, and implementation
      begins only in a later step after approval. Do not paste the final plan as
      a plain reply or ask "should I proceed?" through prose or
      ask_user_question. If review rejects it, incorporate the feedback and
      present again. If the review channel is unavailable or aborted, stay in
      plan mode and ask the user to switch modes manually; do not proceed with
      implementation.
  disabled: true
# == @deepseek-ai/dsh-base
- id: token-meter
  name: '@deepseek-ai/dsh-token-meter'
# == @deepseek-ai/dsh-base, patched by @deepseek-ai/dsh-web-app
- id: compaction-basic
  name: '@deepseek-ai/dsh-compaction-basic'
  disabled: true
- id: command-compact
  name: '@deepseek-ai/dsh-command-compact'
  disabled: true
# == @deepseek-ai/dsh-base
- id: subagent
  name: '@deepseek-ai/dsh-subagent'
- id: subagent-spawn-in-process
  name: '@deepseek-ai/dsh-subagent-spawn-in-process'
  config:
    providerName: spawn
- id: subagent-fork-in-process
  name: '@deepseek-ai/dsh-subagent-fork-in-process'
  config:
    providerName: fork
# == @deepseek-ai/dsh-base, patched by @deepseek-ai/dsh-web-app
- id: tool-subagent-control
  name: '@deepseek-ai/dsh-tool-subagent-control'
  disabled: true
- id: tool-subagent-list-agents
  name: '@deepseek-ai/dsh-tool-subagent-control/list-agents'
  disabled: true
- id: tool-subagent
  name: '@deepseek-ai/dsh-tool-subagent'
  config:
    provider: spawn
    toolName: subagent
  disabled: true
- id: tool-subagent-fork
  name: '@deepseek-ai/dsh-tool-subagent'
  config:
    provider: fork
    toolName: subagent_fork
  disabled: true
# == @deepseek-ai/dsh-base
- id: ptc-runtime
  name: '@deepseek-ai/dsh-ptc-runtime-node'
# == @deepseek-ai/dsh-base, patched by @deepseek-ai/dsh-web-app
- id: workflow-ptc
  name: '@deepseek-ai/dsh-workflow-ptc'
  config:
    provider: spawn
  disabled: true
- id: tool-workflow
  name: '@deepseek-ai/dsh-tool-workflow'
  disabled: true
# == @deepseek-ai/dsh-base
- id: timeout-policy
  name: '@deepseek-ai/dsh-tool-call-timeout-policy'
- id: spill-local
  name: '@deepseek-ai/dsh-spill-local'
- id: spill-policy
  name: '@deepseek-ai/dsh-spill-policy'
  config:
    maxInlineTokens: 12500
- id: session-checkpoint-policy
  name: '@deepseek-ai/dsh-session-checkpoint-policy'
# == @deepseek-ai/dsh-base, patched by @deepseek-ai/dsh-web-app
- id: tool-result-pruner
  name: '@deepseek-ai/dsh-compaction-tool-result-pruner'
  config:
    thresholdChars: 8192
    headChars: 4096
    tailChars: 1024
  disabled: true
# == @deepseek-ai/dsh-base
- id: image-offload
  name: '@deepseek-ai/dsh-compaction-image-offload'
# == @deepseek-ai/dsh-base, patched by @deepseek-ai/dsh-web-app
- id: tool-todo
  name: '@deepseek-ai/dsh-tool-todo'
  config:
    allowParallelInProgress: true
  disabled: true
- id: tool-goal
  name: '@deepseek-ai/dsh-tool-goal'
  disabled: true
# == @deepseek-ai/dsh-base
- id: repeat-tool-reminder
  name: '@deepseek-ai/dsh-repeat-tool-reminder'
  config:
    thresholds:
      - 3
      - 5
      - 8
    argumentsPreviewChars: 500
- id: web
  name: '@deepseek-ai/dsh-web'
  config:
    searchProvider: deepseek-official
    fetchProvider: http
- id: web-search-deepseek
  name: '@deepseek-ai/dsh-web-search-deepseek'
  config:
    apiKeyEnv: DEEPSEEK_API_KEY
- id: web-deepseek-api
  name: '@deepseek-ai/dsh-web-deepseek-api'
  config:
    apiKeyEnv: DEEPSEEK_API_KEY
- id: web-fetch-http
  name: '@deepseek-ai/dsh-web-fetch-http'
# == @deepseek-ai/dsh-base, patched by @deepseek-ai/dsh-web-app
- id: tool-web
  name: '@deepseek-ai/dsh-tool-web'
  config:
    fetch: true
    searchTimeoutMs: 60000
  disabled: true
# == @deepseek-ai/dsh-base
- id: mcp-resources
  name: '@deepseek-ai/dsh-mcp-resources'
# == @deepseek-ai/dsh-base, patched by @deepseek-ai/dsh-web-app
- id: tools
  name: '@deepseek-ai/dsh-tools'
  config:
    mode: !!js process.env.DSH_TOOLS_MODE
- id: system-prompt
  name: '@deepseek-ai/dsh-system-prompt'
  config:
    personaPrefix: You are a coding agent powered by the {{model}} model.
# == @deepseek-ai/dsh-base
- id: agent-loop
  name: '@deepseek-ai/dsh-agent-loop'
  config:
    agents: []
- id: fs-sandbox
  name: '@deepseek-ai/dsh-fs-sandbox'
- id: llm-deepseek
  name: '@deepseek-ai/dsh-llm-deepseek-api-key'
- id: llm-deepseek-account
  name: '@deepseek-ai/dsh-llm-deepseek-account'
# == @deepseek-ai/dsh-web-app
- id: desktop-product-telemetry
  name: '@deepseek-ai/dsh-host-product-telemetry-otel'
  disabled: !!js ctx.get('profileContext')?.name !== 'desktop'
  config:
    scheduledDelayMillis: 30000
    timeoutMillis: 15000
    exportTimeoutMillis: 20000
    shutdownTimeoutMillis: 2000
    serviceName: deepseek-harness-desktop
    serviceVersion: !!js process.env.DSH_CLIENT_VERSION
    endpoint: !!js process.env.DSH_PRODUCT_ANALYTICS_OTLP_URL
- id: product-analytics
  name: '@deepseek-ai/dsh-client-product-analytics'
  disabled: !!js ctx.get('profileContext')?.name !== 'desktop'
  config:
    enabled: true
    appVersion: !!js process.env.DSH_CLIENT_VERSION
- id: subagent-model-selection-settings
  name: '@deepseek-ai/dsh-tool-subagent/model-selection-settings'
- id: message-feedback
  name: '@deepseek-ai/dsh-message-feedback'
  config:
    maxNoteBytes: 8192
- id: session-log-download
  name: '@deepseek-ai/dsh-session-log-export'
- id: open-in-app
  name: '@deepseek-ai/dsh-host-open-in-app'
  config:
    probeTimeoutMs: 10000
    iconTimeoutMs: 10000
    launchWatchMs: 1000
- id: ui-open-in-app
  name: '@deepseek-ai/dsh-client-ui-open-in-app'
- id: workspace
  name: '@deepseek-ai/dsh-workspace'
- id: session-reference
  name: '@deepseek-ai/dsh-session-reference'
- id: file-reference-local
  name: '@deepseek-ai/dsh-file-reference-local'
- id: session-stats
  name: '@deepseek-ai/dsh-session-stats'
- id: session-turn-outline
  name: '@deepseek-ai/dsh-session-turn-outline'
- id: directory-picker
  name: '@deepseek-ai/dsh-host-directory-picker-auto'
- id: plugin-inventory
  name: '@deepseek-ai/dsh-host-plugin-inventory'
- id: session-controller
  name: '@deepseek-ai/dsh-api-session-controller'
- id: job-controller
  name: '@deepseek-ai/dsh-api-job-controller'
- id: terminal-controller
  name: '@deepseek-ai/dsh-api-terminal-controller'
- id: workspace-files
  name: '@deepseek-ai/dsh-api-workspace-files'
- id: schedule
  name: '@deepseek-ai/dsh-schedule'
- id: ui-settings-account
  name: '@deepseek-ai/dsh-client-ui-settings-account'
- id: account-controller
  name: '@deepseek-ai/dsh-api-account-controller'
- id: settings-controller
  name: '@deepseek-ai/dsh-api-settings-controller'
- id: workspace-controller
  name: '@deepseek-ai/dsh-api-workspace-controller'
- id: cordis-host-runner
  name: '@deepseek-ai/dsh-cordis-host-runner'
- id: cordis-inspect-providers
  name: '@deepseek-ai/dsh-tool-cordis/host'
- id: web-startup
  name: '@deepseek-ai/dsh-web-app/startup'
- id: webserver
  name: '@deepseek-ai/dsh-host-webserver'
  inject:
    - webStartup
  config:
    host: !!js ctx.webStartup.host ?? '127.0.0.1'
    port: !!js ctx.webStartup.port ?? 3080
    tls: !!js ctx.webStartup.tls
    compression: gzip
    compressionLevel: 1
    compressionThresholdBytes: 1024
- id: web-runtime
  name: '@deepseek-ai/dsh-web-app'
  inject:
    - webStartup
  config:
    openBrowser: !!js ctx.webStartup.openBrowser
    printUrl: true
    publicUrl: !!js ctx.webStartup.publicUrl
    surfaceContext: true
- id: harness-dev-context
  name: '@deepseek-ai/dsh-harness-dev-context'
- id: client-hmr
  name: '@deepseek-ai/dsh-client-hmr'
- id: modules
  name: '@deepseek-ai/dsh-client-modules'
- id: connection
  name: '@deepseek-ai/dsh-client-connection'
  inject:
    - webStartup
  config:
    trustedHosts: !!js ctx.webStartup.trustedHosts
- id: file-upload
  name: '@deepseek-ai/dsh-client-file-upload'
- id: api-remotes
  name: '@deepseek-ai/dsh-api-remotes'
- id: cordis-client-runner
  name: '@deepseek-ai/dsh-cordis-client-runner'
- id: ui-theme
  name: '@deepseek-ai/dsh-client-ui-theme'
- id: locale
  name: '@deepseek-ai/dsh-client-locale'
- id: shortcuts
  name: '@deepseek-ai/dsh-client-shortcuts'
- id: ui-shortcuts
  name: '@deepseek-ai/dsh-client-ui-shortcuts'
- id: ui-layout
  name: '@deepseek-ai/dsh-client-ui-layout'
- id: ui-renderer
  name: '@deepseek-ai/dsh-client-ui-renderer'
- id: ui-session
  name: '@deepseek-ai/dsh-client-ui-session'
- id: resources
  name: '@deepseek-ai/dsh-client-resources'
- id: ui-sidebar
  name: '@deepseek-ai/dsh-client-ui-sidebar'
- id: ui-sidebar-right
  name: '@deepseek-ai/dsh-client-ui-sidebar-right'
- id: office-to-pdf
  name: '@deepseek-ai/dsh-office-to-pdf'
- id: ui-sidebar-documentpreview
  name: '@deepseek-ai/dsh-client-ui-sidebar-documentpreview'
- id: ui-sidebar-browser
  name: '@deepseek-ai/dsh-client-ui-sidebar-browser'
  disabled: !!js ctx.get('profileContext')?.name !== 'desktop'
- id: ui-sidebar-terminal
  name: '@deepseek-ai/dsh-client-ui-sidebar-terminal'
- id: ui-sidebar-files
  name: '@deepseek-ai/dsh-client-ui-sidebar-files'
- id: ui-settings
  name: '@deepseek-ai/dsh-client-ui-settings'
- id: ui-settings-general
  name: '@deepseek-ai/dsh-client-ui-settings-general'
- id: ui-settings-models
  name: '@deepseek-ai/dsh-client-ui-settings-models'
- id: ui-plugin-manager
  name: '@deepseek-ai/dsh-client-ui-plugin-manager'
- id: ui-settings-plugin-inventory
  name: '@deepseek-ai/dsh-client-ui-settings-plugin-inventory'
- id: ui-conversation
  name: '@deepseek-ai/dsh-client-ui-conversation'
- id: ui-approval
  name: '@deepseek-ai/dsh-client-ui-approval'
- id: ui-chat
  name: '@deepseek-ai/dsh-client-ui-chat'
- id: ui-brand-official
  name: '@deepseek-ai/dsh-client-ui-brand-official'
- id: ui-attachment
  name: '@deepseek-ai/dsh-client-ui-attachment'
- id: ui-tool
  name: '@deepseek-ai/dsh-client-ui-tool'
- id: ui-cordis
  name: '@deepseek-ai/dsh-client-ui-cordis'
- id: ui-deliverables
  name: '@deepseek-ai/dsh-client-ui-deliverables'
- id: workspace-changes
  name: '@deepseek-ai/dsh-workspace-changes'
- id: ui-workspace
  name: '@deepseek-ai/dsh-client-ui-workspace'
- id: ui-workflow-run
  name: '@deepseek-ai/dsh-client-ui-workflow-run'
- id: ui-input-trigger
  name: '@deepseek-ai/dsh-client-ui-input-trigger'
- id: ui-commands
  name: '@deepseek-ai/dsh-client-ui-commands'
- id: ui-skill
  name: '@deepseek-ai/dsh-client-ui-skill'
- id: ui-subagent
  name: '@deepseek-ai/dsh-client-ui-subagent'
- id: ui-reference
  name: '@deepseek-ai/dsh-client-ui-reference'
- id: ui-secret-mask
  name: '@deepseek-ai/dsh-client-ui-secret-mask'
- id: ui-jobs
  name: '@deepseek-ai/dsh-client-ui-jobs'
- id: ui-schedule
  name: '@deepseek-ai/dsh-client-ui-schedule'
- id: ui-goal
  name: '@deepseek-ai/dsh-client-ui-goal'
- id: ui-message-feedback
  name: '@deepseek-ai/dsh-client-ui-message-feedback'
- id: ui-model-selection
  name: '@deepseek-ai/dsh-client-ui-model-selection'
- id: ui-deepseek-model-label-1017
  name: '@deepseek-ai/dsh-client-ui-deepseek-model-label-1017'
- id: ui-permission
  name: '@deepseek-ai/dsh-client-ui-permission-presets'
- id: ui-agent-preset
  name: '@deepseek-ai/dsh-client-ui-agent-preset'
- id: ui-settings-session-log
  name: '@deepseek-ai/dsh-client-ui-settings-session-log'
- id: ui-settings-plugins
  name: '@deepseek-ai/dsh-client-ui-settings-plugins'
- id: ui-settings-shell
  name: '@deepseek-ai/dsh-client-ui-settings-shell'
- id: ui-settings-agent-loop
  name: '@deepseek-ai/dsh-client-ui-settings-agent-loop'
- id: ui-settings-subagent
  name: '@deepseek-ai/dsh-client-ui-settings-subagent'
- id: ui-settings-web-search
  name: '@deepseek-ai/dsh-client-ui-settings-web-search'
- id: ui-plan
  name: '@deepseek-ai/dsh-client-ui-plan'
- id: ui-user-questions
  name: '@deepseek-ai/dsh-client-ui-user-questions'
- id: ui-trajectory
  name: '@deepseek-ai/dsh-client-ui-trajectory'
- id: agent-preset-registry
  name: '@deepseek-ai/dsh-agent-preset-registry'
  config:
    default: standard
- id: preset-standard
  name: '@deepseek-ai/dsh-agent-preset'
  config:
    id: standard
    order: 1
    plugins:
      - id: persona
        name: '@deepseek-ai/dsh-persona'
        config:
          prefix: You are a coding agent powered by the {{model}} model.
      - id: agent-instructions
        name: '@deepseek-ai/dsh-agent-instructions'
        config:
          maxBytes: 65536
      - id: time-context
        name: '@deepseek-ai/dsh-time-context'
      - id: tool-bash
        name: '@deepseek-ai/dsh-tool-bash'
        disabled: !!js process.platform === 'win32'
      - id: tool-pwsh
        name: '@deepseek-ai/dsh-tool-pwsh'
        disabled: !!js process.platform !== 'win32'
      - id: tool-fs
        name: '@deepseek-ai/dsh-tool-fs'
      - id: tool-fs-search
        name: '@deepseek-ai/dsh-tool-fs-search'
        config:
          sampleOverCapGlobResults: false
      - id: tool-jobs
        name: '@deepseek-ai/dsh-tool-jobs'
      - id: tool-schedule
        name: '@deepseek-ai/dsh-tool-schedule'
      - id: skill-filesystem
        name: '@deepseek-ai/dsh-skill-filesystem'
      - id: tool-skill
        name: '@deepseek-ai/dsh-tool-skill'
      - id: command-goal
        name: '@deepseek-ai/dsh-command-goal'
      - id: tool-goal
        name: '@deepseek-ai/dsh-tool-goal'
      - id: planning
        name: cordis:group
        group: true
        isolate:
          planMode: true
        config:
          - id: plan-mode
            name: '@deepseek-ai/dsh-plan-mode'
            config:
              section: >
                You are in plan mode. Stay in plan mode until the user approves
                your plan through exit_plan_mode or switches the session mode.
                Imperative language to implement changes means plan the
                implementation, not execute it. A user's conversational
                agreement — including an answer confirming something you asked —
                approves nothing and does not end plan mode; fold the confirmed
                decision into the plan and submit it through exit_plan_mode.


                Explore first. Use non-mutating reads, searches, static
                analysis, and checks to ground the plan in the actual
                repository. Do not edit or write files, change configuration,
                run formatters or code generation that rewrites tracked files,
                commit, or otherwise carry out the plan. Prefer existing
                functions and patterns over new machinery.


                The tool catalog stays the same across modes for request-cache
                stability. These plan-mode rules override any later tool
                description or guidance that suggests using mutation tools;
                those tools remain listed to keep the tool catalog unchanged. Do
                not use todo_write to track this planning phase: it tracks
                implementation after an approved plan, while the plan itself
                belongs in exit_plan_mode.


                Resolve discoverable facts by inspection. Use ask_user_question
                only for user-owned choices or material ambiguity that
                inspection cannot answer. Do not ask the user where code lives
                or how current behavior works when you can find out.


                Make the plan decision-complete: state the goal and success
                criteria; group implementation changes by subsystem; identify
                public API, schema, and data-flow changes; cover edge cases,
                failure modes, tests, acceptance criteria, and explicit
                assumptions. Keep it concise enough to review but detailed
                enough that another engineer can implement it without making
                design decisions.


                When ready, call exit_plan_mode with the complete plan markdown,
                starting with a # title. Make exit_plan_mode the only and final
                tool call in that assistant response: it presents the plan for
                approval, and implementation begins only in a later step after
                approval. Do not paste the final plan as a plain reply or ask
                "should I proceed?" through prose or ask_user_question. If
                review rejects it, incorporate the feedback and present again.
                If the review channel is unavailable or aborted, stay in plan
                mode and ask the user to switch modes manually; do not proceed
                with implementation.
      - id: compaction
        name: cordis:group
        group: true
        isolate:
          compaction: true
          toolResultPruner: true
        config:
          - id: compaction-basic
            name: '@deepseek-ai/dsh-compaction-basic'
          - id: command-compact
            name: '@deepseek-ai/dsh-command-compact'
          - id: tool-result-pruner
            name: '@deepseek-ai/dsh-compaction-tool-result-pruner'
            config:
              thresholdChars: 8192
              headChars: 4096
              tailChars: 1024
      - id: delegation
        name: cordis:group
        group: true
        isolate:
          workflowEngine: true
        config:
          - id: tool-subagent-control
            name: '@deepseek-ai/dsh-tool-subagent-control'
          - id: tool-subagent-list-agents
            name: '@deepseek-ai/dsh-tool-subagent-control/list-agents'
          - id: tool-subagent
            name: '@deepseek-ai/dsh-tool-subagent'
            config:
              provider: spawn
              toolName: subagent
              modelSelectionSettings: true
          - id: tool-subagent-fork
            name: '@deepseek-ai/dsh-tool-subagent'
            config:
              provider: fork
              toolName: subagent_fork
          - id: workflow-ptc
            name: '@deepseek-ai/dsh-workflow-ptc'
            config:
              provider: spawn
          - id: tool-workflow
            name: '@deepseek-ai/dsh-tool-workflow'
      - id: tool-ask-user
        name: '@deepseek-ai/dsh-tool-ask-user'
      - id: tool-todo
        name: '@deepseek-ai/dsh-tool-todo'
        config:
          allowParallelInProgress: true
      - id: tool-web
        name: '@deepseek-ai/dsh-tool-web'
        config:
          fetch: true
          searchTimeoutMs: 60000
      - id: present
        name: '@deepseek-ai/dsh-tool-present'
      - id: tool-plugin-manager
        name: '@deepseek-ai/dsh-plugin-manager/tools'
        disabled: true
      - id: tool-plugin-market
        name: '@deepseek-ai/dsh-plugin-market/tools'
        disabled: true
- id: preset-ptc
  name: '@deepseek-ai/dsh-agent-preset'
  config:
    id: ptc
    order: 2
    plugins:
      - id: persona
        name: '@deepseek-ai/dsh-persona'
        config:
          prefix: You are a coding agent powered by the {{model}} model.
      - id: agent-instructions
        name: '@deepseek-ai/dsh-agent-instructions'
        config:
          maxBytes: 65536
      - id: time-context
        name: '@deepseek-ai/dsh-time-context'
      - id: tool-bash
        name: '@deepseek-ai/dsh-tool-bash'
        disabled: !!js process.platform === 'win32'
      - id: tool-pwsh
        name: '@deepseek-ai/dsh-tool-pwsh'
        disabled: !!js process.platform !== 'win32'
      - id: tool-fs
        name: '@deepseek-ai/dsh-tool-fs'
      - id: tool-fs-search
        name: '@deepseek-ai/dsh-tool-fs-search'
        config:
          sampleOverCapGlobResults: false
      - id: tool-jobs
        name: '@deepseek-ai/dsh-tool-jobs'
      - id: tool-schedule
        name: '@deepseek-ai/dsh-tool-schedule'
      - id: skill-filesystem
        name: '@deepseek-ai/dsh-skill-filesystem'
      - id: tool-skill
        name: '@deepseek-ai/dsh-tool-skill'
      - id: command-goal
        name: '@deepseek-ai/dsh-command-goal'
      - id: tool-goal
        name: '@deepseek-ai/dsh-tool-goal'
      - id: planning
        name: cordis:group
        group: true
        isolate:
          planMode: true
        config:
          - id: plan-mode
            name: '@deepseek-ai/dsh-plan-mode'
            config:
              section: >
                You are in plan mode. Stay in plan mode until the user approves
                your plan through exit_plan_mode or switches the session mode.
                Imperative language to implement changes means plan the
                implementation, not execute it. A user's conversational
                agreement — including an answer confirming something you asked —
                approves nothing and does not end plan mode; fold the confirmed
                decision into the plan and submit it through exit_plan_mode.


                Explore first. Use non-mutating reads, searches, static
                analysis, and checks to ground the plan in the actual
                repository. Do not edit or write files, change configuration,
                run formatters or code generation that rewrites tracked files,
                commit, or otherwise carry out the plan. Prefer existing
                functions and patterns over new machinery.


                The tool catalog stays the same across modes for request-cache
                stability. These plan-mode rules override any later tool
                description or guidance that suggests using mutation tools;
                those tools remain listed to keep the tool catalog unchanged. Do
                not use todo_write to track this planning phase: it tracks
                implementation after an approved plan, while the plan itself
                belongs in exit_plan_mode.


                Resolve discoverable facts by inspection. Use ask_user_question
                only for user-owned choices or material ambiguity that
                inspection cannot answer. Do not ask the user where code lives
                or how current behavior works when you can find out.


                Make the plan decision-complete: state the goal and success
                criteria; group implementation changes by subsystem; identify
                public API, schema, and data-flow changes; cover edge cases,
                failure modes, tests, acceptance criteria, and explicit
                assumptions. Keep it concise enough to review but detailed
                enough that another engineer can implement it without making
                design decisions.


                When ready, call exit_plan_mode with the complete plan markdown,
                starting with a # title. Make exit_plan_mode the only and final
                tool call in that assistant response: it presents the plan for
                approval, and implementation begins only in a later step after
                approval. Do not paste the final plan as a plain reply or ask
                "should I proceed?" through prose or ask_user_question. If
                review rejects it, incorporate the feedback and present again.
                If the review channel is unavailable or aborted, stay in plan
                mode and ask the user to switch modes manually; do not proceed
                with implementation.
      - id: compaction
        name: cordis:group
        group: true
        isolate:
          compaction: true
          toolResultPruner: true
        config:
          - id: compaction-basic
            name: '@deepseek-ai/dsh-compaction-basic'
          - id: command-compact
            name: '@deepseek-ai/dsh-command-compact'
          - id: tool-result-pruner
            name: '@deepseek-ai/dsh-compaction-tool-result-pruner'
            config:
              thresholdChars: 8192
              headChars: 4096
              tailChars: 1024
      - id: delegation
        name: cordis:group
        group: true
        isolate:
          workflowEngine: true
        config:
          - id: tool-subagent-control
            name: '@deepseek-ai/dsh-tool-subagent-control'
          - id: tool-subagent-list-agents
            name: '@deepseek-ai/dsh-tool-subagent-control/list-agents'
          - id: tool-subagent
            name: '@deepseek-ai/dsh-tool-subagent'
            config:
              provider: spawn
              toolName: subagent
              modelSelectionSettings: true
          - id: tool-subagent-fork
            name: '@deepseek-ai/dsh-tool-subagent'
            config:
              provider: fork
              toolName: subagent_fork
          - id: workflow-ptc
            name: '@deepseek-ai/dsh-workflow-ptc'
            disabled: true
            config:
              provider: spawn
          - id: tool-workflow
            name: '@deepseek-ai/dsh-tool-workflow'
            disabled: true
      - id: tool-ask-user
        name: '@deepseek-ai/dsh-tool-ask-user'
      - id: tool-todo
        name: '@deepseek-ai/dsh-tool-todo'
        config:
          allowParallelInProgress: true
      - id: tool-web
        name: '@deepseek-ai/dsh-tool-web'
        config:
          fetch: true
          searchTimeoutMs: 60000
      - id: tool-presentation
        name: '@deepseek-ai/dsh-agent-tool-presentation'
        config:
          mode: ptc
      - id: present
        name: '@deepseek-ai/dsh-tool-present'
      - id: tool-plugin-manager
        name: '@deepseek-ai/dsh-plugin-manager/tools'
        disabled: true
      - id: tool-plugin-market
        name: '@deepseek-ai/dsh-plugin-market/tools'
        disabled: true
- id: preset-minimal
  name: '@deepseek-ai/dsh-agent-preset'
  config:
    id: minimal
    order: 3
    plugins:
      - id: persona
        name: '@deepseek-ai/dsh-persona'
        config:
          prefix: You are a helpful software engineer assistant.
          complete: true
          includeRuntimeContext: false
      - id: tool-fs
        name: '@deepseek-ai/dsh-tool-fs'
        config:
          read: false
          readImage: true
          write: false
          edit: false
      - id: compaction
        name: cordis:group
        group: true
        isolate:
          compaction: true
        config:
          - id: compaction-basic
            name: '@deepseek-ai/dsh-compaction-basic'
          - id: command-compact
            name: '@deepseek-ai/dsh-command-compact'
      - id: persistent-shell
        name: cordis:group
        group: true
        isolate:
          terminals: true
        config:
          - id: pty
            name: '@deepseek-ai/dsh-terminal'
          - id: terminal-bash
            name: '@deepseek-ai/dsh-terminal-bash'
            disabled: !!js process.platform === 'win32'
            config:
              timeoutMs: 300000
          - id: persistent-bash
            name: '@deepseek-ai/dsh-tool-bash-persistent'
            disabled: !!js process.platform === 'win32'
            config:
              timeoutMs: 300000
              description: >-
                Run commands in a bash shell

                * When invoking this tool, the contents of the "command"
                parameter does NOT need to be XML-escaped.

                * Network access depends on the task environment. Prefer
                configured mirrors/proxies when they are available.

                * 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.
          - id: terminal-pwsh
            name: '@deepseek-ai/dsh-terminal-bash'
            disabled: !!js process.platform !== 'win32'
            config:
              shellDialect: pwsh
              timeoutMs: 300000
          - id: persistent-pwsh
            name: '@deepseek-ai/dsh-tool-pwsh-persistent'
            disabled: !!js process.platform !== 'win32'
            config:
              timeoutMs: 300000
              description: >-
                Run commands in a PowerShell 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.

                * State is persistent across command calls and discussions with
                the user.

                * Use native Windows paths (C:\...) and $env:NAME variables;
                this is PowerShell, not bash.

                * Please avoid commands that may produce a very large amount of
                output.

                * Please run long lived commands in the background, e.g.
                'Start-Job' or start a server with Start-Process.
- id: preset-cordis
  name: '@deepseek-ai/dsh-agent-preset'
  config:
    id: cordis
    order: 4
    plugins:
      - id: persona
        name: '@deepseek-ai/dsh-persona'
        config:
          prefix: You are a coding agent powered by the {{model}} model.
      - id: agent-instructions
        name: '@deepseek-ai/dsh-agent-instructions'
        config:
          maxBytes: 65536
      - id: time-context
        name: '@deepseek-ai/dsh-time-context'
      - id: tool-bash
        name: '@deepseek-ai/dsh-tool-bash'
        disabled: !!js process.platform === 'win32'
      - id: tool-pwsh
        name: '@deepseek-ai/dsh-tool-pwsh'
        disabled: !!js process.platform !== 'win32'
      - id: tool-fs
        name: '@deepseek-ai/dsh-tool-fs'
      - id: tool-fs-search
        name: '@deepseek-ai/dsh-tool-fs-search'
        config:
          sampleOverCapGlobResults: false
      - id: tool-jobs
        name: '@deepseek-ai/dsh-tool-jobs'
      - id: tool-schedule
        name: '@deepseek-ai/dsh-tool-schedule'
      - id: command-goal
        name: '@deepseek-ai/dsh-command-goal'
      - id: tool-goal
        name: '@deepseek-ai/dsh-tool-goal'
      - id: planning
        name: cordis:group
        group: true
        isolate:
          planMode: true
        config:
          - id: plan-mode
            name: '@deepseek-ai/dsh-plan-mode'
            config:
              section: >
                You are in plan mode. Stay in plan mode until the user approves
                your plan through exit_plan_mode or switches the session mode.
                Imperative language to implement changes means plan the
                implementation, not execute it. A user's conversational
                agreement — including an answer confirming something you asked —
                approves nothing and does not end plan mode; fold the confirmed
                decision into the plan and submit it through exit_plan_mode.


                Explore first. Use non-mutating reads, searches, static
                analysis, and checks to ground the plan in the actual
                repository. Do not edit or write files, change configuration,
                run formatters or code generation that rewrites tracked files,
                commit, or otherwise carry out the plan. Prefer existing
                functions and patterns over new machinery.


                The tool catalog stays the same across modes for request-cache
                stability. These plan-mode rules override any later tool
                description or guidance that suggests using mutation tools;
                those tools remain listed to keep the tool catalog unchanged. Do
                not use todo_write to track this planning phase: it tracks
                implementation after an approved plan, while the plan itself
                belongs in exit_plan_mode.


                Resolve discoverable facts by inspection. Use ask_user_question
                only for user-owned choices or material ambiguity that
                inspection cannot answer. Do not ask the user where code lives
                or how current behavior works when you can find out.


                Make the plan decision-complete: state the goal and success
                criteria; group implementation changes by subsystem; identify
                public API, schema, and data-flow changes; cover edge cases,
                failure modes, tests, acceptance criteria, and explicit
                assumptions. Keep it concise enough to review but detailed
                enough that another engineer can implement it without making
                design decisions.


                When ready, call exit_plan_mode with the complete plan markdown,
                starting with a # title. Make exit_plan_mode the only and final
                tool call in that assistant response: it presents the plan for
                approval, and implementation begins only in a later step after
                approval. Do not paste the final plan as a plain reply or ask
                "should I proceed?" through prose or ask_user_question. If
                review rejects it, incorporate the feedback and present again.
                If the review channel is unavailable or aborted, stay in plan
                mode and ask the user to switch modes manually; do not proceed
                with implementation.
      - id: compaction
        name: cordis:group
        group: true
        isolate:
          compaction: true
          toolResultPruner: true
        config:
          - id: compaction-basic
            name: '@deepseek-ai/dsh-compaction-basic'
          - id: command-compact
            name: '@deepseek-ai/dsh-command-compact'
          - id: tool-result-pruner
            name: '@deepseek-ai/dsh-compaction-tool-result-pruner'
            config:
              thresholdChars: 8192
              headChars: 4096
              tailChars: 1024
      - id: delegation
        name: cordis:group
        group: true
        isolate:
          workflowEngine: true
        config:
          - id: tool-subagent-control
            name: '@deepseek-ai/dsh-tool-subagent-control'
          - id: tool-subagent-list-agents
            name: '@deepseek-ai/dsh-tool-subagent-control/list-agents'
          - id: tool-subagent
            name: '@deepseek-ai/dsh-tool-subagent'
            config:
              provider: spawn
              toolName: subagent
              modelSelectionSettings: true
          - id: tool-subagent-fork
            name: '@deepseek-ai/dsh-tool-subagent'
            config:
              provider: fork
              toolName: subagent_fork
          - id: workflow-ptc
            name: '@deepseek-ai/dsh-workflow-ptc'
            config:
              provider: spawn
          - id: tool-workflow
            name: '@deepseek-ai/dsh-tool-workflow'
      - id: tool-ask-user
        name: '@deepseek-ai/dsh-tool-ask-user'
      - id: tool-todo
        name: '@deepseek-ai/dsh-tool-todo'
        config:
          allowParallelInProgress: true
      - id: tool-web
        name: '@deepseek-ai/dsh-tool-web'
        config:
          fetch: true
          searchTimeoutMs: 60000
      - id: tool-cordis
        name: '@deepseek-ai/dsh-tool-cordis'
      - id: skill-filesystem
        name: '@deepseek-ai/dsh-skill-filesystem'
        config:
          customSkillDirs:
            - !!js >-
              process.getBuiltinModule('node:path').join(process.getBuiltinModule('node:path').dirname(process.getBuiltinModule('node:module').createRequire(baseUrl).resolve('@deepseek-ai/dsh-agent-preset/package.json')),
              'skills')
      - id: tool-skill
        name: '@deepseek-ai/dsh-tool-skill'
      - id: present
        name: '@deepseek-ai/dsh-tool-present'
      - id: tool-plugin-manager
        name: '@deepseek-ai/dsh-plugin-manager/tools'
        disabled: !!js '!ctx.get(''profileContext'')'
      - id: tool-plugin-market
        name: '@deepseek-ai/dsh-plugin-market/tools'
        disabled: !!js '!ctx.get(''profileContext'')'
dsh --profile sdk-minimal --dump-config 全文
# == @deepseek-ai/dsh-sdk-minimal
- id: sdk-app-startup
  name: '@deepseek-ai/dsh-sdk-app'
  config:
    profile: sdk-minimal
- id: sdk-jsonrpc-server
  name: '@deepseek-ai/dsh-sdk-jsonrpc-server'
  inject:
    - sdkAppStartup
    - loader
  config:
    maxTokensAsSuccess: false
- id: deepseek-llm-api-extensions
  name: '@deepseek-ai/dsh-deepseek-llm-api-extensions'
- id: session-log-deepseek
  name: '@deepseek-ai/dsh-session-log-deepseek'
- id: plugin-package-inventory-deepseek
  name: '@deepseek-ai/dsh-plugin-package-inventory-deepseek'
- id: llm-deepseek
  name: '@deepseek-ai/dsh-llm-deepseek-api-key'
  config:
    apiKeyEnv: DEEPSEEK_API_KEY
    defaultContextWindow: !!js Number(process.env.DSH_CONTEXT_WINDOW ?? 1000000)
    streamIdleTimeoutMs: 172800000
- id: sandbox
  name: '@deepseek-ai/dsh-sandbox-local'
- id: filesystem
  name: '@deepseek-ai/dsh-fs-local'
- id: attachment-local
  name: '@deepseek-ai/dsh-attachment-local'
- id: tool-fs
  name: '@deepseek-ai/dsh-tool-fs'
  config:
    read: false
    readImage: true
    write: false
    edit: false
- id: working-directory
  name: '@deepseek-ai/dsh-working-directory'
- id: tool-working-directory
  name: '@deepseek-ai/dsh-tool-working-directory'
- id: session-projection
  name: '@deepseek-ai/dsh-session-projection'
- id: token-meter
  name: '@deepseek-ai/dsh-token-meter'
- id: compaction-basic
  name: '@deepseek-ai/dsh-compaction-basic'
- id: image-offload
  name: '@deepseek-ai/dsh-compaction-image-offload'
- id: sandbox-policy
  name: '@deepseek-ai/dsh-sandbox-policy'
  config:
    mode: danger-full-access
    workspaceRoot: !!js process.cwd()
- id: subprocess
  name: '@deepseek-ai/dsh-subprocess-local'
- id: pty
  name: '@deepseek-ai/dsh-terminal'
- id: terminal-bash
  name: '@deepseek-ai/dsh-terminal-bash'
  disabled: !!js process.platform === 'win32'
  config:
    timeoutMs: 300000
- id: terminal-pwsh
  name: '@deepseek-ai/dsh-terminal-bash'
  disabled: !!js process.platform !== 'win32'
  config:
    shellDialect: pwsh
    timeoutMs: 300000
- id: timer
  name: '@deepseek-ai/cordis-plugin-timer'
- id: llm
  name: '@deepseek-ai/dsh-llm'
- id: session
  name: '@deepseek-ai/dsh-session'
- id: session-title
  name: '@deepseek-ai/dsh-session-title'
  config:
    fallbackMaxWords: 5
    fallbackMaxBytes: 40
    maxTitleBytes: 80
- id: system-prompt
  name: '@deepseek-ai/dsh-system-prompt'
  config:
    includeHarnessIdentity: false
    includeRuntimeContext: false
    personaPrefix: !!js >-
      process.env.DSH_SYSTEM_PROMPT ?? 'You are a helpful software engineer
      assistant.'
- id: tools
  name: '@deepseek-ai/dsh-tools'
- id: mcp-resources
  name: '@deepseek-ai/dsh-mcp-resources'
- id: agent
  name: '@deepseek-ai/dsh-agent'
- id: llm-retry
  name: '@deepseek-ai/dsh-llm-retry'
- id: jobs
  name: '@deepseek-ai/dsh-jobs-local'
- id: agent-loop
  name: '@deepseek-ai/dsh-agent-loop'
  config:
    agents: []
- id: persistent-bash
  name: '@deepseek-ai/dsh-tool-bash-persistent'
  disabled: !!js process.platform === 'win32'
  config:
    timeoutMs: 300000
    description: >-
      Run commands in a bash shell

      * When invoking this tool, the contents of the "command" parameter does
      NOT need to be XML-escaped.

      * Network access depends on the task environment. Prefer configured
      mirrors/proxies when they are available.

      * 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.
- id: persistent-pwsh
  name: '@deepseek-ai/dsh-tool-pwsh-persistent'
  disabled: !!js process.platform !== 'win32'
  config:
    timeoutMs: 300000
    description: >-
      Run commands in a PowerShell 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.

      * State is persistent across command calls and discussions with the user.

      * Use native Windows paths (C:\...) and $env:NAME variables; this is
      PowerShell, not bash.

      * Please avoid commands that may produce a very large amount of output.

      * Please run long lived commands in the background, e.g. 'Start-Job' or
      start a server with Start-Process.
- id: sessions
  name: '@deepseek-ai/dsh-session-persistence-jsonl'
  config:
    root: !!js dshHomePath('sessions')
    compression: none

  1. pi 扩展文档与 CHANGELOG,经 Context7 读取,2026-10-20 读。 ↩

  2. dsh 的决策记录能力 seam——Service Definition / Service Provider / Consumer 角色,2026-10-20 读。 ↩

  3. dsh 的决策记录agent 即注册作用域,2026-10-20 读。 ↩

  4. dsh 的决策记录会话的 agent 由一份 preset cordis.yml 组装而成,2026-10-20 读。 ↩