dsh-paoding 0.2.2 → 0.3.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/README.md +27 -32
- package/README_en.md +27 -32
- package/bin/dsh-paoding.mjs +73 -11
- package/docs/architecture.md +41 -34
- package/docs/architecture_en.md +65 -42
- package/docs/configuration.md +159 -65
- package/docs/configuration_en.md +161 -66
- package/docs/images/1.png +0 -0
- package/docs/installation.md +100 -237
- package/docs/installation_en.md +112 -252
- package/docs/orchestration.md +34 -38
- package/docs/orchestration_en.md +34 -38
- package/package.json +8 -5
- package/plugins/paoding-config-ui/api-core.mjs +215 -18
- package/plugins/paoding-config-ui/index.mjs +414 -154
- package/plugins/paoding-config-ui/lib/base.css +1083 -42
- package/plugins/paoding-config-ui/lib/client.js +2266 -252
- package/plugins/paoding-config-ui/package.json +2 -2
- package/plugins/paoding-config-ui/runtime-inventory.mjs +27 -26
- package/presets/orchestrator/agent.cordis.yml +19 -9
- package/presets/orchestrator/preset.yml +3 -2
- package/presets/orchestrator/restrict.mjs +2 -1
- package/tools/install.mjs +9 -4
- package/tools/lib/alloc.mjs +60 -29
- package/tools/lib/cli.mjs +127 -278
- package/tools/lib/compose.mjs +261 -95
- package/tools/lib/config.mjs +266 -86
- package/tools/lib/host.mjs +55 -24
- package/tools/lib/skills.mjs +72 -45
- package/tools/lib/state.mjs +172 -40
- package/tools/lib/upgrade.mjs +170 -0
- package/tools/lib/util.mjs +7 -0
- package/tools/lib/version.mjs +233 -0
- package/tools/lib/workspaces.mjs +74 -0
- package/tools/lib/yaml.mjs +92 -15
- package/docs/images/2.png +0 -0
- package/docs/images/3.png +0 -0
- package/docs/images/4.png +0 -0
- package/docs/images/5.png +0 -0
- package/docs/images/6.png +0 -0
- package/install.sh +0 -31
- package/tools/lib/wizard.mjs +0 -516
package/tools/lib/wizard.mjs
DELETED
|
@@ -1,516 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* 交互式安装向导:runWizard(readline 驱动全流程;node:readline 保持原文件的
|
|
3
|
-
* 动态 import('node:readline'))与 createCustomRole(新角色工具池选择)。
|
|
4
|
-
* 依赖 util(ROLES)、config(normalizeMainAgentName / normalizeModelRef)、
|
|
5
|
-
* host(isHostDependent)与 compose(firstLine);被 cli 引用。
|
|
6
|
-
*/
|
|
7
|
-
import { ROLES } from './util.mjs'
|
|
8
|
-
import { normalizeMainAgentName, normalizeModelRef } from './config.mjs'
|
|
9
|
-
import { isHostDependent } from './host.mjs'
|
|
10
|
-
import { firstLine } from './compose.mjs'
|
|
11
|
-
|
|
12
|
-
// ── interactive wizard ──────────────────────────────────────────────────────
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* Interactive install wizard (readline, zero dependencies). Walks the user
|
|
16
|
-
* through: detection report → MCP server assignment (default roles, main
|
|
17
|
-
* agent, none, or a freshly created custom role) → custom role creation →
|
|
18
|
-
* optional tweaks of the default roles → builtin role deletion
|
|
19
|
-
* (roles_remove) → main-agent rename (main_agent_name) → builtin-role model
|
|
20
|
-
* config (roles.<toolName>.model / .provider,4d:映射到 dsh-tool-subagent 的
|
|
21
|
-
* agentOptions,回车 = 跟随主 agent 不注入) → main-agent base-tool removal →
|
|
22
|
-
* skill assignment → main-agent soft skill rows (optional hard inline subset)
|
|
23
|
-
* → confirm summary. Returns the assignments object { roles, roles_remove,
|
|
24
|
-
* main_agent_extra, main_agent_remove, main_agent_skills,
|
|
25
|
-
* main_agent_skills_inline, main_agent_persona_extra, main_agent_name,
|
|
26
|
-
* skills },roles 条目形状 { name, model, provider, persona, tools }(内置角色
|
|
27
|
-
* model/provider 取向导 4d 结果;自定义角色从 existingConfig 透传保留)。
|
|
28
|
-
* main_agent_persona_extra 无独立问题步骤:从 existingConfig 原样继承(缺省 null),
|
|
29
|
-
* null 时 compose 回落默认常量 DEFAULT_MAIN_AGENT_PERSONA_EXTRA;编辑/清空走配置 UI
|
|
30
|
-
* 或手编配置。roles_remove / main_agent_name 同样从 existingConfig 继承为初值。
|
|
31
|
-
*/
|
|
32
|
-
export async function runWizard({ inventory, mcpReports, pluginReports, skills, staticBase, suggested, existingConfig, personaBlocks, srcText, restrictBase }) {
|
|
33
|
-
const { createInterface } = await import('node:readline')
|
|
34
|
-
// Node's rl.question() hangs on the second call when stdin is not a TTY, so
|
|
35
|
-
// drive prompts from a line queue + one pending resolver instead.
|
|
36
|
-
const rl = createInterface({ input: process.stdin, output: process.stdout, terminal: false })
|
|
37
|
-
const inputQueue = []
|
|
38
|
-
let pending = null
|
|
39
|
-
rl.on('line', (line) => {
|
|
40
|
-
if (pending !== null) {
|
|
41
|
-
const resolve = pending
|
|
42
|
-
pending = null
|
|
43
|
-
resolve(line)
|
|
44
|
-
} else {
|
|
45
|
-
inputQueue.push(line)
|
|
46
|
-
}
|
|
47
|
-
})
|
|
48
|
-
rl.on('close', () => {
|
|
49
|
-
// Resolve any pending prompt with an empty line so the wizard can end
|
|
50
|
-
// cleanly when stdin is exhausted early.
|
|
51
|
-
if (pending !== null) {
|
|
52
|
-
const resolve = pending
|
|
53
|
-
pending = null
|
|
54
|
-
resolve('')
|
|
55
|
-
}
|
|
56
|
-
})
|
|
57
|
-
process.on('SIGINT', () => {
|
|
58
|
-
console.log('\naborted')
|
|
59
|
-
process.exit(130)
|
|
60
|
-
})
|
|
61
|
-
const question = (prompt) =>
|
|
62
|
-
new Promise((resolve) => {
|
|
63
|
-
if (inputQueue.length > 0) {
|
|
64
|
-
resolve(inputQueue.shift())
|
|
65
|
-
return
|
|
66
|
-
}
|
|
67
|
-
process.stdout.write(prompt)
|
|
68
|
-
pending = resolve
|
|
69
|
-
})
|
|
70
|
-
const ask = async (prompt, fallback = '') => {
|
|
71
|
-
const raw = (await question(prompt)).trim()
|
|
72
|
-
return raw === '' ? fallback : raw
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
// Working state. Seeded from the existing config when present (explicit
|
|
76
|
-
// role tools/personas win), otherwise from smart defaults; custom roles are
|
|
77
|
-
// appended during the wizard.
|
|
78
|
-
const roleOrder = [...ROLES]
|
|
79
|
-
const roleTools = new Map()
|
|
80
|
-
const rolePersonas = new Map()
|
|
81
|
-
// 角色专用模型工作态(roles.<toolName>.model / .provider,向导 4d):内置角色
|
|
82
|
-
// 从 existingConfig 播种(?? null = 跟随主 agent),suggested / 静态播种的内置
|
|
83
|
-
// 角色一律 null(smartDefaults 不设模型);自定义角色在结尾 rolesOut 处从
|
|
84
|
-
// existingConfig 透传,不经这两个 Map。
|
|
85
|
-
const roleModels = new Map()
|
|
86
|
-
const roleProviders = new Map()
|
|
87
|
-
const mainAgentExtra = new Set(existingConfig?.main_agent_extra ?? suggested.main_agent_extra)
|
|
88
|
-
const mainAgentRemove = new Set(existingConfig?.main_agent_remove ?? [])
|
|
89
|
-
const mainAgentSkills = new Set(existingConfig?.main_agent_skills ?? [])
|
|
90
|
-
const mainAgentSkillsInline = new Set(existingConfig?.main_agent_skills_inline ?? [])
|
|
91
|
-
const skillsMap = new Map(Object.entries(existingConfig?.skills ?? {}))
|
|
92
|
-
for (const [toolName, role] of Object.entries(suggested.roles)) {
|
|
93
|
-
roleTools.set(toolName, new Set(role.tools))
|
|
94
|
-
rolePersonas.set(toolName, role.persona)
|
|
95
|
-
roleModels.set(toolName, role.model ?? null)
|
|
96
|
-
roleProviders.set(toolName, role.provider ?? null)
|
|
97
|
-
}
|
|
98
|
-
// Ensure every default role is present (seeded from the static base) so the
|
|
99
|
-
// summary and the tweak step always see them, even when a role received no
|
|
100
|
-
// host tools.
|
|
101
|
-
for (const role of ROLES) {
|
|
102
|
-
if (!roleTools.has(role)) {
|
|
103
|
-
roleTools.set(role, new Set(staticBase[role] ?? []))
|
|
104
|
-
rolePersonas.set(role, null)
|
|
105
|
-
roleModels.set(role, null)
|
|
106
|
-
roleProviders.set(role, null)
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
for (const [toolName, role] of Object.entries(existingConfig?.roles ?? {})) {
|
|
110
|
-
if (!roleTools.has(toolName)) roleOrder.push(toolName)
|
|
111
|
-
roleTools.set(toolName, new Set(role.tools ?? []))
|
|
112
|
-
rolePersonas.set(toolName, role.persona ?? null)
|
|
113
|
-
roleModels.set(toolName, role.model ?? null)
|
|
114
|
-
roleProviders.set(toolName, role.provider ?? null)
|
|
115
|
-
}
|
|
116
|
-
// 4b/4c 的持久状态:roles_remove / main_agent_name 从既有配置继承为初值。
|
|
117
|
-
const rolesRemove = new Set(existingConfig?.roles_remove ?? [])
|
|
118
|
-
let mainAgentName = existingConfig?.main_agent_name ?? null
|
|
119
|
-
// 既有配置里已删除的内置角色不进角色列表/摘要/后续分配(同步工作集)。
|
|
120
|
-
for (const role of [...rolesRemove]) {
|
|
121
|
-
const i = roleOrder.indexOf(role)
|
|
122
|
-
if (i !== -1) roleOrder.splice(i, 1)
|
|
123
|
-
roleTools.delete(role)
|
|
124
|
-
rolePersonas.delete(role)
|
|
125
|
-
roleModels.delete(role)
|
|
126
|
-
roleProviders.delete(role)
|
|
127
|
-
}
|
|
128
|
-
const mergeRole = (toolName, tools) => {
|
|
129
|
-
if (!roleTools.has(toolName)) {
|
|
130
|
-
roleTools.set(toolName, new Set())
|
|
131
|
-
rolePersonas.set(toolName, null)
|
|
132
|
-
roleOrder.push(toolName)
|
|
133
|
-
}
|
|
134
|
-
for (const t of tools) roleTools.get(toolName).add(t)
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
// Tool pool for custom-role selection: union of the static role allows plus
|
|
138
|
-
// every detected host tool.
|
|
139
|
-
const toolPool = [...new Set([...ROLES.flatMap((r) => staticBase[r] ?? []), ...inventory])].sort()
|
|
140
|
-
|
|
141
|
-
// 1. welcome + detection report
|
|
142
|
-
console.log('\ndsh-paoding(庖丁)交互式安装向导')
|
|
143
|
-
console.log('-------------------------')
|
|
144
|
-
console.log('检测到的工具:')
|
|
145
|
-
for (const r of mcpReports) {
|
|
146
|
-
const tools = [...inventory].filter((n) => n.startsWith(`mcp__${r.serverName}__`)).join(', ')
|
|
147
|
-
console.log(` MCP ${r.serverName} (${r.transport ?? '-'}): ${tools || '(no tools resolved)'}`)
|
|
148
|
-
}
|
|
149
|
-
for (const p of pluginReports) console.log(` 插件 ${p.label}: ${p.tools.join(', ')}`)
|
|
150
|
-
if (skills.length > 0) console.log(` 技能: ${skills.join(', ')}`)
|
|
151
|
-
console.log()
|
|
152
|
-
|
|
153
|
-
// 2. per-server assignment
|
|
154
|
-
for (const report of mcpReports) {
|
|
155
|
-
const serverTools = [...inventory].filter((n) => n.startsWith(`mcp__${report.serverName}__`))
|
|
156
|
-
if (serverTools.length === 0) continue
|
|
157
|
-
const current = (() => {
|
|
158
|
-
for (const [toolName, tools] of roleTools) {
|
|
159
|
-
if (serverTools.every((t) => tools.has(t))) return toolName
|
|
160
|
-
}
|
|
161
|
-
if (serverTools.every((t) => mainAgentExtra.has(t))) return '主 agent'
|
|
162
|
-
return '未分配'
|
|
163
|
-
})()
|
|
164
|
-
console.log(`服务器 ${report.serverName} 的工具: ${serverTools.join(', ')}`)
|
|
165
|
-
console.log(` 当前分配: ${current}`)
|
|
166
|
-
const choices = ['search_external', 'design', 'implement', '主 agent', '不分配', ...roleOrder.filter((r) => !ROLES.includes(r))]
|
|
167
|
-
const menu = choices.map((c, i) => `[${i + 1}] ${c}`).join(' ')
|
|
168
|
-
console.log(` 分配到哪里? ${menu} [0] 新建角色 [回车] 保持`)
|
|
169
|
-
const pick = (await ask(' > ')).trim()
|
|
170
|
-
if (pick === '') continue
|
|
171
|
-
if (pick === '0') {
|
|
172
|
-
const toolName = await createCustomRole({ question, ask, toolPool, roleTools, rolePersonas, roleOrder })
|
|
173
|
-
if (toolName) {
|
|
174
|
-
mergeRole(toolName, serverTools)
|
|
175
|
-
console.log(` → ${serverTools.length} 个工具已加入角色 ${toolName}`)
|
|
176
|
-
}
|
|
177
|
-
continue
|
|
178
|
-
}
|
|
179
|
-
const idx = Number(pick)
|
|
180
|
-
if (!Number.isInteger(idx) || idx < 1 || idx > choices.length) {
|
|
181
|
-
console.log(' 无效输入,保持当前分配')
|
|
182
|
-
continue
|
|
183
|
-
}
|
|
184
|
-
const target = choices[idx - 1]
|
|
185
|
-
if (target === '主 agent') {
|
|
186
|
-
for (const t of serverTools) {
|
|
187
|
-
for (const set of roleTools.values()) set.delete(t)
|
|
188
|
-
mainAgentExtra.add(t)
|
|
189
|
-
}
|
|
190
|
-
} else if (target === '不分配') {
|
|
191
|
-
for (const t of serverTools) {
|
|
192
|
-
for (const set of roleTools.values()) set.delete(t)
|
|
193
|
-
mainAgentExtra.delete(t)
|
|
194
|
-
}
|
|
195
|
-
} else {
|
|
196
|
-
mergeRole(target, serverTools)
|
|
197
|
-
for (const t of serverTools) mainAgentExtra.delete(t)
|
|
198
|
-
}
|
|
199
|
-
console.log(` → ${report.serverName} → ${target}`)
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
// 3. offer creating more custom roles
|
|
203
|
-
for (;;) {
|
|
204
|
-
const more = (await ask('创建自定义角色?[y/N] ', 'N')).toLowerCase()
|
|
205
|
-
if (more !== 'y') break
|
|
206
|
-
const toolName = await createCustomRole({ question, ask, toolPool, roleTools, rolePersonas, roleOrder })
|
|
207
|
-
if (!toolName) break
|
|
208
|
-
console.log(` 角色 ${toolName} 已创建`)
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
// 4. optional tweaks of the default roles (已删除/将删除的角色跳过)
|
|
212
|
-
const tweak = (await ask('调整默认角色的 persona 或工具?[y/N] ', 'N')).toLowerCase()
|
|
213
|
-
if (tweak === 'y') {
|
|
214
|
-
for (const role of ROLES) {
|
|
215
|
-
if (rolesRemove.has(role)) continue // roles_remove 命中的角色不再可调(整条委派行删除)
|
|
216
|
-
console.log(`\n角色 ${role} — 当前工具 (${roleTools.get(role)?.size ?? 0}): ${[...(roleTools.get(role) ?? [])].join(', ')}`)
|
|
217
|
-
if ((await ask(' 修改 persona?[y/N] ', 'N')).toLowerCase() === 'y') {
|
|
218
|
-
console.log(' 输入新 persona(单独一行 `.` 结束,空行保持默认):')
|
|
219
|
-
const lines = []
|
|
220
|
-
for (;;) {
|
|
221
|
-
const line = await question(' > ')
|
|
222
|
-
if (line.trim() === '.') break
|
|
223
|
-
lines.push(line)
|
|
224
|
-
}
|
|
225
|
-
if (lines.length > 0) rolePersonas.set(role, lines.join('\n').trim())
|
|
226
|
-
}
|
|
227
|
-
const add = (await ask(' 添加工具(工具池编号,逗号分隔,回车跳过)?', '')).trim()
|
|
228
|
-
if (add !== '') {
|
|
229
|
-
for (const n of add.split(',')) {
|
|
230
|
-
const i = Number(n.trim())
|
|
231
|
-
if (Number.isInteger(i) && i >= 1 && i <= toolPool.length) roleTools.get(role).add(toolPool[i - 1])
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
const del = (await ask(' 移除工具(当前列表编号,逗号分隔,回车跳过)?', '')).trim()
|
|
235
|
-
if (del !== '') {
|
|
236
|
-
const list = [...roleTools.get(role)]
|
|
237
|
-
for (const n of del.split(',')) {
|
|
238
|
-
const i = Number(n.trim())
|
|
239
|
-
if (Number.isInteger(i) && i >= 1 && i <= list.length) roleTools.get(role).delete(list[i - 1])
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
|
-
}
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
// 4b. 删除内置角色(roles_remove):整条委派行 + 主 persona 引用 + restrict allow
|
|
246
|
-
// 三处一并移除;非法名提示重问,回车 = 不新增删除(既有配置里已删的保持删除)。
|
|
247
|
-
if (rolesRemove.size > 0) console.log(` 已配置删除内置角色: ${[...rolesRemove].join(', ')}`)
|
|
248
|
-
for (;;) {
|
|
249
|
-
const delBuiltin = (await ask('删除内置角色?(空格分隔名字, 回车=不删):')).trim()
|
|
250
|
-
if (delBuiltin === '') break
|
|
251
|
-
const tokens = delBuiltin.split(/\s+/)
|
|
252
|
-
const unknown = tokens.filter((t) => !ROLES.includes(t))
|
|
253
|
-
if (unknown.length > 0) {
|
|
254
|
-
console.log(` '${unknown.join("', '")}' 不是内置角色(可选: ${ROLES.join(', ')}),请重输`)
|
|
255
|
-
continue
|
|
256
|
-
}
|
|
257
|
-
for (const t of tokens) {
|
|
258
|
-
if (rolesRemove.has(t)) continue
|
|
259
|
-
rolesRemove.add(t)
|
|
260
|
-
const i = roleOrder.indexOf(t)
|
|
261
|
-
if (i !== -1) roleOrder.splice(i, 1)
|
|
262
|
-
roleTools.delete(t)
|
|
263
|
-
rolePersonas.delete(t)
|
|
264
|
-
roleModels.delete(t)
|
|
265
|
-
roleProviders.delete(t)
|
|
266
|
-
console.log(` → 已标记删除内置角色: ${t}`)
|
|
267
|
-
}
|
|
268
|
-
break
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
// 4c. 主 agent 名称(main_agent_name):preset 显示名 + persona 身份行两处生效;
|
|
272
|
-
// 回车 = 维持默认(既有配置已设名则保持该名,否则默认「编排模式 (Orchestrator)」)。
|
|
273
|
-
const nameHint = existingConfig?.main_agent_name ?? '默认编排模式 (Orchestrator)'
|
|
274
|
-
for (;;) {
|
|
275
|
-
const raw = (await ask(`主 agent 名称?(显示名 + persona 身份行,回车 = ${nameHint}):`)).trim()
|
|
276
|
-
if (raw === '') break
|
|
277
|
-
try {
|
|
278
|
-
mainAgentName = normalizeMainAgentName(raw)
|
|
279
|
-
break
|
|
280
|
-
} catch (err) {
|
|
281
|
-
console.log(` ${err.message},请重输(回车 = 保持默认「${nameHint}」)`)
|
|
282
|
-
}
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
// 4d. 内置角色专用模型(roles.<toolName>.model / .provider):生成层映射到
|
|
286
|
-
// dsh-tool-subagent 的 agentOptions(provider 在前、model 在后)。回车 = 跟随
|
|
287
|
-
// 主 agent(null,不注入);provider 仅在模型非空时才问/生效。roles_remove
|
|
288
|
-
// 命中的角色跳过(委派行已整条删除)。非 TTY / 回车跳过时保持播种值不变。
|
|
289
|
-
if ((await ask('为内置角色配置专用模型?[y/N] ', 'N')).toLowerCase() === 'y') {
|
|
290
|
-
for (const role of ROLES) {
|
|
291
|
-
if (rolesRemove.has(role)) continue
|
|
292
|
-
const currentModel = roleModels.get(role) ?? null
|
|
293
|
-
const currentProvider = roleProviders.get(role) ?? null
|
|
294
|
-
console.log(` 角色 ${role} — 当前模型: ${currentModel ? `${currentProvider ? currentProvider + '/' : ''}${currentModel}` : '(跟随主 agent)'}`)
|
|
295
|
-
for (;;) {
|
|
296
|
-
const raw = (await ask(' 专用模型 id(回车=跟随主 agent):')).trim()
|
|
297
|
-
if (raw === '') {
|
|
298
|
-
roleModels.set(role, null)
|
|
299
|
-
break
|
|
300
|
-
}
|
|
301
|
-
try {
|
|
302
|
-
roleModels.set(role, normalizeModelRef(raw, `roles.${role}.model`))
|
|
303
|
-
break
|
|
304
|
-
} catch (err) {
|
|
305
|
-
console.log(` ${err.message},请重输(回车 = 跟随主 agent)`)
|
|
306
|
-
}
|
|
307
|
-
}
|
|
308
|
-
// provider-alone 不生效:模型为空时 provider 强制置 null,不再提问。
|
|
309
|
-
if ((roleModels.get(role) ?? null) !== null) {
|
|
310
|
-
for (;;) {
|
|
311
|
-
const raw = (await ask(' provider 路由(回车=跟随主 agent 的 provider,如 deepseek-official):')).trim()
|
|
312
|
-
if (raw === '') {
|
|
313
|
-
roleProviders.set(role, null)
|
|
314
|
-
break
|
|
315
|
-
}
|
|
316
|
-
try {
|
|
317
|
-
roleProviders.set(role, normalizeModelRef(raw, `roles.${role}.provider`))
|
|
318
|
-
break
|
|
319
|
-
} catch (err) {
|
|
320
|
-
console.log(` ${err.message},请重输(回车 = 跟随主 agent 的 provider)`)
|
|
321
|
-
}
|
|
322
|
-
}
|
|
323
|
-
} else {
|
|
324
|
-
roleProviders.set(role, null)
|
|
325
|
-
}
|
|
326
|
-
}
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
// 5. main-agent base-tool removal (main_agent_remove)
|
|
330
|
-
const mainBase = [...new Set([...(restrictBase ?? []), ...mainAgentExtra])]
|
|
331
|
-
.filter((n) => !isHostDependent(n) || inventory.has(n))
|
|
332
|
-
console.log(`\n主 agent 工具面 (${mainBase.length}): ${mainBase.join(', ')}`)
|
|
333
|
-
if (mainAgentRemove.size > 0) console.log(` 已配置移除: ${[...mainAgentRemove].join(', ')}`)
|
|
334
|
-
const delMain = (await ask(' 移除基础工具(编号,逗号分隔,回车跳过)?', '')).trim()
|
|
335
|
-
if (delMain !== '') {
|
|
336
|
-
const picks = delMain.split(',').map((s) => Number(s.trim())).filter((n) => Number.isInteger(n) && n >= 1 && n <= mainBase.length)
|
|
337
|
-
const removing = picks.map((n) => mainBase[n - 1])
|
|
338
|
-
const remaining = mainBase.filter((n) => !mainAgentRemove.has(n) && !removing.includes(n))
|
|
339
|
-
if (remaining.length === 0) {
|
|
340
|
-
console.log(' 拒绝:这会清空主 agent 的全部工具')
|
|
341
|
-
} else {
|
|
342
|
-
for (const n of removing) mainAgentRemove.add(n)
|
|
343
|
-
console.log(` → 已标记移除: ${removing.join(', ')}`)
|
|
344
|
-
}
|
|
345
|
-
}
|
|
346
|
-
if (mainAgentRemove.size > 0) {
|
|
347
|
-
const removedList = [...mainAgentRemove]
|
|
348
|
-
console.log(` 可恢复项 (${removedList.length}): ${removedList.map((n, i) => `[${i + 1}] ${n}`).join(' ')}`)
|
|
349
|
-
const restore = (await ask(' 恢复已移除工具(编号,逗号分隔,回车跳过)?', '')).trim()
|
|
350
|
-
if (restore !== '') {
|
|
351
|
-
for (const s of restore.split(',')) {
|
|
352
|
-
const i = Number(s.trim())
|
|
353
|
-
if (Number.isInteger(i) && i >= 1 && i <= removedList.length) mainAgentRemove.delete(removedList[i - 1])
|
|
354
|
-
}
|
|
355
|
-
}
|
|
356
|
-
}
|
|
357
|
-
|
|
358
|
-
// 6. skill assignment (only roles whose allow includes `skill` can use them)
|
|
359
|
-
const skillCapable = [...roleTools.entries()].filter(([, tools]) => tools.has('skill')).map(([toolName]) => toolName)
|
|
360
|
-
if (skills.length > 0 && skillCapable.length > 0) {
|
|
361
|
-
console.log('\n技能分配(引导写入角色 persona;skill 目录对角色全量可见,这是 DSH 的软约束):')
|
|
362
|
-
for (const skill of skills) {
|
|
363
|
-
const currentRoles = skillsMap.get(skill) ?? []
|
|
364
|
-
const menu = skillCapable.map((r, i) => `[${i + 1}] ${r}`).join(' ')
|
|
365
|
-
const pick = await ask(` ${skill} 分配给哪些角色? ${menu} [0] 不分配 [回车] 保持(${currentRoles.join(', ') || '不分配'}) `)
|
|
366
|
-
if (pick.trim() === '') continue
|
|
367
|
-
const chosen = pick.split(',').map((s) => Number(s.trim())).filter((n) => Number.isInteger(n) && n >= 1 && n <= skillCapable.length)
|
|
368
|
-
if (chosen.length > 0) skillsMap.set(skill, chosen.map((n) => skillCapable[n - 1]))
|
|
369
|
-
else skillsMap.delete(skill)
|
|
370
|
-
}
|
|
371
|
-
}
|
|
372
|
-
|
|
373
|
-
// 6b. main-agent soft skill rows (main_agent_skills): the selected skills'
|
|
374
|
-
// compact rows (name + description + SKILL.md path) are written into the
|
|
375
|
-
// main-agent persona — the main agent has no skill tool / directory
|
|
376
|
-
// injection, so it loads a skill's full rules on demand with its own read
|
|
377
|
-
// tool when a task matches (same soft-guidance semantics as roles).
|
|
378
|
-
if (skills.length > 0) {
|
|
379
|
-
console.log('\n主 agent 技能(软引导:写入 persona 技能行,主 agent 用 read 按需加载正文):')
|
|
380
|
-
console.log(` ${skills.map((s, i) => `[${i + 1}] ${s}`).join(' ')}`)
|
|
381
|
-
const current = [...mainAgentSkills].filter((s) => skills.includes(s)).join(', ') || '(无)'
|
|
382
|
-
const pick = (await ask(` 选择(编号逗号分隔,0 清空,回车保持${current}):`)).trim()
|
|
383
|
-
if (pick === '0') {
|
|
384
|
-
mainAgentSkills.clear()
|
|
385
|
-
} else if (pick !== '') {
|
|
386
|
-
const chosen = pick.split(',').map((s) => Number(s.trim())).filter((n) => Number.isInteger(n) && n >= 1 && n <= skills.length)
|
|
387
|
-
if (chosen.length > 0) {
|
|
388
|
-
mainAgentSkills.clear()
|
|
389
|
-
for (const n of chosen) mainAgentSkills.add(skills[n - 1])
|
|
390
|
-
}
|
|
391
|
-
}
|
|
392
|
-
}
|
|
393
|
-
|
|
394
|
-
// 6c. optional hard inline subset (main_agent_skills_inline): the skills
|
|
395
|
-
// picked here are inlined in full into the main-agent persona instead of a
|
|
396
|
-
// soft row (fixed per-response persona cost) — for style skills like caveman
|
|
397
|
-
// that must apply unconditionally.
|
|
398
|
-
if (skills.length > 0) {
|
|
399
|
-
console.log('\n其中哪些需全文内联硬生效(风格技能,如 caveman;编号逗号分隔,0 清空,回车保持)?')
|
|
400
|
-
console.log(` ${skills.map((s, i) => `[${i + 1}] ${s}`).join(' ')}`)
|
|
401
|
-
const current = [...mainAgentSkillsInline].filter((s) => skills.includes(s)).join(', ') || '(无)'
|
|
402
|
-
const pick = (await ask(` 选择(编号逗号分隔,0 清空,回车保持${current}):`)).trim()
|
|
403
|
-
if (pick === '0') {
|
|
404
|
-
mainAgentSkillsInline.clear()
|
|
405
|
-
} else if (pick !== '') {
|
|
406
|
-
const chosen = pick.split(',').map((s) => Number(s.trim())).filter((n) => Number.isInteger(n) && n >= 1 && n <= skills.length)
|
|
407
|
-
if (chosen.length > 0) {
|
|
408
|
-
mainAgentSkillsInline.clear()
|
|
409
|
-
for (const n of chosen) mainAgentSkillsInline.add(skills[n - 1])
|
|
410
|
-
}
|
|
411
|
-
}
|
|
412
|
-
}
|
|
413
|
-
|
|
414
|
-
// 7. confirmation summary
|
|
415
|
-
console.log('\n确认摘要:')
|
|
416
|
-
for (const toolName of roleOrder) {
|
|
417
|
-
const persona = rolePersonas.get(toolName)
|
|
418
|
-
// 模型信息:内置角色取 4d 工作态;自定义角色播种自 existingConfig(新 建 角色
|
|
419
|
-
// 无 Map 项 → ?? null = 跟随主 agent)。
|
|
420
|
-
const model = roleModels.get(toolName) ?? null
|
|
421
|
-
const provider = roleProviders.get(toolName) ?? null
|
|
422
|
-
console.log(
|
|
423
|
-
` ${toolName}: ${roleTools.get(toolName)?.size ?? 0} 工具${persona ? ` | persona: ${firstLine(persona)}` : ''}` +
|
|
424
|
-
`${model ? ` | 模型: ${provider ? provider + '/' : ''}${model}` : ''}`,
|
|
425
|
-
)
|
|
426
|
-
}
|
|
427
|
-
console.log(` 主 agent 追加: ${[...mainAgentExtra].join(', ') || '(无)'}`)
|
|
428
|
-
console.log(` 主 agent 移除: ${[...mainAgentRemove].join(', ') || '(无)'}`)
|
|
429
|
-
console.log(` 主 agent 技能: ${[...mainAgentSkills].join(', ') || '(无)'}`)
|
|
430
|
-
if (mainAgentSkillsInline.size > 0) console.log(` 主 agent 技能(内联): ${[...mainAgentSkillsInline].join(', ')}`)
|
|
431
|
-
if (rolesRemove.size > 0) console.log(` 内置角色已删除: ${[...rolesRemove].join(', ')}(委派行/主 persona 引用/restrict allow 一并移除)`)
|
|
432
|
-
if (mainAgentName !== null) console.log(` 主 agent 名称: ${mainAgentName}`)
|
|
433
|
-
if (skillsMap.size > 0) {
|
|
434
|
-
for (const [skill, roleList] of skillsMap) console.log(` 技能 ${skill} → ${roleList.join(', ')}`)
|
|
435
|
-
}
|
|
436
|
-
const confirmed = (await ask('\n确认安装并写入配置?[Y/n] ', 'Y')).toLowerCase()
|
|
437
|
-
rl.close()
|
|
438
|
-
if (confirmed === 'n') {
|
|
439
|
-
console.log('已取消,未写入任何文件')
|
|
440
|
-
return null
|
|
441
|
-
}
|
|
442
|
-
|
|
443
|
-
const roles = {}
|
|
444
|
-
for (const toolName of roleOrder) {
|
|
445
|
-
const tools = [...(roleTools.get(toolName) ?? [])]
|
|
446
|
-
if (tools.length === 0 && ROLES.includes(toolName)) continue
|
|
447
|
-
// 内置角色显示名(roles.<toolName>.name)从 existingConfig 继承:向导不提供改名步骤,
|
|
448
|
-
// 但不许在保存时静默丢键(用户手编或 UI 面板配过的 name 要在向导重跑后保留)。
|
|
449
|
-
// 专用模型同理:内置角色取 4d 工作态;自定义角色向导不提供模型步骤,model /
|
|
450
|
-
// provider 从 existingConfig 透传保留(同款保留理由)。
|
|
451
|
-
roles[toolName] = {
|
|
452
|
-
name: ROLES.includes(toolName) ? (existingConfig?.roles?.[toolName]?.name ?? null) : null,
|
|
453
|
-
model: ROLES.includes(toolName) ? (roleModels.get(toolName) ?? null) : (existingConfig?.roles?.[toolName]?.model ?? null),
|
|
454
|
-
provider: ROLES.includes(toolName) ? (roleProviders.get(toolName) ?? null) : (existingConfig?.roles?.[toolName]?.provider ?? null),
|
|
455
|
-
persona: rolePersonas.get(toolName) ?? null,
|
|
456
|
-
tools,
|
|
457
|
-
}
|
|
458
|
-
}
|
|
459
|
-
const skillsOut = {}
|
|
460
|
-
for (const [skill, roleList] of skillsMap) if (roleList.length > 0) skillsOut[skill] = roleList
|
|
461
|
-
return {
|
|
462
|
-
roles,
|
|
463
|
-
roles_remove: [...rolesRemove],
|
|
464
|
-
main_agent_extra: [...mainAgentExtra],
|
|
465
|
-
main_agent_remove: [...mainAgentRemove],
|
|
466
|
-
main_agent_skills: [...mainAgentSkills],
|
|
467
|
-
main_agent_skills_inline: [...mainAgentSkillsInline],
|
|
468
|
-
main_agent_persona_extra: existingConfig?.main_agent_persona_extra ?? null,
|
|
469
|
-
main_agent_name: mainAgentName,
|
|
470
|
-
skills: skillsOut,
|
|
471
|
-
}
|
|
472
|
-
}
|
|
473
|
-
|
|
474
|
-
/** Prompt-driven creation of one custom role; returns its toolName or null. */
|
|
475
|
-
export async function createCustomRole({ ask, toolPool, roleTools, rolePersonas, roleOrder }) {
|
|
476
|
-
const toolName = (await ask(' 新角色 toolName(小写字母/数字/下划线,done 取消): ')).trim()
|
|
477
|
-
if (toolName === '' || toolName === 'done') return null
|
|
478
|
-
if (!/^[a-z][a-z0-9_]*$/.test(toolName)) {
|
|
479
|
-
console.log(' toolName 需匹配 /^[a-z][a-z0-9_]*$/')
|
|
480
|
-
return null
|
|
481
|
-
}
|
|
482
|
-
if (roleTools.has(toolName)) {
|
|
483
|
-
console.log(` 角色 ${toolName} 已存在`)
|
|
484
|
-
return null
|
|
485
|
-
}
|
|
486
|
-
console.log(' 工具池(输入逗号分隔编号,all 全选,none 清空):')
|
|
487
|
-
// 零工具重问循环:tools 为空的自定义角色会在应用时被生成器拒绝(空
|
|
488
|
-
// toolFilter.allow → 子 agent 零工具),所以这里选不出工具就重显工具池重问,
|
|
489
|
-
// 直到至少选了一个;回车 / 'none' 视为清空重问。刻意不设跳出口——想放弃就回
|
|
490
|
-
// toolName 步骤输 done(本函数开头已处理)或直接 Ctrl-C;非 TTY 下向导根本
|
|
491
|
-
// 不会进入(cli 已挡),不存在 stdin 耗尽死循环的路径。
|
|
492
|
-
const picked = new Set()
|
|
493
|
-
while (picked.size === 0) {
|
|
494
|
-
for (let i = 0; i < toolPool.length; i++) {
|
|
495
|
-
if (i % 4 === 0) console.log(' ')
|
|
496
|
-
process.stdout.write(`${i + 1}:${toolPool[i]} `)
|
|
497
|
-
}
|
|
498
|
-
console.log()
|
|
499
|
-
const raw = (await ask(' > ')).trim()
|
|
500
|
-
if (raw === 'all') toolPool.forEach((t) => picked.add(t))
|
|
501
|
-
else if (raw !== 'none' && raw !== '') {
|
|
502
|
-
for (const n of raw.split(',')) {
|
|
503
|
-
const i = Number(n.trim())
|
|
504
|
-
if (Number.isInteger(i) && i >= 1 && i <= toolPool.length) picked.add(toolPool[i - 1])
|
|
505
|
-
}
|
|
506
|
-
}
|
|
507
|
-
if (picked.size === 0) console.log(' 至少选择一个工具(零工具自定义角色会在应用时被拒绝)')
|
|
508
|
-
}
|
|
509
|
-
const personaInput = (await ask(' persona 一句话(回车默认): ')).trim()
|
|
510
|
-
const persona = personaInput === '' ? null : `You are the ${toolName} agent. ${personaInput}`
|
|
511
|
-
roleTools.set(toolName, picked)
|
|
512
|
-
rolePersonas.set(toolName, persona)
|
|
513
|
-
roleOrder.push(toolName)
|
|
514
|
-
console.log(` ✓ 角色 ${toolName} 已创建(${picked.size} 工具)`)
|
|
515
|
-
return toolName
|
|
516
|
-
}
|