pi-subagents 0.81.0 → 0.82.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 +38 -0
- package/bundle/agents.js +3 -3
- package/bundle/child-tool-plan.js +2 -2
- package/bundle/{chunk-LSRUTHT7.js → chunk-43SCHIMD.js} +1 -1
- package/bundle/{chunk-G5DKFLLI.js → chunk-4SDU2BZX.js} +15 -10
- package/bundle/{chunk-V2JWDB5U.js → chunk-52UBALJL.js} +2 -2
- package/bundle/{chunk-3DC5KBVP.js → chunk-5TYMSZX3.js} +1 -1
- package/bundle/{chunk-CEKS3TFO.js → chunk-7IVSUMQQ.js} +6 -6
- package/bundle/{chunk-KSWQVGDW.js → chunk-7PCKZHR5.js} +225 -18
- package/bundle/{chunk-563BHAZ5.js → chunk-7YMXOYEU.js} +33 -25
- package/bundle/{chunk-XMFLCKMQ.js → chunk-B5EKXDHE.js} +336 -130
- package/bundle/{chunk-ZBC44JJ3.js → chunk-CIZQXJ5Q.js} +22 -9
- package/bundle/{chunk-L5ZHE4L4.js → chunk-GC4DSPMH.js} +4 -4
- package/bundle/{chunk-ARRKBB2B.js → chunk-GJECHSAR.js} +2 -2
- package/bundle/{chunk-2ZSNH26H.js → chunk-M2CTFYT3.js} +7 -7
- package/bundle/{chunk-TUWLXTAM.js → chunk-MHZ4G23A.js} +681 -320
- package/bundle/{chunk-WSBNENFS.js → chunk-NGGAFDL6.js} +14 -4
- package/bundle/{chunk-VJHOELQP.js → chunk-NRQ75MGH.js} +160 -78
- package/bundle/{chunk-BJN5LUQY.js → chunk-OYF6AFDY.js} +1 -1
- package/bundle/{chunk-EYNJ6VL6.js → chunk-PLCCBLPY.js} +2 -2
- package/bundle/{chunk-74RT5YQE.js → chunk-RMQFMMAT.js} +123 -33
- package/bundle/{chunk-V6MR5TOQ.js → chunk-SIJ4S3EN.js} +25 -19
- package/bundle/{chunk-NNLASTQE.js → chunk-VH5OAWVG.js} +19 -5
- package/bundle/{chunk-BPS2AGBZ.js → chunk-W2SZ22EM.js} +93 -52
- package/bundle/{chunk-V3BIUPLU.js → chunk-WVD4UEL4.js} +256 -52
- package/bundle/{chunk-ZP3HODFH.js → chunk-XSWOTIA3.js} +1 -1
- package/bundle/{chunk-3IF2PGW7.js → chunk-YKEWIB4M.js} +1 -1
- package/bundle/{chunk-26C3WLYQ.js → chunk-ZLINIYTP.js} +497 -272
- package/bundle/{chunk-2XYWN7QJ.js → chunk-ZQGSDHL6.js} +2 -2
- package/bundle/control-channel.js +2 -2
- package/bundle/index.js +1 -1
- package/bundle/intercom-bridge.js +2 -2
- package/bundle/preflight.js +8 -8
- package/bundle/project-panes.js +3 -3
- package/docs/agents.md +31 -0
- package/docs/configuration.md +9 -1
- package/docs/tool-reference.md +3 -1
- package/docs/workflows.md +16 -0
- package/package.json +2 -2
- package/skills/pi-subagents/references/execution-controls.md +5 -4
- package/src/agents/agent-management.js +3 -3
- package/src/agents/agent-memory.js +3 -1
- package/src/agents/agents.js +6 -6
- package/src/agents/frontmatter.d.ts +2 -0
- package/src/agents/frontmatter.js +15 -1
- package/src/agents/skills.js +2 -1
- package/src/extension/config.js +3 -0
- package/src/extension/herdr-pi-bridge.js +17 -10
- package/src/extension/index.js +82 -2
- package/src/extension/schemas.js +3 -3
- package/src/intercom/native-supervisor-channel.js +20 -0
- package/src/missions/lifecycle.js +7 -1
- package/src/runs/background/active-async-capacity.d.ts +1 -0
- package/src/runs/background/active-async-capacity.js +9 -3
- package/src/runs/background/async-execution.js +20 -10
- package/src/runs/background/async-job-tracker.d.ts +6 -1
- package/src/runs/background/async-job-tracker.js +9 -4
- package/src/runs/background/async-status.d.ts +2 -0
- package/src/runs/background/async-status.js +21 -2
- package/src/runs/background/await-async-run.js +3 -7
- package/src/runs/background/boot-id.d.ts +12 -0
- package/src/runs/background/boot-id.js +47 -0
- package/src/runs/background/interrupted-elsewhere.d.ts +22 -0
- package/src/runs/background/interrupted-elsewhere.js +68 -0
- package/src/runs/background/notify.d.ts +2 -0
- package/src/runs/background/notify.js +13 -0
- package/src/runs/background/result-delivery-ownership.d.ts +6 -0
- package/src/runs/background/result-delivery-ownership.js +19 -1
- package/src/runs/background/result-watcher.d.ts +3 -1
- package/src/runs/background/result-watcher.js +24 -8
- package/src/runs/background/run-child-session.d.ts +1 -6
- package/src/runs/background/run-child-session.js +1 -1
- package/src/runs/background/run-status.js +2 -1
- package/src/runs/background/stale-run-reconciler.d.ts +25 -0
- package/src/runs/background/stale-run-reconciler.js +52 -13
- package/src/runs/background/subagent-runner-bootstrap.js +3 -0
- package/src/runs/background/subagent-runner.d.ts +4 -0
- package/src/runs/background/subagent-runner.js +13 -5
- package/src/runs/background/subagent-wait.d.ts +1 -1
- package/src/runs/background/subagent-wait.js +6 -1
- package/src/runs/background/terminal-run-index.d.ts +1 -0
- package/src/runs/background/terminal-run-index.js +4 -2
- package/src/runs/foreground/async-stop-action.js +3 -1
- package/src/runs/foreground/execution.js +6 -3
- package/src/runs/foreground/foreground-history.d.ts +16 -2
- package/src/runs/foreground/foreground-history.js +133 -31
- package/src/runs/foreground/subagent-executor.d.ts +11 -0
- package/src/runs/foreground/subagent-executor.js +277 -74
- package/src/runs/shared/child-launch.d.ts +2 -2
- package/src/runs/shared/child-session.d.ts +3 -2
- package/src/runs/shared/child-session.js +51 -8
- package/src/runs/shared/child-tool-plan.d.ts +7 -0
- package/src/runs/shared/child-tool-plan.js +21 -6
- package/src/runs/shared/herdr-connection.d.ts +17 -8
- package/src/runs/shared/herdr-connection.js +19 -7
- package/src/runs/shared/herdr-external-adapters.d.ts +3 -2
- package/src/runs/shared/herdr-external-adapters.js +1 -1
- package/src/runs/shared/herdr-machine.d.ts +12 -2
- package/src/runs/shared/herdr-machine.js +55 -9
- package/src/runs/shared/herdr-pane-host.d.ts +88 -0
- package/src/runs/shared/herdr-pane-host.js +155 -0
- package/src/runs/shared/herdr-placed-run.d.ts +27 -150
- package/src/runs/shared/herdr-placed-run.js +48 -395
- package/src/runs/shared/local-machine-access.d.ts +5 -0
- package/src/runs/shared/local-machine-access.js +28 -0
- package/src/runs/shared/orca-progress-tabs.d.ts +0 -1
- package/src/runs/shared/orca-progress-tabs.js +1 -24
- package/src/runs/shared/parallel-utils.d.ts +1 -1
- package/src/runs/shared/placed-run-owner.d.ts +49 -0
- package/src/runs/shared/placed-run-owner.js +93 -0
- package/src/runs/shared/placement-host.d.ts +99 -0
- package/src/runs/shared/placement-host.js +157 -0
- package/src/runs/shared/rex-machine.d.ts +25 -0
- package/src/runs/shared/rex-machine.js +83 -0
- package/src/runs/shared/rex-pane-host.d.ts +62 -0
- package/src/runs/shared/rex-pane-host.js +327 -0
- package/src/shared/completion-owner.d.ts +5 -0
- package/src/shared/completion-owner.js +5 -0
- package/src/shared/session-identity.d.ts +2 -0
- package/src/shared/session-identity.js +26 -0
- package/src/shared/types.d.ts +45 -6
- package/src/tui/fleet.d.ts +1 -0
- package/src/tui/fleet.js +24 -15
- package/src/tui/render.d.ts +6 -0
- package/src/tui/render.js +3 -3
- package/src/tui/subagent-messages.js +1 -0
- package/src/workflows/scripted-workflow.d.ts +1 -1
- package/src/workflows/workflow-auto-resume.d.ts +32 -0
- package/src/workflows/workflow-auto-resume.js +102 -0
- package/src/workflows/workflow-launch-record.d.ts +27 -0
- package/src/workflows/workflow-launch-record.js +48 -0
- package/src/workflows/workflow-resume-claim.d.ts +82 -0
- package/src/workflows/workflow-resume-claim.js +210 -0
- package/src/workflows/workflow-resume.d.ts +56 -0
- package/src/workflows/workflow-resume.js +106 -0
- package/src/workflows/workflow-reuse.d.ts +15 -9
- package/src/workflows/workflow-reuse.js +77 -14
- package/src/workflows/workflow-revival.d.ts +2 -0
- package/src/workflows/workflow-revival.js +2 -1
- package/src/workflows/workflow-stop-cause.d.ts +6 -0
- package/src/workflows/workflow-stop-cause.js +8 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// package.json
|
|
2
2
|
var package_default = {
|
|
3
3
|
name: "pi-subagents",
|
|
4
|
-
version: "0.
|
|
4
|
+
version: "0.82.0",
|
|
5
5
|
private: true,
|
|
6
6
|
description: "Pi extension for single-agent delegation and scripted multi-agent workflows",
|
|
7
7
|
author: "Nico Bailon",
|
|
@@ -106,7 +106,7 @@ var package_default = {
|
|
|
106
106
|
"@js-temporal/polyfill": "0.5.1",
|
|
107
107
|
acorn: "8.18.0",
|
|
108
108
|
jiti: "2.7.0",
|
|
109
|
-
undici: "8.
|
|
109
|
+
undici: "8.12.3",
|
|
110
110
|
yaml: "2.8.3"
|
|
111
111
|
},
|
|
112
112
|
devDependencies: {
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import {
|
|
2
2
|
requestAsyncStop
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-OYF6AFDY.js";
|
|
4
4
|
import "./chunk-NIXWDZZV.js";
|
|
5
|
-
import "./chunk-
|
|
5
|
+
import "./chunk-CIZQXJ5Q.js";
|
|
6
6
|
import "./chunk-5LFLVSTM.js";
|
|
7
7
|
import "./chunk-YFCK7H2S.js";
|
|
8
8
|
import "./chunk-Z7KVHQHL.js";
|
package/bundle/index.js
CHANGED
|
@@ -17,7 +17,7 @@ function readInstalledPackageVersion() {
|
|
|
17
17
|
var loadedPackageVersion = readInstalledPackageVersion();
|
|
18
18
|
|
|
19
19
|
// index.ts
|
|
20
|
-
var registerExtension = process.env[HERDR_PI_MODE_ENV] === "1" ? (await import("./chunk-
|
|
20
|
+
var registerExtension = process.env[HERDR_PI_MODE_ENV] === "1" ? (await import("./chunk-SIJ4S3EN.js")).default : process.env.PI_SUBAGENT_CHILD === "1" ? void 0 : (await import("./chunk-WVD4UEL4.js")).default;
|
|
21
21
|
function registerSubagentExtension(pi) {
|
|
22
22
|
const installedVersion = readInstalledPackageVersion();
|
|
23
23
|
if (installedVersion !== loadedPackageVersion) {
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import {
|
|
2
2
|
resolveIntercomSessionTarget
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-XSWOTIA3.js";
|
|
4
4
|
import "./chunk-7F6RKT6W.js";
|
|
5
|
-
import "./chunk-
|
|
5
|
+
import "./chunk-CIZQXJ5Q.js";
|
|
6
6
|
import "./chunk-5LFLVSTM.js";
|
|
7
7
|
import "./chunk-YFCK7H2S.js";
|
|
8
8
|
import "./chunk-Z7KVHQHL.js";
|
package/bundle/preflight.js
CHANGED
|
@@ -2,22 +2,22 @@ import {
|
|
|
2
2
|
buildEffectiveSystemPrompt,
|
|
3
3
|
canPreferForkFromSnapshot,
|
|
4
4
|
resolveSubagentLaunchContext
|
|
5
|
-
} from "./chunk-
|
|
6
|
-
import "./chunk-
|
|
5
|
+
} from "./chunk-GJECHSAR.js";
|
|
6
|
+
import "./chunk-PLCCBLPY.js";
|
|
7
7
|
import {
|
|
8
8
|
loadConfig
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-NGGAFDL6.js";
|
|
10
10
|
import {
|
|
11
11
|
nestedResultsPath,
|
|
12
12
|
processTerminalCandidatePath,
|
|
13
13
|
processTerminalPath,
|
|
14
14
|
resultFilePath
|
|
15
|
-
} from "./chunk-
|
|
15
|
+
} from "./chunk-NRQ75MGH.js";
|
|
16
16
|
import {
|
|
17
17
|
applyIntercomBridgeToAgent,
|
|
18
18
|
resolveIntercomBridge,
|
|
19
19
|
validateIntercomBridgeConfig
|
|
20
|
-
} from "./chunk-
|
|
20
|
+
} from "./chunk-XSWOTIA3.js";
|
|
21
21
|
import {
|
|
22
22
|
AGENT_DEFINITION_PROJECTION_VERSION,
|
|
23
23
|
resolveLaunchBinding,
|
|
@@ -26,7 +26,7 @@ import {
|
|
|
26
26
|
import {
|
|
27
27
|
applyThinkingSuffix,
|
|
28
28
|
resolvePiLaunchToolPlan
|
|
29
|
-
} from "./chunk-
|
|
29
|
+
} from "./chunk-4SDU2BZX.js";
|
|
30
30
|
import {
|
|
31
31
|
assertRequiredChildExtensionsAdmitted,
|
|
32
32
|
resolveRequiredChildExtensions
|
|
@@ -34,7 +34,7 @@ import {
|
|
|
34
34
|
import {
|
|
35
35
|
getArtifactPaths,
|
|
36
36
|
getArtifactsDir
|
|
37
|
-
} from "./chunk-
|
|
37
|
+
} from "./chunk-5TYMSZX3.js";
|
|
38
38
|
import {
|
|
39
39
|
assertClaudeCodeModelScope,
|
|
40
40
|
assertThinkingWithinCeiling,
|
|
@@ -61,7 +61,7 @@ import {
|
|
|
61
61
|
resolveSkillsWithFallback,
|
|
62
62
|
resolveStepBehavior,
|
|
63
63
|
unknownAgentDiagnosticContext
|
|
64
|
-
} from "./chunk-
|
|
64
|
+
} from "./chunk-CIZQXJ5Q.js";
|
|
65
65
|
import {
|
|
66
66
|
DIRS,
|
|
67
67
|
SUBAGENT_LIFECYCLE_ARTIFACT_VERSION,
|
package/bundle/project-panes.js
CHANGED
|
@@ -8,10 +8,10 @@ import {
|
|
|
8
8
|
openProjectPane,
|
|
9
9
|
projectPaneBindingPath,
|
|
10
10
|
readProjectPaneBinding
|
|
11
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-52UBALJL.js";
|
|
12
12
|
import "./chunk-7J4ZXMY7.js";
|
|
13
|
-
import "./chunk-
|
|
14
|
-
import "./chunk-
|
|
13
|
+
import "./chunk-5TYMSZX3.js";
|
|
14
|
+
import "./chunk-CIZQXJ5Q.js";
|
|
15
15
|
import "./chunk-5LFLVSTM.js";
|
|
16
16
|
import "./chunk-YFCK7H2S.js";
|
|
17
17
|
import "./chunk-Z7KVHQHL.js";
|
package/docs/agents.md
CHANGED
|
@@ -282,6 +282,36 @@ Placed external profiles are one-shot and stop-only: they cannot steer, resume,
|
|
|
282
282
|
|
|
283
283
|
pi-subagents never clones, pulls, or checks out on the machine. Generic `external-cli` commands and managed worktrees are rejected before launch; saved-machine placement accepts native Pi and only the six code-owned external profiles.
|
|
284
284
|
|
|
285
|
+
## Running a Pi child in a Rex pane on this machine
|
|
286
|
+
|
|
287
|
+
`machine: "local"` runs a native Pi child as a visible Pi in a new background tab of your own Rex session, labeled `subagent-<run id tail>`. It works wherever `machine` works: frontmatter, settings overrides, the `subagent` tool, workflow `runs.run`, chain, parallel, and dynamic-fanout steps. `local` is reserved and never looks in the Herdr catalog. The parent Pi must run inside Rex: with no `REX_SESSION`, or a session the Rex server does not confirm, the launch fails before anything starts.
|
|
288
|
+
|
|
289
|
+
The parent drives the child through the same bridge as a Herdr-placed Pi: prompt, steer, follow-up, abort, `contact_supervisor`, and results. The child Pi loads pi-subagents from its own extensions, so it must be installed at the same version. `cwd` is a directory on this machine; a relative path joins the launch directory. pi-subagents finds `rex` in `$REX_BIN_DIR`, then on `PATH`, then in the Rex server app.
|
|
290
|
+
|
|
291
|
+
A finished child keeps its tab open, with the idle Pi, until you close it; an aborted or stopped child's tab is closed. Limits: native Pi only (Claude Code, Codex, Cursor, and generic `external-cli` agents are rejected), fresh context only, and the same restrictions as a Herdr-placed Pi (no local extension paths, process environment, nested delegation, permission rules, or tool budgets).
|
|
292
|
+
|
|
293
|
+
## Running a Pi child in a Rex pane on another Mac
|
|
294
|
+
|
|
295
|
+
Name a Mac you reach over SSH in your user settings (`~/.pi/agent/settings.json`), with its repo root:
|
|
296
|
+
|
|
297
|
+
```json
|
|
298
|
+
{
|
|
299
|
+
"subagents": {
|
|
300
|
+
"machines": {
|
|
301
|
+
"workmac": { "rex": { "ssh": "100.82.67.118" }, "cwd": "~/dev/app" }
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
`machine: "workmac"` then runs a native Pi child as a visible Pi in a new background tab of the Rex session labeled `subagents` on that Mac. pi-subagents creates the session the first time and reuses it after that; if two sessions there have that label, the launch fails until you close or rename one. Every `rex` command runs on that Mac over SSH, and the bridge socket comes back through an SSH forward, so SSH must work without a prompt (`ssh <target>` once to accept the host key). A name with a `rex` entry never looks in the Herdr catalog, so Herdr does not need to be installed.
|
|
308
|
+
|
|
309
|
+
Only user settings may name a Rex host: a `rex` entry in a project's `.pi/settings.json` fails the launch, so a cloned repo cannot choose where a task runs. `cwd` can come from project or user settings, as for Herdr machines: a relative step `cwd` joins it, and an absolute or `~` path is used as given. The other Mac needs Rex, Pi on its login shell's `PATH`, and pi-subagents installed at the same version. The child edits that Mac's disk, so its changed files come from that Mac's Git state, as for a Herdr-placed child.
|
|
310
|
+
|
|
311
|
+
Programs in a Rex tab run under the Rex app's macOS privacy permissions. If Pi on that Mac reads files in Desktop, Documents, or Downloads (for example a package path in its settings), allow Rex there first: open a Rex tab on that Mac, run `ls ~/Documents`, and accept the prompt. Until then Pi waits on the prompt and the launch times out.
|
|
312
|
+
|
|
313
|
+
To watch the tab, open the `subagents` session in Rex on that Mac. To watch it from this Mac, add that Mac's Rex server to your hosts here once (`rex hosts add workmac <endpoint>`; after `rex server tailscale on` there, `rex whoami` on that Mac shows its Tailscale address). Tab lifecycle and limits are the same as for `machine: "local"`.
|
|
314
|
+
|
|
285
315
|
## Sandboxing background children with a launcher
|
|
286
316
|
|
|
287
317
|
A launcher wraps an agent's background runner in a command you choose, so that agent can run in a different sandbox from the parent session. Define the command in the user config key [`runnerLaunchers`](configuration.md#runnerlaunchers), then name it in the agent file:
|
|
@@ -512,6 +542,7 @@ How `tools` behaves:
|
|
|
512
542
|
- `tools` omitted: the child session gets Pi's normal builtin tools.
|
|
513
543
|
- `tools` present: regular tool names become an explicit allowlist.
|
|
514
544
|
- `tools:` empty: the child session gets no tools.
|
|
545
|
+
- `tools: +codemode` or `tools: -write`: rejected. Pi's `--tools` and `defaultTools` accept `+name`/`-name` entries that edit the default tool set, but a child's `tools` list replaces that set, so name every tool the child needs, or omit `tools` and use `excludeTools` to drop one. A local agent with such an entry fails before launch. A Herdr- or Rex-placed child resolves its agent on that machine, so an entry only there fails when the pane bridge is configured, before the first prompt.
|
|
515
546
|
- `allowNestedSubagents: true`: explicitly enables child-safe nested fanout without turning omitted `tools` into an allowlist. Depth and inherited capability ceilings still apply.
|
|
516
547
|
- `allowedAgents: scout, reviewer`: if nesting is separately enabled, restricts this agent's descendant launches to those names. `allowedAgents:` denies all descendants and omission is unrestricted except for inherited/runtime policy.
|
|
517
548
|
|
package/docs/configuration.md
CHANGED
|
@@ -244,6 +244,14 @@ Set `enabled` to `false` (or remove the block) as a kill switch. In that state,
|
|
|
244
244
|
|
|
245
245
|
Workflow script calls use background execution when the request omits `async`. Set `asyncByDefault` to `false` to restore foreground-by-default behavior for tool launches that still use the internal single-run primitive. Callers can still force foreground with `async: false` unless `forceTopLevelAsync` is enabled.
|
|
246
246
|
|
|
247
|
+
## `autoResume`
|
|
248
|
+
|
|
249
|
+
```json
|
|
250
|
+
{ "autoResume": false }
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
Default `true`. When a session is reopened (`pi -c`, `pi --session`, `/resume`) or reloaded (`/reload`), its own async workflows that a Pi exit or a reload interrupted in the last 24 hours are resumed once, without delaying session start; set `autoResume` to `false` to resume them only by hand. A one-shot `pi -p` run does not auto-resume, so it does not wait on the workflow. See [Workflows](workflows.md).
|
|
254
|
+
|
|
247
255
|
## `runnerLaunchers`
|
|
248
256
|
|
|
249
257
|
```json
|
|
@@ -387,8 +395,8 @@ Adds a short `active_subagents` section to the parent's system prompt while this
|
|
|
387
395
|
|
|
388
396
|
`defaultTimeoutMs` sets the blocking window used when a `bg_wait` call omits `timeoutMs`; explicit call values win, followed by this setting, then the 30-minute fallback. `bg_wait` is the only registered wait tool. When the window elapses, the tool returns a non-error `window_elapsed` result with the still-active work identities, and that work keeps running. Set `enabled` to `false` to make direct calls return immediately instead of blocking. The default is enabled. You can also set `"waitTool": false`; set `PI_SUBAGENT_WAIT_TOOL_ENABLED=false` (or `0`, `off`, `disabled`) to override config for one process. The effective enabled and default-timeout values are passed explicitly to child runtimes. Headless `agent_end` auto-drain retains its own strict deadline and fails if required work remains unresolved. Invalid config or environment values fail instead of being coerced.
|
|
389
397
|
|
|
390
|
-
Blocking `bg_wait({ id: "..." })` keeps the current tool call open until that run changes. By default it returns when a run needs attention. Use `bg_wait({ stopOnAttention: false })` only for run-to-completion flows that should wait through idle or long-thinking attention; supervisor/contact requests still stop the wait. A steer or follow-up the operator sends while a blocking wait is open also ends it, with a non-error `user_input` result listing the still-active work, so the message reaches the model without waiting out the window. In a long-lived interactive parent session, `bg_wait({ id: "...", nonBlocking: true })` instead resolves the prefix once, persists the exact run identity, returns a subscription token immediately, and wakes that session on completion, failure, attention, reconciliation failure, or timeout. Use it for provider, detached, or other background work without a native completion notification; ordinary async subagent runs notify the parent natively and do not need a wait subscription. Armed subscriptions appear in ordinary `subagent({ action: "status" })` output and are not counted as active child work. Native completion notifications wake the session through the Pi process that launched the run, while that process is still running; a session reopened in a new process does not get that wake and should read the result with `subagent({ action: "status" })`. `nonBlocking` subscriptions need a host with a UI context, such as interactive or RPC mode; plain SDK embedding without a UI context rejects them.
|
|
391
398
|
|
|
399
|
+
Blocking `bg_wait({ id: "..." })` keeps the current tool call open until that run changes. By default it returns when a run needs attention, even when the run is still working normally, for example a `bash` command open longer than `activeNoticeAfterMs`; the result then says the run is still running. Use `bg_wait({ stopOnAttention: false })` for run-to-completion flows: it waits through idle time, a tool held open, or repeated tool failures, and still ends for a supervisor/contact request, a finished run, the timeout, user input, or abort. A steer or follow-up the operator sends while a blocking wait is open also ends it, with a non-error `user_input` result listing the still-active work, so the message reaches the model without waiting out the window. In a long-lived interactive parent session, `bg_wait({ id: "...", nonBlocking: true })` instead resolves the prefix once, persists the exact run identity, returns a subscription token immediately, and wakes that session on completion, failure, attention, reconciliation failure, or timeout. Use it for provider, detached, or other background work without a native completion notification; ordinary async subagent runs notify the parent natively and do not need a wait subscription. Armed subscriptions appear in ordinary `subagent({ action: "status" })` output and are not counted as active child work. A native completion notification goes to the Pi process that launched the run while that process is running; once it has exited, the session gets the result the next time it is open in any Pi. `nonBlocking` subscriptions need a host with a UI context, such as interactive or RPC mode; plain SDK embedding without a UI context rejects them.
|
|
392
400
|
This is different from `waitTool.enabled=false`, which returns immediately without registering any future wake. Provider items remain available only to blocking fleet-wide waits; non-blocking subscriptions require one async or remembered detached foreground run id.
|
|
393
401
|
|
|
394
402
|
## `resultScanLogging`
|
package/docs/tool-reference.md
CHANGED
|
@@ -82,7 +82,7 @@ The `subagent` tool schema lists `agent`, `task`, `workflow`, `args`, `async`, `
|
|
|
82
82
|
| `options.context` | `fresh \| fork \| profile` | global or per-agent default, else `fresh` | Explicit `fresh` or `fork` overrides every workflow child. `profile` requires the selected agent's declared `defaultContext` and ignores config `defaultSubagentContext`; missing agent defaults fail. When omitted, [`defaultSubagentContext`](configuration.md#defaultsubagentcontext) wins over each agent's `defaultContext`; implicit fork falls back to fresh without a persisted parent session and leaf. Explicit fork is strict. Packaged `worker` defaults to `fresh`; packaged `oracle` and `advisor` default to `fork`. |
|
|
83
83
|
| `model` | string | agent default | An exact `provider/id`; bare ids resolve only if unique, and agent names are not model ids. An unknown or ambiguous model fails with up to 5 closest registry ids to copy; `{action:"models"}` lists them all. A suffix such as `provider/id:high` (`off/minimal/low/medium/high/xhigh/max`) overrides agent thinking. The `thinking` field is only for `watchdog.configure`, ignored on dispatch. On the `claude-code` and `claude-code-writer` adapters the model is Claude Code's own alias or id, and the same suffix becomes its `--effort`, bounded by `subagents.maxThinking` and by an enforced `subagents.modelScope`. |
|
|
84
84
|
| `options.missionId` | string | - | Attach a workflow, including each fire of `schedule.create`, to an existing project mission. Scheduled attachment uses ordinary lifecycle and retention rules. |
|
|
85
|
-
| `options.reuseFrom` | string | - | Exact id of an async workflow run stopped by a runtime replacement (`workflow.stopCause: "runtime-replaced"`). An async relaunch of the same script in the same cwd, from any session, reuses that run's finished children and waits for its running ones; see [Workflows](workflows.md). |
|
|
85
|
+
| `options.reuseFrom` | string | - | Exact id of an async workflow run stopped by a runtime replacement or whose Pi process exited (`workflow.stopCause: "runtime-replaced"` or `"owner-exited"`). An async relaunch of the same script in the same cwd, from any session, reuses that run's finished children and waits for its running ones; see [Workflows](workflows.md). |
|
|
86
86
|
| `options.mission` | object/false | auto-create | Override the default enclosing mission with `{ title \| summary, objective?, goal?, budget?, labels? }`. Set exactly one non-empty `title` or `summary`; `objective` and `labels` are optional. `goal` may only be `true`, requires `budget.tokens`, and enables continuation notices. Pass `false` for an intentionally ephemeral workflow with no mission for it or its children and no `state` global. Explicit mission persistence failures are strict. |
|
|
87
87
|
| `options.handoffPath` | string | - | Aggregate handoff manifest for `action: "worktree.discard"` or lane evidence actions, or optional explicit metadata for `action: "worktree.cleanup"`. |
|
|
88
88
|
| `options.repo` | string | runtime cwd | Repository path for `action: "worktree.cleanup"`; required explicitly for apply. The configured worktree base filters candidates by their per-project folder under it but never discovers them. |
|
|
@@ -329,6 +329,7 @@ subagent({ action: "stop", id: "<run-id>" })
|
|
|
329
329
|
subagent({ action: "resume", id: "<run-id>", message: "follow-up question after it pauses or finishes" })
|
|
330
330
|
subagent({ action: "resume", id: "<run-id>", message: "follow-up for child 2", options: { index: 1 } })
|
|
331
331
|
subagent({ action: "resume", id: "<nested-run-id>", message: "follow-up for a nested child" })
|
|
332
|
+
subagent({ action: "resume", id: "<stopped-workflow-run-id>" })
|
|
332
333
|
subagent({ action: "steer", id: "<run-id>", message: "guidance for the running child" })
|
|
333
334
|
subagent({ action: "steer", id: "<run-id>", message: "check this after the current turn", options: { mode: "follow_up" } })
|
|
334
335
|
subagent({ action: "steer", id: "<run-id>", message: "guidance for child 2", options: { index: 1, mode: "auto" } })
|
|
@@ -379,6 +380,7 @@ Commands remain owned by the child session. Normal completion with unfinished co
|
|
|
379
380
|
- Completed external-job runs, including nested runs, can use the same `resume` action as a provider follow-up when the registered provider exposes `followUp(input)`. Running external-job parents fail closed with guidance to wait for completion. Unsupported providers fail with an update/reload message.
|
|
380
381
|
- Revive starts a new child session from the old session context; it does not resume the live session, and it requires the chosen child to have a persisted `.jsonl` session file.
|
|
381
382
|
- Direct revival takes an exclusive cross-process lease on the canonical session file until the new child finishes. A concurrent attempt fails before Pi is spawned and identifies the owning revived run; dead-owner leases are reclaimed only when staleness can be proved.
|
|
383
|
+
- Without `message` (and without `index` or `chain`), `resume` on a top-level async workflow run that stopped because its session was replaced or its Pi process exited (`workflow.stopCause: "runtime-replaced"` or `"owner-exited"`) relaunches it once from the launch it saved, reusing its finished children, and its receipt starts with `Resumed workflow '<id>' as <new id>.`; a run that cannot be resumed is refused as `Cannot resume workflow '<id>': <reason>.` The session that owns the run also resumes it on its own when reopened or reloaded, unless [`autoResume`](configuration.md#autoresume) is off; see [Workflows](workflows.md).
|
|
382
384
|
|
|
383
385
|
### stop
|
|
384
386
|
|
package/docs/workflows.md
CHANGED
|
@@ -14,6 +14,8 @@ Packaged `worker` defaults to fresh context so implementation starts from its as
|
|
|
14
14
|
|
|
15
15
|
Launch children in the background (`async` follows `asyncByDefault`, normally true) and consume each result at the point a later step depends on it. Native completion wakes the parent session, so the parent returns control instead of sleeping or polling. Use `async: false` only when the parent must block, not for final reviews or gates. When an oracle or advisor reaches an unknown that needs a decision, it asks through supervisor dialogue; use a one-shot oracle call only when one was requested.
|
|
16
16
|
|
|
17
|
+
A background child awaited by an `async: false` workflow cannot receive a supervisor decision while its parent is blocked in that call. Its native `contact_supervisor` decision or interview request returns a tool error promptly, asking it to report the blocker and partial results and stop. The transport error is not a supervisor reply or approval. Use `async: true` for workflows whose children may need a decision. Progress updates remain non-blocking, and an explicit child `async: true` still returns its launch receipt without being awaited.
|
|
18
|
+
|
|
17
19
|
Child-safety boundaries are enforced at runtime:
|
|
18
20
|
|
|
19
21
|
- Child sessions do not receive the bundled `pi-subagents` skill.
|
|
@@ -31,8 +33,22 @@ Failed workflow details and async `status.json` include `workflow.failureKind` a
|
|
|
31
33
|
|
|
32
34
|
When `/reload`, a session resume, or a pi-web project switch replaces the extension runtime, a running async workflow stops with `workflow.stopCause: "runtime-replaced"` in `status.json`, and its awaited async children keep running. Its in-process foreground children are stopped, including ones that detached to ask their supervisor. When the same workflow script is launched again with the same `args` in that session, a `runs.run` with the same key and params returns a child that already finished successfully without launching it, and waits for a child that is still running instead of starting another. Those results carry `reused: true`, their step and trace entry are marked `reused`, and the new run records `workflow.reusedFrom`. Failed and stopped children, and children that ran in-process, launch again. Reuse applies only when the newest run of that script and args in the session was stopped this way; a user stop or a completed run ends it. From another session, such as one that took over after a handoff, relaunch the same script as an async workflow with `options: { reuseFrom: "<stopped run id>" }`; the run must have stopped this way in the same cwd, and `args` may differ, because each child is still reused only when its key and params match.
|
|
33
35
|
|
|
36
|
+
When the Pi process that ran an async workflow exits before the workflow finishes (a crash, a kill, or a machine restart), the stale-run check that runs when its session reopens, or when `options.reuseFrom` names the run, marks it failed with `workflow.stopCause: "owner-exited"`, and a relaunch reuses its finished children the same way. On any relaunch, `runs.steer` and `runs.host` calls run again and only `runs.run` results are reused, so a replayed check such as "tests passed" can be stale.
|
|
37
|
+
|
|
38
|
+
A stopped run is continued by one relaunch only. While that relaunch exists, another relaunch of the same stopped run, by the same script or by `options.reuseFrom`, is refused with `workflow run '<id>' was resumed as <new id>; wait on it or stop it.` If that relaunch stops the same way, relaunch it instead: it carries the children of every run before it. If the Pi process that started the relaunch exits before the relaunch begins, the stopped run can be relaunched again.
|
|
39
|
+
|
|
40
|
+
To continue a stopped run without sending its script again, resume it by id from the session that owns it: `subagent({ action: "resume", id: "<stopped run id>" })` with no `message`. Every async top-level workflow saves its launch (the script or named resource, its `args` and options) when it starts, and the resume relaunches that launch with `options.reuseFrom` set to the stopped run, in the stopped run's cwd, with no more authority than it had. A named resource is resolved again and must still have the same script. The new run records `workflow.resumeOrigin: { sourceRunId, attempt }`, where `attempt` counts the resumes that followed a Pi exit. Another session cannot resume the run by id; reopen the session that started it, or relaunch the same script with `options.reuseFrom`. When the run cannot be resumed this way (it started before launches were saved, was launched from `tasks` or `chain`, or its resource changed), the refusal says so; relaunch the same script by hand with `options: { reuseFrom: "<stopped run id>" }`.
|
|
41
|
+
|
|
42
|
+
When the session that owns such a run is reopened or reloaded, the run is resumed this way automatically, once, if it was interrupted in the last 24 hours: for a Pi exit, measured from the last time the dead Pi process updated the run. Session start does not wait for the relaunch. A run that cannot be relaunched is named in its notice (`Not auto-resumed: …`, with how to continue by hand); a launch that fails after session start also posts one `Workflow <id> was not auto-resumed: <reason>.` message. Either way the run is not tried again. `/new` and forks do not resume the runs of the session they replaced. The `autoResume` setting turns this off; see [Configuration](configuration.md#autoresume).
|
|
43
|
+
|
|
44
|
+
Starting Pi in a folder where another session's subagent runs were cut off by its Pi dying in the last 24 hours shows one info line per such session, such as ``2 subagent runs were interrupted in another session. Reopen it to resume them: `pi --session <id>` ``; it only points to that session, changes nothing in it, and is not sent to the model.
|
|
45
|
+
|
|
46
|
+
The completion notice of a workflow stopped by a Pi exit or a runtime replacement has one line right after `Workflow run: <id>` that says what happens next: `Auto-resumed as <new id>.`, `Resumed as <new id>.` (resumed by id), `Relaunched as <new id>.` (relaunched with the same script or `options.reuseFrom`), `Not auto-resumed: <reason>.` followed by the way to continue by hand, or, when nothing continues it, `Resume by hand: subagent({ action: "resume", id: "<id>" })` (or the `options.reuseFrom` form when its launch cannot be resumed by id).
|
|
47
|
+
|
|
34
48
|
After `/new`, `/resume`, or a fork in the same Pi process, the new session owns the async runs of the session it replaced, and of the sessions that one replaced. It can list, `bg_wait` on, steer, stop, and resume them, receives their completion notices, and answers their children's pending supervisor requests. Over RPC, a host can steer, interrupt, stop, resume, and inspect them, and sees them in the fleet and `subagent-async` snapshots. Startup and other Pi processes inherit nothing; `/reload` adds nothing new but keeps what the session already inherited. To relaunch a stopped workflow instead of controlling its children one by one, use `options.reuseFrom`.
|
|
35
49
|
|
|
50
|
+
Once the Pi process that started a background single, parallel, or chain run has exited, the run is reported once to a Pi window that has its session open, whether it finished while Pi was closed or finishes later. A workflow's children are reported through their workflow.
|
|
51
|
+
|
|
36
52
|
Pi core may print a generic `pi -ne` extension-load hint; that out-of-repo hint is not protocol-approved fallback. A verified compaction abort may continue the retained child once on its already resolved model; it does not authorize an execution-mode or model switch.
|
|
37
53
|
|
|
38
54
|
## Prompt shortcuts
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "module",
|
|
3
3
|
"name": "pi-subagents",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.82.0",
|
|
5
5
|
"description": "Pi extension for single-agent delegation and scripted multi-agent workflows",
|
|
6
6
|
"author": "Nico Bailon",
|
|
7
7
|
"license": "MIT",
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
"@js-temporal/polyfill": "0.5.1",
|
|
21
21
|
"acorn": "8.18.0",
|
|
22
22
|
"jiti": "2.7.0",
|
|
23
|
-
"undici": "8.
|
|
23
|
+
"undici": "8.12.3",
|
|
24
24
|
"yaml": "2.8.3"
|
|
25
25
|
},
|
|
26
26
|
"peerDependencies": {
|
|
@@ -169,10 +169,11 @@ subagent runs notify the parent automatically. Use `bg_wait({ all: true })` for
|
|
|
169
169
|
all work active at call time, `bg_wait({ id: "..." })` for one async or
|
|
170
170
|
remembered detached foreground run, and `bg_wait({ timeoutMs })` to cap the
|
|
171
171
|
block; active work keeps running if it elapses. `bg_wait({ stopOnAttention:
|
|
172
|
-
false })` keeps a blocking wait through idle
|
|
173
|
-
supervisor/contact requests
|
|
174
|
-
|
|
175
|
-
|
|
172
|
+
false })` keeps a blocking wait through idle time, a tool held open, or repeated
|
|
173
|
+
tool failures; supervisor/contact requests, finished runs, timeout and user
|
|
174
|
+
input still stop it. In a long-lived interactive parent session, use
|
|
175
|
+
`bg_wait({ id: "...", nonBlocking: true })` only for a known detached or
|
|
176
|
+
otherwise non-notifying run to resolve the prefix to one exact run,
|
|
176
177
|
persist an armed subscription, return immediately, and wake later on
|
|
177
178
|
completion, failure, attention, reconciliation failure, or timeout. Ordinary
|
|
178
179
|
status lists armed subscriptions separately from active children. This differs
|
|
@@ -5,7 +5,7 @@ import { serializeAgent } from "./agent-serializer.js";
|
|
|
5
5
|
import { mergeAgentsForScope } from "./agent-selection.js";
|
|
6
6
|
import { discoverAvailableSkills, resolveSkills } from "./skills.js";
|
|
7
7
|
import { buildProactiveSkillSubagentRecommendationLines, } from "./proactive-skills.js";
|
|
8
|
-
import { parseFrontmatter,
|
|
8
|
+
import { parseFrontmatter, parseUnquotedList } from "./frontmatter.js";
|
|
9
9
|
import { resolveEffectiveThinking, toModelInfo } from "../shared/model-info.js";
|
|
10
10
|
import { resolveSubagentModelOverride } from "../runs/shared/model-resolution.js";
|
|
11
11
|
import { validateToolBudgetConfig } from "../runs/shared/tool-budget.js";
|
|
@@ -175,9 +175,9 @@ function withDeclaredExtensionPaths(config, filePath) {
|
|
|
175
175
|
const { extensions: _extensions, subagentOnlyExtensions: _subagentOnlyExtensions, ...withoutResolvedExtensions } = config;
|
|
176
176
|
return {
|
|
177
177
|
...withoutResolvedExtensions,
|
|
178
|
-
...(frontmatter.extensions !== undefined ? { extensions:
|
|
178
|
+
...(frontmatter.extensions !== undefined ? { extensions: parseUnquotedList(frontmatter.extensions) } : {}),
|
|
179
179
|
...(frontmatter.subagentOnlyExtensions !== undefined
|
|
180
|
-
? { subagentOnlyExtensions:
|
|
180
|
+
? { subagentOnlyExtensions: parseUnquotedList(frontmatter.subagentOnlyExtensions) }
|
|
181
181
|
: {}),
|
|
182
182
|
};
|
|
183
183
|
}
|
|
@@ -236,7 +236,9 @@ export function readMemoryFile(memoryDir) {
|
|
|
236
236
|
let fd;
|
|
237
237
|
try {
|
|
238
238
|
const noFollow = typeof fs.constants.O_NOFOLLOW === "number" ? fs.constants.O_NOFOLLOW : 0;
|
|
239
|
-
|
|
239
|
+
// A FIFO must not block the launch before fstat can reject non-files.
|
|
240
|
+
const nonBlock = typeof fs.constants.O_NONBLOCK === "number" ? fs.constants.O_NONBLOCK : 0;
|
|
241
|
+
fd = fs.openSync(file, fs.constants.O_RDONLY | noFollow | nonBlock);
|
|
240
242
|
}
|
|
241
243
|
catch (error) {
|
|
242
244
|
const code = error && typeof error === "object" && "code" in error ? String(error.code) : "";
|
package/src/agents/agents.js
CHANGED
|
@@ -19,7 +19,7 @@ import { parseChain, parseJsonChain } from "./chain-serializer.js";
|
|
|
19
19
|
import { mergeAgentsForScope } from "./agent-selection.js";
|
|
20
20
|
import { projectScopeRequiresTrustMessage } from "./agent-scope.js";
|
|
21
21
|
import { closestMatch } from "../shared/edit-distance.js";
|
|
22
|
-
import { parseFrontmatter, parseFrontmatterList } from "./frontmatter.js";
|
|
22
|
+
import { parseFrontmatter, parseFrontmatterList, parseUnquotedList } from "./frontmatter.js";
|
|
23
23
|
import { buildRuntimeName, parsePackageName } from "./identity.js";
|
|
24
24
|
import { parseModelScopeConfig } from "../runs/shared/model-scope.js";
|
|
25
25
|
export { BUILTIN_AGENT_NAMES } from "./builtin-names.js";
|
|
@@ -1919,11 +1919,11 @@ function loadAgentsFromDefinitionFiles(files, source, discoveryPriority, package
|
|
|
1919
1919
|
else
|
|
1920
1920
|
throw new Error(`Agent '${localName}' has invalid advertise frontmatter; expected true or false.`);
|
|
1921
1921
|
}
|
|
1922
|
-
const rawTools =
|
|
1922
|
+
const rawTools = parseUnquotedList(frontmatter.tools);
|
|
1923
1923
|
const parsedTools = splitToolList(rawTools);
|
|
1924
1924
|
const tools = parsedTools.tools ?? [];
|
|
1925
1925
|
const mcpDirectTools = parsedTools.mcpDirectTools ?? [];
|
|
1926
|
-
const excludeTools =
|
|
1926
|
+
const excludeTools = parseUnquotedList(frontmatter.excludeTools);
|
|
1927
1927
|
const parsedAllowedAgents = parseFrontmatterList(frontmatter.allowedAgents);
|
|
1928
1928
|
const allowedAgents = parsedAllowedAgents === undefined ? undefined : normalizeCapabilityCeilingAllowedAgents(parsedAllowedAgents);
|
|
1929
1929
|
const defaultReads = parseFrontmatterList(frontmatter.defaultReads);
|
|
@@ -1997,9 +1997,9 @@ function loadAgentsFromDefinitionFiles(files, source, discoveryPriority, package
|
|
|
1997
1997
|
else
|
|
1998
1998
|
throw new Error(`Agent '${localName}' has invalid acceptanceRole frontmatter; expected 'read-only' or 'writer'.`);
|
|
1999
1999
|
}
|
|
2000
|
-
const extensions = resolveAgentRelativeExtensionPaths(
|
|
2001
|
-
const subagentOnlyExtensions = resolveAgentRelativeExtensionPaths(
|
|
2002
|
-
const mutationTools =
|
|
2000
|
+
const extensions = resolveAgentRelativeExtensionPaths(parseUnquotedList(frontmatter.extensions), filePath);
|
|
2001
|
+
const subagentOnlyExtensions = resolveAgentRelativeExtensionPaths(parseUnquotedList(frontmatter.subagentOnlyExtensions), filePath);
|
|
2002
|
+
const mutationTools = parseUnquotedList(frontmatter.mutationTools);
|
|
2003
2003
|
let fast;
|
|
2004
2004
|
if (frontmatter.fast !== undefined) {
|
|
2005
2005
|
if (frontmatter.fast === "true")
|
|
@@ -5,6 +5,8 @@
|
|
|
5
5
|
* alone because parseFrontmatter has already removed their quotes, so `"notes #1.md"` must survive.
|
|
6
6
|
*/
|
|
7
7
|
export declare function parseFrontmatterList(raw: string | undefined): string[] | undefined;
|
|
8
|
+
/** A tool or extension name list whose items may each be quoted, as in `"read", "bash"` or `- "read"`. */
|
|
9
|
+
export declare function parseUnquotedList(raw: string | undefined): string[] | undefined;
|
|
8
10
|
/**
|
|
9
11
|
* Parse YAML frontmatter from agent/chain files.
|
|
10
12
|
* Handles both flat (key: value) and nested block (key: \n sub: val) values.
|
|
@@ -56,6 +56,16 @@ export function parseFrontmatterList(raw) {
|
|
|
56
56
|
.map((value) => value.trim())
|
|
57
57
|
.filter(Boolean);
|
|
58
58
|
}
|
|
59
|
+
function unquoteName(value) {
|
|
60
|
+
if (value.length >= 2 && ((value.startsWith("'") && value.endsWith("'")) || (value.startsWith('"') && value.endsWith('"')))) {
|
|
61
|
+
return value.slice(1, -1);
|
|
62
|
+
}
|
|
63
|
+
return value;
|
|
64
|
+
}
|
|
65
|
+
/** A tool or extension name list whose items may each be quoted, as in `"read", "bash"` or `- "read"`. */
|
|
66
|
+
export function parseUnquotedList(raw) {
|
|
67
|
+
return parseFrontmatterList(raw)?.map(unquoteName);
|
|
68
|
+
}
|
|
59
69
|
/**
|
|
60
70
|
* Parse YAML frontmatter from agent/chain files.
|
|
61
71
|
* Handles both flat (key: value) and nested block (key: \n sub: val) values.
|
|
@@ -115,7 +125,11 @@ export function parseFrontmatter(content) {
|
|
|
115
125
|
if (key === undefined || rawValueValue === undefined)
|
|
116
126
|
continue;
|
|
117
127
|
const rawValue = rawValueValue.trim();
|
|
118
|
-
const
|
|
128
|
+
const quote = rawValue[0];
|
|
129
|
+
const nameList = key === "tools" || key === "excludeTools" || key === "mutationTools" || key === "extensions" || key === "subagentOnlyExtensions";
|
|
130
|
+
// `"read", "bash"` starts and ends with a quote, but it is a list. Keep those item quotes for the splitter.
|
|
131
|
+
const quotedList = nameList && (quote === '"' || quote === "'") && (rawValue.includes(`${quote},`) || rawValue.includes(`, ${quote}`));
|
|
132
|
+
const isQuoted = !quotedList && (quote === '"' || quote === "'") && rawValue.endsWith(quote);
|
|
119
133
|
const value = isQuoted ? rawValue.slice(1, -1) : rawValue;
|
|
120
134
|
const isFolded = !isQuoted && (rawValue === ">" || rawValue === ">-");
|
|
121
135
|
const isLiteral = !isQuoted && (rawValue === "|" || rawValue === "|-");
|
package/src/agents/skills.js
CHANGED
|
@@ -45,7 +45,8 @@ function readOptionalJsonFile(filePath, label) {
|
|
|
45
45
|
const code = typeof error === "object" && error !== null && "code" in error
|
|
46
46
|
? error.code
|
|
47
47
|
: undefined;
|
|
48
|
-
|
|
48
|
+
// ENOTDIR: a settings package entry may name a single extension file, so `<file>/package.json` is no manifest.
|
|
49
|
+
if (code === "ENOENT" || code === "ENOTDIR")
|
|
49
50
|
return null;
|
|
50
51
|
const message = error instanceof Error ? error.message : String(error);
|
|
51
52
|
throw new Error(`Failed to read ${label} '${filePath}': ${message}`, {
|
package/src/extension/config.js
CHANGED
|
@@ -217,6 +217,9 @@ function validateConfig(config) {
|
|
|
217
217
|
if (config.enableSlashCommands !== undefined && typeof config.enableSlashCommands !== "boolean") {
|
|
218
218
|
throw new Error("config.enableSlashCommands must be a boolean");
|
|
219
219
|
}
|
|
220
|
+
if (config.autoResume !== undefined && typeof config.autoResume !== "boolean") {
|
|
221
|
+
throw new Error("config.autoResume must be a boolean");
|
|
222
|
+
}
|
|
220
223
|
if (config.toolActivation !== undefined && config.toolActivation !== "auto" && config.toolActivation !== "dynamic" && config.toolActivation !== "eager") {
|
|
221
224
|
throw new Error('config.toolActivation must be "auto", "dynamic", or "eager"');
|
|
222
225
|
}
|
|
@@ -10,6 +10,7 @@ import { buildSkillInjection, resolveSkills } from "../agents/skills.js";
|
|
|
10
10
|
import { buildAgentMemoryInjection } from "../agents/agent-memory.js";
|
|
11
11
|
import { appendAgentRefinementOverlay } from "../agents/agent-refinements.js";
|
|
12
12
|
import { rewriteSubagentPrompt } from "../runs/shared/subagent-prompt-runtime.js";
|
|
13
|
+
import { assertNoToolModifierEntries } from "../runs/shared/child-tool-plan.js";
|
|
13
14
|
import { resolveExistingReadPaths } from "../shared/settings.js";
|
|
14
15
|
import { MODEL_ONLY_TOOL } from "../shared/extension-context.js";
|
|
15
16
|
function gitEvidence(cwd) {
|
|
@@ -37,6 +38,7 @@ export function resolveRemoteHerdrResources(cwd, resources, remoteDefaultTools =
|
|
|
37
38
|
const reads = resources.reads === undefined ? agent.defaultReads ?? false : resources.reads;
|
|
38
39
|
const readPaths = Array.isArray(reads) ? resolveExistingReadPaths(reads, cwd) : [];
|
|
39
40
|
const systemPrompt = appendAgentRefinementOverlay(`${agent.systemPrompt}${buildAgentMemoryInjection(agent, cwd)}${skillPrompt ? `\n\n${skillPrompt}` : ""}${readPaths.length ? `\n\n[Read from: ${readPaths.join(", ")}]` : ""}`, { cwd, agentName: agent.name });
|
|
41
|
+
assertNoToolModifierEntries(agent.name, agent.tools ?? []);
|
|
40
42
|
const denied = new Set(agent.excludeTools ?? []);
|
|
41
43
|
const tools = (agent.tools === undefined ? remoteDefaultTools : agent.tools).filter((tool) => !denied.has(tool) && (!resources.toolCeiling || resources.toolCeiling.includes(tool)));
|
|
42
44
|
if (agent.tools === undefined && !tools.length)
|
|
@@ -91,7 +93,6 @@ export default function registerHerdrPiBridge(pi) {
|
|
|
91
93
|
client.write(encodeHerdrPiFrame(record));
|
|
92
94
|
};
|
|
93
95
|
const sessionId = (ctx) => ctx.sessionManager?.getSessionId?.();
|
|
94
|
-
const activeTools = (ctx) => ctx.getActiveTools?.() ?? [];
|
|
95
96
|
pi.registerTool({
|
|
96
97
|
name: "contact_supervisor",
|
|
97
98
|
...MODEL_ONLY_TOOL,
|
|
@@ -104,11 +105,11 @@ export default function registerHerdrPiBridge(pi) {
|
|
|
104
105
|
const id = `sup_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`;
|
|
105
106
|
send({ type: "supervisor-request", requestId: id, reason: input.reason, message: input.message?.slice(0, 65_536) ?? "", ...(input.interview !== undefined ? { interview: input.interview } : {}), expectsReply: input.reason !== "progress_update", nativeSessionId });
|
|
106
107
|
if (input.reason === "progress_update") {
|
|
107
|
-
await new Promise((resolve) => supervisor.set(id, { reason: input.reason, resolve }));
|
|
108
|
+
await new Promise((resolve, reject) => supervisor.set(id, { reason: input.reason, resolve, reject }));
|
|
108
109
|
return { content: [{ type: "text", text: "Supervisor progress update queued." }], details: { delivered: true, requestId: id, reason: input.reason } };
|
|
109
110
|
}
|
|
110
111
|
const reason = input.reason;
|
|
111
|
-
const answer = await new Promise((resolve) => supervisor.set(id, { reason, resolve }));
|
|
112
|
+
const answer = await new Promise((resolve, reject) => supervisor.set(id, { reason, resolve, reject }));
|
|
112
113
|
const details = { requestId: id, reason };
|
|
113
114
|
if (reason === "interview_request")
|
|
114
115
|
try {
|
|
@@ -145,7 +146,10 @@ export default function registerHerdrPiBridge(pi) {
|
|
|
145
146
|
if (!entry)
|
|
146
147
|
throw new Error("Unknown supervisor request.");
|
|
147
148
|
supervisor.delete(request.supervisorId);
|
|
148
|
-
|
|
149
|
+
if (request.error !== undefined)
|
|
150
|
+
entry.reject(new Error(request.error));
|
|
151
|
+
else
|
|
152
|
+
entry.resolve(request.text ?? "");
|
|
149
153
|
}
|
|
150
154
|
send({ type: "control-ack", requestId, operation: request.operation, nativeSessionId });
|
|
151
155
|
};
|
|
@@ -165,7 +169,7 @@ export default function registerHerdrPiBridge(pi) {
|
|
|
165
169
|
if (!nativeSessionId)
|
|
166
170
|
throw new Error("Pane-native Pi bridge requires a persisted native session identity.");
|
|
167
171
|
fs.writeFileSync(manifestPath, JSON.stringify({ protocol: HERDR_PI_PROTOCOL, packageVersion: packageJson.version, runId, socketPath, nativeSessionId, pid: process.pid }), { mode: 0o600, flag: "wx" });
|
|
168
|
-
send({ type: "ready", packageVersion: packageJson.version, nativeSessionId, sessionFile: ctx.sessionManager?.getSessionFile?.(), cwd: process.cwd(), tools:
|
|
172
|
+
send({ type: "ready", packageVersion: packageJson.version, nativeSessionId, sessionFile: ctx.sessionManager?.getSessionFile?.(), cwd: process.cwd(), tools: pi.getActiveTools(), model: ctx.model?.provider && ctx.model.id ? `${ctx.model.provider}/${ctx.model.id}` : undefined, configured, activeRequestId, evidenceCursor, evidenceFloor: journal[0]?.cursor ?? evidenceCursor + 1, initialGit: gitEvidence(process.cwd()) });
|
|
169
173
|
});
|
|
170
174
|
on("message_end", (event) => {
|
|
171
175
|
const message = event.message;
|
|
@@ -215,11 +219,11 @@ export default function registerHerdrPiBridge(pi) {
|
|
|
215
219
|
const invalidReads = resources.reads !== undefined && resources.reads !== false && (!Array.isArray(resources.reads) || resources.reads.some((name) => typeof name !== "string"));
|
|
216
220
|
if (typeof resources.agent !== "string" || !/^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/u.test(resources.agent) || invalidSkills || invalidReads)
|
|
217
221
|
throw new Error("Remote logical resource names are invalid.");
|
|
218
|
-
const resolved = resolveRemoteHerdrResources(process.cwd(), { agent: resources.agent, ...(resources.skills ? { skills: resources.skills } : {}), ...(resources.toolCeiling ? { toolCeiling: resources.toolCeiling } : {}), ...(resources.reads !== undefined ? { reads: resources.reads } : {}) }, context ?
|
|
222
|
+
const resolved = resolveRemoteHerdrResources(process.cwd(), { agent: resources.agent, ...(resources.skills ? { skills: resources.skills } : {}), ...(resources.toolCeiling ? { toolCeiling: resources.toolCeiling } : {}), ...(resources.reads !== undefined ? { reads: resources.reads } : {}) }, context ? pi.getActiveTools() : []);
|
|
219
223
|
resolvedSystemPrompt = resolved.systemPrompt;
|
|
220
224
|
resolvedContextPolicy = resolved;
|
|
221
|
-
pi.setActiveTools
|
|
222
|
-
const acknowledged = context ?
|
|
225
|
+
pi.setActiveTools(resolved.tools);
|
|
226
|
+
const acknowledged = context ? pi.getActiveTools() : [];
|
|
223
227
|
if (acknowledged.some((tool) => !resolved.tools.includes(tool)) || resolved.tools.some((tool) => !acknowledged.includes(tool)))
|
|
224
228
|
throw new Error("Remote Pi did not apply the resolved active-tool set exactly.");
|
|
225
229
|
configured = true;
|
|
@@ -234,7 +238,10 @@ export default function registerHerdrPiBridge(pi) {
|
|
|
234
238
|
const text = frame.text;
|
|
235
239
|
if (text !== undefined && (typeof text !== "string" || Buffer.byteLength(text) > 256 * 1024))
|
|
236
240
|
throw new Error("Bridge request text is invalid or too large.");
|
|
237
|
-
|
|
241
|
+
const error = frame.error;
|
|
242
|
+
if (error !== undefined && (operation !== "supervisor-reply" || typeof error !== "string" || Buffer.byteLength(error) > 256 * 1024))
|
|
243
|
+
throw new Error("Bridge supervisor error is invalid or too large.");
|
|
244
|
+
pending.set(frame.requestId, { operation: operation, ...(text !== undefined ? { text } : {}), ...(typeof frame.supervisorId === "string" ? { supervisorId: frame.supervisorId } : {}), ...(error !== undefined ? { error } : {}) });
|
|
238
245
|
send({ type: "prepared", requestId: frame.requestId, nativeSessionId });
|
|
239
246
|
}
|
|
240
247
|
}
|
|
@@ -245,7 +252,7 @@ export default function registerHerdrPiBridge(pi) {
|
|
|
245
252
|
socket.on("close", () => { if (client === socket)
|
|
246
253
|
client = undefined; });
|
|
247
254
|
if (context && nativeSessionId)
|
|
248
|
-
send({ type: "ready", packageVersion: packageJson.version, nativeSessionId, sessionFile: context.sessionManager?.getSessionFile?.(), cwd: process.cwd(), tools:
|
|
255
|
+
send({ type: "ready", packageVersion: packageJson.version, nativeSessionId, sessionFile: context.sessionManager?.getSessionFile?.(), cwd: process.cwd(), tools: pi.getActiveTools(), model: context.model?.provider && context.model.id ? `${context.model.provider}/${context.model.id}` : undefined, configured, activeRequestId, evidenceCursor, evidenceFloor: journal[0]?.cursor ?? evidenceCursor + 1, initialGit: gitEvidence(process.cwd()) });
|
|
249
256
|
});
|
|
250
257
|
server.listen(socketPath, () => { fs.chmodSync(socketPath, 0o600); });
|
|
251
258
|
}
|