Sandbox mode comes from DSH_PERMISSION_MODE ∈ read-only | workspace-write | danger-full-access, read at boot | base bundle cordis.patch.yml | lib/dsh.mjs env handling |
Approval policy row id approval, field policy; base default ask (fails closed unattended) unless danger mode | base bundle patch | buildUnattendedOverlayYaml, cc profile patch |
Permission row id permission (@deepseek-ai/dsh-permission-presets): base presets are read-only+ask, workspace-write+ask, danger-full-access+never; boot throws when the composed sandbox+approval pair matches no preset and no defaultPreset is configured; the default preset's knobs are pinned into fresh sessions, overriding the env sandbox mode | base bundle patch; packages/interaction/permission-presets/src/index.ts (constructor + pinInitialPermission) | buildUnattendedOverlayYaml (defines a matching unattended preset per launch mode; applied to one-shot runs and the broker's --profile cc spawn) |
Default model row id agent-default-model with { provider, model }; DeepSeek adapter row id llm-deepseek with { thinking, reasoningEffort }. reasoningEffort schema is 'off' | 'low' | 'high' | 'max' (medium is invalid and fails boot). The plugin overlay accepts low|high|max and defaults to max; it does not expose off | base bundle patch; packages/llm/llm-deepseek schema | lib/dsh.mjs buildModelOverlayYaml, VALID_EFFORTS |
Base defaults: provider deepseek-official, model deepseek-v4-flash | base bundle patch | informational only — the plugin always overrides with its own defaults (lib/dsh.mjs DEFAULT_MODEL = deepseek-v4-pro, DEFAULT_REASONING_EFFORT = max), applied via the model overlay on one-shot runs and via dsh-broker.mjs initialize + effort overlay on broker spawns |
The DeepSeek adapter's advisory catalog defaults to deepseek-v4-flash, deepseek-v4-pro, and deepseek-v4-flash-vision-exp (inputModalities: [text, image]). Unlisted --model ids pass through as text-only routes. Selecting the vision id only changes the model string; this plugin still does not send image content. Plugin default stays deepseek-v4-pro | packages/llm/llm-deepseek/src/index.ts DEFAULT_MODELS; adapter README | DEFAULT_MODEL, --model |
| Files API image upload/reuse and request-time preprocess (resize/format) are adapter-internal. They run when a request already contains image blocks or tool-produced attachments. No cc overlay or plugin flag is required or provided | llm-deepseek README (Files API / image policy) | informational — not consumed |
tool-fs advertises read_image (durable results need ctx.attachments, which dsh-base mounts as attachment-local). That tool can attach a workspace file if the selected route is image-capable. It is not a Claude-paste path. minimal disables the tool-fs row | packages/fs/tool-fs README; base bundle row tool-fs | MINIMAL_MODE_DISABLED_ROWS |
hmr row exists in base and must be disabled for a stdio-serving profile | base bundle patch | cc profile patch block |
A patch row - id: <row> / disabled: true disables a composed row (the headless bundle disables hmr this way), and rows compose by id with the launcher --patch layer last. The minimal mode overlay relies on both, and on the exact row ids in MINIMAL_MODE_DISABLED_ROWS (persona row id system-prompt, tools kept: tool-bash, tool-str-replace-editor). On every dsh upgrade, diff that list against dsh-base's cordis.patch.yml — a renamed or added model-facing row silently widens minimal | base bundle + headless bundle cordis.patch.yml; boot dsh --profile headless --patch <overlay> -- "list your tools" | lib/dsh.mjs MINIMAL_MODE_DISABLED_ROWS, buildModeOverlayYaml |
tool-bash defaults enableRunInBackground to true, advertising a run_in_background parameter whose description names job_output/job_kill; its admission gate checks the jobs SERVICE (ctx.get('jobs')), not the tool-jobs tools. Minimal disables tool-jobs but keeps the service composed, so without the override a background call would spawn and return a job id the model can neither read nor kill — the overlay therefore pins enableRunInBackground: false (removes the schema knob and hard-rejects undeclared use) | packages/shell/tool-bash/src/index.ts (z.boolean().default(true), the ctx.get('jobs') gate) | buildModeOverlayYaml tool-bash section |
DSH_TOOLS_MODE (native|code|both; other values fail boot) is read by the headless/web bundles' tools row to flip Code Mode process-wide, and is documented upstream as a temporary hook slated for removal once per-session tool-mode selection lands. The plugin strips it from every dsh spawn env — mode ownership belongs to --mode, and Code Mode (PTC) is out of the plugin's scope | headless/web bundle cordis.patch.yml (mode: !!js process.env.DSH_TOOLS_MODE) | lib/dsh.mjs runHeadlessAgent, dsh-broker.mjs spawn env |
system-prompt config accepts persona, includeHarnessIdentity (default true — prepends You are an AI agent powered by DeepSeek Harness.), and includeRuntimeContext (default true — sandbox/approval/workspace snapshots as sourced user messages). Both minimal and anchored-standard overlays set both flags false. Remaining tool:bash guidance sections are collapsed by lib/tool-bootstrap.mjs registering ctx.systemPrompt.section({ complete: true }) (a complete: true field on a waterfall-returned section is ignored — complete is captured from the registry before the waterfall) — @deepseek-ai/dsh-persona cannot mount on headless/cc | packages/core/system-prompt README | buildModeOverlayYaml, lib/tool-bootstrap.mjs registerCompletePersona / applyCompletePersona |
Official Web Minimal uses persistent PTY bash (@deepseek-ai/dsh-tool-bash-persistent, needs an owning Agent + PTY backend) and dsh-fs-local (unsandboxed). This plugin's minimal and anchored-standard keep the sandboxed one-shot tool-bash + sandboxed str_replace_editor. That remaining schema delta is what toolSchemaHashes in a DSH_CC_SNAPSHOT_FILE capture is for. Do not copy dsh-fs-local. PTY bash is a follow-up, not a fourth mode name | webui minimal preset; packages/shell/tool-bash-persistent; packages/fs/fs-local | lib/request-snapshot.mjs, docs/testing.md items 13–17 |
@deepseek-ai/dsh-persona is scope-only: mounting it outside an agent preset collides with deployment:persona and fails loud. Headless/cc have no preset scope, so complete-persona semantics are applied in lib/tool-bootstrap.mjs by ctx.systemPrompt.section({ name: dsh-plugin-cc:persona, complete: true }) plus an outermost assemble filter — not by inserting that package | packages/preset/persona README ("No global mount") | lib/tool-bootstrap.mjs |
--patch can insert a row whose name is an absolute path to a local .mjs Cordis plugin (copied into the run's overlays/ next to the yaml). The plugin must use only Node builtins plus relative imports that are copied beside it | dsh --profile headless --patch <overlay> --dump-config; boot a trivial task | writeModeOverlay, copyBootstrapModules |
system-prompt/assemble is a waterfall whose result includes sections and tools. Filtering assembled.tools after next() changes the model-visible catalog for that request without disabling composition rows. The assemble listener must register with { prepend: true } so it is the outermost post-transform — an appended listener can otherwise re-add tools/sections after await next(). The next assemble after a durable tool/call or assistant/message on that session can return the full list. Same-step parallel tool calls keep the catalog from the assemble that produced them: the plugin freezes that phase at assemble and holds it until session step/end | packages/core/system-prompt README; session request/header | lib/tool-bootstrap.mjs |
Session request/header data is { header: EpochHeader, reason } (initial | resume | change). EpochHeader is { config: LlmCallConfig, adapterDefaults?, system?, tools? } — model / maxTokens / reasoningEffort live on header.config, not the header root. DSH_CC_SNAPSHOT_FILE reads that config on the request line. A new assemble replaces the pending snapshot bag so a later pre-step line cannot inherit the previous header's tools | packages/core/session EpochHeader / foldRequestHeader; token-meter event.data.header | lib/tool-bootstrap.mjs requestFromHeader, lib/request-snapshot.mjs |
The event is an epoch delta, not a per-request record: those three reasons are the only ones, so a step whose header is unchanged emits nothing while the latest snapshot still reconstructs that step's header. minimal holds one header for a whole run. The recorder therefore keeps the last header per session and writes the wire line at step/end — recording on the event alone left every steady-state step with no source: "request" line | run --mode minimal with DSH_CC_SNAPSHOT_FILE and two tool calls: expect one request line per step, each naming the pair | lib/tool-bootstrap.mjs headerBySession |
A Cordis context throws on any service property the accessing plugin did not inject (cannot get property "<name>" without inject), so ctx.<service>?.x is a crash, not a probe — and a top-level inject would gate the whole plugin on that service. lib/tool-bootstrap.mjs registers the complete persona inside ctx.inject(["systemPrompt"], …): the dependency scopes to that one effect, so the assemble / pre-step / pre-execute filters still attach on a composition without the registry | cordis ReflectService.handler.get; boot any --patch plugin that reads an un-injected service | lib/tool-bootstrap.mjs registerCompletePersona, inject |
Tool dispatch may still reach a registered tool that was filtered out of the assemble catalog. 0.1.1-rc.2 still persists assistant/message and the current tool/call before tools/pre-execute, so a live event-log scan at execute time would treat the bootstrap response as already promoted. The plugin therefore freezes the assemble-time phase and, on tools/pre-execute only, returns { kind: "deny", reason } for names outside the bootstrap pair. There is no tool/pre-execute event. If tools/pre-execute is missing on a given pin, the listen is best-effort (caught) and the assemble filter is the remaining control | tool-bash README (tools/pre-execute waterfall); tools.md PreToolDecision | lib/tool-bootstrap.mjs |