pi-subagents 0.56.0 → 0.58.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 (108) hide show
  1. package/CHANGELOG.md +92 -0
  2. package/agents/claude-code-writer.md +15 -0
  3. package/agents/claude-code.md +15 -0
  4. package/agents/codex-exec-writer.md +15 -0
  5. package/agents/codex-exec.md +15 -0
  6. package/agents/cursor-agent-writer.md +14 -0
  7. package/agents/cursor-agent.md +14 -0
  8. package/docs/agents.md +124 -21
  9. package/docs/configuration.md +37 -0
  10. package/docs/extension-api.md +41 -2
  11. package/docs/models.md +3 -3
  12. package/docs/observability.md +8 -7
  13. package/docs/tool-reference.md +14 -3
  14. package/docs/workflows.md +44 -0
  15. package/package.json +1 -1
  16. package/skills/pi-subagents/SKILL.md +2 -0
  17. package/skills/pi-subagents/references/execution-controls.md +2 -2
  18. package/skills/pi-subagents/references/management-authoring-rpc.md +1 -0
  19. package/skills/pi-subagents/references/prompting-and-roles.md +2 -2
  20. package/src/agents/agent-management.ts +36 -5
  21. package/src/agents/agent-refinements.ts +4 -4
  22. package/src/agents/agent-serializer.ts +5 -0
  23. package/src/agents/agents.ts +257 -51
  24. package/src/agents/builtin-names.ts +6 -0
  25. package/src/agents/runtime-agent-events.ts +70 -0
  26. package/src/agents/runtime-agent-registry.ts +18 -4
  27. package/src/api/agents.ts +10 -5
  28. package/src/api/preflight.ts +28 -3
  29. package/src/extension/config.ts +70 -0
  30. package/src/extension/doctor.ts +3 -3
  31. package/src/extension/index.ts +33 -8
  32. package/src/extension/public-execution.ts +29 -13
  33. package/src/extension/rpc.ts +55 -19
  34. package/src/extension/schemas.ts +6 -5
  35. package/src/extension/tool-description.ts +18 -12
  36. package/src/inspectors/herdr/actions.ts +2 -1
  37. package/src/inspectors/herdr/inspector-runner.ts +2 -10
  38. package/src/inspectors/herdr/session-roots-codec.ts +42 -0
  39. package/src/integrations/herdr-status.ts +51 -3
  40. package/src/runs/background/active-async-capacity.ts +77 -10
  41. package/src/runs/background/async-execution.ts +127 -19
  42. package/src/runs/background/async-job-tracker.ts +5 -0
  43. package/src/runs/background/async-resume.ts +6 -2
  44. package/src/runs/background/async-retention.ts +20 -3
  45. package/src/runs/background/async-status.ts +7 -0
  46. package/src/runs/background/chain-append.ts +2 -0
  47. package/src/runs/background/chain-root-attachment.ts +15 -1
  48. package/src/runs/background/fleet-view.ts +16 -10
  49. package/src/runs/background/inspect-rpc.ts +8 -8
  50. package/src/runs/background/notify.ts +26 -3
  51. package/src/runs/background/result-delivery-ownership.ts +45 -0
  52. package/src/runs/background/result-files.ts +27 -14
  53. package/src/runs/background/result-watcher.ts +36 -15
  54. package/src/runs/background/run-status.ts +33 -6
  55. package/src/runs/background/scheduled-runs.ts +7 -1
  56. package/src/runs/background/subagent-runner.ts +239 -56
  57. package/src/runs/background/wait-completions.ts +4 -0
  58. package/src/runs/foreground/execution.ts +92 -11
  59. package/src/runs/foreground/foreground-control.ts +6 -0
  60. package/src/runs/foreground/foreground-history.ts +22 -1
  61. package/src/runs/foreground/subagent-executor.ts +322 -102
  62. package/src/runs/foreground/workflow-detach-reconcile.ts +144 -18
  63. package/src/runs/shared/child-protocol.ts +21 -7
  64. package/src/runs/shared/claude-code-adapter.ts +129 -0
  65. package/src/runs/shared/codex-exec-adapter.ts +129 -0
  66. package/src/runs/shared/completion-guard.ts +4 -3
  67. package/src/runs/shared/cursor-agent-adapter.ts +114 -0
  68. package/src/runs/shared/dynamic-fanout.ts +3 -3
  69. package/src/runs/shared/external-cli-contract.ts +167 -0
  70. package/src/runs/shared/external-cli-preflight.ts +122 -0
  71. package/src/runs/shared/external-cli-runner.ts +348 -55
  72. package/src/runs/shared/fast-mode-extension.ts +5 -5
  73. package/src/runs/shared/launch-cwd.ts +16 -0
  74. package/src/runs/shared/long-running-guard.ts +2 -1
  75. package/src/runs/shared/mcp-config-sources.ts +386 -0
  76. package/src/runs/shared/mcp-direct-tool-allowlist.ts +155 -42
  77. package/src/runs/shared/model-exclusions.ts +69 -7
  78. package/src/runs/shared/model-fallback.ts +39 -5
  79. package/src/runs/shared/mutation-evidence.ts +7 -2
  80. package/src/runs/shared/nested-events.ts +3 -1
  81. package/src/runs/shared/nested-render.ts +2 -2
  82. package/src/runs/shared/parallel-utils.ts +8 -1
  83. package/src/runs/shared/pi-args.ts +61 -6
  84. package/src/runs/shared/process-signal.ts +13 -0
  85. package/src/runs/shared/run-history.ts +21 -1
  86. package/src/runs/shared/single-output.ts +17 -0
  87. package/src/runs/shared/subagent-prompt-runtime.ts +85 -9
  88. package/src/shared/fork-context.ts +21 -0
  89. package/src/shared/formatters.ts +13 -1
  90. package/src/shared/launch-contract.ts +4 -0
  91. package/src/shared/pruned-fork.ts +450 -0
  92. package/src/shared/session-file-trust.ts +19 -0
  93. package/src/shared/session-tokens.ts +14 -3
  94. package/src/shared/settings.ts +10 -2
  95. package/src/shared/shortcuts.ts +17 -0
  96. package/src/shared/types.ts +160 -10
  97. package/src/shared/utils.ts +6 -29
  98. package/src/shared/workflow-child-permit.ts +116 -0
  99. package/src/slash/delegation-adapters.ts +0 -1
  100. package/src/slash/slash-commands.ts +8 -6
  101. package/src/slash/subagents-admin.ts +3 -0
  102. package/src/tui/fleet-status.ts +27 -10
  103. package/src/tui/fleet-transcript.ts +11 -5
  104. package/src/tui/fleet.ts +28 -13
  105. package/src/tui/render.ts +55 -21
  106. package/src/workflows/scripted-workflow.ts +299 -31
  107. package/src/workflows/workflow-child-summary.ts +117 -0
  108. package/src/workflows/workflow-receipt.ts +155 -5
@@ -6,6 +6,16 @@ Parameters and actions for the `subagent` tool. These are what the LLM passes wh
6
6
 
7
7
  Chaining is code-driven through `workflowScript`. Use `await runs.run(...)` for sequential steps and `await runs.all([{ key, agent, task }, ...])` for ordinary parallel fanout. `runs.all` resolves to an ordered array, not a key map, so use indexes, destructuring, or `.map(...)`, not `results.<key>`. Do not read `.output` from an unawaited `runs.run` launch. Stored `runs.run` promises are only for the advanced rolling fanout pattern under [Workflow steering](#workflow-steering), where every promise is later observed with direct `await`, `Promise.race`, or `Promise.all`. Legacy top-level `chain`, `tasks`, and `parallel` inputs are not supported. Helper functions must be plain functions or explicit Promise chains. Nested `async function` helpers, async arrows, and async methods are rejected so child-launch tracking stays portable across Node and Bun.
8
8
 
9
+ Use `{ action: "validate", workflowScript }` to check statically decidable syntax and structure without launching children. It returns `{ ok, errors }` and fails the tool call when `ok` is false. Dynamic keys and values remain valid because runtime-only cases are not guessed.
10
+
11
+ Use `workflowScriptPath` instead of `workflowScript` to load the same JavaScript statement body from a file. The two fields are mutually exclusive. Relative paths resolve against the request `cwd`, and absolute paths pass through. The host reads the file before validation, scheduling, or sandbox execution. The workflow sandbox still has no filesystem access. Missing, unreadable, and empty files fail as file input errors.
12
+
13
+ ```js
14
+ { workflowScriptPath: "workflows/review.js", cwd: "/path/to/project" }
15
+ { action: "validate", workflowScriptPath: "workflows/review.js" }
16
+ { action: "schedule.create", every: "6h", workflowScriptPath: "workflows/review.js" }
17
+ ```
18
+
9
19
  ```js
10
20
  // One child; return the child promise explicitly
11
21
  { workflowScript: `return runs.run("main", { agent: "scout", task: "Analyze the auth flow" })` }
@@ -31,7 +41,7 @@ Chaining is code-driven through `workflowScript`. Use `await runs.run(...)` for
31
41
  | Param | Type | Default | Description |
32
42
  |-------|------|---------|-------------|
33
43
  | `agent` | string | - | Agent target for management actions. Workflow child agents are set inside `runs.run` or `runs.all`. |
34
- | `action` | string | - | Agent management (including `guide`, `children.list`, and `refine`/`refine.show`/`refine.rollback`), mission (`mission.create/list/show/update/resolve-decision/attach-run/close`), Herdr inspector (`inspector.open/status/close`), Herdr project pane (`project.open/status/close`), status/control, schedule, watchdog, or doctor action. |
44
+ | `action` | string | - | Offline workflow `validate`, agent management (including `guide`, `children.list`, and `refine`/`refine.show`/`refine.rollback`), mission (`mission.create/list/show/update/resolve-decision/attach-run/close`), Herdr inspector (`inspector.open/status/close`), Herdr project pane (`project.open/status/close`), status/control, schedule, watchdog, or doctor action. |
35
45
  | `topic` | `overview \| workflows \| agents \| missions \| observability \| tool-reference \| configuration \| models \| watchdog \| extension-api` | `overview` | Packaged guide topic for `action: "guide"`. |
36
46
  | `config` | object/string | - | Agent config for management create/update. |
37
47
  | `context` | `fresh \| fork` | global or per-agent default, else `fresh` | Explicit `fresh` or `fork` overrides every workflow child. When omitted, [`defaultSubagentContext`](configuration.md#defaultsubagentcontext) wins over each agent's `defaultContext`; `"fork"` creates a real branched session when the parent session file and current leaf exist, otherwise it falls back to `fresh`. Packaged `worker`, `oracle`, and `advisor` default to `fork`. |
@@ -149,9 +159,10 @@ Agent definitions are not loaded into context by default. Management actions let
149
159
  systemPrompt: "You are a code scout...",
150
160
  systemPromptMode: "replace",
151
161
  inheritProjectContext: false,
162
+ inheritGlobalContext: false,
152
163
  inheritSkills: false,
153
164
  model: "anthropic/claude-sonnet-4",
154
- fallbackModels: ["openai/gpt-5-mini", "anthropic/claude-haiku-4-5"],
165
+ fallbackModels: ["openai-codex/gpt-5.6-luna:low", "anthropic/claude-haiku-4-5"],
155
166
  tools: "read, bash, mcp:github/search_repositories",
156
167
  extensions: "",
157
168
  skills: "parallel-scout",
@@ -361,7 +372,7 @@ async: true
361
372
 
362
373
  Supported: status artifacts, stdout/stderr logs, timeout, and stop. Full stdout and stderr are written to log files, while the in-memory final stdout response and stderr error are limited to their last 64 KiB.
363
374
 
364
- Intentionally unsupported: foreground/clarify, steer/resume/interrupt-as-pause, Pi models/tools/extensions, skills, structured output, nested subagents, and fallback models.
375
+ Intentionally unsupported: native Pi child options such as model override, structured output, acceptance/agent contract, tool budgets, fast mode, fork context, skills, or native Pi tools unless the runner explicitly implements them. Foreground/clarify, steer/resume/interrupt-as-pause, nested subagents, and fallback models are also unsupported.
365
376
 
366
377
  ## Session sharing
367
378
 
package/docs/workflows.md CHANGED
@@ -39,6 +39,50 @@ All model-facing subagent execution is expressed through `workflowScript` in the
39
39
 
40
40
  Child results cross into the script as plain JSON data. Non-JSON host metadata is omitted, so use returned fields such as `runId`, `ok`, `output`, and `structuredOutput` for workflow control.
41
41
 
42
+ Validate a script without launching children:
43
+
44
+ ```js
45
+ subagent({ action: "validate", workflowScript: `
46
+ const results = await runs.all([{ key: "scan", agent: "scout", task: "Scan" }]);
47
+ return results[0].output;
48
+ ` });
49
+ ```
50
+
51
+ For a script stored in a file, use `workflowScriptPath` instead of `workflowScript`:
52
+
53
+ ```js
54
+ subagent({ workflowScriptPath: "workflows/review.js", cwd: "/path/to/project" });
55
+ subagent({ action: "validate", workflowScriptPath: "workflows/review.js" });
56
+ ```
57
+
58
+ The fields are mutually exclusive. Relative paths resolve against the request `cwd`; absolute paths pass through. The host reads the file before validation, schedule creation, or workflow sandbox execution. The sandbox still has no filesystem access. Missing, unreadable, and empty files return file input errors instead of script syntax errors.
59
+
60
+ ### Opt-in bounded workflows
61
+
62
+ Composite workflows have no default parent deadline. Add bounds only when the workflow contract calls for them:
63
+
64
+ ```js
65
+ subagent({
66
+ workflowScript: `
67
+ const scan = await runs.run("scan", { agent: "scout", task: "Inspect the named files." });
68
+ return runs.run("review", { agent: "reviewer", task: "Review:\n" + scan.output });
69
+ `,
70
+ timeoutMs: 900000,
71
+ turnBudget: { maxTurns: 30, graceTurns: 2 },
72
+ toolBudget: { soft: 40, hard: 60 },
73
+ usageBudget: { tokens: { soft: 100000, hard: 150000 } }
74
+ });
75
+ ```
76
+
77
+ - `timeoutMs` sets the workflow deadline and bounds child deadlines to the remaining time.
78
+ - `turnBudget` and `toolBudget` become defaults for each child unless that child supplies a narrower value.
79
+ - `usageBudget` accounts for reported usage across completed workflow children. Once exhausted, it rejects later child launches but does not stop children that are already running.
80
+ - Budget and timeout stops return a structured `terminalOutcome` with `state: "partial"` and reason `budget_exhausted` or `timeout`. Workflow receipts keep settled child evidence for recovery.
81
+
82
+ These controls are opt-in. Avoid tight hard budgets for mutation-capable workers unless the workflow has an explicit checkpoint and handoff path.
83
+
84
+ The result is `{ ok, errors }`. Invalid scripts return a tool error and include line and column data when available. Validation checks syntax, portable nested-async rules, literal `runs.run` and `runs.all` keys, duplicate literal keys in one `runs.all` group, direct keyed access to a known `runs.all` result, and statically clear non-JSON boundary values. Dynamic keys and other runtime-only values are accepted without a warning. Validation does not discover agents, launch children, or create run artifacts.
85
+
42
86
  ```js
43
87
  subagent({ workflowScript: `
44
88
  const scan = await runs.run("scan", { agent: "scout", task: "Scan the codebase" });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-subagents",
3
- "version": "0.56.0",
3
+ "version": "0.58.0",
4
4
  "description": "Pi extension for single-agent delegation and scripted multi-agent workflows",
5
5
  "author": "Nico Bailon",
6
6
  "license": "MIT",
@@ -31,6 +31,8 @@ Read the matching reference file before acting. Paths are relative to this `SKIL
31
31
 
32
32
  For broad or uncertain requests, read more than one reference. For complex work, start with `references/prompting-and-roles.md` and `references/execution-controls.md`, then consult `references/constraints-and-recipes.md` before launching or reviewing child work.
33
33
 
34
+ External CLI agents such as `codex-exec`, `codex-exec-writer`, `claude-code`, and `cursor-agent` use their own runner contract. Do not pass native Pi child options such as model override, structured output, acceptance/agent contract, tool budgets, fast mode, fork context, skills, or native Pi tools unless that runner explicitly implements them.
35
+
34
36
  ## Always-on constraints
35
37
 
36
38
  - Keep the parent as orchestrator and final decision-maker.
@@ -24,7 +24,7 @@ Project settings resolve from the nearest parent directory containing `.pi` or `
24
24
 
25
25
  An agent may set `runner.type: external-cli` with a non-empty `command`, optional string `args`, and `promptDelivery: stdin` (the default). The command runs with `shell: false`, inherits the resolved cwd and environment, and receives the combined agent instructions and task through stdin. It must already be installed; pi-subagents adds no CLI dependency.
26
26
 
27
- External CLI profiles are async-only and one-shot. They support lifecycle artifacts, stdout/stderr logs, timeout, and stop. Full stdout and stderr are retained in their log files, while the final stdout response and stderr error kept in memory are each limited to their last 64 KiB. They do not support foreground/clarify, steer/resume/interrupt-as-pause, Pi models/tools/extensions/skills, tool or turn budgets, structured output, nested subagents, fallbacks, or sessions.
27
+ External CLI profiles are async-only and one-shot. They support lifecycle artifacts, stdout/stderr logs, timeout, and stop. Full stdout and stderr are retained in their log files, while the final stdout response and stderr error kept in memory are each limited to their last 64 KiB. They do not support native Pi child options such as model override, structured output, acceptance/agent contract, tool budgets, fast mode, fork context, skills, or native Pi tools unless the runner explicitly implements them. Foreground/clarify, steer/resume/interrupt-as-pause, nested subagents, fallbacks, and sessions are also unsupported.
28
28
 
29
29
  ### External job profiles
30
30
 
@@ -93,7 +93,7 @@ return runs.run("cross-oracle", {
93
93
  });
94
94
  ```
95
95
 
96
- Keyed resume reads that one exact receipt and revalidates the retained run at launch. It fails when the workflow or key is missing, the receipt is stale, `latest` is not `true`, or the recorded child is no longer resumable. Foreground workflow results expose the same receipt in `details.workflow.receipt`, but cross-workflow keyed lookup requires the durable receipt from an async workflow.
96
+ Keyed resume reads that one exact receipt and revalidates the retained run at launch. It fails when the workflow or key is missing, the receipt is stale, `latest` is not `true`, or the recorded child is no longer resumable. The receipt is terminal-only: if `status.json` or `events.jsonl` exists without it, the workflow may still be active or terminal receipt writing may have failed. Use direct child run IDs from status/events for direct resume after the normal retained-child checks; do not reconstruct keyed entries from those files. Foreground workflow results expose the same receipt in `details.workflow.receipt`, but cross-workflow keyed lookup requires the durable receipt from an async workflow.
97
97
 
98
98
  ### Async/background
99
99
 
@@ -102,6 +102,7 @@ thinking: high
102
102
  tools: read, grep, find, ls, bash
103
103
  systemPromptMode: replace
104
104
  inheritProjectContext: true
105
+ inheritGlobalContext: false
105
106
  inheritSkills: false
106
107
  skills: safe-bash, review-checklist
107
108
  skillPath: ./skills, ../shared-skills
@@ -230,7 +230,7 @@ Direct settings example:
230
230
  "reviewer": {
231
231
  "model": "anthropic/claude-sonnet-4",
232
232
  "thinking": "high",
233
- "fallbackModels": ["openai/gpt-5-mini"],
233
+ "fallbackModels": ["openai-codex/gpt-5.6-luna:low"],
234
234
  "acceptanceRole": "read-only"
235
235
  }
236
236
  }
@@ -239,7 +239,7 @@ Direct settings example:
239
239
  ```
240
240
 
241
241
  Useful override fields: `description`, `model`, `fallbackModels`, `thinking`,
242
- `systemPromptMode`, `inheritProjectContext`, `inheritSkills`, `defaultContext`,
242
+ `systemPromptMode`, `inheritProjectContext`, `inheritGlobalContext`, `inheritSkills`, `defaultContext`,
243
243
  `acceptanceRole`, `disabled`, `skills`, `tools`, `extensions`, and `systemPrompt`.
244
244
  `description` replaces the discovered description for builtin and custom agents
245
245
  in `list` output, which is useful for deployment-specific routing notes.
@@ -33,6 +33,7 @@ import { resolveSubagentModelOverride, type ParentModel } from "../runs/shared/m
33
33
  import { validateToolBudgetConfig } from "../runs/shared/tool-budget.ts";
34
34
  import { resolveTurnBudgetConfig } from "../runs/shared/turn-budget.ts";
35
35
  import { validateAcceptanceInput } from "../runs/shared/acceptance.ts";
36
+ import { CODE_OWNED_EXTERNAL_CLI_ADAPTER_LABEL, isCodeOwnedExternalCliAdapterId, validateCodeOwnedProfileRunner } from "../runs/shared/external-cli-contract.ts";
36
37
  import type { AcceptanceInput, Details, ExtensionConfig, ToolBudgetConfig } from "../shared/types.ts";
37
38
  import { getProjectConfigDir } from "../shared/utils.ts";
38
39
  import { capabilityCeilingAgentRestrictionSources, isAgentAllowedByCapabilityCeiling, resolveCurrentSubagentCapabilityCeiling } from "../runs/shared/capability-ceiling.ts";
@@ -229,6 +230,7 @@ export function editableAgentConfig(agent: AgentConfig): AgentConfig {
229
230
  thinking: _thinking,
230
231
  systemPromptMode: _systemPromptMode,
231
232
  inheritProjectContext: _inheritProjectContext,
233
+ inheritGlobalContext: _inheritGlobalContext,
232
234
  inheritSkills: _inheritSkills,
233
235
  defaultContext: _defaultContext,
234
236
  acceptanceRole: _acceptanceRole,
@@ -239,6 +241,7 @@ export function editableAgentConfig(agent: AgentConfig): AgentConfig {
239
241
  tools: _tools,
240
242
  mcpDirectTools: _mcpDirectTools,
241
243
  subagentOnlyExtensions: _subagentOnlyExtensions,
244
+ mutationTools: _mutationTools,
242
245
  completionGuard: _completionGuard,
243
246
  ...editable
244
247
  } = withoutExtensions;
@@ -259,6 +262,7 @@ export function editableAgentConfig(agent: AgentConfig): AgentConfig {
259
262
  ...(base.thinking !== undefined ? { thinking: base.thinking } : {}),
260
263
  systemPromptMode: base.systemPromptMode,
261
264
  inheritProjectContext: base.inheritProjectContext,
265
+ inheritGlobalContext: base.inheritGlobalContext,
262
266
  inheritSkills: base.inheritSkills,
263
267
  ...(base.defaultContext !== undefined ? { defaultContext: base.defaultContext } : {}),
264
268
  ...(base.acceptanceRole !== undefined ? { acceptanceRole: base.acceptanceRole } : {}),
@@ -270,6 +274,7 @@ export function editableAgentConfig(agent: AgentConfig): AgentConfig {
270
274
  ...(base.mcpDirectTools !== undefined ? { mcpDirectTools: [...base.mcpDirectTools] } : {}),
271
275
  ...(base.extensions !== undefined ? { extensions: [...base.extensions] } : {}),
272
276
  ...(base.subagentOnlyExtensions !== undefined ? { subagentOnlyExtensions: [...base.subagentOnlyExtensions] } : {}),
277
+ ...(base.mutationTools !== undefined ? { mutationTools: [...base.mutationTools] } : {}),
273
278
  ...(base.completionGuard !== undefined ? { completionGuard: base.completionGuard } : {}),
274
279
  }, agent.filePath);
275
280
  }
@@ -302,6 +307,7 @@ export function preservedAgentFrontmatterFields(agent: AgentConfig, cfg: Record<
302
307
  if (hasKey(cfg, "skillPath")) changed("skillPath");
303
308
  if (hasKey(cfg, "extensions")) changed("extensions");
304
309
  if (hasKey(cfg, "subagentOnlyExtensions")) changed("subagentOnlyExtensions");
310
+ if (hasKey(cfg, "mutationTools")) changed("mutationTools");
305
311
  if (hasKey(cfg, "thinking")) {
306
312
  changed("thinking");
307
313
  if (cfg.thinking === "off") fields.add("thinking");
@@ -314,6 +320,10 @@ export function preservedAgentFrontmatterFields(agent: AgentConfig, cfg: Record<
314
320
  changed("inheritProjectContext");
315
321
  fields.add("inheritProjectContext");
316
322
  }
323
+ if (hasKey(cfg, "inheritGlobalContext")) {
324
+ changed("inheritGlobalContext");
325
+ fields.add("inheritGlobalContext");
326
+ }
317
327
  if (hasKey(cfg, "inheritSkills")) {
318
328
  changed("inheritSkills");
319
329
  fields.add("inheritSkills");
@@ -378,15 +388,17 @@ function applyAgentConfig(target: AgentConfig, cfg: Record<string, unknown>): st
378
388
  if (runner.type === "pi" && Object.keys(runner).every((key) => key === "type")) target.runner = { type: "pi" };
379
389
  else if (runner.type === "external-cli" && typeof runner.command === "string" && runner.command.trim()
380
390
  && (runner.args === undefined || (Array.isArray(runner.args) && runner.args.every((arg) => typeof arg === "string")))
391
+ && (runner.adapter === undefined || isCodeOwnedExternalCliAdapterId(runner.adapter))
392
+ && (runner.adapter === undefined || runner.args === undefined || runner.args.length === 0)
381
393
  && (runner.promptDelivery === undefined || runner.promptDelivery === "stdin")
382
- && Object.keys(runner).every((key) => ["type", "command", "args", "promptDelivery"].includes(key))) {
394
+ && Object.keys(runner).every((key) => ["type", "adapter", "command", "args", "promptDelivery"].includes(key))) {
383
395
  const runnerArgs = Array.isArray(runner.args) ? runner.args.filter((arg): arg is string => typeof arg === "string") : undefined;
384
- target.runner = { type: "external-cli", command: runner.command.trim(), ...(runnerArgs?.length ? { args: runnerArgs } : {}), ...(runner.promptDelivery ? { promptDelivery: "stdin" } : {}) };
396
+ target.runner = { type: "external-cli", ...(isCodeOwnedExternalCliAdapterId(runner.adapter) ? { adapter: runner.adapter } : {}), command: runner.command.trim(), ...(runnerArgs?.length ? { args: runnerArgs } : {}), ...(runner.promptDelivery ? { promptDelivery: "stdin" } : {}) };
385
397
  } else if (runner.type === "external-job" && typeof runner.provider === "string" && runner.provider.trim() === runner.provider && runner.provider
386
398
  && (runner.options === undefined || (runner.options && typeof runner.options === "object" && !Array.isArray(runner.options) && isJsonSerializable(runner.options)))
387
399
  && Object.keys(runner).every((key) => ["type", "provider", "options"].includes(key))) {
388
400
  target.runner = { type: "external-job", provider: runner.provider, ...(runner.options ? { options: runner.options as Record<string, unknown> } : {}) };
389
- } 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 }.";
401
+ } else return `config.runner must be { type: 'pi' }, { type: 'external-cli', adapter?: ${CODE_OWNED_EXTERNAL_CLI_ADAPTER_LABEL}, command: string, args?: string[], promptDelivery?: 'stdin' }, or { type: 'external-job', provider: string, options?: object }.`;
390
402
  } else return "config.runner must be an object, false, or empty string when provided.";
391
403
  }
392
404
  if (hasKey(cfg, "model")) {
@@ -454,6 +466,12 @@ function applyAgentConfig(target: AgentConfig, cfg: Record<string, unknown>): st
454
466
  else if (typeof cfg.subagentOnlyExtensions === "string") target.subagentOnlyExtensions = parseCsv(cfg.subagentOnlyExtensions);
455
467
  else return "config.subagentOnlyExtensions must be a comma-separated string, empty string, or false when provided.";
456
468
  }
469
+ if (hasKey(cfg, "mutationTools")) {
470
+ if (cfg.mutationTools === false) delete target.mutationTools;
471
+ else if (cfg.mutationTools === "") target.mutationTools = [];
472
+ else if (typeof cfg.mutationTools === "string") target.mutationTools = parseCsv(cfg.mutationTools);
473
+ else return "config.mutationTools must be a comma-separated string, empty string, or false when provided.";
474
+ }
457
475
  if (hasKey(cfg, "thinking")) {
458
476
  if (cfg.thinking === false || cfg.thinking === "") delete target.thinking;
459
477
  else if (typeof cfg.thinking === "string") {
@@ -470,6 +488,10 @@ function applyAgentConfig(target: AgentConfig, cfg: Record<string, unknown>): st
470
488
  if (typeof cfg.inheritProjectContext !== "boolean") return "config.inheritProjectContext must be a boolean when provided.";
471
489
  target.inheritProjectContext = cfg.inheritProjectContext;
472
490
  }
491
+ if (hasKey(cfg, "inheritGlobalContext")) {
492
+ if (typeof cfg.inheritGlobalContext !== "boolean") return "config.inheritGlobalContext must be a boolean when provided.";
493
+ target.inheritGlobalContext = cfg.inheritGlobalContext;
494
+ }
473
495
  if (hasKey(cfg, "inheritSkills")) {
474
496
  if (typeof cfg.inheritSkills !== "boolean") return "config.inheritSkills must be a boolean when provided.";
475
497
  target.inheritSkills = cfg.inheritSkills;
@@ -558,6 +580,7 @@ function applyAgentConfig(target: AgentConfig, cfg: Record<string, unknown>): st
558
580
  target.thinking ? "thinking" : undefined,
559
581
  target.extensions?.length ? "extensions" : undefined,
560
582
  target.subagentOnlyExtensions?.length ? "subagentOnlyExtensions" : undefined,
583
+ target.mutationTools?.length ? "mutationTools" : undefined,
561
584
  target.skills?.length || target.skillPath?.length ? "skills" : undefined,
562
585
  target.maxSubagentDepth !== undefined ? "maxSubagentDepth" : undefined,
563
586
  target.completionGuard !== undefined ? "completionGuard" : undefined,
@@ -696,6 +719,7 @@ function formatAgentDetail(agent: AgentConfig): string {
696
719
  if (agent.runner?.type === "external-job" && agent.runner.options) lines.push(`Runner options: ${JSON.stringify(agent.runner.options)}`);
697
720
  }
698
721
  lines.push(`Inherit project context: ${agent.inheritProjectContext ? "true" : "false"}`);
722
+ lines.push(`Inherit global context: ${agent.inheritGlobalContext ? "true" : "false"}`);
699
723
  lines.push(`Inherit skills: ${agent.inheritSkills ? "true" : "false"}`);
700
724
  if (agent.defaultContext) lines.push(`Default context: ${agent.defaultContext}`);
701
725
  if (agent.defaultAsync !== undefined) lines.push(`Async: ${agent.defaultAsync ? "true" : "false"}`);
@@ -706,6 +730,7 @@ function formatAgentDetail(agent: AgentConfig): string {
706
730
  if (agent.source === "builtin") lines.push(`Disabled: ${agent.disabled ? "true" : "false"}`);
707
731
  if (agent.extensions !== undefined) lines.push(`Extensions: ${agent.extensions.length ? agent.extensions.join(", ") : "(none)"}`);
708
732
  if (agent.subagentOnlyExtensions !== undefined) lines.push(`Subagent-only extensions: ${agent.subagentOnlyExtensions.length ? agent.subagentOnlyExtensions.join(", ") : "(none)"}`);
733
+ if (agent.mutationTools !== undefined) lines.push(`Mutation tools: ${agent.mutationTools.length ? agent.mutationTools.join(", ") : "(none)"}`);
709
734
  if (agent.thinking) lines.push(`Thinking: ${agent.thinking}`);
710
735
  if (agent.output) lines.push(`Output: ${agent.output}`);
711
736
  if (agent.outputMode) lines.push(`Output mode: ${agent.outputMode}`);
@@ -784,13 +809,14 @@ function handleModels(params: ManagementParams, ctx: ManagementContext): AgentTo
784
809
 
785
810
  const discovered = discoverAgentsAll(ctx.cwd);
786
811
  const builtinByName = new Map(discovered.builtin.map((agent) => [agent.name, agent]));
812
+ const resolveBuiltinModelAgent = (name: string): AgentConfig | undefined => builtinByName.get(name) ?? resolveAgentName(name, discovered.builtin).agent;
787
813
  const availableModels = ctx.modelRegistry.getAvailable().map(toModelInfo);
788
814
  const currentModel = ctx.model ? { provider: ctx.model.provider, id: ctx.model.id } : undefined;
789
815
  const preferredProvider = ctx.model?.provider;
790
816
  const names = requestedAgent ? [requestedAgent] : [...BUILTIN_AGENT_NAMES];
791
817
 
792
818
  if (requestedAgent) {
793
- const agent = builtinByName.get(requestedAgent);
819
+ const agent = resolveBuiltinModelAgent(requestedAgent);
794
820
  if (!agent) return result(`Builtin agent '${requestedAgent}' not found.`, true);
795
821
  const resolvedModel = resolveSubagentModelOverride(agent.model, currentModel, availableModels, preferredProvider);
796
822
  const lines = [
@@ -824,7 +850,7 @@ function handleModels(params: ManagementParams, ctx: ManagementContext): AgentTo
824
850
  ];
825
851
 
826
852
  for (const name of names) {
827
- const agent = builtinByName.get(name);
853
+ const agent = resolveBuiltinModelAgent(name);
828
854
  if (!agent) {
829
855
  lines.push(name);
830
856
  lines.push(" model:");
@@ -911,10 +937,13 @@ export function handleCreate(params: ManagementParams, ctx: ManagementContext):
911
937
  systemPrompt: "",
912
938
  systemPromptMode: defaultSystemPromptMode(name),
913
939
  inheritProjectContext: defaultInheritProjectContext(name),
940
+ inheritGlobalContext: false,
914
941
  inheritSkills: defaultInheritSkills(),
915
942
  };
916
943
  const applyError = applyAgentConfig(agent, cfg);
917
944
  if (applyError) return result(applyError, true);
945
+ const profileError = validateCodeOwnedProfileRunner(agent);
946
+ if (profileError) return result(profileError, true);
918
947
  const mw = modelWarning(ctx, agent.model);
919
948
  if (mw) warnings.push(mw);
920
949
  const fmw = fallbackModelsWarning(ctx, agent.fallbackModels);
@@ -966,6 +995,8 @@ export function handleUpdate(params: ManagementParams, ctx: ManagementContext):
966
995
  if (newPackageName !== undefined) updated.packageName = newPackageName;
967
996
  else delete updated.packageName;
968
997
  updated.name = buildRuntimeName(newLocalName, newPackageName);
998
+ const profileError = validateCodeOwnedProfileRunner(updated);
999
+ if (profileError) return result(profileError, true);
969
1000
  if (hasKey(cfg, "description")) updated.description = (cfg.description as string).trim();
970
1001
  if (hasKey(cfg, "model")) {
971
1002
  const mw = modelWarning(ctx, updated.model);
@@ -2,7 +2,7 @@ import { createHash, randomUUID } from "node:crypto";
2
2
  import * as fs from "node:fs";
3
3
  import * as path from "node:path";
4
4
  import type { AgentToolResult } from "@earendil-works/pi-agent-core";
5
- import { discoverAgents, resolveAgentName, type AgentConfig } from "./agents.ts";
5
+ import { discoverAgents, formatUnknownAgentError, resolveAgentName, unknownAgentDiagnosticContext, type AgentConfig } from "./agents.ts";
6
6
  import { getProjectSubagentsDir } from "../shared/artifacts.ts";
7
7
  import type { Details, JsonSchemaObject, SingleResult, SubagentState } from "../shared/types.ts";
8
8
 
@@ -536,10 +536,10 @@ function guidanceFromProposal(proposal: RefinementProposal): string {
536
536
  }
537
537
 
538
538
  function resolveOneAgent(cwd: string, agentName: string): { ok: true; agent: AgentConfig } | { ok: false; error: string } {
539
- const agents = discoverAgents(cwd, "both").agents;
540
- const resolved = resolveAgentName(agentName, agents);
539
+ const discovered = discoverAgents(cwd, "both");
540
+ const resolved = resolveAgentName(agentName, discovered.agents);
541
541
  if (resolved.error) return { ok: false, error: resolved.error };
542
- if (!resolved.agent) return { ok: false, error: `Unknown agent: ${agentName}` };
542
+ if (!resolved.agent) return { ok: false, error: formatUnknownAgentError(agentName, unknownAgentDiagnosticContext(discovered)) };
543
543
  return { ok: true, agent: resolved.agent };
544
544
  }
545
545
 
@@ -15,6 +15,7 @@ export const KNOWN_FIELDS = new Set([
15
15
  "thinking",
16
16
  "systemPromptMode",
17
17
  "inheritProjectContext",
18
+ "inheritGlobalContext",
18
19
  "inheritSkills",
19
20
  "defaultContext",
20
21
  "async",
@@ -28,6 +29,7 @@ export const KNOWN_FIELDS = new Set([
28
29
  "skillPath",
29
30
  "extensions",
30
31
  "subagentOnlyExtensions",
32
+ "mutationTools",
31
33
  "output",
32
34
  "outputMode",
33
35
  "defaultReads",
@@ -78,6 +80,7 @@ export function serializeAgent(config: AgentConfig, options: SerializeAgentOptio
78
80
  }
79
81
  if (!preservingExistingFrontmatter || preserve("systemPromptMode")) lines.push(`systemPromptMode: ${config.systemPromptMode}`);
80
82
  if (!preservingExistingFrontmatter || preserve("inheritProjectContext")) lines.push(`inheritProjectContext: ${config.inheritProjectContext ? "true" : "false"}`);
83
+ if (config.inheritGlobalContext || preserve("inheritGlobalContext")) lines.push(`inheritGlobalContext: ${config.inheritGlobalContext ? "true" : "false"}`);
81
84
  if (!preservingExistingFrontmatter || preserve("inheritSkills")) lines.push(`inheritSkills: ${config.inheritSkills ? "true" : "false"}`);
82
85
  if (config.defaultContext || preserve("defaultContext")) lines.push(`defaultContext: ${config.defaultContext ?? ""}`);
83
86
  if (config.runner || preserve("runner")) {
@@ -114,6 +117,8 @@ export function serializeAgent(config: AgentConfig, options: SerializeAgentOptio
114
117
  const subagentOnlyExtensionsValue = joinComma(config.subagentOnlyExtensions);
115
118
  lines.push(`subagentOnlyExtensions: ${subagentOnlyExtensionsValue ?? ""}`);
116
119
  }
120
+ const mutationToolsValue = joinComma(config.mutationTools);
121
+ if (mutationToolsValue || preserve("mutationTools")) lines.push(`mutationTools: ${mutationToolsValue ?? ""}`);
117
122
 
118
123
  if (config.output || preserve("output")) lines.push(`output: ${config.output ?? ""}`);
119
124
  if (config.outputMode || preserve("outputMode")) lines.push(`outputMode: ${config.outputMode ?? ""}`);