odai-dsh-agent 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +86 -0
- package/bin/odai-dsh-agent.mjs +87 -0
- package/package.json +68 -0
- package/preset/odai/agent.cordis.yml +260 -0
- package/preset/odai/preset.yml +3 -0
- package/preset/odai/runtime/governance.mjs +120 -0
- package/preset/odai/runtime/index.mjs +584 -0
- package/preset/odai/runtime/router.mjs +290 -0
- package/preset/odai/runtime/routing-config.mjs +322 -0
- package/preset/odai/skills/odai/SKILL.md +65 -0
- package/preset/odai/skills/odai/agents/openai.yaml +4 -0
- package/preset/odai/skills/odai/assets/claude-agents/agent.md +11 -0
- package/preset/odai/skills/odai/assets/codex-agents/config.toml +11 -0
- package/preset/odai/skills/odai/assets/codex-agents/role.toml +5 -0
- package/preset/odai/skills/odai/assets/copilot-agents/agent.md +13 -0
- package/preset/odai/skills/odai/assets/hooks-policy.example.json +24 -0
- package/preset/odai/skills/odai/assets/routing-roles/controller.md +15 -0
- package/preset/odai/skills/odai/assets/routing-roles/executor.md +11 -0
- package/preset/odai/skills/odai/assets/routing-roles/planner.md +29 -0
- package/preset/odai/skills/odai/assets/routing-roles/reviewer.md +11 -0
- package/preset/odai/skills/odai/assets/task-state.md +12 -0
- package/preset/odai/skills/odai/references/craft.md +53 -0
- package/preset/odai/skills/odai/references/dao.md +30 -0
- package/preset/odai/skills/odai/references/leverage.md +45 -0
- package/preset/odai/skills/odai/references/support.md +36 -0
- package/preset/odai/skills/odai/references/verification.md +23 -0
- package/preset/odai/skills/odai/scripts/build-hooks.mjs +273 -0
- package/preset/odai/skills/odai/scripts/build-routing.mjs +239 -0
- package/preset/odai/skills/odai/scripts/install-routing.mjs +375 -0
- package/preset/odai/skills/odai/scripts/odai-hook.mjs +457 -0
- package/preset/odai/skills/odai/scripts/run-role.mjs +336 -0
- package/preset/odai/skills/odai/scripts/run-routing.mjs +1154 -0
- package/preset/odai/skills/odai/scripts/verify-routing.mjs +103 -0
- package/scripts/verify-agent-load.mjs +217 -0
- package/src/installer.mjs +270 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 orziz
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
# odai dsh agent
|
|
2
|
+
|
|
3
|
+
`odai-dsh-agent` installs a selectable, session-scoped Odai Agent preset for DeepSeek Harness (`dsh`). It is independent from the profile-wide `odai-dsh-plugin`: installing this package does not edit any DSH profile or activate a global bundle.
|
|
4
|
+
|
|
5
|
+
The installed preset is self-contained:
|
|
6
|
+
|
|
7
|
+
```text
|
|
8
|
+
$DSH_HOME/.agent-presets/odai/
|
|
9
|
+
agent.cordis.yml
|
|
10
|
+
preset.yml
|
|
11
|
+
runtime/*.mjs
|
|
12
|
+
skills/odai/**
|
|
13
|
+
.odai-agent.json
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
The composition is pinned to the standard agent surface from `@deepseek-ai/dsh@0.1.0-rc.6`, then adds the scoped Odai runtime in default `auto` mode. Host-owned persistence, sandbox, approval, registries, and base controller selection remain in the selected DSH profile. Ordinary requests stay on that controller. A complete contextual high-impact decision gap upgrades the same turn only when a planner mapping exists; the controller's durable `request/header` proves the actual route. Explicit independent planning/review requests still start a verified child when their mappings exist. `execute` remains an explicit all-delegation comparison mode. `observe` changes no model and starts no child; it injects a local evidence-and-decision protocol and makes an unresolved high-impact turn read-only.
|
|
17
|
+
|
|
18
|
+
## Install
|
|
19
|
+
|
|
20
|
+
With DSH already installed, run the single Agent package command below. The package already contains the canonical skill and shared runtime; it does not require the Plugin or a separate skill installation:
|
|
21
|
+
|
|
22
|
+
```sh
|
|
23
|
+
npx odai-dsh-agent install
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
The installer checks `dsh -V` and currently requires exactly `0.1.0-rc.6`; a preset built from one developer-preview composition is not silently installed into another version.
|
|
27
|
+
|
|
28
|
+
`DSH_HOME` is honored. An explicit location can be supplied without changing the environment:
|
|
29
|
+
|
|
30
|
+
```sh
|
|
31
|
+
npx odai-dsh-agent install --dsh-home /path/to/dsh-home
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Open a new DSH session and select `Odai` from the Agent preset picker. Existing sessions retain the preset they were composed with.
|
|
35
|
+
|
|
36
|
+
In `dsh@0.1.0-rc.6`, the one-shot `--profile headless` driver creates a global agent directly and neither mounts nor accepts a session Agent preset. Automated Agent runs must create a Web session with `agentPreset: odai`; use the profile-wide Plugin for one-shot headless tasks.
|
|
37
|
+
|
|
38
|
+
The installer copies through a mode-tightened staging directory and atomically publishes the preset. Updates verify every previously managed file first, refuse to overwrite local edits, and always change the composition generation key so new sessions in a running DSH process do not reuse stale runtime code.
|
|
39
|
+
|
|
40
|
+
DSH classifies this as a `trust: user` preset. User presets have the same privileges as shell access, so install only reviewed package versions; the installer repeats this trust notice in both plain and JSON output.
|
|
41
|
+
|
|
42
|
+
## Responsibility models
|
|
43
|
+
|
|
44
|
+
The Agent ships no planner, executor, or reviewer model mapping. It stays quiet when an unconfigured responsibility is not needed. If a real task gap needs one, Odai says which responsibility is missing, confirms that no route ran, and asks for the provider, model, and optional reasoning effort in natural language. For example:
|
|
45
|
+
|
|
46
|
+
```text
|
|
47
|
+
规划用 provider-x/model-plan,推理档 high。
|
|
48
|
+
执行模型设为 provider-x/model-y。
|
|
49
|
+
验收改用 provider-z/model-review,推理档 max。
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
The controller calls `odai_routing_config` to persist that explicit choice. The user does not edit Agent files, YAML, or JSON and does not add trigger terms to later tasks. Mappings live in `$DSH_HOME/odai/routing.json`, outside the managed preset, so installer updates do not report them as drift. Changes apply from the next user turn. If reasoning effort is omitted, the target provider/model uses its own default rather than inheriting the source controller's setting. Plugin and Agent read the same store when both are deliberately present.
|
|
53
|
+
|
|
54
|
+
Planner, executor, and reviewer are independent optional responsibilities. If any needed one has no mapping, high-impact work fails closed and remains read-only; lower-impact work continues only where it does not depend on the missing independent responsibility. Odai never chooses a model on the user's behalf.
|
|
55
|
+
|
|
56
|
+
## Status and uninstall
|
|
57
|
+
|
|
58
|
+
```sh
|
|
59
|
+
npx odai-dsh-agent status
|
|
60
|
+
npx odai-dsh-agent status --json
|
|
61
|
+
npx odai-dsh-agent uninstall
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
`status` reports `absent`, `installed`, or `drifted`. Update and uninstall fail closed when managed files were changed or unmanaged files were added. Uninstall also refuses while `agent-presets.default` still names `odai`; select another default first so the next session cannot fail on a missing preset.
|
|
65
|
+
|
|
66
|
+
## Plugin versus Agent
|
|
67
|
+
|
|
68
|
+
Use only the surface that matches the desired scope:
|
|
69
|
+
|
|
70
|
+
- `odai-dsh-plugin`: profile-wide governance for every preset in that profile.
|
|
71
|
+
- `odai-dsh-agent`: selectable Odai governance for only sessions using this preset.
|
|
72
|
+
- both: supported only for a deliberate combination of profile-wide and Agent-scoped behavior; normally redundant, so it is not the default recommendation.
|
|
73
|
+
|
|
74
|
+
When both are deliberate, durable tool and route evidence is deduplicated and denials remain monotonic. Neither package installs or changes the provider-neutral `odai-cli`.
|
|
75
|
+
|
|
76
|
+
## Development
|
|
77
|
+
|
|
78
|
+
`dsh/runtime/` and `skills/odai/` remain the only editable sources. `npm pack` generates the preset's `runtime/` and `skills/` directories and removes them immediately afterward:
|
|
79
|
+
|
|
80
|
+
```sh
|
|
81
|
+
npm --prefix dsh/agent test
|
|
82
|
+
npm --prefix dsh/agent run verify:dsh
|
|
83
|
+
npm --prefix dsh/agent run pack:dry-run
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
The DSH verification uses a temporary home and one isolated Web process. It creates standard and Odai sessions, proves the canonical prompt appears only for Odai, dispatches `odai_routing_config` through the live Odai session and checks its persisted mapping, and proves the child write guard does not leak into the standard preset.
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { execFileSync } from "node:child_process";
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
inspectAgentInstallation,
|
|
7
|
+
installAgentPreset,
|
|
8
|
+
SUPPORTED_DSH_VERSION,
|
|
9
|
+
uninstallAgentPreset,
|
|
10
|
+
} from "../src/installer.mjs";
|
|
11
|
+
|
|
12
|
+
const HELP = `Usage: odai-dsh-agent <command> [options]
|
|
13
|
+
|
|
14
|
+
Commands:
|
|
15
|
+
install Install or update the managed Odai preset
|
|
16
|
+
status Inspect the managed Odai preset
|
|
17
|
+
uninstall Remove the preset when its managed files are unchanged
|
|
18
|
+
|
|
19
|
+
Options:
|
|
20
|
+
--dsh-home <path> Override DSH_HOME
|
|
21
|
+
--json Print JSON
|
|
22
|
+
-h, --help Show this help
|
|
23
|
+
`;
|
|
24
|
+
|
|
25
|
+
try {
|
|
26
|
+
const args = parseArgs(process.argv.slice(2));
|
|
27
|
+
if (args.help) {
|
|
28
|
+
process.stdout.write(HELP);
|
|
29
|
+
} else if (args.command === "install") {
|
|
30
|
+
assertDshVersion();
|
|
31
|
+
print(await installAgentPreset({ dshHome: args.dshHome }), args.json);
|
|
32
|
+
} else if (args.command === "status") {
|
|
33
|
+
const result = await inspectAgentInstallation({ dshHome: args.dshHome });
|
|
34
|
+
print(result, args.json);
|
|
35
|
+
if (result.status === "drifted") process.exitCode = 2;
|
|
36
|
+
} else if (args.command === "uninstall") {
|
|
37
|
+
print(await uninstallAgentPreset({ dshHome: args.dshHome }), args.json);
|
|
38
|
+
} else {
|
|
39
|
+
throw new Error("a command is required\n\n" + HELP);
|
|
40
|
+
}
|
|
41
|
+
} catch (error) {
|
|
42
|
+
process.stderr.write(`odai-dsh-agent: ${error instanceof Error ? error.message : String(error)}\n`);
|
|
43
|
+
process.exitCode = 1;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function assertDshVersion() {
|
|
47
|
+
const dsh = process.env.DSH_BIN ?? "dsh";
|
|
48
|
+
let actual;
|
|
49
|
+
try {
|
|
50
|
+
actual = execFileSync(dsh, ["-V"], { encoding: "utf8" }).trim();
|
|
51
|
+
} catch (error) {
|
|
52
|
+
throw new Error(`cannot run ${dsh} -V; install DSH ${SUPPORTED_DSH_VERSION} before installing the preset`);
|
|
53
|
+
}
|
|
54
|
+
if (actual !== SUPPORTED_DSH_VERSION) {
|
|
55
|
+
throw new Error(`unsupported DSH version ${actual || "<empty>"}; expected ${SUPPORTED_DSH_VERSION}`);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function parseArgs(argv) {
|
|
60
|
+
const parsed = { command: undefined, dshHome: undefined, json: false, help: false };
|
|
61
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
62
|
+
const arg = argv[index];
|
|
63
|
+
if (arg === "-h" || arg === "--help") parsed.help = true;
|
|
64
|
+
else if (arg === "--json") parsed.json = true;
|
|
65
|
+
else if (arg === "--dsh-home") parsed.dshHome = argv[++index];
|
|
66
|
+
else if (!parsed.command && ["install", "status", "uninstall"].includes(arg)) parsed.command = arg;
|
|
67
|
+
else throw new Error(`unknown argument: ${arg}`);
|
|
68
|
+
}
|
|
69
|
+
if (parsed.dshHome !== undefined && (typeof parsed.dshHome !== "string" || parsed.dshHome.trim() === "")) {
|
|
70
|
+
throw new Error("--dsh-home requires a non-empty path");
|
|
71
|
+
}
|
|
72
|
+
return parsed;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function print(result, json) {
|
|
76
|
+
if (json) {
|
|
77
|
+
process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
if (result.status) {
|
|
81
|
+
process.stdout.write(`${result.status}: ${result.target}\n`);
|
|
82
|
+
for (const issue of result.issues ?? []) process.stdout.write(`- ${issue}\n`);
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
process.stdout.write(`${result.operation}: ${result.target}\n`);
|
|
86
|
+
if (result.security) process.stdout.write(`security: ${result.security}\n`);
|
|
87
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "odai-dsh-agent",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "Self-contained Odai agent preset for DeepSeek Harness.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git+https://github.com/orziz/odai.git",
|
|
9
|
+
"directory": "dsh/agent"
|
|
10
|
+
},
|
|
11
|
+
"homepage": "https://github.com/orziz/odai#readme",
|
|
12
|
+
"bugs": {
|
|
13
|
+
"url": "https://github.com/orziz/odai/issues"
|
|
14
|
+
},
|
|
15
|
+
"type": "module",
|
|
16
|
+
"main": "./src/installer.mjs",
|
|
17
|
+
"exports": {
|
|
18
|
+
".": "./src/installer.mjs",
|
|
19
|
+
"./package.json": "./package.json"
|
|
20
|
+
},
|
|
21
|
+
"bin": {
|
|
22
|
+
"odai-dsh-agent": "./bin/odai-dsh-agent.mjs"
|
|
23
|
+
},
|
|
24
|
+
"publishConfig": {
|
|
25
|
+
"registry": "https://registry.npmjs.org/",
|
|
26
|
+
"access": "public"
|
|
27
|
+
},
|
|
28
|
+
"files": [
|
|
29
|
+
"bin",
|
|
30
|
+
"src",
|
|
31
|
+
"scripts/verify-agent-load.mjs",
|
|
32
|
+
"preset",
|
|
33
|
+
"README.md",
|
|
34
|
+
"LICENSE"
|
|
35
|
+
],
|
|
36
|
+
"scripts": {
|
|
37
|
+
"check": "find bin src scripts tests -name '*.mjs' -print0 | xargs -0 -n1 node --check",
|
|
38
|
+
"prepare:artifact": "node ../../scripts/package-odai-artifact.mjs prepare --skill-root preset/odai/skills --runtime-root preset/odai/runtime",
|
|
39
|
+
"clean:artifact": "node ../../scripts/package-odai-artifact.mjs clean --skill-root preset/odai/skills --runtime-root preset/odai/runtime",
|
|
40
|
+
"prepare": "npm run prepare:artifact",
|
|
41
|
+
"prepack": "node ../../scripts/validate-odai-skill.mjs",
|
|
42
|
+
"postpack": "npm run clean:artifact",
|
|
43
|
+
"pack:dry-run": "node ../../scripts/run-package-pack.mjs --clean preset/odai/runtime --clean preset/odai/skills -- --dry-run",
|
|
44
|
+
"test": "node --test ./tests/*.test.mjs",
|
|
45
|
+
"verify:dsh": "node ./scripts/verify-agent-load.mjs"
|
|
46
|
+
},
|
|
47
|
+
"dependencies": {
|
|
48
|
+
"yaml": "^2.8.1"
|
|
49
|
+
},
|
|
50
|
+
"peerDependencies": {
|
|
51
|
+
"@deepseek-ai/dsh": "0.1.0-rc.6"
|
|
52
|
+
},
|
|
53
|
+
"peerDependenciesMeta": {
|
|
54
|
+
"@deepseek-ai/dsh": {
|
|
55
|
+
"optional": true
|
|
56
|
+
}
|
|
57
|
+
},
|
|
58
|
+
"engines": {
|
|
59
|
+
"node": ">=20"
|
|
60
|
+
},
|
|
61
|
+
"keywords": [
|
|
62
|
+
"agent",
|
|
63
|
+
"deepseek-harness",
|
|
64
|
+
"dsh",
|
|
65
|
+
"odai",
|
|
66
|
+
"preset"
|
|
67
|
+
]
|
|
68
|
+
}
|
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
# The `standard` agent preset: the full coding agent, mounted once per process.
|
|
2
|
+
#
|
|
3
|
+
# This file is an AGENT-PLANE composition. The roster mounts it ONCE under a
|
|
4
|
+
# standing scope; every session naming it joins by scope parentage, so the
|
|
5
|
+
# tools and prompt sections registered here cover each joined agent while a
|
|
6
|
+
# session's own state stays keyed per Session/Agent inside the plugins. The
|
|
7
|
+
# host composition (`base.cordis.yml` + `web.cordis.yml`) keeps everything a
|
|
8
|
+
# preset must not own: the registries themselves, the sandbox and approval
|
|
9
|
+
# stack, persistence, and the model route.
|
|
10
|
+
#
|
|
11
|
+
# A service row here MUST sit inside a group carrying an `isolate` realm.
|
|
12
|
+
# Without one it publishes into the root realm, where it is process-global —
|
|
13
|
+
# another preset publishing the same name collides, and a host reader would
|
|
14
|
+
# resolve one preset's instance for every session; `dsh-agent-presets` rejects
|
|
15
|
+
# that at mount. `true` means an entry-local realm: this standing mount's own
|
|
16
|
+
# private instance, apart from every other preset's. (A shared label does NOT
|
|
17
|
+
# pool instances — `provide()` throws on the second registration under the
|
|
18
|
+
# same realm symbol; labels join REALMS, and are not what this file needs.)
|
|
19
|
+
|
|
20
|
+
# ── identity ────────────────────────────────────────────────────────────────
|
|
21
|
+
|
|
22
|
+
# The preset's own model-neutral persona shadows the deployment default. Auto
|
|
23
|
+
# routing can upgrade after prompt assembly; `{{cwd}}` remains workspace-local.
|
|
24
|
+
- id: persona
|
|
25
|
+
name: '@deepseek-ai/dsh-persona'
|
|
26
|
+
config:
|
|
27
|
+
text: >-
|
|
28
|
+
You are Odai, a coding agent. Your working directory is {{cwd}}.
|
|
29
|
+
|
|
30
|
+
- id: agent-instructions
|
|
31
|
+
name: '@deepseek-ai/dsh-agent-instructions'
|
|
32
|
+
config:
|
|
33
|
+
maxBytes: 65536
|
|
34
|
+
|
|
35
|
+
# ── shell ───────────────────────────────────────────────────────────────────
|
|
36
|
+
|
|
37
|
+
# `shell-env` stays in the HOST composition: `apps/cli/src/web.ts` injects it to
|
|
38
|
+
# publish `DSH_WEB_URL`/`DSH_WEB_MODE`, and a host row that injects a service is
|
|
39
|
+
# the criterion for host-plane ownership — injection resolves before any session
|
|
40
|
+
# exists, so there is no agent to key by. Behind a preset realm those variables
|
|
41
|
+
# never reached the model's shell at all. Both shell tools consume the host
|
|
42
|
+
# registry from here; their executors (`bash-sandbox`/`pwsh-sandbox`) are
|
|
43
|
+
# host-plane too.
|
|
44
|
+
- id: tool-bash
|
|
45
|
+
name: '@deepseek-ai/dsh-tool-bash'
|
|
46
|
+
disabled: !!js process.platform === 'win32'
|
|
47
|
+
|
|
48
|
+
- id: tool-pwsh
|
|
49
|
+
name: '@deepseek-ai/dsh-tool-pwsh'
|
|
50
|
+
disabled: !!js process.platform !== 'win32'
|
|
51
|
+
|
|
52
|
+
# ── filesystem ──────────────────────────────────────────────────────────────
|
|
53
|
+
|
|
54
|
+
# Both register into the host `tools` registry and provide nothing, so
|
|
55
|
+
# they need no realm. The `fs` service and its policy stay in the host.
|
|
56
|
+
- id: tool-fs
|
|
57
|
+
name: '@deepseek-ai/dsh-tool-fs'
|
|
58
|
+
|
|
59
|
+
- id: tool-fs-search
|
|
60
|
+
name: '@deepseek-ai/dsh-tool-fs-search'
|
|
61
|
+
config:
|
|
62
|
+
sampleOverCapGlobResults: false
|
|
63
|
+
|
|
64
|
+
# ── background jobs ────────────────────────────────────────────────────────
|
|
65
|
+
|
|
66
|
+
# Only the model-facing controls. The task REGISTRY stays on the host plane:
|
|
67
|
+
# its producers sit outside any realm this file could put it in — `tool-bash`
|
|
68
|
+
# above resolves it with `ctx.get`, and an entry-local realm here is invisible
|
|
69
|
+
# to every sibling row, so `run_in_background` would answer "background jobs
|
|
70
|
+
# unavailable" while these controls sat in the catalog. The registry is keyed by
|
|
71
|
+
# owning agent anyway, so one host instance serves every session. What a preset
|
|
72
|
+
# chooses is whether its agent can collect and stop background work at all.
|
|
73
|
+
- id: tool-jobs
|
|
74
|
+
name: '@deepseek-ai/dsh-tool-jobs'
|
|
75
|
+
|
|
76
|
+
# ── skills ──────────────────────────────────────────────────────────────────
|
|
77
|
+
|
|
78
|
+
# The skill REGISTRY lives in the host composition and is layered per scope:
|
|
79
|
+
# these rows register into THIS preset's layer of it, so they need no realm.
|
|
80
|
+
# `skill-filesystem` contributes local-root discovery for agents on this preset, and
|
|
81
|
+
# `tool-skill` gives them the catalog and loader; the merged catalog also
|
|
82
|
+
# carries whatever the deployment registered globally (repository plugins).
|
|
83
|
+
- id: skill-filesystem
|
|
84
|
+
name: '@deepseek-ai/dsh-skill-filesystem'
|
|
85
|
+
|
|
86
|
+
- id: tool-skill
|
|
87
|
+
name: '@deepseek-ai/dsh-tool-skill'
|
|
88
|
+
|
|
89
|
+
# ── goals ───────────────────────────────────────────────────────────────────
|
|
90
|
+
|
|
91
|
+
# Only the model-facing tool. The goal SERVICE, its session driver, and the
|
|
92
|
+
# `/goal` command stay on the host plane: the Gateway serves the goal domain as
|
|
93
|
+
# Remote endpoints whose receiver comes from a generated descriptor, so it
|
|
94
|
+
# resolves `goals` on the host and an entry-local realm here would hide it. The
|
|
95
|
+
# registry is keyed by session anyway, so one host instance serves every
|
|
96
|
+
# session. What a preset chooses is whether its agent can call the goal tool.
|
|
97
|
+
- id: tool-goal
|
|
98
|
+
name: '@deepseek-ai/dsh-tool-goal'
|
|
99
|
+
|
|
100
|
+
# ── plan mode ───────────────────────────────────────────────────────────────
|
|
101
|
+
|
|
102
|
+
# Plan state is per-agent by nature, so an entry-local realm is not a
|
|
103
|
+
# workaround here — it is the correct lifetime.
|
|
104
|
+
- id: planning
|
|
105
|
+
name: cordis:group
|
|
106
|
+
group: true
|
|
107
|
+
isolate:
|
|
108
|
+
planMode: true
|
|
109
|
+
config:
|
|
110
|
+
- id: plan-mode
|
|
111
|
+
name: '@deepseek-ai/dsh-plan-mode'
|
|
112
|
+
config:
|
|
113
|
+
section: |
|
|
114
|
+
You are in plan mode. Stay in plan mode until exit_plan_mode succeeds or the user 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.
|
|
115
|
+
|
|
116
|
+
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.
|
|
117
|
+
|
|
118
|
+
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.
|
|
119
|
+
|
|
120
|
+
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.
|
|
121
|
+
|
|
122
|
+
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.
|
|
123
|
+
|
|
124
|
+
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.
|
|
125
|
+
|
|
126
|
+
# ── compaction ──────────────────────────────────────────────────────────────
|
|
127
|
+
|
|
128
|
+
# `compaction-basic` reads `toolResultPrune` through `ctx.get`, so the pruner must
|
|
129
|
+
# share this realm rather than sit outside it.
|
|
130
|
+
#
|
|
131
|
+
# `tokenMeter` is deliberately NOT in this realm: the meter stays on the HOST
|
|
132
|
+
# plane, and the rows here resolve that one instance. It takes no configuration,
|
|
133
|
+
# keys every fold by Session, and owns the context-meter projection units the
|
|
134
|
+
# browser reads for every session — behind a realm those units would come and go
|
|
135
|
+
# with whichever presets happen to be mounted. What a preset chooses is whether
|
|
136
|
+
# its agent compacts at all, which is `compaction-basic` below.
|
|
137
|
+
- id: compaction
|
|
138
|
+
name: cordis:group
|
|
139
|
+
group: true
|
|
140
|
+
isolate:
|
|
141
|
+
compaction: true
|
|
142
|
+
toolResultPruner: true
|
|
143
|
+
config:
|
|
144
|
+
- id: compaction-basic
|
|
145
|
+
name: '@deepseek-ai/dsh-compaction-basic'
|
|
146
|
+
|
|
147
|
+
- id: command-compact
|
|
148
|
+
name: '@deepseek-ai/dsh-command-compact'
|
|
149
|
+
|
|
150
|
+
- id: tool-result-pruner
|
|
151
|
+
name: '@deepseek-ai/dsh-compaction-tool-result-pruner'
|
|
152
|
+
config:
|
|
153
|
+
thresholdChars: 8192
|
|
154
|
+
headChars: 4096
|
|
155
|
+
tailChars: 1024
|
|
156
|
+
|
|
157
|
+
# ── delegation and workflows ────────────────────────────────────────────────
|
|
158
|
+
|
|
159
|
+
# The `subagents` registry and its spawn/fork backends live in the HOST
|
|
160
|
+
# composition: the registry is a process singleton whose cross-session queries
|
|
161
|
+
# the api-proxy serves to the browser, and a provider name may only be
|
|
162
|
+
# registered once. This preset contributes the delegation TOOLS, which resolve
|
|
163
|
+
# that host registry.
|
|
164
|
+
#
|
|
165
|
+
# `workflows` is different — nothing outside an agent reads it — so every row
|
|
166
|
+
# that reaches it shares one entry-local realm here, and a consumer left
|
|
167
|
+
# outside would resolve a host registry this preset does not populate.
|
|
168
|
+
#
|
|
169
|
+
# `tool-subagent-report` is host-plane for the same reason as the registry,
|
|
170
|
+
# not because a preset may not want it: it registers a CONTINUABLE SETUP on
|
|
171
|
+
# that singleton rather than a tool this agent calls, and the setup list is
|
|
172
|
+
# not scope-aware — one copy per mounted preset means every child gets
|
|
173
|
+
# `report` registered once per live session, which throws on the second.
|
|
174
|
+
- id: delegation
|
|
175
|
+
name: cordis:group
|
|
176
|
+
group: true
|
|
177
|
+
isolate:
|
|
178
|
+
workflowEngine: true
|
|
179
|
+
config:
|
|
180
|
+
- id: tool-subagent-control
|
|
181
|
+
name: '@deepseek-ai/dsh-tool-subagent-control'
|
|
182
|
+
|
|
183
|
+
- id: tool-subagent-list-agents
|
|
184
|
+
name: '@deepseek-ai/dsh-tool-subagent-control/list-agents'
|
|
185
|
+
|
|
186
|
+
- id: tool-subagent
|
|
187
|
+
name: '@deepseek-ai/dsh-tool-subagent'
|
|
188
|
+
config:
|
|
189
|
+
provider: spawn
|
|
190
|
+
toolName: subagent
|
|
191
|
+
backgroundMode: continuable
|
|
192
|
+
|
|
193
|
+
- id: tool-subagent-fork
|
|
194
|
+
name: '@deepseek-ai/dsh-tool-subagent'
|
|
195
|
+
config:
|
|
196
|
+
provider: fork
|
|
197
|
+
toolName: subagent_fork
|
|
198
|
+
backgroundMode: continuable
|
|
199
|
+
|
|
200
|
+
# Product providers are host-plane singletons. Copy this preset, then
|
|
201
|
+
# remove `disabled` from either ordinary tool row to expose that product
|
|
202
|
+
# only to agents composed from the copy.
|
|
203
|
+
- id: tool-subagent-codex
|
|
204
|
+
name: '@deepseek-ai/dsh-tool-subagent'
|
|
205
|
+
disabled: true
|
|
206
|
+
config:
|
|
207
|
+
provider: codex
|
|
208
|
+
toolName: subagent_codex
|
|
209
|
+
enableRunInBackground: false
|
|
210
|
+
maxDepth: provider-managed
|
|
211
|
+
|
|
212
|
+
- id: tool-subagent-claude-code
|
|
213
|
+
name: '@deepseek-ai/dsh-tool-subagent'
|
|
214
|
+
disabled: true
|
|
215
|
+
config:
|
|
216
|
+
provider: claude-code
|
|
217
|
+
toolName: subagent_claude_code
|
|
218
|
+
enableRunInBackground: false
|
|
219
|
+
maxDepth: provider-managed
|
|
220
|
+
|
|
221
|
+
- id: workflow-worker-thread
|
|
222
|
+
name: '@deepseek-ai/dsh-workflow-worker-thread'
|
|
223
|
+
config:
|
|
224
|
+
provider: spawn
|
|
225
|
+
|
|
226
|
+
- id: tool-workflow
|
|
227
|
+
name: '@deepseek-ai/dsh-tool-workflow'
|
|
228
|
+
|
|
229
|
+
- id: tool-ralph
|
|
230
|
+
name: '@deepseek-ai/dsh-tool-ralph'
|
|
231
|
+
config:
|
|
232
|
+
subagentProvider: spawn
|
|
233
|
+
maxRounds: 64
|
|
234
|
+
|
|
235
|
+
# ── remaining model-facing rows ─────────────────────────────────────────────
|
|
236
|
+
|
|
237
|
+
- id: tool-ask-user
|
|
238
|
+
name: '@deepseek-ai/dsh-tool-ask-user'
|
|
239
|
+
|
|
240
|
+
- id: tool-todo
|
|
241
|
+
name: '@deepseek-ai/dsh-tool-todo'
|
|
242
|
+
config:
|
|
243
|
+
allowParallelInProgress: true
|
|
244
|
+
|
|
245
|
+
# The `web` service and its search provider stay in the host composition; only
|
|
246
|
+
# the model-facing tool is per-session.
|
|
247
|
+
- id: tool-web
|
|
248
|
+
name: '@deepseek-ai/dsh-tool-web'
|
|
249
|
+
config:
|
|
250
|
+
fetch: false
|
|
251
|
+
searchTimeoutMs: 60000
|
|
252
|
+
|
|
253
|
+
# Odai contributes scoped prompt, guard, routing, user-owned responsibility
|
|
254
|
+
# mappings, and evidence listeners. Base controller selection stays host-owned.
|
|
255
|
+
- id: odai-governance
|
|
256
|
+
name: ./runtime/index.mjs
|
|
257
|
+
config:
|
|
258
|
+
routing:
|
|
259
|
+
mode: auto
|
|
260
|
+
provider: spawn
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
export const DEFAULT_CHILD_DENIED_TOOLS = Object.freeze([
|
|
2
|
+
"write",
|
|
3
|
+
"edit",
|
|
4
|
+
"str_replace_editor",
|
|
5
|
+
"bash",
|
|
6
|
+
"pwsh",
|
|
7
|
+
"ask_user_question",
|
|
8
|
+
"subagent",
|
|
9
|
+
"subagent_fork",
|
|
10
|
+
"subagent_codex",
|
|
11
|
+
"subagent_claude_code",
|
|
12
|
+
"send_message",
|
|
13
|
+
"interrupt_agent",
|
|
14
|
+
"list_agents",
|
|
15
|
+
"workflow",
|
|
16
|
+
"ralph",
|
|
17
|
+
"job_output",
|
|
18
|
+
"job_list",
|
|
19
|
+
"job_kill",
|
|
20
|
+
]);
|
|
21
|
+
|
|
22
|
+
export const DEFAULT_PROTECTED_CONTROLLER_ALLOWED_TOOLS = Object.freeze([
|
|
23
|
+
"read",
|
|
24
|
+
"read_image",
|
|
25
|
+
"glob",
|
|
26
|
+
"grep",
|
|
27
|
+
"web_search",
|
|
28
|
+
"web_fetch",
|
|
29
|
+
"ask_user_question",
|
|
30
|
+
"get_goal",
|
|
31
|
+
"list_agents",
|
|
32
|
+
"job_output",
|
|
33
|
+
"job_list",
|
|
34
|
+
"skill",
|
|
35
|
+
]);
|
|
36
|
+
|
|
37
|
+
export function isSubagent(agent) {
|
|
38
|
+
const header = agent?.session?.header;
|
|
39
|
+
return header?.origin === "subagent"
|
|
40
|
+
|| (Number.isSafeInteger(header?.delegationDepth) && header.delegationDepth > 0);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function createChildToolGuard(options = {}) {
|
|
44
|
+
const denied = new Set([
|
|
45
|
+
...DEFAULT_CHILD_DENIED_TOOLS,
|
|
46
|
+
...(Array.isArray(options.additionalDeniedTools) ? options.additionalDeniedTools : []),
|
|
47
|
+
]);
|
|
48
|
+
const onDenied = typeof options.onDenied === "function" ? options.onDenied : () => {};
|
|
49
|
+
|
|
50
|
+
return (execution) => {
|
|
51
|
+
if (!isSubagent(execution?.agent)) return undefined;
|
|
52
|
+
if (!denied.has(execution?.name)) return undefined;
|
|
53
|
+
|
|
54
|
+
const reason = `ODAI_SUBAGENT_BOUNDARY: child agents may not execute ${execution.name}; return evidence to the controller instead.`;
|
|
55
|
+
onDenied(execution, reason);
|
|
56
|
+
return reason;
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function activeRouteProtection(agent) {
|
|
61
|
+
if (isSubagent(agent)) return undefined;
|
|
62
|
+
const events = agent?.session?.events;
|
|
63
|
+
if (!Array.isArray(events)) return undefined;
|
|
64
|
+
|
|
65
|
+
let currentTurn;
|
|
66
|
+
for (let index = events.length - 1; index >= 0; index -= 1) {
|
|
67
|
+
const event = events[index];
|
|
68
|
+
if (event?.type !== "odai/route-decided") continue;
|
|
69
|
+
if (!Number.isSafeInteger(event.data?.turn)) return undefined;
|
|
70
|
+
currentTurn = event.data.turn;
|
|
71
|
+
break;
|
|
72
|
+
}
|
|
73
|
+
if (currentTurn === undefined) return undefined;
|
|
74
|
+
|
|
75
|
+
for (let index = events.length - 1; index >= 0; index -= 1) {
|
|
76
|
+
const event = events[index];
|
|
77
|
+
if (event?.type !== "odai/route-protection") continue;
|
|
78
|
+
if (event.data?.turn === currentTurn && event.data?.mode === "read-only") return event.data;
|
|
79
|
+
}
|
|
80
|
+
return undefined;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export function createRouteProtectionGuard(options = {}) {
|
|
84
|
+
const allowed = new Set(DEFAULT_PROTECTED_CONTROLLER_ALLOWED_TOOLS);
|
|
85
|
+
for (const name of Array.isArray(options.additionalDeniedTools) ? options.additionalDeniedTools : []) {
|
|
86
|
+
allowed.delete(name);
|
|
87
|
+
}
|
|
88
|
+
const onDenied = typeof options.onDenied === "function" ? options.onDenied : () => {};
|
|
89
|
+
const protectionFor = typeof options.protectionFor === "function"
|
|
90
|
+
? options.protectionFor
|
|
91
|
+
: activeRouteProtection;
|
|
92
|
+
|
|
93
|
+
return (execution) => {
|
|
94
|
+
if (isSubagent(execution?.agent)) return undefined;
|
|
95
|
+
if (allowed.has(execution?.name)) return undefined;
|
|
96
|
+
const protection = protectionFor(execution?.agent);
|
|
97
|
+
if (!protection) return undefined;
|
|
98
|
+
|
|
99
|
+
const reasonCode = protection.reasonCode ?? "unresolved-high-impact-route";
|
|
100
|
+
const reason = `ODAI_HIGH_IMPACT_ROUTE_BLOCKED: controller may not execute ${execution.name} while ${reasonCode} remains unresolved; use read-only evidence and provide an actionable decision path.`;
|
|
101
|
+
onDenied(execution, reason);
|
|
102
|
+
return reason;
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export function summarizeToolResult(execution, result) {
|
|
107
|
+
const summary = {
|
|
108
|
+
callId: String(execution.callId),
|
|
109
|
+
rootCallId: String(execution.rootCallId),
|
|
110
|
+
tool: execution.name,
|
|
111
|
+
child: isSubagent(execution.agent),
|
|
112
|
+
isError: result.isError === true,
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
if (result.isError === true && result.error?.code) {
|
|
116
|
+
summary.errorCode = String(result.error.code);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return summary;
|
|
120
|
+
}
|