pi-subagents 0.50.0 → 0.52.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/CHANGELOG.md +103 -0
- package/agents/oracle.md +3 -1
- package/agents/reviewer.md +1 -0
- package/agents/scout.md +2 -2
- package/agents/worker.md +2 -1
- package/async-retention-discovery-worker.mjs +180 -0
- package/docs/agents.md +40 -2
- package/docs/configuration.md +30 -12
- package/docs/extension-api.md +42 -1
- package/docs/models.md +2 -0
- package/docs/observability.md +41 -5
- package/docs/tool-reference.md +38 -39
- package/docs/workflows.md +171 -3
- package/package.json +4 -2
- package/skills/pi-subagents/SKILL.md +6 -4
- package/skills/pi-subagents/references/constraints-and-recipes.md +12 -6
- package/skills/pi-subagents/references/execution-controls.md +29 -15
- package/skills/pi-subagents/references/management-authoring-rpc.md +3 -3
- package/skills/pi-subagents/references/prompting-and-roles.md +4 -2
- package/src/agents/agent-management.ts +124 -351
- package/src/agents/agents.ts +157 -31
- package/src/agents/skills.ts +1 -1
- package/src/api/external-job-provider.ts +185 -0
- package/src/api/preflight.ts +36 -8
- package/src/api/shared-types.ts +2 -0
- package/src/extension/config.ts +3 -3
- package/src/extension/doctor.ts +3 -6
- package/src/extension/fanout-child.ts +2 -2
- package/src/extension/index.ts +166 -88
- package/src/extension/public-execution.ts +31 -2
- package/src/extension/schemas.ts +12 -35
- package/src/extension/tool-description.ts +35 -24
- package/src/inspectors/herdr/actions.ts +2 -2
- package/src/inspectors/herdr/inspector-runner.ts +2 -1
- package/src/inspectors/herdr/project-panes.ts +2 -2
- package/src/intercom/native-supervisor-channel.ts +32 -10
- package/src/missions/lifecycle.ts +6 -1
- package/src/missions/store.ts +4 -9
- package/src/missions/workflow-state.ts +2 -2
- package/src/profiles/profiles.ts +3 -1
- package/src/runs/background/active-run-index.ts +31 -8
- package/src/runs/background/async-execution.ts +68 -40
- package/src/runs/background/async-job-tracker.ts +20 -4
- package/src/runs/background/async-resume.ts +47 -15
- package/src/runs/background/async-retention.ts +886 -0
- package/src/runs/background/async-status.ts +39 -53
- package/src/runs/background/chain-append.ts +3 -33
- package/src/runs/background/completion-dedupe.ts +5 -1
- package/src/runs/background/completion-replay.ts +22 -12
- package/src/runs/background/control-channel.ts +14 -68
- package/src/runs/background/fleet-view.ts +68 -19
- package/src/runs/background/index-segment.ts +59 -0
- package/src/runs/background/inspect-rpc.ts +443 -0
- package/src/runs/background/notify.ts +31 -5
- package/src/runs/background/result-files.ts +158 -90
- package/src/runs/background/result-watcher.ts +116 -20
- package/src/runs/background/resume-guidance.ts +8 -5
- package/src/runs/background/retained-children.ts +13 -3
- package/src/runs/background/run-id-query.ts +7 -0
- package/src/runs/background/run-id-resolver.ts +11 -9
- package/src/runs/background/run-status.ts +27 -18
- package/src/runs/background/scheduled-runs.ts +24 -4
- package/src/runs/background/stale-run-reconciler.ts +6 -3
- package/src/runs/background/steering.ts +11 -1
- package/src/runs/background/subagent-runner.ts +253 -140
- package/src/runs/background/subagent-wait.ts +8 -8
- package/src/runs/background/terminal-run-index.ts +129 -0
- package/src/runs/background/wait-completions.ts +21 -4
- package/src/runs/background/wait-subscriptions.ts +81 -2
- package/src/runs/foreground/async-steering-action.ts +21 -14
- package/src/runs/foreground/execution.ts +3 -1
- package/src/runs/foreground/subagent-executor.ts +666 -1579
- package/src/runs/foreground/workflow-detach-reconcile.ts +194 -0
- package/src/runs/foreground/workflow-foreground-steering.ts +6 -5
- package/src/runs/shared/acceptance.ts +4 -4
- package/src/runs/shared/chain-outputs.ts +1 -3
- package/src/runs/shared/external-job-bridge.ts +444 -0
- package/src/runs/shared/external-job-runner.ts +286 -0
- package/src/runs/shared/mcp-direct-tool-allowlist.ts +14 -0
- package/src/runs/shared/model-fallback.ts +98 -38
- package/src/runs/shared/orca-progress-tabs.ts +84 -22
- package/src/runs/shared/parallel-handoff.ts +46 -4
- package/src/runs/shared/parallel-utils.ts +4 -15
- package/src/runs/shared/permissions.ts +5 -1
- package/src/runs/shared/pi-args.ts +8 -1
- package/src/runs/shared/session-lease.ts +0 -6
- package/src/runs/shared/subagent-control.ts +26 -4
- package/src/runs/shared/subagent-prompt-runtime.ts +12 -6
- package/src/runs/shared/workflow-graph.ts +1 -23
- package/src/runs/shared/worktree.ts +14 -2
- package/src/shared/atomic-json.ts +22 -2
- package/src/shared/capacity-resilient-json.ts +102 -0
- package/src/shared/completion-owner.ts +14 -0
- package/src/shared/file-system-retry.ts +49 -1
- package/src/shared/fork-context.ts +42 -0
- package/src/shared/prompt-resources.ts +0 -40
- package/src/shared/settings.ts +3 -27
- package/src/shared/types.ts +71 -26
- package/src/shared/utils.ts +8 -0
- package/src/shared/watch-strategy.ts +10 -0
- package/src/slash/slash-bridge.ts +2 -1
- package/src/slash/slash-commands.ts +29 -3
- package/src/tui/fleet.ts +63 -15
- package/src/watchdog/change-signature.ts +1 -1
- package/src/watchdog/lsp-diagnostics.ts +1 -0
- package/src/workflows/chat-progress.ts +2 -2
- package/src/workflows/scripted-workflow.ts +220 -67
- package/src/runs/foreground/chain-clarify.ts +0 -1354
- package/src/runs/foreground/chain-execution.ts +0 -1581
|
@@ -4,16 +4,16 @@ import type { AgentToolResult } from "@earendil-works/pi-agent-core";
|
|
|
4
4
|
import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
5
5
|
import {
|
|
6
6
|
type AgentConfig,
|
|
7
|
+
type AgentDiscoveryDiagnostic,
|
|
7
8
|
type AgentScope,
|
|
8
9
|
type AgentSource,
|
|
9
|
-
type ChainConfig,
|
|
10
|
-
type ChainStepConfig,
|
|
11
10
|
BUILTIN_AGENT_NAMES,
|
|
12
11
|
defaultInheritProjectContext,
|
|
13
12
|
defaultInheritSkills,
|
|
14
13
|
defaultSystemPromptMode,
|
|
15
14
|
discoverAgentsAll,
|
|
16
15
|
buildRuntimeName,
|
|
16
|
+
findBlockingAgentDiagnostic,
|
|
17
17
|
frontmatterNameForConfig,
|
|
18
18
|
parsePackageName,
|
|
19
19
|
mergeBuiltinAgentOverride,
|
|
@@ -23,12 +23,11 @@ import {
|
|
|
23
23
|
} from "./agents.ts";
|
|
24
24
|
import { serializeAgent } from "./agent-serializer.ts";
|
|
25
25
|
import { mergeAgentsForScope } from "./agent-selection.ts";
|
|
26
|
-
import { serializeChain, serializeJsonChain } from "./chain-serializer.ts";
|
|
27
26
|
import { discoverAvailableSkills, resolveSkills } from "./skills.ts";
|
|
28
27
|
import {
|
|
29
28
|
buildProactiveSkillSubagentRecommendationLines,
|
|
30
29
|
} from "./proactive-skills.ts";
|
|
31
|
-
import { parseFrontmatter } from "./frontmatter.ts";
|
|
30
|
+
import { parseFrontmatter, parseFrontmatterList } from "./frontmatter.ts";
|
|
32
31
|
import { toModelInfo } from "../shared/model-info.ts";
|
|
33
32
|
import { resolveSubagentModelOverride, type ParentModel } from "../runs/shared/model-fallback.ts";
|
|
34
33
|
import { validateToolBudgetConfig } from "../runs/shared/tool-budget.ts";
|
|
@@ -45,7 +44,6 @@ type ManagementContext = Pick<ExtensionContext, "cwd" | "modelRegistry"> & { mod
|
|
|
45
44
|
interface ManagementParams {
|
|
46
45
|
action?: string;
|
|
47
46
|
agent?: string;
|
|
48
|
-
chainName?: string;
|
|
49
47
|
agentScope?: unknown;
|
|
50
48
|
config?: unknown;
|
|
51
49
|
}
|
|
@@ -76,6 +74,14 @@ function hasKey(obj: Record<string, unknown>, key: string): boolean {
|
|
|
76
74
|
return Object.prototype.hasOwnProperty.call(obj, key);
|
|
77
75
|
}
|
|
78
76
|
|
|
77
|
+
function isJsonSerializable(value: unknown): boolean {
|
|
78
|
+
if (value === null || typeof value === "string" || typeof value === "boolean") return true;
|
|
79
|
+
if (typeof value === "number") return Number.isFinite(value);
|
|
80
|
+
if (Array.isArray(value)) return value.every(isJsonSerializable);
|
|
81
|
+
if (value && typeof value === "object") return Object.values(value).every(isJsonSerializable);
|
|
82
|
+
return false;
|
|
83
|
+
}
|
|
84
|
+
|
|
79
85
|
function asDisambiguationScope(scope: unknown): ManagementScope | undefined {
|
|
80
86
|
if (scope === "user" || scope === "project") return scope;
|
|
81
87
|
return undefined;
|
|
@@ -105,14 +111,15 @@ function allAgents(d: { builtin: AgentConfig[]; package: AgentConfig[]; user: Ag
|
|
|
105
111
|
return [...d.builtin, ...d.package, ...d.user, ...d.project];
|
|
106
112
|
}
|
|
107
113
|
|
|
108
|
-
function
|
|
109
|
-
|
|
110
|
-
const items = kind === "agent" ? allAgents(d) : d.chains;
|
|
111
|
-
return [...new Set(items.map((x) => x.name))].sort((a, b) => a.localeCompare(b));
|
|
114
|
+
function availableAgentNamesFromDiscovery(d: { builtin: AgentConfig[]; package: AgentConfig[]; user: AgentConfig[]; project: AgentConfig[] }): string[] {
|
|
115
|
+
return [...new Set(allAgents(d).map((agent) => agent.name))].sort((a, b) => a.localeCompare(b));
|
|
112
116
|
}
|
|
113
117
|
|
|
114
|
-
function
|
|
115
|
-
|
|
118
|
+
function availableAgentNames(cwd: string): string[] {
|
|
119
|
+
return availableAgentNamesFromDiscovery(discoverAgentsAll(cwd));
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function findAgentsInDiscovery(name: string, d: { builtin: AgentConfig[]; package: AgentConfig[]; user: AgentConfig[]; project: AgentConfig[] }, scope: AgentScope = "both"): AgentConfig[] {
|
|
116
123
|
const raw = name.trim();
|
|
117
124
|
const sanitized = sanitizeName(raw);
|
|
118
125
|
const scoped = mergeAgentsForScope(scope, d.user, d.project, d.builtin, d.package);
|
|
@@ -125,12 +132,14 @@ function findAgents(name: string, cwd: string, scope: AgentScope = "both"): Agen
|
|
|
125
132
|
.sort((a, b) => a.source.localeCompare(b.source));
|
|
126
133
|
}
|
|
127
134
|
|
|
128
|
-
function
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
135
|
+
function findAgents(name: string, cwd: string, scope: AgentScope = "both"): AgentConfig[] {
|
|
136
|
+
return findAgentsInDiscovery(name, discoverAgentsAll(cwd), scope);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function diagnosticsForScope(diagnostics: AgentDiscoveryDiagnostic[] | undefined, scope: AgentScope): AgentDiscoveryDiagnostic[] | undefined {
|
|
140
|
+
if (scope === "both") return diagnostics;
|
|
141
|
+
const excludedSource = scope === "user" ? "project" : "user";
|
|
142
|
+
return diagnostics?.filter((diagnostic) => diagnostic.source !== excludedSource);
|
|
134
143
|
}
|
|
135
144
|
|
|
136
145
|
const AGENT_SOURCE_PRECEDENCE: Record<AgentSource, number> = { builtin: 0, package: 1, user: 2, project: 3 };
|
|
@@ -156,9 +165,6 @@ function nameExistsInScope(cwd: string, scope: ManagementScope, name: string, ex
|
|
|
156
165
|
for (const a of scope === "user" ? d.user : d.project) {
|
|
157
166
|
if (a.name === name && a.filePath !== excludePath) return true;
|
|
158
167
|
}
|
|
159
|
-
for (const c of d.chains) {
|
|
160
|
-
if (c.source === scope && c.name === name && c.filePath !== excludePath) return true;
|
|
161
|
-
}
|
|
162
168
|
return false;
|
|
163
169
|
}
|
|
164
170
|
|
|
@@ -166,30 +172,6 @@ function isMutableSource(source: AgentSource): source is ManagementScope {
|
|
|
166
172
|
return source === "user" || source === "project";
|
|
167
173
|
}
|
|
168
174
|
|
|
169
|
-
function unknownChainAgents(cwd: string, steps: ChainStepConfig[]): string[] {
|
|
170
|
-
const d = discoverAgentsAll(cwd);
|
|
171
|
-
const agents = allAgents(d);
|
|
172
|
-
return [...new Set(steps.map((s) => s.agent).filter((agentName): agentName is string => typeof agentName === "string" && !resolveAgentName(agentName, agents).agent))]
|
|
173
|
-
.sort((a, b) => a.localeCompare(b));
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
function chainStepWarnings(ctx: ManagementContext, steps: ChainStepConfig[]): string[] {
|
|
177
|
-
const warnings: string[] = [];
|
|
178
|
-
const available = new Set(discoverAvailableSkills(ctx.cwd).map((s) => s.name));
|
|
179
|
-
for (let i = 0; i < steps.length; i++) {
|
|
180
|
-
const s = steps[i]!;
|
|
181
|
-
if (s.model) {
|
|
182
|
-
const found = ctx.modelRegistry.getAvailable().some((m) => `${m.provider}/${m.id}` === s.model || m.id === s.model);
|
|
183
|
-
if (!found) warnings.push(`Warning: step ${i + 1} (${s.agent}): model '${s.model}' is not in the current model registry.`);
|
|
184
|
-
}
|
|
185
|
-
if (Array.isArray(s.skills) && s.skills.length > 0) {
|
|
186
|
-
const missing = s.skills.filter((sk) => !available.has(sk));
|
|
187
|
-
if (missing.length) warnings.push(`Warning: step ${i + 1} (${s.agent}): skills not found: ${missing.join(", ")}.`);
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
return warnings;
|
|
191
|
-
}
|
|
192
|
-
|
|
193
175
|
function modelWarning(ctx: ManagementContext, model: string | undefined): string | undefined {
|
|
194
176
|
if (!model) return undefined;
|
|
195
177
|
const found = ctx.modelRegistry.getAvailable().some((m) => `${m.provider}/${m.id}` === model || m.id === model);
|
|
@@ -214,6 +196,18 @@ function skillsWarning(cwd: string, agent: Pick<AgentConfig, "skills" | "skillPa
|
|
|
214
196
|
return missing.length ? `Warning: skills not found: ${missing.join(", ")}.` : undefined;
|
|
215
197
|
}
|
|
216
198
|
|
|
199
|
+
function withDeclaredExtensionPaths(config: AgentConfig, filePath: string): AgentConfig {
|
|
200
|
+
const { frontmatter } = parseFrontmatter(fs.readFileSync(filePath, "utf-8"));
|
|
201
|
+
const { extensions: _extensions, subagentOnlyExtensions: _subagentOnlyExtensions, ...withoutResolvedExtensions } = config;
|
|
202
|
+
return {
|
|
203
|
+
...withoutResolvedExtensions,
|
|
204
|
+
...(frontmatter.extensions !== undefined ? { extensions: parseFrontmatterList(frontmatter.extensions) } : {}),
|
|
205
|
+
...(frontmatter.subagentOnlyExtensions !== undefined
|
|
206
|
+
? { subagentOnlyExtensions: parseFrontmatterList(frontmatter.subagentOnlyExtensions) }
|
|
207
|
+
: {}),
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
|
|
217
211
|
export function editableAgentConfig(agent: AgentConfig): AgentConfig {
|
|
218
212
|
const { extensions: _extensions, ...withoutExtensions } = agent;
|
|
219
213
|
const base = agent.override?.base;
|
|
@@ -238,13 +232,13 @@ export function editableAgentConfig(agent: AgentConfig): AgentConfig {
|
|
|
238
232
|
...editable
|
|
239
233
|
} = withoutExtensions;
|
|
240
234
|
if (!base) {
|
|
241
|
-
return {
|
|
235
|
+
return withDeclaredExtensionPaths({
|
|
242
236
|
...withoutExtensions,
|
|
243
237
|
...(agent.extensionsFromDefault ? {} : agent.extensions !== undefined ? { extensions: [...agent.extensions] } : {}),
|
|
244
|
-
};
|
|
238
|
+
}, agent.filePath);
|
|
245
239
|
}
|
|
246
240
|
|
|
247
|
-
return {
|
|
241
|
+
return withDeclaredExtensionPaths({
|
|
248
242
|
...editable,
|
|
249
243
|
...(base.model !== undefined ? { model: base.model } : {}),
|
|
250
244
|
...(base.fallbackModels !== undefined ? { fallbackModels: [...base.fallbackModels] } : {}),
|
|
@@ -263,7 +257,7 @@ export function editableAgentConfig(agent: AgentConfig): AgentConfig {
|
|
|
263
257
|
...(base.extensions !== undefined ? { extensions: [...base.extensions] } : {}),
|
|
264
258
|
...(base.subagentOnlyExtensions !== undefined ? { subagentOnlyExtensions: [...base.subagentOnlyExtensions] } : {}),
|
|
265
259
|
...(base.completionGuard !== undefined ? { completionGuard: base.completionGuard } : {}),
|
|
266
|
-
};
|
|
260
|
+
}, agent.filePath);
|
|
267
261
|
}
|
|
268
262
|
|
|
269
263
|
function readAgentFrontmatterFields(filePath: string): Set<string> {
|
|
@@ -329,69 +323,6 @@ export function preservedAgentFrontmatterFields(agent: AgentConfig, cfg: Record<
|
|
|
329
323
|
return fields;
|
|
330
324
|
}
|
|
331
325
|
|
|
332
|
-
function parseStepList(raw: unknown): { steps?: ChainStepConfig[]; error?: string } {
|
|
333
|
-
if (!Array.isArray(raw)) return { error: "config.steps must be an array." };
|
|
334
|
-
if (raw.length === 0) return { error: "config.steps must include at least one step." };
|
|
335
|
-
const steps: ChainStepConfig[] = [];
|
|
336
|
-
for (let i = 0; i < raw.length; i++) {
|
|
337
|
-
const item = raw[i];
|
|
338
|
-
if (!item || typeof item !== "object" || Array.isArray(item)) return { error: `config.steps[${i}] must be an object.` };
|
|
339
|
-
const s = item as Record<string, unknown>;
|
|
340
|
-
if (typeof s.agent !== "string" || !s.agent.trim()) return { error: `config.steps[${i}].agent must be a non-empty string.` };
|
|
341
|
-
const step: ChainStepConfig = { agent: s.agent.trim(), task: typeof s.task === "string" ? s.task : "" };
|
|
342
|
-
if (hasKey(s, "phase")) {
|
|
343
|
-
if (typeof s.phase === "string") step.phase = s.phase;
|
|
344
|
-
else return { error: `config.steps[${i}].phase must be a string.` };
|
|
345
|
-
}
|
|
346
|
-
if (hasKey(s, "label")) {
|
|
347
|
-
if (typeof s.label === "string") step.label = s.label;
|
|
348
|
-
else return { error: `config.steps[${i}].label must be a string.` };
|
|
349
|
-
}
|
|
350
|
-
if (hasKey(s, "as")) {
|
|
351
|
-
if (typeof s.as === "string") step.as = s.as;
|
|
352
|
-
else return { error: `config.steps[${i}].as must be a string.` };
|
|
353
|
-
}
|
|
354
|
-
if (hasKey(s, "outputSchema")) {
|
|
355
|
-
if (typeof s.outputSchema === "string") step.outputSchema = s.outputSchema;
|
|
356
|
-
else return { error: `config.steps[${i}].outputSchema must be a schema file path string for saved chains.` };
|
|
357
|
-
}
|
|
358
|
-
if (hasKey(s, "output")) {
|
|
359
|
-
if (s.output === false) step.output = false;
|
|
360
|
-
else if (typeof s.output === "string") step.output = s.output;
|
|
361
|
-
else return { error: `config.steps[${i}].output must be a string or false.` };
|
|
362
|
-
}
|
|
363
|
-
if (hasKey(s, "outputMode")) {
|
|
364
|
-
if (s.outputMode === "inline" || s.outputMode === "file-only") step.outputMode = s.outputMode;
|
|
365
|
-
else return { error: `config.steps[${i}].outputMode must be 'inline' or 'file-only'.` };
|
|
366
|
-
}
|
|
367
|
-
if (hasKey(s, "reads")) {
|
|
368
|
-
if (s.reads === false) step.reads = false;
|
|
369
|
-
else if (Array.isArray(s.reads)) step.reads = s.reads.filter((v): v is string => typeof v === "string").map((v) => v.trim()).filter(Boolean);
|
|
370
|
-
else return { error: `config.steps[${i}].reads must be an array or false.` };
|
|
371
|
-
}
|
|
372
|
-
if (hasKey(s, "model")) {
|
|
373
|
-
if (typeof s.model === "string") step.model = s.model;
|
|
374
|
-
else return { error: `config.steps[${i}].model must be a string.` };
|
|
375
|
-
}
|
|
376
|
-
if (hasKey(s, "skills")) {
|
|
377
|
-
if (s.skills === false) step.skills = false;
|
|
378
|
-
else if (Array.isArray(s.skills)) step.skills = s.skills.filter((v): v is string => typeof v === "string").map((v) => v.trim()).filter(Boolean);
|
|
379
|
-
else return { error: `config.steps[${i}].skills must be an array or false.` };
|
|
380
|
-
}
|
|
381
|
-
if (hasKey(s, "progress")) {
|
|
382
|
-
if (typeof s.progress === "boolean") step.progress = s.progress;
|
|
383
|
-
else return { error: `config.steps[${i}].progress must be a boolean.` };
|
|
384
|
-
}
|
|
385
|
-
if (hasKey(s, "toolBudget")) {
|
|
386
|
-
const validation = validateToolBudgetConfig(s.toolBudget, `config.steps[${i}].toolBudget`);
|
|
387
|
-
if (validation.error) return { error: validation.error };
|
|
388
|
-
if (s.toolBudget !== undefined) step.toolBudget = s.toolBudget as ToolBudgetConfig;
|
|
389
|
-
}
|
|
390
|
-
steps.push(step);
|
|
391
|
-
}
|
|
392
|
-
return { steps };
|
|
393
|
-
}
|
|
394
|
-
|
|
395
326
|
function parseTools(raw: string): { tools?: string[]; mcpDirectTools?: string[] } {
|
|
396
327
|
const tools: string[] = [];
|
|
397
328
|
const mcpDirectTools: string[] = [];
|
|
@@ -436,7 +367,11 @@ function applyAgentConfig(target: AgentConfig, cfg: Record<string, unknown>): st
|
|
|
436
367
|
&& Object.keys(runner).every((key) => ["type", "command", "args", "promptDelivery"].includes(key))) {
|
|
437
368
|
const runnerArgs = Array.isArray(runner.args) ? runner.args.filter((arg): arg is string => typeof arg === "string") : undefined;
|
|
438
369
|
target.runner = { type: "external-cli", command: runner.command.trim(), ...(runnerArgs?.length ? { args: runnerArgs } : {}), ...(runner.promptDelivery ? { promptDelivery: "stdin" } : {}) };
|
|
439
|
-
} else
|
|
370
|
+
} else if (runner.type === "external-job" && typeof runner.provider === "string" && runner.provider.trim() === runner.provider && runner.provider
|
|
371
|
+
&& (runner.options === undefined || (runner.options && typeof runner.options === "object" && !Array.isArray(runner.options) && isJsonSerializable(runner.options)))
|
|
372
|
+
&& Object.keys(runner).every((key) => ["type", "provider", "options"].includes(key))) {
|
|
373
|
+
target.runner = { type: "external-job", provider: runner.provider, ...(runner.options ? { options: runner.options as Record<string, unknown> } : {}) };
|
|
374
|
+
} else return "config.runner must be { type: 'pi' }, { type: 'external-cli', command: string, args?: string[], promptDelivery?: 'stdin' }, or { type: 'external-job', provider: string, options?: object }.";
|
|
440
375
|
} else return "config.runner must be an object, false, or empty string when provided.";
|
|
441
376
|
}
|
|
442
377
|
if (hasKey(cfg, "model")) {
|
|
@@ -596,7 +531,7 @@ function applyAgentConfig(target: AgentConfig, cfg: Record<string, unknown>): st
|
|
|
596
531
|
target.toolBudget = cfg.toolBudget as ToolBudgetConfig;
|
|
597
532
|
}
|
|
598
533
|
}
|
|
599
|
-
if (target.runner?.type === "external-cli") {
|
|
534
|
+
if (target.runner?.type === "external-cli" || target.runner?.type === "external-job") {
|
|
600
535
|
const unsupported = [
|
|
601
536
|
target.tools?.length || target.mcpDirectTools?.length ? "tools" : undefined,
|
|
602
537
|
target.model ? "model" : undefined,
|
|
@@ -609,55 +544,40 @@ function applyAgentConfig(target: AgentConfig, cfg: Record<string, unknown>): st
|
|
|
609
544
|
target.completionGuard !== undefined ? "completionGuard" : undefined,
|
|
610
545
|
target.toolBudget ? "toolBudget" : undefined,
|
|
611
546
|
].filter((field): field is string => Boolean(field));
|
|
612
|
-
if (unsupported.length > 0) return `config.runner type '
|
|
547
|
+
if (unsupported.length > 0) return `config.runner type '${target.runner.type}' does not support Pi-only fields: ${unsupported.join(", ")}.`;
|
|
613
548
|
}
|
|
614
549
|
return undefined;
|
|
615
550
|
}
|
|
616
551
|
|
|
617
552
|
function resolveTarget<T extends { name: string; source: AgentSource; filePath: string }>(
|
|
618
|
-
kind: "agent" | "chain",
|
|
619
553
|
name: string,
|
|
620
554
|
matches: T[],
|
|
621
555
|
cwd: string,
|
|
622
556
|
scopeHint?: string,
|
|
623
557
|
): T | AgentToolResult<Details> {
|
|
624
|
-
const distinctNames = [...new Set(matches.map((
|
|
625
|
-
if (distinctNames.length > 1) {
|
|
626
|
-
|
|
627
|
-
}
|
|
628
|
-
const mutable = matches.filter((m): m is T & { source: ManagementScope } => isMutableSource(m.source));
|
|
558
|
+
const distinctNames = [...new Set(matches.map((match) => match.name))];
|
|
559
|
+
if (distinctNames.length > 1) return result(`Ambiguous agent alias or name '${name}': ${distinctNames.sort((a, b) => a.localeCompare(b)).join(", ")}`, true);
|
|
560
|
+
const mutable = matches.filter((match): match is T & { source: ManagementScope } => isMutableSource(match.source));
|
|
629
561
|
if (mutable.length === 0) {
|
|
630
|
-
if (matches.length > 0) {
|
|
631
|
-
|
|
632
|
-
}
|
|
633
|
-
const available = availableNames(cwd, kind);
|
|
634
|
-
return result(`${kind === "agent" ? "Agent" : "Chain"} '${name}' not found. Available: ${available.join(", ") || "none"}.`, true);
|
|
562
|
+
if (matches.length > 0) return result(`Agent '${name}' is read-only and cannot be modified. Create a same-named agent in user or project scope to override it.`, true);
|
|
563
|
+
return result(`Agent '${name}' not found. Available: ${availableAgentNames(cwd).join(", ") || "none"}.`, true);
|
|
635
564
|
}
|
|
636
565
|
if (mutable.length === 1) return mutable[0]!;
|
|
637
566
|
const scope = asDisambiguationScope(scopeHint);
|
|
638
567
|
if (!scope) {
|
|
639
|
-
const paths = mutable.map((
|
|
640
|
-
return result(
|
|
568
|
+
const paths = mutable.map((match) => `${match.source}: ${match.filePath}`).join("\n");
|
|
569
|
+
return result(`Agent '${name}' exists in both scopes. Specify agentScope: 'user' or 'project'.\n${paths}`, true);
|
|
641
570
|
}
|
|
642
|
-
const scoped = mutable.filter((
|
|
643
|
-
if (scoped.length === 0) return result(
|
|
644
|
-
if (scoped.length > 1) return result(`Multiple
|
|
571
|
+
const scoped = mutable.filter((match) => match.source === scope);
|
|
572
|
+
if (scoped.length === 0) return result(`Agent '${name}' not found in scope '${scope}'.`, true);
|
|
573
|
+
if (scoped.length > 1) return result(`Multiple agents named '${name}' found in scope '${scope}': ${scoped.map((match) => match.filePath).join(", ")}`, true);
|
|
645
574
|
return scoped[0]!;
|
|
646
575
|
}
|
|
647
576
|
|
|
648
|
-
function renamePath(
|
|
649
|
-
kind: "agent" | "chain",
|
|
650
|
-
currentPath: string,
|
|
651
|
-
newName: string,
|
|
652
|
-
scope: ManagementScope,
|
|
653
|
-
cwd: string,
|
|
654
|
-
): { filePath?: string; error?: string } {
|
|
577
|
+
function renamePath(currentPath: string, newName: string, scope: ManagementScope, cwd: string): { filePath?: string; error?: string } {
|
|
655
578
|
if (nameExistsInScope(cwd, scope, newName, currentPath)) return { error: `Name '${newName}' already exists in ${scope} scope.` };
|
|
656
|
-
const
|
|
657
|
-
|
|
658
|
-
if (fs.existsSync(filePath) && filePath !== currentPath) {
|
|
659
|
-
return { error: `File already exists at ${filePath} but is not a valid ${kind} definition. Remove or rename it first.` };
|
|
660
|
-
}
|
|
579
|
+
const filePath = path.join(path.dirname(currentPath), `${newName}.md`);
|
|
580
|
+
if (fs.existsSync(filePath) && filePath !== currentPath) return { error: `File already exists at ${filePath} but is not a valid agent definition. Remove or rename it first.` };
|
|
661
581
|
fs.renameSync(currentPath, filePath);
|
|
662
582
|
return { filePath };
|
|
663
583
|
}
|
|
@@ -700,56 +620,6 @@ function formatAgentDetail(agent: AgentConfig): string {
|
|
|
700
620
|
return lines.join("\n");
|
|
701
621
|
}
|
|
702
622
|
|
|
703
|
-
function formatChainStepDetail(step: ChainStepConfig, index: number): string[] {
|
|
704
|
-
const lines: string[] = [];
|
|
705
|
-
if (step.expand || step.collect) {
|
|
706
|
-
const parallel = step.parallel && !Array.isArray(step.parallel) && typeof step.parallel === "object" ? step.parallel as { agent?: unknown; task?: unknown; label?: unknown; outputSchema?: unknown } : undefined;
|
|
707
|
-
const expand = step.expand && typeof step.expand === "object" ? step.expand as { from?: { output?: unknown; path?: unknown }; item?: unknown; key?: unknown; maxItems?: unknown; onEmpty?: unknown } : undefined;
|
|
708
|
-
const collect = step.collect && typeof step.collect === "object" ? step.collect as { as?: unknown; outputSchema?: unknown } : undefined;
|
|
709
|
-
lines.push(`${index + 1}. Dynamic fanout${typeof collect?.as === "string" ? ` -> ${collect.as}` : ""}`);
|
|
710
|
-
if (expand?.from) lines.push(` Expand: ${String(expand.from.output ?? "?")}${String(expand.from.path ?? "")}`);
|
|
711
|
-
if (typeof expand?.item === "string") lines.push(` Item variable: ${expand.item}`);
|
|
712
|
-
if (typeof expand?.key === "string") lines.push(` Key: ${expand.key}`);
|
|
713
|
-
if (typeof expand?.maxItems === "number") lines.push(` Max items: ${expand.maxItems}`);
|
|
714
|
-
if (typeof expand?.onEmpty === "string") lines.push(` On empty: ${expand.onEmpty}`);
|
|
715
|
-
if (parallel?.agent) lines.push(` Agent: ${String(parallel.agent)}`);
|
|
716
|
-
if (typeof parallel?.label === "string") lines.push(` Label: ${parallel.label}`);
|
|
717
|
-
if (typeof parallel?.task === "string" && parallel.task.trim()) lines.push(` Task: ${parallel.task}`);
|
|
718
|
-
if (parallel?.outputSchema) lines.push(" Structured output: true");
|
|
719
|
-
if (parallel && "toolBudget" in parallel) lines.push(` Tool budget: ${JSON.stringify((parallel as { toolBudget?: unknown }).toolBudget)}`);
|
|
720
|
-
if (collect?.outputSchema) lines.push(" Collect schema: true");
|
|
721
|
-
if (step.concurrency !== undefined) lines.push(` Concurrency: ${step.concurrency}`);
|
|
722
|
-
if (step.failFast !== undefined) lines.push(` Fail fast: ${step.failFast ? "true" : "false"}`);
|
|
723
|
-
return lines;
|
|
724
|
-
}
|
|
725
|
-
lines.push(`${index + 1}. ${step.agent}`);
|
|
726
|
-
if (step.task?.trim()) lines.push(` Task: ${step.task}`);
|
|
727
|
-
if (step.output === false) lines.push(" Output: false");
|
|
728
|
-
else if (step.output) lines.push(` Output: ${step.output}`);
|
|
729
|
-
if (step.outputMode) lines.push(` Output mode: ${step.outputMode}`);
|
|
730
|
-
if (step.toolBudget) lines.push(` Tool budget: ${JSON.stringify(step.toolBudget)}`);
|
|
731
|
-
if (step.reads === false) lines.push(" Reads: false");
|
|
732
|
-
else if (Array.isArray(step.reads) && step.reads.length > 0) lines.push(` Reads: ${step.reads.join(", ")}`);
|
|
733
|
-
if (step.model) lines.push(` Model: ${step.model}`);
|
|
734
|
-
if (step.skills === false) lines.push(" Skills: false");
|
|
735
|
-
else if (Array.isArray(step.skills) && step.skills.length > 0) lines.push(` Skills: ${step.skills.join(", ")}`);
|
|
736
|
-
if (step.progress !== undefined) lines.push(` Progress: ${step.progress ? "true" : "false"}`);
|
|
737
|
-
return lines;
|
|
738
|
-
}
|
|
739
|
-
|
|
740
|
-
function formatChainDetail(chain: ChainConfig): string {
|
|
741
|
-
const lines: string[] = [`Chain: ${chain.name} (${chain.source})`, `Path: ${chain.filePath}`, `Description: ${chain.description}`];
|
|
742
|
-
if (chain.packageName) {
|
|
743
|
-
lines.push(`Local name: ${frontmatterNameForConfig(chain)}`);
|
|
744
|
-
lines.push(`Package: ${chain.packageName}`);
|
|
745
|
-
}
|
|
746
|
-
lines.push("", "Steps:");
|
|
747
|
-
for (let i = 0; i < chain.steps.length; i++) {
|
|
748
|
-
lines.push(...formatChainStepDetail(chain.steps[i]!, i));
|
|
749
|
-
}
|
|
750
|
-
return lines.join("\n");
|
|
751
|
-
}
|
|
752
|
-
|
|
753
623
|
export function handleList(params: ManagementParams, ctx: ManagementContext): AgentToolResult<Details> {
|
|
754
624
|
const scope = normalizeListScope(params.agentScope) ?? "both";
|
|
755
625
|
const d = discoverAgentsAll(ctx.cwd);
|
|
@@ -760,11 +630,8 @@ export function handleList(params: ManagementParams, ctx: ManagementContext): Ag
|
|
|
760
630
|
const agents = visibleAgents.filter((a) => isAgentAllowedByCapabilityCeiling(a.name, capabilityCeiling));
|
|
761
631
|
const restrictedAgents = visibleAgents.filter((a) => !isAgentAllowedByCapabilityCeiling(a.name, capabilityCeiling));
|
|
762
632
|
const restrictedSources = capabilityCeilingAgentRestrictionSources(capabilityCeiling);
|
|
763
|
-
const chains = d.chains.filter((c) => scope === "both" || c.source === "package" || c.source === scope).sort((a, b) => a.name.localeCompare(b.name));
|
|
764
|
-
const diagnostics = d.chainDiagnostics.filter((entry) => scope === "both" || entry.source === scope);
|
|
765
633
|
const proactiveSuggestions = buildProactiveSkillSubagentRecommendationLines({
|
|
766
634
|
agents,
|
|
767
|
-
chains,
|
|
768
635
|
...(ctx.config?.proactiveSkillSubagents !== undefined ? { config: ctx.config.proactiveSkillSubagents } : {}),
|
|
769
636
|
discoverAvailableSkills: () => discoverAvailableSkills(ctx.cwd),
|
|
770
637
|
});
|
|
@@ -778,11 +645,12 @@ export function handleList(params: ManagementParams, ctx: ManagementContext): Ag
|
|
|
778
645
|
`Restricted agents (not executable in this session${restrictedSources?.length ? `; capability ceiling: ${restrictedSources.join(", ")}` : ""}):`,
|
|
779
646
|
...restrictedAgents.map((a) => `- ${a.name} (${a.source}${a.aliases?.length ? `, aliases: ${a.aliases.join(", ")}` : ""}): ${a.description}`),
|
|
780
647
|
] : []),
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
648
|
+
...(d.agentDiagnostics?.length ? [
|
|
649
|
+
"",
|
|
650
|
+
"Invalid agent definitions:",
|
|
651
|
+
...d.agentDiagnostics.map((diagnostic) => `- ${diagnostic.name ?? diagnostic.filePath} (${diagnostic.source}): ${diagnostic.error}`),
|
|
652
|
+
] : []),
|
|
784
653
|
...(proactiveSuggestions.length ? ["", ...proactiveSuggestions] : []),
|
|
785
|
-
...(diagnostics.length ? ["", "Chain diagnostics:", ...diagnostics.map((entry) => `- ${entry.filePath}: ${entry.error}`)] : []),
|
|
786
654
|
];
|
|
787
655
|
return result(lines.join("\n"));
|
|
788
656
|
}
|
|
@@ -869,40 +737,23 @@ function handleModels(params: ManagementParams, ctx: ManagementContext): AgentTo
|
|
|
869
737
|
}
|
|
870
738
|
|
|
871
739
|
function handleGet(params: ManagementParams, ctx: ManagementContext): AgentToolResult<Details> {
|
|
872
|
-
if (!params.agent
|
|
740
|
+
if (!params.agent) return result("Specify 'agent' for get.", true);
|
|
873
741
|
const scope = normalizeListScope(params.agentScope);
|
|
874
742
|
if (!scope) return result("agentScope must be 'user', 'project', or 'both' for get.", true);
|
|
875
|
-
const
|
|
876
|
-
const
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
} else {
|
|
890
|
-
anyFound = true;
|
|
891
|
-
blocks.push(...matches.map(formatAgentDetail));
|
|
892
|
-
}
|
|
893
|
-
}
|
|
894
|
-
if (params.chainName) {
|
|
895
|
-
const matches = findChains(params.chainName, ctx.cwd, scope);
|
|
896
|
-
if (!matches.length) {
|
|
897
|
-
const msg = `Chain '${params.chainName}' not found. Available: ${availableNames(ctx.cwd, "chain").join(", ") || "none"}.`;
|
|
898
|
-
if (!hasBoth) return result(msg, true);
|
|
899
|
-
blocks.push(msg);
|
|
900
|
-
} else {
|
|
901
|
-
anyFound = true;
|
|
902
|
-
blocks.push(...matches.map(formatChainDetail));
|
|
903
|
-
}
|
|
904
|
-
}
|
|
905
|
-
return result(blocks.join("\n\n"), !anyFound);
|
|
743
|
+
const discovered = discoverAgentsAll(ctx.cwd);
|
|
744
|
+
const matches = findAgentsInDiscovery(params.agent, discovered, scope);
|
|
745
|
+
const diagnostics = diagnosticsForScope(discovered.agentDiagnostics, scope);
|
|
746
|
+
const rawName = params.agent.trim();
|
|
747
|
+
const normalizedName = sanitizeName(rawName);
|
|
748
|
+
const diagnostic = findBlockingAgentDiagnostic(rawName, matches, diagnostics)
|
|
749
|
+
?? (normalizedName !== rawName ? findBlockingAgentDiagnostic(normalizedName, matches, diagnostics) : undefined);
|
|
750
|
+
if (diagnostic) return result(`Agent '${params.agent}' has invalid configuration: ${diagnostic.error}`, true);
|
|
751
|
+
const distinctNames = [...new Set(matches.map((agent) => agent.name))];
|
|
752
|
+
if (distinctNames.length > 1) return result(`Ambiguous agent alias or name '${params.agent}': ${distinctNames.sort((a, b) => a.localeCompare(b)).join(", ")}`, true);
|
|
753
|
+
if (!matches.length) {
|
|
754
|
+
return result(`Agent '${params.agent}' not found. Available: ${availableAgentNamesFromDiscovery(discovered).join(", ") || "none"}.`, true);
|
|
755
|
+
}
|
|
756
|
+
return result(matches.map(formatAgentDetail).join("\n\n"));
|
|
906
757
|
}
|
|
907
758
|
|
|
908
759
|
export function handleCreate(params: ManagementParams, ctx: ManagementContext): AgentToolResult<Details> {
|
|
@@ -920,36 +771,16 @@ export function handleCreate(params: ManagementParams, ctx: ManagementContext):
|
|
|
920
771
|
const scopeRaw = cfg.scope ?? "user";
|
|
921
772
|
if (scopeRaw !== "user" && scopeRaw !== "project") return result("config.scope must be 'user' or 'project'.", true);
|
|
922
773
|
const scope = scopeRaw as ManagementScope;
|
|
923
|
-
|
|
774
|
+
if (hasKey(cfg, "steps")) return result("Durable chain definitions were removed; use workflowScript or /prompt-workflow for repeatable workflows.", true);
|
|
924
775
|
const d = discoverAgentsAll(ctx.cwd);
|
|
925
776
|
const projectConfigDir = getProjectConfigDir(ctx.cwd);
|
|
926
|
-
const targetDir =
|
|
927
|
-
? scope === "user" ? d.userChainDir : d.projectChainDir ?? path.join(projectConfigDir, "chains")
|
|
928
|
-
: scope === "user" ? d.userDir : d.projectDir ?? path.join(projectConfigDir, "agents");
|
|
777
|
+
const targetDir = scope === "user" ? d.userDir : d.projectDir ?? path.join(projectConfigDir, "agents");
|
|
929
778
|
fs.mkdirSync(targetDir, { recursive: true });
|
|
930
779
|
if (nameExistsInScope(ctx.cwd, scope, runtimeName)) return result(`Name '${runtimeName}' already exists in ${scope} scope. Use update instead.`, true);
|
|
931
|
-
const targetPath = path.join(targetDir,
|
|
932
|
-
if (fs.existsSync(targetPath)) return result(`File already exists at ${targetPath} but is not a valid
|
|
780
|
+
const targetPath = path.join(targetDir, `${runtimeName}.md`);
|
|
781
|
+
if (fs.existsSync(targetPath)) return result(`File already exists at ${targetPath} but is not a valid agent definition. Remove or rename it first.`, true);
|
|
933
782
|
const warnings: string[] = [];
|
|
934
|
-
if (
|
|
935
|
-
if (isChain) {
|
|
936
|
-
const parsed = parseStepList(cfg.steps);
|
|
937
|
-
if (parsed.error) return result(parsed.error, true);
|
|
938
|
-
const chain: ChainConfig = {
|
|
939
|
-
name: runtimeName,
|
|
940
|
-
localName: name,
|
|
941
|
-
...(parsedPackage.packageName !== undefined ? { packageName: parsedPackage.packageName } : {}),
|
|
942
|
-
description: cfg.description.trim(),
|
|
943
|
-
source: scope,
|
|
944
|
-
filePath: targetPath,
|
|
945
|
-
steps: parsed.steps!,
|
|
946
|
-
};
|
|
947
|
-
fs.writeFileSync(targetPath, serializeChain(chain), "utf-8");
|
|
948
|
-
const missing = unknownChainAgents(ctx.cwd, chain.steps);
|
|
949
|
-
if (missing.length) warnings.push(`Warning: chain steps reference unknown agents: ${missing.join(", ")}.`);
|
|
950
|
-
warnings.push(...chainStepWarnings(ctx, chain.steps));
|
|
951
|
-
return result([`Created chain '${runtimeName}' at ${targetPath}.`, ...warnings].join("\n"));
|
|
952
|
-
}
|
|
783
|
+
if (d.builtin.some((a) => a.name === runtimeName)) warnings.push(`Note: this shadows the builtin agent '${runtimeName}'.`);
|
|
953
784
|
const agent: AgentConfig = {
|
|
954
785
|
name: runtimeName,
|
|
955
786
|
localName: name,
|
|
@@ -975,75 +806,25 @@ export function handleCreate(params: ManagementParams, ctx: ManagementContext):
|
|
|
975
806
|
}
|
|
976
807
|
|
|
977
808
|
export function handleUpdate(params: ManagementParams, ctx: ManagementContext): AgentToolResult<Details> {
|
|
978
|
-
if (!params.agent
|
|
979
|
-
if (params.agent && params.chainName) return result("Specify either 'agent' or 'chainName', not both.", true);
|
|
809
|
+
if (!params.agent) return result("Specify 'agent' for update.", true);
|
|
980
810
|
const parsedConfig = configObject(params.config);
|
|
981
811
|
if (parsedConfig.error) return result(parsedConfig.error, true);
|
|
982
812
|
const cfg = parsedConfig.value;
|
|
983
813
|
if (!cfg) return result("config required for update.", true);
|
|
814
|
+
if (hasKey(cfg, "steps")) return result("Durable chain definitions were removed; use workflowScript or /prompt-workflow for repeatable workflows.", true);
|
|
984
815
|
const warnings: string[] = [];
|
|
985
|
-
if (params.agent) {
|
|
986
|
-
const scopeHint = asDisambiguationScope(params.agentScope);
|
|
987
|
-
const targetOrError = resolveTarget("agent", params.agent, findAgents(params.agent, ctx.cwd, scopeHint ?? "both"), ctx.cwd, scopeHint);
|
|
988
|
-
if ("content" in targetOrError) return targetOrError;
|
|
989
|
-
const target = targetOrError;
|
|
990
|
-
if (target.source !== "user" && target.source !== "project") return result(`Cannot update ${target.source} agent '${target.name}'. Eject it to user or project scope first.`, true);
|
|
991
|
-
const updated = editableAgentConfig(target);
|
|
992
|
-
const oldName = target.name;
|
|
993
|
-
if (hasKey(cfg, "name") && (typeof cfg.name !== "string" || !cfg.name.trim())) return result("config.name must be a non-empty string when provided.", true);
|
|
994
|
-
if (hasKey(cfg, "description") && (typeof cfg.description !== "string" || !cfg.description.trim())) return result("config.description must be a non-empty string when provided.", true);
|
|
995
|
-
let newLocalName = target.localName ?? frontmatterNameForConfig(target);
|
|
996
|
-
if (hasKey(cfg, "name")) {
|
|
997
|
-
newLocalName = sanitizeName(cfg.name as string);
|
|
998
|
-
if (!newLocalName) return result("config.name is invalid after sanitization.", true);
|
|
999
|
-
}
|
|
1000
|
-
let newPackageName = target.packageName;
|
|
1001
|
-
if (hasKey(cfg, "package")) {
|
|
1002
|
-
const parsedPackage = parsePackageConfig(cfg.package);
|
|
1003
|
-
if (parsedPackage.error) return result(parsedPackage.error, true);
|
|
1004
|
-
newPackageName = parsedPackage.packageName;
|
|
1005
|
-
}
|
|
1006
|
-
const applyError = applyAgentConfig(updated, cfg);
|
|
1007
|
-
if (applyError) return result(applyError, true);
|
|
1008
|
-
const preserveFrontmatterFields = preservedAgentFrontmatterFields(target, cfg);
|
|
1009
|
-
updated.localName = newLocalName;
|
|
1010
|
-
if (newPackageName !== undefined) updated.packageName = newPackageName;
|
|
1011
|
-
else delete updated.packageName;
|
|
1012
|
-
updated.name = buildRuntimeName(newLocalName, newPackageName);
|
|
1013
|
-
if (hasKey(cfg, "description")) updated.description = (cfg.description as string).trim();
|
|
1014
|
-
if (hasKey(cfg, "model")) {
|
|
1015
|
-
const mw = modelWarning(ctx, updated.model);
|
|
1016
|
-
if (mw) warnings.push(mw);
|
|
1017
|
-
}
|
|
1018
|
-
if (hasKey(cfg, "fallbackModels")) {
|
|
1019
|
-
const fmw = fallbackModelsWarning(ctx, updated.fallbackModels);
|
|
1020
|
-
if (fmw) warnings.push(fmw);
|
|
1021
|
-
}
|
|
1022
|
-
if (hasKey(cfg, "skills") || hasKey(cfg, "skillPath")) {
|
|
1023
|
-
const sw = skillsWarning(ctx.cwd, updated);
|
|
1024
|
-
if (sw) warnings.push(sw);
|
|
1025
|
-
}
|
|
1026
|
-
if (updated.name !== oldName) {
|
|
1027
|
-
const renamed = renamePath("agent", target.filePath, updated.name, target.source, ctx.cwd);
|
|
1028
|
-
if (renamed.error) return result(renamed.error, true);
|
|
1029
|
-
updated.filePath = renamed.filePath!;
|
|
1030
|
-
}
|
|
1031
|
-
fs.writeFileSync(updated.filePath, serializeAgent(updated, { preserveFrontmatterFields }), "utf-8");
|
|
1032
|
-
if (updated.name !== oldName) {
|
|
1033
|
-
const refs = discoverAgentsAll(ctx.cwd).chains.filter((c) => c.steps.some((s) => s.agent === oldName)).map((c) => `${c.name} (${c.source})`);
|
|
1034
|
-
if (refs.length) warnings.push(`Warning: chains still reference '${oldName}': ${refs.join(", ")}.`);
|
|
1035
|
-
}
|
|
1036
|
-
const headline = updated.name === oldName
|
|
1037
|
-
? `Updated agent '${updated.name}' at ${updated.filePath}.`
|
|
1038
|
-
: `Updated agent '${oldName}' to '${updated.name}' at ${updated.filePath}.`;
|
|
1039
|
-
return result([headline, ...warnings].join("\n"));
|
|
1040
|
-
}
|
|
1041
816
|
const scopeHint = asDisambiguationScope(params.agentScope);
|
|
1042
|
-
const targetOrError = resolveTarget(
|
|
817
|
+
const targetOrError = resolveTarget(params.agent, findAgents(params.agent, ctx.cwd, scopeHint ?? "both"), ctx.cwd, scopeHint);
|
|
1043
818
|
if ("content" in targetOrError) return targetOrError;
|
|
1044
819
|
const target = targetOrError;
|
|
1045
|
-
if (target.source !== "user" && target.source !== "project") return result(`Cannot update ${target.source}
|
|
1046
|
-
|
|
820
|
+
if (target.source !== "user" && target.source !== "project") return result(`Cannot update ${target.source} agent '${target.name}'. Eject it to user or project scope first.`, true);
|
|
821
|
+
let updated: AgentConfig;
|
|
822
|
+
try {
|
|
823
|
+
updated = editableAgentConfig(target);
|
|
824
|
+
} catch (error) {
|
|
825
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
826
|
+
return result(`Could not reread agent definition ${target.filePath} before updating '${target.name}': ${message}`, true);
|
|
827
|
+
}
|
|
1047
828
|
const oldName = target.name;
|
|
1048
829
|
if (hasKey(cfg, "name") && (typeof cfg.name !== "string" || !cfg.name.trim())) return result("config.name must be a non-empty string when provided.", true);
|
|
1049
830
|
if (hasKey(cfg, "description") && (typeof cfg.description !== "string" || !cfg.description.trim())) return result("config.description must be a non-empty string when provided.", true);
|
|
@@ -1058,54 +839,46 @@ export function handleUpdate(params: ManagementParams, ctx: ManagementContext):
|
|
|
1058
839
|
if (parsedPackage.error) return result(parsedPackage.error, true);
|
|
1059
840
|
newPackageName = parsedPackage.packageName;
|
|
1060
841
|
}
|
|
1061
|
-
|
|
1062
|
-
if (
|
|
1063
|
-
|
|
1064
|
-
if (parsed.error) return result(parsed.error, true);
|
|
1065
|
-
parsedSteps = parsed.steps!;
|
|
1066
|
-
}
|
|
842
|
+
const applyError = applyAgentConfig(updated, cfg);
|
|
843
|
+
if (applyError) return result(applyError, true);
|
|
844
|
+
const preserveFrontmatterFields = preservedAgentFrontmatterFields(target, cfg);
|
|
1067
845
|
updated.localName = newLocalName;
|
|
1068
846
|
if (newPackageName !== undefined) updated.packageName = newPackageName;
|
|
1069
847
|
else delete updated.packageName;
|
|
1070
848
|
updated.name = buildRuntimeName(newLocalName, newPackageName);
|
|
1071
849
|
if (hasKey(cfg, "description")) updated.description = (cfg.description as string).trim();
|
|
1072
|
-
if (
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
850
|
+
if (hasKey(cfg, "model")) {
|
|
851
|
+
const mw = modelWarning(ctx, updated.model);
|
|
852
|
+
if (mw) warnings.push(mw);
|
|
853
|
+
}
|
|
854
|
+
if (hasKey(cfg, "fallbackModels")) {
|
|
855
|
+
const fmw = fallbackModelsWarning(ctx, updated.fallbackModels);
|
|
856
|
+
if (fmw) warnings.push(fmw);
|
|
857
|
+
}
|
|
858
|
+
if (hasKey(cfg, "skills") || hasKey(cfg, "skillPath")) {
|
|
859
|
+
const sw = skillsWarning(ctx.cwd, updated);
|
|
860
|
+
if (sw) warnings.push(sw);
|
|
1077
861
|
}
|
|
1078
862
|
if (updated.name !== oldName) {
|
|
1079
|
-
const renamed = renamePath(
|
|
863
|
+
const renamed = renamePath(target.filePath, updated.name, target.source, ctx.cwd);
|
|
1080
864
|
if (renamed.error) return result(renamed.error, true);
|
|
1081
865
|
updated.filePath = renamed.filePath!;
|
|
1082
866
|
}
|
|
1083
|
-
fs.writeFileSync(updated.filePath, updated
|
|
867
|
+
fs.writeFileSync(updated.filePath, serializeAgent(updated, { preserveFrontmatterFields }), "utf-8");
|
|
1084
868
|
const headline = updated.name === oldName
|
|
1085
|
-
? `Updated
|
|
1086
|
-
: `Updated
|
|
869
|
+
? `Updated agent '${updated.name}' at ${updated.filePath}.`
|
|
870
|
+
: `Updated agent '${oldName}' to '${updated.name}' at ${updated.filePath}.`;
|
|
1087
871
|
return result([headline, ...warnings].join("\n"));
|
|
1088
872
|
}
|
|
1089
873
|
|
|
1090
874
|
function handleDelete(params: ManagementParams, ctx: ManagementContext): AgentToolResult<Details> {
|
|
1091
|
-
if (!params.agent
|
|
1092
|
-
if (params.agent && params.chainName) return result("Specify either 'agent' or 'chainName', not both.", true);
|
|
875
|
+
if (!params.agent) return result("Specify 'agent' for delete.", true);
|
|
1093
876
|
const scopeHint = asDisambiguationScope(params.agentScope);
|
|
1094
|
-
|
|
1095
|
-
const targetOrError = resolveTarget("agent", params.agent, findAgents(params.agent, ctx.cwd, scopeHint ?? "both"), ctx.cwd, scopeHint);
|
|
1096
|
-
if ("content" in targetOrError) return targetOrError;
|
|
1097
|
-
const target = targetOrError;
|
|
1098
|
-
fs.unlinkSync(target.filePath);
|
|
1099
|
-
const refs = discoverAgentsAll(ctx.cwd).chains.filter((c) => c.steps.some((s) => s.agent === target.name)).map((c) => `${c.name} (${c.source})`);
|
|
1100
|
-
const lines = [`Deleted agent '${target.name}' at ${target.filePath}.`];
|
|
1101
|
-
if (refs.length) lines.push(`Warning: chains reference deleted agent '${target.name}': ${refs.join(", ")}.`);
|
|
1102
|
-
return result(lines.join("\n"));
|
|
1103
|
-
}
|
|
1104
|
-
const targetOrError = resolveTarget("chain", params.chainName!, findChains(params.chainName!, ctx.cwd, scopeHint ?? "both"), ctx.cwd, scopeHint);
|
|
877
|
+
const targetOrError = resolveTarget(params.agent, findAgents(params.agent, ctx.cwd, scopeHint ?? "both"), ctx.cwd, scopeHint);
|
|
1105
878
|
if ("content" in targetOrError) return targetOrError;
|
|
1106
879
|
const target = targetOrError;
|
|
1107
880
|
fs.unlinkSync(target.filePath);
|
|
1108
|
-
return result(`Deleted
|
|
881
|
+
return result(`Deleted agent '${target.name}' at ${target.filePath}.`);
|
|
1109
882
|
}
|
|
1110
883
|
|
|
1111
884
|
function handleEject(params: ManagementParams, ctx: ManagementContext): AgentToolResult<Details> {
|
|
@@ -1118,7 +891,7 @@ function handleEject(params: ManagementParams, ctx: ManagementContext): AgentToo
|
|
|
1118
891
|
const d = discoverAgentsAll(ctx.cwd);
|
|
1119
892
|
const source = [...d.package, ...d.builtin].find((a) => a.name === raw || a.name === sanitized);
|
|
1120
893
|
if (!source) {
|
|
1121
|
-
return result(`Agent '${raw}' not found or is not a bundled/package agent. eject copies a builtin or package agent to ${scope} scope so it can be customized. Available: ${
|
|
894
|
+
return result(`Agent '${raw}' not found or is not a bundled/package agent. eject copies a builtin or package agent to ${scope} scope so it can be customized. Available: ${availableAgentNames(ctx.cwd).join(", ") || "none"}.`, true);
|
|
1122
895
|
}
|
|
1123
896
|
const runtimeName = source.name;
|
|
1124
897
|
const existingCustom = (scope === "user" ? d.user : d.project).find((a) => a.name === runtimeName);
|
|
@@ -1126,7 +899,7 @@ function handleEject(params: ManagementParams, ctx: ManagementContext): AgentToo
|
|
|
1126
899
|
return result(`Agent '${runtimeName}' is already a custom ${scope} agent at ${existingCustom.filePath}. Edit it with { action: "update", agent: "${runtimeName}" } or delete it first.`, true);
|
|
1127
900
|
}
|
|
1128
901
|
if (nameExistsInScope(ctx.cwd, scope, runtimeName)) {
|
|
1129
|
-
return result(`An agent
|
|
902
|
+
return result(`An agent named '${runtimeName}' already exists in ${scope} scope. Remove or rename it first.`, true);
|
|
1130
903
|
}
|
|
1131
904
|
const projectConfigDir = getProjectConfigDir(ctx.cwd);
|
|
1132
905
|
const targetDir = scope === "user" ? d.userDir : d.projectDir ?? path.join(projectConfigDir, "agents");
|
|
@@ -1159,7 +932,7 @@ function handleDisable(params: ManagementParams, ctx: ManagementContext): AgentT
|
|
|
1159
932
|
const effective = resolveEffectiveAgent(d, raw);
|
|
1160
933
|
if (effective.error) return result(effective.error, true);
|
|
1161
934
|
if (!effective.agent) {
|
|
1162
|
-
return result(`Agent '${raw}' not found. Available: ${
|
|
935
|
+
return result(`Agent '${raw}' not found. Available: ${availableAgentNames(ctx.cwd).join(", ") || "none"}.`, true);
|
|
1163
936
|
}
|
|
1164
937
|
const runtimeName = effective.agent.name;
|
|
1165
938
|
const settingsPath = mergeBuiltinAgentOverride(ctx.cwd, runtimeName, scope, { disabled: true });
|
|
@@ -1183,7 +956,7 @@ function handleEnable(params: ManagementParams, ctx: ManagementContext): AgentTo
|
|
|
1183
956
|
const effective = resolveEffectiveAgent(d, raw);
|
|
1184
957
|
if (effective.error) return result(effective.error, true);
|
|
1185
958
|
if (!effective.agent) {
|
|
1186
|
-
return result(`Agent '${raw}' not found. Available: ${
|
|
959
|
+
return result(`Agent '${raw}' not found. Available: ${availableAgentNames(ctx.cwd).join(", ") || "none"}.`, true);
|
|
1187
960
|
}
|
|
1188
961
|
const runtimeName = effective.agent.name;
|
|
1189
962
|
const { path: settingsPath, removed } = removeBuiltinAgentOverrideFields(ctx.cwd, runtimeName, scope, ["disabled"]);
|
|
@@ -1215,7 +988,7 @@ function handleReset(params: ManagementParams, ctx: ManagementContext): AgentToo
|
|
|
1215
988
|
if (custom) {
|
|
1216
989
|
return result(`Agent '${raw}' has no bundled default to reset to. Use { action: "delete", agent: "${custom.name}" } to remove the custom ${custom.source} agent.`, true);
|
|
1217
990
|
}
|
|
1218
|
-
return result(`Agent '${raw}' not found. Available: ${
|
|
991
|
+
return result(`Agent '${raw}' not found. Available: ${availableAgentNames(ctx.cwd).join(", ") || "none"}.`, true);
|
|
1219
992
|
}
|
|
1220
993
|
const runtimeName = bundled.name;
|
|
1221
994
|
const custom = (scope === "user" ? d.user : d.project).find((a) => a.name === raw || a.name === sanitized);
|