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.
- package/CHANGELOG.md +92 -0
- package/agents/claude-code-writer.md +15 -0
- package/agents/claude-code.md +15 -0
- package/agents/codex-exec-writer.md +15 -0
- package/agents/codex-exec.md +15 -0
- package/agents/cursor-agent-writer.md +14 -0
- package/agents/cursor-agent.md +14 -0
- package/docs/agents.md +124 -21
- package/docs/configuration.md +37 -0
- package/docs/extension-api.md +41 -2
- package/docs/models.md +3 -3
- package/docs/observability.md +8 -7
- package/docs/tool-reference.md +14 -3
- package/docs/workflows.md +44 -0
- package/package.json +1 -1
- package/skills/pi-subagents/SKILL.md +2 -0
- package/skills/pi-subagents/references/execution-controls.md +2 -2
- package/skills/pi-subagents/references/management-authoring-rpc.md +1 -0
- package/skills/pi-subagents/references/prompting-and-roles.md +2 -2
- package/src/agents/agent-management.ts +36 -5
- package/src/agents/agent-refinements.ts +4 -4
- package/src/agents/agent-serializer.ts +5 -0
- package/src/agents/agents.ts +257 -51
- package/src/agents/builtin-names.ts +6 -0
- package/src/agents/runtime-agent-events.ts +70 -0
- package/src/agents/runtime-agent-registry.ts +18 -4
- package/src/api/agents.ts +10 -5
- package/src/api/preflight.ts +28 -3
- package/src/extension/config.ts +70 -0
- package/src/extension/doctor.ts +3 -3
- package/src/extension/index.ts +33 -8
- package/src/extension/public-execution.ts +29 -13
- package/src/extension/rpc.ts +55 -19
- package/src/extension/schemas.ts +6 -5
- package/src/extension/tool-description.ts +18 -12
- package/src/inspectors/herdr/actions.ts +2 -1
- package/src/inspectors/herdr/inspector-runner.ts +2 -10
- package/src/inspectors/herdr/session-roots-codec.ts +42 -0
- package/src/integrations/herdr-status.ts +51 -3
- package/src/runs/background/active-async-capacity.ts +77 -10
- package/src/runs/background/async-execution.ts +127 -19
- package/src/runs/background/async-job-tracker.ts +5 -0
- package/src/runs/background/async-resume.ts +6 -2
- package/src/runs/background/async-retention.ts +20 -3
- package/src/runs/background/async-status.ts +7 -0
- package/src/runs/background/chain-append.ts +2 -0
- package/src/runs/background/chain-root-attachment.ts +15 -1
- package/src/runs/background/fleet-view.ts +16 -10
- package/src/runs/background/inspect-rpc.ts +8 -8
- package/src/runs/background/notify.ts +26 -3
- package/src/runs/background/result-delivery-ownership.ts +45 -0
- package/src/runs/background/result-files.ts +27 -14
- package/src/runs/background/result-watcher.ts +36 -15
- package/src/runs/background/run-status.ts +33 -6
- package/src/runs/background/scheduled-runs.ts +7 -1
- package/src/runs/background/subagent-runner.ts +239 -56
- package/src/runs/background/wait-completions.ts +4 -0
- package/src/runs/foreground/execution.ts +92 -11
- package/src/runs/foreground/foreground-control.ts +6 -0
- package/src/runs/foreground/foreground-history.ts +22 -1
- package/src/runs/foreground/subagent-executor.ts +322 -102
- package/src/runs/foreground/workflow-detach-reconcile.ts +144 -18
- package/src/runs/shared/child-protocol.ts +21 -7
- package/src/runs/shared/claude-code-adapter.ts +129 -0
- package/src/runs/shared/codex-exec-adapter.ts +129 -0
- package/src/runs/shared/completion-guard.ts +4 -3
- package/src/runs/shared/cursor-agent-adapter.ts +114 -0
- package/src/runs/shared/dynamic-fanout.ts +3 -3
- package/src/runs/shared/external-cli-contract.ts +167 -0
- package/src/runs/shared/external-cli-preflight.ts +122 -0
- package/src/runs/shared/external-cli-runner.ts +348 -55
- package/src/runs/shared/fast-mode-extension.ts +5 -5
- package/src/runs/shared/launch-cwd.ts +16 -0
- package/src/runs/shared/long-running-guard.ts +2 -1
- package/src/runs/shared/mcp-config-sources.ts +386 -0
- package/src/runs/shared/mcp-direct-tool-allowlist.ts +155 -42
- package/src/runs/shared/model-exclusions.ts +69 -7
- package/src/runs/shared/model-fallback.ts +39 -5
- package/src/runs/shared/mutation-evidence.ts +7 -2
- package/src/runs/shared/nested-events.ts +3 -1
- package/src/runs/shared/nested-render.ts +2 -2
- package/src/runs/shared/parallel-utils.ts +8 -1
- package/src/runs/shared/pi-args.ts +61 -6
- package/src/runs/shared/process-signal.ts +13 -0
- package/src/runs/shared/run-history.ts +21 -1
- package/src/runs/shared/single-output.ts +17 -0
- package/src/runs/shared/subagent-prompt-runtime.ts +85 -9
- package/src/shared/fork-context.ts +21 -0
- package/src/shared/formatters.ts +13 -1
- package/src/shared/launch-contract.ts +4 -0
- package/src/shared/pruned-fork.ts +450 -0
- package/src/shared/session-file-trust.ts +19 -0
- package/src/shared/session-tokens.ts +14 -3
- package/src/shared/settings.ts +10 -2
- package/src/shared/shortcuts.ts +17 -0
- package/src/shared/types.ts +160 -10
- package/src/shared/utils.ts +6 -29
- package/src/shared/workflow-child-permit.ts +116 -0
- package/src/slash/delegation-adapters.ts +0 -1
- package/src/slash/slash-commands.ts +8 -6
- package/src/slash/subagents-admin.ts +3 -0
- package/src/tui/fleet-status.ts +27 -10
- package/src/tui/fleet-transcript.ts +11 -5
- package/src/tui/fleet.ts +28 -13
- package/src/tui/render.ts +55 -21
- package/src/workflows/scripted-workflow.ts +299 -31
- package/src/workflows/workflow-child-summary.ts +117 -0
- package/src/workflows/workflow-receipt.ts +155 -5
package/docs/tool-reference.md
CHANGED
|
@@ -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 | - |
|
|
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-
|
|
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:
|
|
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
|
@@ -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
|
|
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
|
|
|
@@ -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-
|
|
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
|
|
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 =
|
|
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 =
|
|
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
|
|
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:
|
|
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 ?? ""}`);
|