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.
Files changed (117) hide show
  1. package/CHANGELOG.md +97 -0
  2. package/agents/gpt-pro.md +17 -0
  3. package/agents/oracle.md +7 -5
  4. package/agents/researcher.md +1 -1
  5. package/agents/reviewer.md +2 -2
  6. package/agents/scout.md +1 -1
  7. package/agents/worker.md +1 -1
  8. package/async-retention-discovery-worker.mjs +180 -0
  9. package/docs/agents.md +37 -2
  10. package/docs/configuration.md +76 -14
  11. package/docs/extension-api.md +78 -1
  12. package/docs/missions.md +1 -1
  13. package/docs/observability.md +20 -4
  14. package/docs/tool-reference.md +55 -39
  15. package/docs/workflows.md +171 -5
  16. package/package.json +4 -2
  17. package/skills/pi-subagents/SKILL.md +5 -4
  18. package/skills/pi-subagents/references/constraints-and-recipes.md +9 -6
  19. package/skills/pi-subagents/references/execution-controls.md +22 -18
  20. package/skills/pi-subagents/references/management-authoring-rpc.md +5 -5
  21. package/skills/pi-subagents/references/prompting-and-roles.md +33 -17
  22. package/src/agents/agent-management.ts +100 -345
  23. package/src/agents/agent-serializer.ts +2 -0
  24. package/src/agents/agents.ts +135 -25
  25. package/src/api/external-job-provider.ts +185 -0
  26. package/src/api/external-runs.ts +174 -84
  27. package/src/api/preflight.ts +42 -11
  28. package/src/api/shared-types.ts +2 -0
  29. package/src/extension/config.ts +36 -3
  30. package/src/extension/doctor.ts +3 -6
  31. package/src/extension/fanout-child.ts +2 -2
  32. package/src/extension/index.ts +210 -90
  33. package/src/extension/public-execution.ts +31 -2
  34. package/src/extension/rpc.ts +5 -1
  35. package/src/extension/schemas.ts +14 -36
  36. package/src/extension/tool-description.ts +37 -24
  37. package/src/inspectors/herdr/actions.ts +5 -9
  38. package/src/inspectors/herdr/inspector-runner.ts +2 -1
  39. package/src/inspectors/herdr/project-panes.ts +4 -8
  40. package/src/inspectors/herdr/shell-command.ts +16 -0
  41. package/src/intercom/intercom-bridge.ts +2 -3
  42. package/src/intercom/native-supervisor-channel.ts +49 -51
  43. package/src/missions/goal-driver.ts +3 -1
  44. package/src/missions/lifecycle.ts +6 -1
  45. package/src/missions/store.ts +4 -9
  46. package/src/profiles/profiles.ts +3 -1
  47. package/src/runs/background/active-run-index.ts +94 -1
  48. package/src/runs/background/async-execution.ts +98 -38
  49. package/src/runs/background/async-job-tracker.ts +21 -4
  50. package/src/runs/background/async-resume.ts +30 -17
  51. package/src/runs/background/async-retention.ts +888 -0
  52. package/src/runs/background/async-status-snapshot.ts +277 -0
  53. package/src/runs/background/async-status.ts +47 -56
  54. package/src/runs/background/chain-append.ts +3 -33
  55. package/src/runs/background/chain-root-attachment.ts +2 -2
  56. package/src/runs/background/completion-replay.ts +11 -1
  57. package/src/runs/background/control-channel.ts +14 -68
  58. package/src/runs/background/fleet-view.ts +3 -1
  59. package/src/runs/background/index-segment.ts +59 -0
  60. package/src/runs/background/notify.ts +3 -1
  61. package/src/runs/background/result-files.ts +505 -0
  62. package/src/runs/background/result-watcher.ts +250 -51
  63. package/src/runs/background/retained-children.ts +79 -20
  64. package/src/runs/background/run-id-query.ts +7 -0
  65. package/src/runs/background/run-id-resolver.ts +37 -29
  66. package/src/runs/background/run-status.ts +34 -20
  67. package/src/runs/background/scheduled-runs.ts +71 -27
  68. package/src/runs/background/stale-run-reconciler.ts +33 -16
  69. package/src/runs/background/steering.ts +11 -1
  70. package/src/runs/background/subagent-runner.ts +535 -161
  71. package/src/runs/background/subagent-wait.ts +9 -7
  72. package/src/runs/background/terminal-run-index.ts +129 -0
  73. package/src/runs/background/wait-completions.ts +22 -2
  74. package/src/runs/background/wait-subscriptions.ts +80 -1
  75. package/src/runs/foreground/async-dismiss-action.ts +2 -1
  76. package/src/runs/foreground/async-steering-action.ts +21 -14
  77. package/src/runs/foreground/execution.ts +221 -15
  78. package/src/runs/foreground/subagent-executor.ts +529 -1519
  79. package/src/runs/foreground/workflow-foreground-steering.ts +6 -5
  80. package/src/runs/shared/chain-outputs.ts +1 -3
  81. package/src/runs/shared/completion-guard.ts +96 -6
  82. package/src/runs/shared/external-cli-runner.ts +4 -0
  83. package/src/runs/shared/external-job-bridge.ts +450 -0
  84. package/src/runs/shared/external-job-runner.ts +286 -0
  85. package/src/runs/shared/mcp-direct-tool-allowlist.ts +14 -0
  86. package/src/runs/shared/model-fallback.ts +34 -3
  87. package/src/runs/shared/nested-events.ts +66 -62
  88. package/src/runs/shared/orca-progress-tabs.ts +437 -0
  89. package/src/runs/shared/parallel-handoff.ts +46 -4
  90. package/src/runs/shared/parallel-utils.ts +6 -15
  91. package/src/runs/shared/permissions.ts +5 -1
  92. package/src/runs/shared/pi-args.ts +8 -1
  93. package/src/runs/shared/subagent-control.ts +41 -4
  94. package/src/runs/shared/subagent-prompt-runtime.ts +13 -15
  95. package/src/runs/shared/subagent-startup-retry.ts +12 -0
  96. package/src/runs/shared/tool-timeout.ts +93 -0
  97. package/src/runs/shared/workflow-graph.ts +1 -23
  98. package/src/runs/shared/worktree.ts +12 -1
  99. package/src/shared/atomic-json.ts +22 -2
  100. package/src/shared/capacity-resilient-json.ts +102 -0
  101. package/src/shared/completion-owner.ts +14 -0
  102. package/src/shared/file-system-retry.ts +49 -1
  103. package/src/shared/fork-context.ts +42 -0
  104. package/src/shared/prompt-resources.ts +0 -40
  105. package/src/shared/settings.ts +3 -27
  106. package/src/shared/types.ts +88 -27
  107. package/src/shared/utils.ts +8 -0
  108. package/src/shared/watch-strategy.ts +10 -0
  109. package/src/slash/slash-commands.ts +45 -28
  110. package/src/slash/slash-live-state.ts +3 -0
  111. package/src/tui/fleet-status.ts +160 -45
  112. package/src/tui/fleet.ts +186 -28
  113. package/src/tui/render.ts +41 -10
  114. package/src/workflows/chat-progress.ts +7 -5
  115. package/src/workflows/scripted-workflow.ts +424 -125
  116. package/src/runs/foreground/chain-clarify.ts +0 -1354
  117. 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 availableNames(cwd: string, kind: "agent" | "chain"): string[] {
109
- const d = discoverAgentsAll(cwd);
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 findAgents(name: string, cwd: string, scope: AgentScope = "both"): AgentConfig[] {
115
- const d = discoverAgentsAll(cwd);
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 findChains(name: string, cwd: string, scope: AgentScope = "both"): ChainConfig[] {
129
- const raw = name.trim();
130
- const sanitized = sanitizeName(raw);
131
- return discoverAgentsAll(cwd).chains
132
- .filter((c) => (scope === "both" || c.source === "package" || c.source === scope) && (c.name === raw || c.name === sanitized))
133
- .sort((a, b) => a.source.localeCompare(b.source));
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 return "config.runner must be { type: 'pi' } or { type: 'external-cli', command: string, args?: string[], promptDelivery?: 'stdin' }.";
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 'external-cli' does not support Pi-only fields: ${unsupported.join(", ")}.`;
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((m) => m.name))];
625
- if (distinctNames.length > 1) {
626
- return result(`Ambiguous ${kind} alias or name '${name}': ${distinctNames.sort((a, b) => a.localeCompare(b)).join(", ")}`, true);
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
- return result(`${kind === "agent" ? "Agent" : "Chain"} '${name}' is read-only and cannot be modified. Create a same-named ${kind} in user or project scope to override it.`, true);
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((m) => `${m.source}: ${m.filePath}`).join("\n");
640
- return result(`${kind === "agent" ? "Agent" : "Chain"} '${name}' exists in both scopes. Specify agentScope: 'user' or 'project'.\n${paths}`, true);
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((m) => m.source === scope);
643
- if (scoped.length === 0) return result(`${kind === "agent" ? "Agent" : "Chain"} '${name}' not found in scope '${scope}'.`, true);
644
- if (scoped.length > 1) return result(`Multiple ${kind}s named '${name}' found in scope '${scope}': ${scoped.map((m) => m.filePath).join(", ")}`, true);
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 ext = kind === "agent" ? ".md" : currentPath.endsWith(".chain.json") ? ".chain.json" : ".chain.md";
657
- const filePath = path.join(path.dirname(currentPath), `${newName}${ext}`);
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
- "Chains:",
783
- ...(chains.length ? chains.map((c) => `- ${c.name} (${c.source}): ${c.description}`) : ["- (none)"]),
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 && !params.chainName) return result("Specify 'agent' or 'chainName' for get.", true);
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 hasBoth = Boolean(params.agent && params.chainName);
876
- const blocks: string[] = [];
877
- let anyFound = false;
878
- if (params.agent) {
879
- const matches = findAgents(params.agent, ctx.cwd, scope);
880
- const distinctNames = [...new Set(matches.map((agent) => agent.name))];
881
- if (distinctNames.length > 1) {
882
- const msg = `Ambiguous agent alias or name '${params.agent}': ${distinctNames.sort((a, b) => a.localeCompare(b)).join(", ")}`;
883
- if (!hasBoth) return result(msg, true);
884
- blocks.push(msg);
885
- } else if (!matches.length) {
886
- const msg = `Agent '${params.agent}' not found. Available: ${availableNames(ctx.cwd, "agent").join(", ") || "none"}.`;
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(blocks.join("\n\n"), !anyFound);
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
- const isChain = hasKey(cfg, "steps");
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 = isChain
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, isChain ? `${runtimeName}.chain.md` : `${runtimeName}.md`);
932
- if (fs.existsSync(targetPath)) return result(`File already exists at ${targetPath} but is not a valid ${isChain ? "chain" : "agent"} definition. Remove or rename it first.`, true);
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 (!isChain && d.builtin.some((a) => a.name === runtimeName)) warnings.push(`Note: this shadows the builtin agent '${runtimeName}'.`);
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 && !params.chainName) return result("Specify 'agent' or 'chainName' for update.", true);
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("chain", params.chainName!, findChains(params.chainName!, ctx.cwd, scopeHint ?? "both"), ctx.cwd, scopeHint);
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} chain '${target.name}'. Eject it to user or project scope first.`, true);
1046
- const updated: ChainConfig = { ...target, steps: [...target.steps] };
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
- let parsedSteps: ChainStepConfig[] | undefined;
1062
- if (hasKey(cfg, "steps")) {
1063
- const parsed = parseStepList(cfg.steps);
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 (parsedSteps) {
1073
- updated.steps = parsedSteps;
1074
- const missing = unknownChainAgents(ctx.cwd, updated.steps);
1075
- if (missing.length) warnings.push(`Warning: chain steps reference unknown agents: ${missing.join(", ")}.`);
1076
- warnings.push(...chainStepWarnings(ctx, updated.steps));
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("chain", target.filePath, updated.name, target.source, ctx.cwd);
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.filePath.endsWith(".chain.json") ? serializeJsonChain(updated) : serializeChain(updated), "utf-8");
849
+ fs.writeFileSync(updated.filePath, serializeAgent(updated, { preserveFrontmatterFields }), "utf-8");
1084
850
  const headline = updated.name === oldName
1085
- ? `Updated chain '${updated.name}' at ${updated.filePath}.`
1086
- : `Updated chain '${oldName}' to '${updated.name}' at ${updated.filePath}.`;
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 && !params.chainName) return result("Specify 'agent' or 'chainName' for delete.", true);
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
- if (params.agent) {
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 chain '${target.name}' at ${target.filePath}.`);
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: ${availableNames(ctx.cwd, "agent").join(", ") || "none"}.`, true);
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 or chain named '${runtimeName}' already exists in ${scope} scope. Remove or rename it first.`, true);
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: ${availableNames(ctx.cwd, "agent").join(", ") || "none"}.`, true);
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: ${availableNames(ctx.cwd, "agent").join(", ") || "none"}.`, true);
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: ${availableNames(ctx.cwd, "agent").join(", ") || "none"}.`, true);
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);