pi-subagents 0.49.0 → 0.51.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 +97 -0
- package/agents/gpt-pro.md +17 -0
- package/agents/oracle.md +7 -5
- package/agents/researcher.md +1 -1
- package/agents/reviewer.md +2 -2
- package/agents/scout.md +1 -1
- package/agents/worker.md +1 -1
- package/async-retention-discovery-worker.mjs +180 -0
- package/docs/agents.md +37 -2
- package/docs/configuration.md +76 -14
- package/docs/extension-api.md +78 -1
- package/docs/missions.md +1 -1
- package/docs/observability.md +20 -4
- package/docs/tool-reference.md +55 -39
- package/docs/workflows.md +171 -5
- package/package.json +4 -2
- package/skills/pi-subagents/SKILL.md +5 -4
- package/skills/pi-subagents/references/constraints-and-recipes.md +9 -6
- package/skills/pi-subagents/references/execution-controls.md +22 -18
- package/skills/pi-subagents/references/management-authoring-rpc.md +5 -5
- package/skills/pi-subagents/references/prompting-and-roles.md +33 -17
- package/src/agents/agent-management.ts +100 -345
- package/src/agents/agent-serializer.ts +2 -0
- package/src/agents/agents.ts +135 -25
- package/src/api/external-job-provider.ts +185 -0
- package/src/api/external-runs.ts +174 -84
- package/src/api/preflight.ts +42 -11
- package/src/api/shared-types.ts +2 -0
- package/src/extension/config.ts +36 -3
- package/src/extension/doctor.ts +3 -6
- package/src/extension/fanout-child.ts +2 -2
- package/src/extension/index.ts +210 -90
- package/src/extension/public-execution.ts +31 -2
- package/src/extension/rpc.ts +5 -1
- package/src/extension/schemas.ts +14 -36
- package/src/extension/tool-description.ts +37 -24
- package/src/inspectors/herdr/actions.ts +5 -9
- package/src/inspectors/herdr/inspector-runner.ts +2 -1
- package/src/inspectors/herdr/project-panes.ts +4 -8
- package/src/inspectors/herdr/shell-command.ts +16 -0
- package/src/intercom/intercom-bridge.ts +2 -3
- package/src/intercom/native-supervisor-channel.ts +49 -51
- package/src/missions/goal-driver.ts +3 -1
- package/src/missions/lifecycle.ts +6 -1
- package/src/missions/store.ts +4 -9
- package/src/profiles/profiles.ts +3 -1
- package/src/runs/background/active-run-index.ts +94 -1
- package/src/runs/background/async-execution.ts +98 -38
- package/src/runs/background/async-job-tracker.ts +21 -4
- package/src/runs/background/async-resume.ts +30 -17
- package/src/runs/background/async-retention.ts +888 -0
- package/src/runs/background/async-status-snapshot.ts +277 -0
- package/src/runs/background/async-status.ts +47 -56
- package/src/runs/background/chain-append.ts +3 -33
- package/src/runs/background/chain-root-attachment.ts +2 -2
- package/src/runs/background/completion-replay.ts +11 -1
- package/src/runs/background/control-channel.ts +14 -68
- package/src/runs/background/fleet-view.ts +3 -1
- package/src/runs/background/index-segment.ts +59 -0
- package/src/runs/background/notify.ts +3 -1
- package/src/runs/background/result-files.ts +505 -0
- package/src/runs/background/result-watcher.ts +250 -51
- package/src/runs/background/retained-children.ts +79 -20
- package/src/runs/background/run-id-query.ts +7 -0
- package/src/runs/background/run-id-resolver.ts +37 -29
- package/src/runs/background/run-status.ts +34 -20
- package/src/runs/background/scheduled-runs.ts +71 -27
- package/src/runs/background/stale-run-reconciler.ts +33 -16
- package/src/runs/background/steering.ts +11 -1
- package/src/runs/background/subagent-runner.ts +535 -161
- package/src/runs/background/subagent-wait.ts +9 -7
- package/src/runs/background/terminal-run-index.ts +129 -0
- package/src/runs/background/wait-completions.ts +22 -2
- package/src/runs/background/wait-subscriptions.ts +80 -1
- package/src/runs/foreground/async-dismiss-action.ts +2 -1
- package/src/runs/foreground/async-steering-action.ts +21 -14
- package/src/runs/foreground/execution.ts +221 -15
- package/src/runs/foreground/subagent-executor.ts +529 -1519
- package/src/runs/foreground/workflow-foreground-steering.ts +6 -5
- package/src/runs/shared/chain-outputs.ts +1 -3
- package/src/runs/shared/completion-guard.ts +96 -6
- package/src/runs/shared/external-cli-runner.ts +4 -0
- package/src/runs/shared/external-job-bridge.ts +450 -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 +34 -3
- package/src/runs/shared/nested-events.ts +66 -62
- package/src/runs/shared/orca-progress-tabs.ts +437 -0
- package/src/runs/shared/parallel-handoff.ts +46 -4
- package/src/runs/shared/parallel-utils.ts +6 -15
- package/src/runs/shared/permissions.ts +5 -1
- package/src/runs/shared/pi-args.ts +8 -1
- package/src/runs/shared/subagent-control.ts +41 -4
- package/src/runs/shared/subagent-prompt-runtime.ts +13 -15
- package/src/runs/shared/subagent-startup-retry.ts +12 -0
- package/src/runs/shared/tool-timeout.ts +93 -0
- package/src/runs/shared/workflow-graph.ts +1 -23
- package/src/runs/shared/worktree.ts +12 -1
- 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 +88 -27
- package/src/shared/utils.ts +8 -0
- package/src/shared/watch-strategy.ts +10 -0
- package/src/slash/slash-commands.ts +45 -28
- package/src/slash/slash-live-state.ts +3 -0
- package/src/tui/fleet-status.ts +160 -45
- package/src/tui/fleet.ts +186 -28
- package/src/tui/render.ts +41 -10
- package/src/workflows/chat-progress.ts +7 -5
- package/src/workflows/scripted-workflow.ts +424 -125
- package/src/runs/foreground/chain-clarify.ts +0 -1354
- package/src/runs/foreground/chain-execution.ts +0 -1565
|
@@ -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,7 +23,6 @@ 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,
|
|
@@ -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);
|
|
@@ -329,69 +311,6 @@ export function preservedAgentFrontmatterFields(agent: AgentConfig, cfg: Record<
|
|
|
329
311
|
return fields;
|
|
330
312
|
}
|
|
331
313
|
|
|
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
314
|
function parseTools(raw: string): { tools?: string[]; mcpDirectTools?: string[] } {
|
|
396
315
|
const tools: string[] = [];
|
|
397
316
|
const mcpDirectTools: string[] = [];
|
|
@@ -436,7 +355,11 @@ function applyAgentConfig(target: AgentConfig, cfg: Record<string, unknown>): st
|
|
|
436
355
|
&& Object.keys(runner).every((key) => ["type", "command", "args", "promptDelivery"].includes(key))) {
|
|
437
356
|
const runnerArgs = Array.isArray(runner.args) ? runner.args.filter((arg): arg is string => typeof arg === "string") : undefined;
|
|
438
357
|
target.runner = { type: "external-cli", command: runner.command.trim(), ...(runnerArgs?.length ? { args: runnerArgs } : {}), ...(runner.promptDelivery ? { promptDelivery: "stdin" } : {}) };
|
|
439
|
-
} else
|
|
358
|
+
} else if (runner.type === "external-job" && typeof runner.provider === "string" && runner.provider.trim() === runner.provider && runner.provider
|
|
359
|
+
&& (runner.options === undefined || (runner.options && typeof runner.options === "object" && !Array.isArray(runner.options) && isJsonSerializable(runner.options)))
|
|
360
|
+
&& Object.keys(runner).every((key) => ["type", "provider", "options"].includes(key))) {
|
|
361
|
+
target.runner = { type: "external-job", provider: runner.provider, ...(runner.options ? { options: runner.options as Record<string, unknown> } : {}) };
|
|
362
|
+
} 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
363
|
} else return "config.runner must be an object, false, or empty string when provided.";
|
|
441
364
|
}
|
|
442
365
|
if (hasKey(cfg, "model")) {
|
|
@@ -596,7 +519,7 @@ function applyAgentConfig(target: AgentConfig, cfg: Record<string, unknown>): st
|
|
|
596
519
|
target.toolBudget = cfg.toolBudget as ToolBudgetConfig;
|
|
597
520
|
}
|
|
598
521
|
}
|
|
599
|
-
if (target.runner?.type === "external-cli") {
|
|
522
|
+
if (target.runner?.type === "external-cli" || target.runner?.type === "external-job") {
|
|
600
523
|
const unsupported = [
|
|
601
524
|
target.tools?.length || target.mcpDirectTools?.length ? "tools" : undefined,
|
|
602
525
|
target.model ? "model" : undefined,
|
|
@@ -609,55 +532,40 @@ function applyAgentConfig(target: AgentConfig, cfg: Record<string, unknown>): st
|
|
|
609
532
|
target.completionGuard !== undefined ? "completionGuard" : undefined,
|
|
610
533
|
target.toolBudget ? "toolBudget" : undefined,
|
|
611
534
|
].filter((field): field is string => Boolean(field));
|
|
612
|
-
if (unsupported.length > 0) return `config.runner type '
|
|
535
|
+
if (unsupported.length > 0) return `config.runner type '${target.runner.type}' does not support Pi-only fields: ${unsupported.join(", ")}.`;
|
|
613
536
|
}
|
|
614
537
|
return undefined;
|
|
615
538
|
}
|
|
616
539
|
|
|
617
540
|
function resolveTarget<T extends { name: string; source: AgentSource; filePath: string }>(
|
|
618
|
-
kind: "agent" | "chain",
|
|
619
541
|
name: string,
|
|
620
542
|
matches: T[],
|
|
621
543
|
cwd: string,
|
|
622
544
|
scopeHint?: string,
|
|
623
545
|
): 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));
|
|
546
|
+
const distinctNames = [...new Set(matches.map((match) => match.name))];
|
|
547
|
+
if (distinctNames.length > 1) return result(`Ambiguous agent alias or name '${name}': ${distinctNames.sort((a, b) => a.localeCompare(b)).join(", ")}`, true);
|
|
548
|
+
const mutable = matches.filter((match): match is T & { source: ManagementScope } => isMutableSource(match.source));
|
|
629
549
|
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);
|
|
550
|
+
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);
|
|
551
|
+
return result(`Agent '${name}' not found. Available: ${availableAgentNames(cwd).join(", ") || "none"}.`, true);
|
|
635
552
|
}
|
|
636
553
|
if (mutable.length === 1) return mutable[0]!;
|
|
637
554
|
const scope = asDisambiguationScope(scopeHint);
|
|
638
555
|
if (!scope) {
|
|
639
|
-
const paths = mutable.map((
|
|
640
|
-
return result(
|
|
556
|
+
const paths = mutable.map((match) => `${match.source}: ${match.filePath}`).join("\n");
|
|
557
|
+
return result(`Agent '${name}' exists in both scopes. Specify agentScope: 'user' or 'project'.\n${paths}`, true);
|
|
641
558
|
}
|
|
642
|
-
const scoped = mutable.filter((
|
|
643
|
-
if (scoped.length === 0) return result(
|
|
644
|
-
if (scoped.length > 1) return result(`Multiple
|
|
559
|
+
const scoped = mutable.filter((match) => match.source === scope);
|
|
560
|
+
if (scoped.length === 0) return result(`Agent '${name}' not found in scope '${scope}'.`, true);
|
|
561
|
+
if (scoped.length > 1) return result(`Multiple agents named '${name}' found in scope '${scope}': ${scoped.map((match) => match.filePath).join(", ")}`, true);
|
|
645
562
|
return scoped[0]!;
|
|
646
563
|
}
|
|
647
564
|
|
|
648
|
-
function renamePath(
|
|
649
|
-
kind: "agent" | "chain",
|
|
650
|
-
currentPath: string,
|
|
651
|
-
newName: string,
|
|
652
|
-
scope: ManagementScope,
|
|
653
|
-
cwd: string,
|
|
654
|
-
): { filePath?: string; error?: string } {
|
|
565
|
+
function renamePath(currentPath: string, newName: string, scope: ManagementScope, cwd: string): { filePath?: string; error?: string } {
|
|
655
566
|
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
|
-
}
|
|
567
|
+
const filePath = path.join(path.dirname(currentPath), `${newName}.md`);
|
|
568
|
+
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
569
|
fs.renameSync(currentPath, filePath);
|
|
662
570
|
return { filePath };
|
|
663
571
|
}
|
|
@@ -700,56 +608,6 @@ function formatAgentDetail(agent: AgentConfig): string {
|
|
|
700
608
|
return lines.join("\n");
|
|
701
609
|
}
|
|
702
610
|
|
|
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
611
|
export function handleList(params: ManagementParams, ctx: ManagementContext): AgentToolResult<Details> {
|
|
754
612
|
const scope = normalizeListScope(params.agentScope) ?? "both";
|
|
755
613
|
const d = discoverAgentsAll(ctx.cwd);
|
|
@@ -760,11 +618,8 @@ export function handleList(params: ManagementParams, ctx: ManagementContext): Ag
|
|
|
760
618
|
const agents = visibleAgents.filter((a) => isAgentAllowedByCapabilityCeiling(a.name, capabilityCeiling));
|
|
761
619
|
const restrictedAgents = visibleAgents.filter((a) => !isAgentAllowedByCapabilityCeiling(a.name, capabilityCeiling));
|
|
762
620
|
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
621
|
const proactiveSuggestions = buildProactiveSkillSubagentRecommendationLines({
|
|
766
622
|
agents,
|
|
767
|
-
chains,
|
|
768
623
|
...(ctx.config?.proactiveSkillSubagents !== undefined ? { config: ctx.config.proactiveSkillSubagents } : {}),
|
|
769
624
|
discoverAvailableSkills: () => discoverAvailableSkills(ctx.cwd),
|
|
770
625
|
});
|
|
@@ -778,11 +633,12 @@ export function handleList(params: ManagementParams, ctx: ManagementContext): Ag
|
|
|
778
633
|
`Restricted agents (not executable in this session${restrictedSources?.length ? `; capability ceiling: ${restrictedSources.join(", ")}` : ""}):`,
|
|
779
634
|
...restrictedAgents.map((a) => `- ${a.name} (${a.source}${a.aliases?.length ? `, aliases: ${a.aliases.join(", ")}` : ""}): ${a.description}`),
|
|
780
635
|
] : []),
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
636
|
+
...(d.agentDiagnostics?.length ? [
|
|
637
|
+
"",
|
|
638
|
+
"Invalid agent definitions:",
|
|
639
|
+
...d.agentDiagnostics.map((diagnostic) => `- ${diagnostic.name ?? diagnostic.filePath} (${diagnostic.source}): ${diagnostic.error}`),
|
|
640
|
+
] : []),
|
|
784
641
|
...(proactiveSuggestions.length ? ["", ...proactiveSuggestions] : []),
|
|
785
|
-
...(diagnostics.length ? ["", "Chain diagnostics:", ...diagnostics.map((entry) => `- ${entry.filePath}: ${entry.error}`)] : []),
|
|
786
642
|
];
|
|
787
643
|
return result(lines.join("\n"));
|
|
788
644
|
}
|
|
@@ -869,40 +725,23 @@ function handleModels(params: ManagementParams, ctx: ManagementContext): AgentTo
|
|
|
869
725
|
}
|
|
870
726
|
|
|
871
727
|
function handleGet(params: ManagementParams, ctx: ManagementContext): AgentToolResult<Details> {
|
|
872
|
-
if (!params.agent
|
|
728
|
+
if (!params.agent) return result("Specify 'agent' for get.", true);
|
|
873
729
|
const scope = normalizeListScope(params.agentScope);
|
|
874
730
|
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
|
-
if (!hasBoth) return result(msg, true);
|
|
888
|
-
blocks.push(msg);
|
|
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
|
-
}
|
|
731
|
+
const discovered = discoverAgentsAll(ctx.cwd);
|
|
732
|
+
const matches = findAgentsInDiscovery(params.agent, discovered, scope);
|
|
733
|
+
const diagnostics = diagnosticsForScope(discovered.agentDiagnostics, scope);
|
|
734
|
+
const rawName = params.agent.trim();
|
|
735
|
+
const normalizedName = sanitizeName(rawName);
|
|
736
|
+
const diagnostic = findBlockingAgentDiagnostic(rawName, matches, diagnostics)
|
|
737
|
+
?? (normalizedName !== rawName ? findBlockingAgentDiagnostic(normalizedName, matches, diagnostics) : undefined);
|
|
738
|
+
if (diagnostic) return result(`Agent '${params.agent}' has invalid configuration: ${diagnostic.error}`, true);
|
|
739
|
+
const distinctNames = [...new Set(matches.map((agent) => agent.name))];
|
|
740
|
+
if (distinctNames.length > 1) return result(`Ambiguous agent alias or name '${params.agent}': ${distinctNames.sort((a, b) => a.localeCompare(b)).join(", ")}`, true);
|
|
741
|
+
if (!matches.length) {
|
|
742
|
+
return result(`Agent '${params.agent}' not found. Available: ${availableAgentNamesFromDiscovery(discovered).join(", ") || "none"}.`, true);
|
|
904
743
|
}
|
|
905
|
-
return result(
|
|
744
|
+
return result(matches.map(formatAgentDetail).join("\n\n"));
|
|
906
745
|
}
|
|
907
746
|
|
|
908
747
|
export function handleCreate(params: ManagementParams, ctx: ManagementContext): AgentToolResult<Details> {
|
|
@@ -920,36 +759,16 @@ export function handleCreate(params: ManagementParams, ctx: ManagementContext):
|
|
|
920
759
|
const scopeRaw = cfg.scope ?? "user";
|
|
921
760
|
if (scopeRaw !== "user" && scopeRaw !== "project") return result("config.scope must be 'user' or 'project'.", true);
|
|
922
761
|
const scope = scopeRaw as ManagementScope;
|
|
923
|
-
|
|
762
|
+
if (hasKey(cfg, "steps")) return result("Durable chain definitions were removed; use workflowScript or /prompt-workflow for repeatable workflows.", true);
|
|
924
763
|
const d = discoverAgentsAll(ctx.cwd);
|
|
925
764
|
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");
|
|
765
|
+
const targetDir = scope === "user" ? d.userDir : d.projectDir ?? path.join(projectConfigDir, "agents");
|
|
929
766
|
fs.mkdirSync(targetDir, { recursive: true });
|
|
930
767
|
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
|
|
768
|
+
const targetPath = path.join(targetDir, `${runtimeName}.md`);
|
|
769
|
+
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
770
|
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
|
-
}
|
|
771
|
+
if (d.builtin.some((a) => a.name === runtimeName)) warnings.push(`Note: this shadows the builtin agent '${runtimeName}'.`);
|
|
953
772
|
const agent: AgentConfig = {
|
|
954
773
|
name: runtimeName,
|
|
955
774
|
localName: name,
|
|
@@ -975,75 +794,19 @@ export function handleCreate(params: ManagementParams, ctx: ManagementContext):
|
|
|
975
794
|
}
|
|
976
795
|
|
|
977
796
|
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);
|
|
797
|
+
if (!params.agent) return result("Specify 'agent' for update.", true);
|
|
980
798
|
const parsedConfig = configObject(params.config);
|
|
981
799
|
if (parsedConfig.error) return result(parsedConfig.error, true);
|
|
982
800
|
const cfg = parsedConfig.value;
|
|
983
801
|
if (!cfg) return result("config required for update.", true);
|
|
802
|
+
if (hasKey(cfg, "steps")) return result("Durable chain definitions were removed; use workflowScript or /prompt-workflow for repeatable workflows.", true);
|
|
984
803
|
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
804
|
const scopeHint = asDisambiguationScope(params.agentScope);
|
|
1042
|
-
const targetOrError = resolveTarget(
|
|
805
|
+
const targetOrError = resolveTarget(params.agent, findAgents(params.agent, ctx.cwd, scopeHint ?? "both"), ctx.cwd, scopeHint);
|
|
1043
806
|
if ("content" in targetOrError) return targetOrError;
|
|
1044
807
|
const target = targetOrError;
|
|
1045
|
-
if (target.source !== "user" && target.source !== "project") return result(`Cannot update ${target.source}
|
|
1046
|
-
const updated
|
|
808
|
+
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);
|
|
809
|
+
const updated = editableAgentConfig(target);
|
|
1047
810
|
const oldName = target.name;
|
|
1048
811
|
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
812
|
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 +821,46 @@ export function handleUpdate(params: ManagementParams, ctx: ManagementContext):
|
|
|
1058
821
|
if (parsedPackage.error) return result(parsedPackage.error, true);
|
|
1059
822
|
newPackageName = parsedPackage.packageName;
|
|
1060
823
|
}
|
|
1061
|
-
|
|
1062
|
-
if (
|
|
1063
|
-
|
|
1064
|
-
if (parsed.error) return result(parsed.error, true);
|
|
1065
|
-
parsedSteps = parsed.steps!;
|
|
1066
|
-
}
|
|
824
|
+
const applyError = applyAgentConfig(updated, cfg);
|
|
825
|
+
if (applyError) return result(applyError, true);
|
|
826
|
+
const preserveFrontmatterFields = preservedAgentFrontmatterFields(target, cfg);
|
|
1067
827
|
updated.localName = newLocalName;
|
|
1068
828
|
if (newPackageName !== undefined) updated.packageName = newPackageName;
|
|
1069
829
|
else delete updated.packageName;
|
|
1070
830
|
updated.name = buildRuntimeName(newLocalName, newPackageName);
|
|
1071
831
|
if (hasKey(cfg, "description")) updated.description = (cfg.description as string).trim();
|
|
1072
|
-
if (
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
832
|
+
if (hasKey(cfg, "model")) {
|
|
833
|
+
const mw = modelWarning(ctx, updated.model);
|
|
834
|
+
if (mw) warnings.push(mw);
|
|
835
|
+
}
|
|
836
|
+
if (hasKey(cfg, "fallbackModels")) {
|
|
837
|
+
const fmw = fallbackModelsWarning(ctx, updated.fallbackModels);
|
|
838
|
+
if (fmw) warnings.push(fmw);
|
|
839
|
+
}
|
|
840
|
+
if (hasKey(cfg, "skills") || hasKey(cfg, "skillPath")) {
|
|
841
|
+
const sw = skillsWarning(ctx.cwd, updated);
|
|
842
|
+
if (sw) warnings.push(sw);
|
|
1077
843
|
}
|
|
1078
844
|
if (updated.name !== oldName) {
|
|
1079
|
-
const renamed = renamePath(
|
|
845
|
+
const renamed = renamePath(target.filePath, updated.name, target.source, ctx.cwd);
|
|
1080
846
|
if (renamed.error) return result(renamed.error, true);
|
|
1081
847
|
updated.filePath = renamed.filePath!;
|
|
1082
848
|
}
|
|
1083
|
-
fs.writeFileSync(updated.filePath, updated
|
|
849
|
+
fs.writeFileSync(updated.filePath, serializeAgent(updated, { preserveFrontmatterFields }), "utf-8");
|
|
1084
850
|
const headline = updated.name === oldName
|
|
1085
|
-
? `Updated
|
|
1086
|
-
: `Updated
|
|
851
|
+
? `Updated agent '${updated.name}' at ${updated.filePath}.`
|
|
852
|
+
: `Updated agent '${oldName}' to '${updated.name}' at ${updated.filePath}.`;
|
|
1087
853
|
return result([headline, ...warnings].join("\n"));
|
|
1088
854
|
}
|
|
1089
855
|
|
|
1090
856
|
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);
|
|
857
|
+
if (!params.agent) return result("Specify 'agent' for delete.", true);
|
|
1093
858
|
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);
|
|
859
|
+
const targetOrError = resolveTarget(params.agent, findAgents(params.agent, ctx.cwd, scopeHint ?? "both"), ctx.cwd, scopeHint);
|
|
1105
860
|
if ("content" in targetOrError) return targetOrError;
|
|
1106
861
|
const target = targetOrError;
|
|
1107
862
|
fs.unlinkSync(target.filePath);
|
|
1108
|
-
return result(`Deleted
|
|
863
|
+
return result(`Deleted agent '${target.name}' at ${target.filePath}.`);
|
|
1109
864
|
}
|
|
1110
865
|
|
|
1111
866
|
function handleEject(params: ManagementParams, ctx: ManagementContext): AgentToolResult<Details> {
|
|
@@ -1118,7 +873,7 @@ function handleEject(params: ManagementParams, ctx: ManagementContext): AgentToo
|
|
|
1118
873
|
const d = discoverAgentsAll(ctx.cwd);
|
|
1119
874
|
const source = [...d.package, ...d.builtin].find((a) => a.name === raw || a.name === sanitized);
|
|
1120
875
|
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: ${
|
|
876
|
+
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
877
|
}
|
|
1123
878
|
const runtimeName = source.name;
|
|
1124
879
|
const existingCustom = (scope === "user" ? d.user : d.project).find((a) => a.name === runtimeName);
|
|
@@ -1126,7 +881,7 @@ function handleEject(params: ManagementParams, ctx: ManagementContext): AgentToo
|
|
|
1126
881
|
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
882
|
}
|
|
1128
883
|
if (nameExistsInScope(ctx.cwd, scope, runtimeName)) {
|
|
1129
|
-
return result(`An agent
|
|
884
|
+
return result(`An agent named '${runtimeName}' already exists in ${scope} scope. Remove or rename it first.`, true);
|
|
1130
885
|
}
|
|
1131
886
|
const projectConfigDir = getProjectConfigDir(ctx.cwd);
|
|
1132
887
|
const targetDir = scope === "user" ? d.userDir : d.projectDir ?? path.join(projectConfigDir, "agents");
|
|
@@ -1159,7 +914,7 @@ function handleDisable(params: ManagementParams, ctx: ManagementContext): AgentT
|
|
|
1159
914
|
const effective = resolveEffectiveAgent(d, raw);
|
|
1160
915
|
if (effective.error) return result(effective.error, true);
|
|
1161
916
|
if (!effective.agent) {
|
|
1162
|
-
return result(`Agent '${raw}' not found. Available: ${
|
|
917
|
+
return result(`Agent '${raw}' not found. Available: ${availableAgentNames(ctx.cwd).join(", ") || "none"}.`, true);
|
|
1163
918
|
}
|
|
1164
919
|
const runtimeName = effective.agent.name;
|
|
1165
920
|
const settingsPath = mergeBuiltinAgentOverride(ctx.cwd, runtimeName, scope, { disabled: true });
|
|
@@ -1183,7 +938,7 @@ function handleEnable(params: ManagementParams, ctx: ManagementContext): AgentTo
|
|
|
1183
938
|
const effective = resolveEffectiveAgent(d, raw);
|
|
1184
939
|
if (effective.error) return result(effective.error, true);
|
|
1185
940
|
if (!effective.agent) {
|
|
1186
|
-
return result(`Agent '${raw}' not found. Available: ${
|
|
941
|
+
return result(`Agent '${raw}' not found. Available: ${availableAgentNames(ctx.cwd).join(", ") || "none"}.`, true);
|
|
1187
942
|
}
|
|
1188
943
|
const runtimeName = effective.agent.name;
|
|
1189
944
|
const { path: settingsPath, removed } = removeBuiltinAgentOverrideFields(ctx.cwd, runtimeName, scope, ["disabled"]);
|
|
@@ -1215,7 +970,7 @@ function handleReset(params: ManagementParams, ctx: ManagementContext): AgentToo
|
|
|
1215
970
|
if (custom) {
|
|
1216
971
|
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
972
|
}
|
|
1218
|
-
return result(`Agent '${raw}' not found. Available: ${
|
|
973
|
+
return result(`Agent '${raw}' not found. Available: ${availableAgentNames(ctx.cwd).join(", ") || "none"}.`, true);
|
|
1219
974
|
}
|
|
1220
975
|
const runtimeName = bundled.name;
|
|
1221
976
|
const custom = (scope === "user" ? d.user : d.project).find((a) => a.name === raw || a.name === sanitized);
|