dsh-paoding 0.2.0

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.
Files changed (42) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +95 -0
  3. package/README_en.md +95 -0
  4. package/bin/dsh-paoding.mjs +12 -0
  5. package/cordis.patch.yml +6 -0
  6. package/docs/architecture.md +292 -0
  7. package/docs/architecture_en.md +363 -0
  8. package/docs/configuration.md +551 -0
  9. package/docs/configuration_en.md +560 -0
  10. package/docs/images/1.png +0 -0
  11. package/docs/images/2.png +0 -0
  12. package/docs/images/3.png +0 -0
  13. package/docs/images/4.png +0 -0
  14. package/docs/images/5.png +0 -0
  15. package/docs/images/6.png +0 -0
  16. package/docs/installation.md +364 -0
  17. package/docs/installation_en.md +367 -0
  18. package/docs/orchestration.md +254 -0
  19. package/docs/orchestration_en.md +255 -0
  20. package/install.sh +31 -0
  21. package/package.json +29 -0
  22. package/plugins/paoding-config-ui/api-core.mjs +100 -0
  23. package/plugins/paoding-config-ui/index.mjs +327 -0
  24. package/plugins/paoding-config-ui/lib/base.css +615 -0
  25. package/plugins/paoding-config-ui/lib/client.js +1154 -0
  26. package/plugins/paoding-config-ui/package.json +23 -0
  27. package/plugins/paoding-config-ui/runtime-inventory.mjs +212 -0
  28. package/presets/orchestrator/agent.cordis.yml +323 -0
  29. package/presets/orchestrator/preset.yml +6 -0
  30. package/presets/orchestrator/restrict.mjs +191 -0
  31. package/tools/install.mjs +24 -0
  32. package/tools/lib/alloc.mjs +138 -0
  33. package/tools/lib/cli.mjs +522 -0
  34. package/tools/lib/compose.mjs +647 -0
  35. package/tools/lib/config.mjs +297 -0
  36. package/tools/lib/host.mjs +288 -0
  37. package/tools/lib/skills.mjs +146 -0
  38. package/tools/lib/spans.mjs +188 -0
  39. package/tools/lib/state.mjs +328 -0
  40. package/tools/lib/util.mjs +26 -0
  41. package/tools/lib/wizard.mjs +516 -0
  42. package/tools/lib/yaml.mjs +266 -0
@@ -0,0 +1,191 @@
1
+ /**
2
+ * Orchestrator tool-surface filter (restrict.mjs)
3
+ *
4
+ * Part of the dsh-paoding 编排模式 (orchestrator) preset. Hooks the
5
+ * `system-prompt/assemble` waterfall and narrows the MAIN agent's model-facing
6
+ * tool catalog to an orchestration + internal-search surface. Delegated child
7
+ * agents are left untouched here: their tool surface is already constrained by
8
+ * the `toolFilter` each dsh-tool-subagent delegation tool declares (applied as
9
+ * a per-child `tools.restrict()` at child creation), so re-filtering them here
10
+ * would intersect with — and wrongly shrink — those role surfaces.
11
+ *
12
+ * What the main agent keeps:
13
+ * - orchestration: search_external / design / implement (role delegation),
14
+ * send_message / list_agents / interrupt_agent (child management)
15
+ * - internal search (hot path, zero delegation round-trip): glob / grep /
16
+ * read / read_image / bash
17
+ * - coordination: todo_write / ask_user_question / goal_* / job_* /
18
+ * exit_plan_mode
19
+ *
20
+ * Host 工具(mcp__*、插件工具:codegraph / memory_search / mnemon_* 等)一律
21
+ * 不硬编码进本文件——它们是"检测期事实":主 agent 需要时经 dsh-paoding 配置
22
+ * (main_agent_extra)勾选,由生成层按运行时检测把仍启用的注入下方 allow,
23
+ * 停用即自动清除。本文件只保留 dsh 核心工具。
24
+ *
25
+ * What the main agent deliberately does NOT see (all costs drop to zero for
26
+ * its requests): web_search, every mcp__tavily__*, write / edit /
27
+ * str_replace_editor, skill (+ its ~1.3k-token catalog), workflow, ralph,
28
+ * ssh_*, mcp__tablepro__*, describe_image, and the unfiltered subagent /
29
+ * subagent_fork delegations.
30
+ *
31
+ * The prompt-level filter alone cannot keep the skill catalog out: the
32
+ * dsh-tool-skill catalog injection guards on REGISTRY visibility
33
+ * (`ctx.tools.get('skill', agent)`), not on the model-facing list above, so a
34
+ * registry-level `deny: ['skill']` for the main agent is applied at
35
+ * `agent/created` (see MAIN_AGENT_REGISTRY_DENY below) — the ~1.3k-token
36
+ * catalog injection then stops as well.
37
+ *
38
+ * The filter is fail-open: any unexpected error returns the assembly
39
+ * unchanged so a filter bug can never brick every request of a session.
40
+ */
41
+
42
+ /** Cordis plugin name used by loader diagnostics. */
43
+ export const name = 'orchestrator-restrict'
44
+
45
+ /** Prompt assembly and the tool registry must exist before this filter runs. */
46
+ export const inject = ['systemPrompt', 'tools']
47
+
48
+ /**
49
+ * Main-agent allow list. Names are matched against the resolved
50
+ * `assembly.tools` array (post-registration), so entries that are not
51
+ * currently mounted are simply absent — never an error.
52
+ */
53
+ const MAIN_AGENT_ALLOW = new Set([
54
+ // ── role delegation ──────────────────────────────────────────────
55
+ 'search_external',
56
+ 'design',
57
+ 'implement',
58
+ 'search_internal_deep',
59
+ // ── child management ─────────────────────────────────────────────
60
+ 'send_message',
61
+ 'list_agents',
62
+ 'interrupt_agent',
63
+ // ── internal search (hot path) ───────────────────────────────────
64
+ 'glob',
65
+ 'grep',
66
+ 'read',
67
+ 'read_image',
68
+ 'bash',
69
+ // Host 工具(mcp__*、插件工具)不在本常量硬编码:主 agent 需要时经
70
+ // dsh-paoding 配置写入 main_agent_extra,由生成层注入下方 allow(见文件头说明)。
71
+ // ── coordination ─────────────────────────────────────────────────
72
+ 'todo_write',
73
+ 'ask_user_question',
74
+ 'get_goal',
75
+ 'create_goal',
76
+ 'update_goal',
77
+ 'exit_plan_mode',
78
+ 'job_output',
79
+ 'job_list',
80
+ 'job_kill',
81
+ ])
82
+
83
+ /**
84
+ * Registry-level supplement for the MAIN agent. The skill-catalog injection
85
+ * (dsh-tool-skill) guards on REGISTRY visibility — `ctx.tools.get('skill',
86
+ * agent)` — not on the model-facing surface filtered by MAIN_AGENT_ALLOW, so a
87
+ * prompt-only filter still lets the ~1.3k-token catalog leak into the main
88
+ * agent's session history. Denying 'skill' at the main agent's registry scope
89
+ * short-circuits that guard. Children are exempt: their surface comes from
90
+ * each delegation's toolFilter, and per-agent restrictions never cross scope
91
+ * layers (a child scope is a sibling under the shared standing mount, not a
92
+ * descendant of the main agent's scope).
93
+ */
94
+ const MAIN_AGENT_REGISTRY_DENY = ['skill']
95
+
96
+ /** Warn once so a misbehaving filter never spams the log per request. */
97
+ let warned = false
98
+
99
+ /**
100
+ * Whether `context.agent` is a delegated child rather than the session's main
101
+ * agent. Child sessions are stamped `origin: 'subagent'` with
102
+ * `delegationDepth >= 1` in their session header (see dsh-subagent's
103
+ * childSessionMeta). The origin marker is the durable, header-level signal;
104
+ * delegationDepth is kept as a belt-and-braces fallback.
105
+ */
106
+ function isDelegatedChild(agent) {
107
+ const header = agent?.session?.header
108
+ if (header === undefined) return false
109
+ return header.origin === 'subagent' || (header.delegationDepth ?? 0) > 0
110
+ }
111
+
112
+ export function apply(ctx, config = {}) {
113
+ const allow = new Set(config.allow ?? MAIN_AGENT_ALLOW)
114
+ if (allow.size === 0) {
115
+ throw new TypeError(
116
+ `${name}: configured allow list is empty — refusing to strip every tool from the main agent`,
117
+ )
118
+ }
119
+
120
+ // `prepend: true` places this filter at the outermost position of the
121
+ // waterfall, so `await next()` observes the complete downstream result
122
+ // before the allow list is applied.
123
+ ctx.on(
124
+ 'system-prompt/assemble',
125
+ async (_assembly, context, next) => {
126
+ // Downstream errors propagate untouched (the same contract as the
127
+ // liangshen tool-bootstrap): only this filter's own logic is guarded.
128
+ const assembled = await next()
129
+ try {
130
+ const agent = context?.agent
131
+ if (agent === undefined) return assembled
132
+ // Children carry their own role surface via tool-subagent's
133
+ // toolFilter; filtering them here would intersect and shrink it.
134
+ if (isDelegatedChild(agent)) return assembled
135
+
136
+ const tools = Array.isArray(assembled.tools) ? assembled.tools : []
137
+ const filtered = tools.filter((tool) => allow.has(tool?.name))
138
+ if (!warned) {
139
+ ctx.logger.info(
140
+ `${name}: main agent tool surface ${tools.length} -> ${filtered.length} tools (allow=${[...allow].sort().join(', ')})`,
141
+ )
142
+ warned = true
143
+ }
144
+ return { ...assembled, tools: filtered }
145
+ } catch (error) {
146
+ // Fail open: a filter bug must never brick every request of a session.
147
+ ctx.logger.warn(`${name}: filter failed, exposing full catalog: ${String(error)}`)
148
+ return assembled
149
+ }
150
+ },
151
+ { prepend: true },
152
+ )
153
+
154
+ // Registry-level supplement for the main agent. The skill-catalog injection
155
+ // (dsh-tool-skill) guards on REGISTRY visibility — `ctx.tools.get('skill',
156
+ // agent)` — not on the model-facing surface filtered above, so a prompt-only
157
+ // filter still lets the ~1.3k-token catalog leak into the main agent's
158
+ // session history. Denying 'skill' at the main agent's registry scope short-
159
+ // circuits that guard. Children are exempt (their surface comes from each
160
+ // delegation's toolFilter; per-agent restrictions never cross scopes), and
161
+ // whitelisting 'skill' in `allow` opts the main agent back into the catalog.
162
+ const disposers = []
163
+ ctx.on('agent/created', ({ agent }) => {
164
+ if (agent === undefined || isDelegatedChild(agent)) return
165
+ try {
166
+ const tools = agent?.ctx?.tools
167
+ if (tools === undefined || typeof tools.restrict !== 'function') return
168
+ const deny = MAIN_AGENT_REGISTRY_DENY.filter(
169
+ (name) => !allow.has(name) && tools.get(name, agent) !== undefined,
170
+ )
171
+ if (deny.length === 0) return
172
+ disposers.push(tools.restrict({ deny }))
173
+ ctx.logger.info(`${name}: main agent registry deny: ${deny.sort().join(', ')}`)
174
+ } catch (error) {
175
+ // Fail open: without the registry denial only the skill catalog leaks
176
+ // back — the prompt-level filter above still applies either way.
177
+ ctx.logger.warn(`${name}: registry deny failed, skill catalog may inject: ${String(error)}`)
178
+ }
179
+ })
180
+ if (typeof ctx.effect === 'function') {
181
+ ctx.effect(() => () => {
182
+ for (const dispose of disposers.splice(0)) {
183
+ try {
184
+ dispose()
185
+ } catch {
186
+ // best-effort lift on plugin unload
187
+ }
188
+ }
189
+ }, `${name}: main agent registry deny lift`)
190
+ }
191
+ }
@@ -0,0 +1,24 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * dsh-paoding installer — CLI 入口 + 公共 API 聚合薄层。
4
+ *
5
+ * 原单文件(2767 行)的功能实现已按模块拆分到 tools/lib/*.mjs(util / yaml /
6
+ * config / host / skills / spans / compose / alloc / wizard / state / cli 模块
7
+ * 树);本文件仅保留:
8
+ * - 公共导出面(与旧单文件完全一致,供 plugins/paoding-config-ui 消费):
9
+ * DEFAULT_MAIN_AGENT_PERSONA_EXTRA / collectState / generateAndInstall;
10
+ * - 直接运行守卫(CLI entry:import 本模块不触发安装,仅直接执行时经
11
+ * lib/cli.mjs 的 main() 运行)。
12
+ */
13
+ import { pathToFileURL } from 'node:url'
14
+ import { main } from './lib/cli.mjs'
15
+
16
+ export { DEFAULT_MAIN_AGENT_PERSONA_EXTRA } from './lib/util.mjs'
17
+ export { collectState, generateAndInstall } from './lib/state.mjs'
18
+
19
+ // CLI entry: only run main() when invoked directly (importing this module for
20
+ // the visual config UI must not trigger an install).
21
+ if (import.meta.url === pathToFileURL(process.argv[1] ?? '').href) {
22
+ const exitCode = await main()
23
+ process.exit(exitCode)
24
+ }
@@ -0,0 +1,138 @@
1
+ /**
2
+ * 分配策略:smartDefaults 按工具名关键词(keywords)给出默认角色分配、在无既有
3
+ * 配置时作 --auto 的兜底 assignments;baseAssignments 给出「基础模板」(仅 dsh
4
+ * 内置工具,fresh 非交互安装的默认起点);resolveAssignments 把既有配置 / 建议 /
5
+ * 静态基础合成最终 assignments。纯参数计算,仅依赖 util 的 ROLES 常量;
6
+ * 被 state(smartDefaults)/ cli(resolveAssignments / baseAssignments)引用。
7
+ */
8
+ import { ROLES } from './util.mjs'
9
+
10
+
11
+ // ── smart default allocation ────────────────────────────────────────────────
12
+
13
+ /**
14
+ * Default host-tool → role assignment used by --auto without a config file
15
+ * and as the wizard's suggested values. codegraph and memory_search stay on
16
+ * the main agent (matching the static preset); other MCP tools are suggested
17
+ * by tool-name keywords. 角色条目形状 { model, provider, persona, tools }:
18
+ * model / provider(角色专用模型)在此恒为 null(= 不注入 agentOptions,子 agent
19
+ * 继承主 agent 当前模型),仅配置文件 / 向导 4d 显式设置。
20
+ */
21
+ export function smartDefaults(mcpReports, inventory, staticBase) {
22
+ const roles = {}
23
+ const extra = []
24
+ const byServer = new Map()
25
+ for (const report of mcpReports) {
26
+ const server = report.serverName
27
+ const tools = [...inventory].filter((name) => name.startsWith(`mcp__${server}__`))
28
+ byServer.set(server, tools)
29
+ const assign = (role) => {
30
+ roles[role] ??= { model: null, provider: null, persona: null, tools: [...staticBase[role]] }
31
+ for (const t of tools) if (!roles[role].tools.includes(t)) roles[role].tools.push(t)
32
+ }
33
+ if (server === 'codegraph') {
34
+ extra.push(...tools)
35
+ } else if (keywords(tools, /search|research|crawl|extract|map|web/)) {
36
+ assign('search_external')
37
+ } else if (keywords(tools, /code|coding|fs|file|write|edit|bash|exec|run/)) {
38
+ assign('implement')
39
+ } else if (keywords(tools, /design|render|image|screenshot|paint/)) {
40
+ assign('design')
41
+ } else {
42
+ assign('search_external')
43
+ }
44
+ }
45
+ if (inventory.has('memory_search')) extra.push('memory_search')
46
+ // main_agent_persona_extra 不在此赋值(null 即可):compose 时 null 回落默认常量。
47
+ // roles_remove / main_agent_name 不在此赋值([] / null 即可):删除与改名是显式配置。
48
+ return {
49
+ roles,
50
+ roles_remove: [],
51
+ main_agent_extra: extra,
52
+ main_agent_remove: [],
53
+ main_agent_skills: [],
54
+ main_agent_skills_inline: [],
55
+ main_agent_persona_extra: null,
56
+ main_agent_name: null,
57
+ skills: {},
58
+ }
59
+ }
60
+
61
+ export function keywords(names, pattern) {
62
+ return names.some((name) => pattern.test(name))
63
+ }
64
+
65
+ /**
66
+ * 基础模板 assignments(fresh 非交互安装的默认起点):每个内置角色保留静态
67
+ * preset 的 dsh 基础工具,host/MCP 工具一概不纳入 —— npx 场景先装完可用基础
68
+ * 版,host 工具留给用户在 设置 → 庖丁配置(或向导)里显式开启,避免智能默认
69
+ * 把检测到的 host 工具静默塞进委派链。形状与 smartDefaults 返回值完全一致,
70
+ * 生成层 / 序列化层无需区分来源;staticBase 复制而非别名,防调用方原地改写。
71
+ */
72
+ export function baseAssignments(staticBase) {
73
+ const roles = {}
74
+ for (const role of ROLES) {
75
+ // static preset 的角色 allow 自带静态 host 工具名(如 mcp__tavily__*);模板
76
+ // 一律剔除 mcp__ 前缀名(KNOWN_HOST_PLUGINS 工具不在 staticBase 里,无需再滤),
77
+ // 保证落盘的配置文件只含 dsh 基础工具 —— host 工具全部经 UI/向导显式开启。
78
+ const baseTools = (staticBase[role] ?? []).filter((name) => !name.startsWith('mcp__'))
79
+ roles[role] = { name: null, model: null, provider: null, persona: null, tools: [...baseTools] }
80
+ }
81
+ return {
82
+ roles,
83
+ roles_remove: [],
84
+ main_agent_extra: [],
85
+ main_agent_remove: [],
86
+ main_agent_skills: [],
87
+ main_agent_skills_inline: [],
88
+ main_agent_persona_extra: null,
89
+ main_agent_name: null,
90
+ skills: {},
91
+ }
92
+ }
93
+
94
+ /**
95
+ * Resolve the final assignments from the existing config, smart defaults, or
96
+ * the interactive wizard. Returns { roles, roles_remove, main_agent_extra,
97
+ * main_agent_remove, main_agent_skills, main_agent_skills_inline,
98
+ * main_agent_persona_extra, main_agent_name, skills };roles 条目形状
99
+ * { name, model, provider, persona, tools }(model / provider = 角色专用模型,
100
+ * 缺省 null = 不注入 agentOptions)。
101
+ */
102
+ export function resolveAssignments(existing, suggested, staticBase) {
103
+ if (existing) {
104
+ const roles = {}
105
+ // roles_remove 命中的内置角色即使仍残留在 config.roles 里也跳过:不让回落逻辑
106
+ // (suggested/staticBase 默认 entry)把已删除的角色悄悄复活。
107
+ const removedBuiltins = new Set(existing.roles_remove ?? [])
108
+ for (const [toolName, role] of Object.entries(existing.roles)) {
109
+ if (removedBuiltins.has(toolName)) continue
110
+ roles[toolName] = {
111
+ name: role.name ?? null,
112
+ model: role.model ?? null,
113
+ provider: role.provider ?? null,
114
+ persona: role.persona ?? null,
115
+ tools: role.tools ?? (suggested.roles[toolName]?.tools ?? staticBase[toolName] ?? []),
116
+ }
117
+ }
118
+ return {
119
+ roles,
120
+ roles_remove: existing.roles_remove ?? [],
121
+ // main_agent_extra 回填语义:配置文件里「显式写了键」(含显式空列表,见
122
+ // normalizeConfig.has_main_agent_extra)一律尊重 —— 显式 [] = 用户明确不
123
+ // 要任何 host 工具,不得被智能默认悄悄回填;只有键完全缺失(老配置,无
124
+ // 该标记)才按旧行为用 suggested.main_agent_extra 兜底。
125
+ main_agent_extra:
126
+ existing.has_main_agent_extra || existing.main_agent_extra.length > 0
127
+ ? existing.main_agent_extra
128
+ : suggested.main_agent_extra,
129
+ main_agent_remove: existing.main_agent_remove ?? [],
130
+ main_agent_skills: existing.main_agent_skills ?? [],
131
+ main_agent_skills_inline: existing.main_agent_skills_inline ?? [],
132
+ main_agent_persona_extra: existing.main_agent_persona_extra ?? null,
133
+ main_agent_name: existing.main_agent_name ?? null,
134
+ skills: existing.skills,
135
+ }
136
+ }
137
+ return suggested
138
+ }