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.
- package/LICENSE +21 -0
- package/README.md +95 -0
- package/README_en.md +95 -0
- package/bin/dsh-paoding.mjs +12 -0
- package/cordis.patch.yml +6 -0
- package/docs/architecture.md +292 -0
- package/docs/architecture_en.md +363 -0
- package/docs/configuration.md +551 -0
- package/docs/configuration_en.md +560 -0
- package/docs/images/1.png +0 -0
- 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/docs/installation.md +364 -0
- package/docs/installation_en.md +367 -0
- package/docs/orchestration.md +254 -0
- package/docs/orchestration_en.md +255 -0
- package/install.sh +31 -0
- package/package.json +29 -0
- package/plugins/paoding-config-ui/api-core.mjs +100 -0
- package/plugins/paoding-config-ui/index.mjs +327 -0
- package/plugins/paoding-config-ui/lib/base.css +615 -0
- package/plugins/paoding-config-ui/lib/client.js +1154 -0
- package/plugins/paoding-config-ui/package.json +23 -0
- package/plugins/paoding-config-ui/runtime-inventory.mjs +212 -0
- package/presets/orchestrator/agent.cordis.yml +323 -0
- package/presets/orchestrator/preset.yml +6 -0
- package/presets/orchestrator/restrict.mjs +191 -0
- package/tools/install.mjs +24 -0
- package/tools/lib/alloc.mjs +138 -0
- package/tools/lib/cli.mjs +522 -0
- package/tools/lib/compose.mjs +647 -0
- package/tools/lib/config.mjs +297 -0
- package/tools/lib/host.mjs +288 -0
- package/tools/lib/skills.mjs +146 -0
- package/tools/lib/spans.mjs +188 -0
- package/tools/lib/state.mjs +328 -0
- package/tools/lib/util.mjs +26 -0
- package/tools/lib/wizard.mjs +516 -0
- 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
|
+
}
|