@esso0428/pi-subagents 0.17.5 → 0.17.7
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 +14 -0
- package/CONTRIBUTING.md +4 -0
- package/README.md +1 -1
- package/dist/abortable.d.ts +13 -0
- package/dist/abortable.d.ts.map +1 -0
- package/dist/abortable.js +43 -0
- package/dist/abortable.js.map +1 -0
- package/dist/agent-color.d.ts +36 -0
- package/dist/agent-color.d.ts.map +1 -0
- package/dist/agent-color.js +124 -0
- package/dist/agent-color.js.map +1 -0
- package/dist/agent-file-toggle.d.ts +126 -0
- package/dist/agent-file-toggle.d.ts.map +1 -0
- package/dist/agent-file-toggle.js +259 -0
- package/dist/agent-file-toggle.js.map +1 -0
- package/dist/agent-history.d.ts +4 -0
- package/dist/agent-history.d.ts.map +1 -1
- package/dist/agent-history.js +47 -1
- package/dist/agent-history.js.map +1 -1
- package/dist/agent-manager.d.ts +370 -56
- package/dist/agent-manager.d.ts.map +1 -1
- package/dist/agent-manager.js +1123 -409
- package/dist/agent-manager.js.map +1 -1
- package/dist/agent-runner.d.ts +100 -10
- package/dist/agent-runner.d.ts.map +1 -1
- package/dist/agent-runner.js +166 -21
- package/dist/agent-runner.js.map +1 -1
- package/dist/agent-types.d.ts +57 -5
- package/dist/agent-types.d.ts.map +1 -1
- package/dist/agent-types.js +164 -32
- package/dist/agent-types.js.map +1 -1
- package/dist/child-context.d.ts +3 -0
- package/dist/child-context.d.ts.map +1 -0
- package/dist/child-context.js +13 -0
- package/dist/child-context.js.map +1 -0
- package/dist/cross-extension-rpc.d.ts +23 -3
- package/dist/cross-extension-rpc.d.ts.map +1 -1
- package/dist/cross-extension-rpc.js +79 -17
- package/dist/cross-extension-rpc.js.map +1 -1
- package/dist/custom-agents.d.ts +38 -1
- package/dist/custom-agents.d.ts.map +1 -1
- package/dist/custom-agents.js +164 -12
- package/dist/custom-agents.js.map +1 -1
- package/dist/index.d.ts +34 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1908 -495
- package/dist/index.js.map +1 -1
- package/dist/invocation-config.d.ts +87 -2
- package/dist/invocation-config.d.ts.map +1 -1
- package/dist/invocation-config.js +71 -3
- package/dist/invocation-config.js.map +1 -1
- package/dist/mention-clone.d.ts +88 -0
- package/dist/mention-clone.d.ts.map +1 -0
- package/dist/mention-clone.js +154 -0
- package/dist/mention-clone.js.map +1 -0
- package/dist/mention.d.ts +82 -0
- package/dist/mention.d.ts.map +1 -0
- package/dist/mention.js +132 -0
- package/dist/mention.js.map +1 -0
- package/dist/model-resolver.d.ts +17 -0
- package/dist/model-resolver.d.ts.map +1 -1
- package/dist/model-resolver.js +15 -0
- package/dist/model-resolver.js.map +1 -1
- package/dist/model-scope.d.ts +50 -0
- package/dist/model-scope.d.ts.map +1 -0
- package/dist/model-scope.js +49 -0
- package/dist/model-scope.js.map +1 -0
- package/dist/nested-tools.d.ts +57 -0
- package/dist/nested-tools.d.ts.map +1 -0
- package/dist/nested-tools.js +301 -0
- package/dist/nested-tools.js.map +1 -0
- package/dist/output-file.d.ts +22 -3
- package/dist/output-file.d.ts.map +1 -1
- package/dist/output-file.js +58 -7
- package/dist/output-file.js.map +1 -1
- package/dist/prompts.d.ts +23 -0
- package/dist/prompts.d.ts.map +1 -1
- package/dist/prompts.js +20 -2
- package/dist/prompts.js.map +1 -1
- package/dist/schedule.d.ts.map +1 -1
- package/dist/schedule.js +36 -15
- package/dist/schedule.js.map +1 -1
- package/dist/settings.d.ts +228 -2
- package/dist/settings.d.ts.map +1 -1
- package/dist/settings.js +94 -0
- package/dist/settings.js.map +1 -1
- package/dist/status-note.d.ts +49 -1
- package/dist/status-note.d.ts.map +1 -1
- package/dist/status-note.js +62 -1
- package/dist/status-note.js.map +1 -1
- package/dist/structured-output.d.ts +62 -0
- package/dist/structured-output.d.ts.map +1 -0
- package/dist/structured-output.js +113 -0
- package/dist/structured-output.js.map +1 -0
- package/dist/types.d.ts +176 -10
- package/dist/types.d.ts.map +1 -1
- package/dist/ui/agent-mention.d.ts +83 -0
- package/dist/ui/agent-mention.d.ts.map +1 -0
- package/dist/ui/agent-mention.js +188 -0
- package/dist/ui/agent-mention.js.map +1 -0
- package/dist/ui/agent-widget.d.ts +96 -75
- package/dist/ui/agent-widget.d.ts.map +1 -1
- package/dist/ui/agent-widget.js +397 -420
- package/dist/ui/agent-widget.js.map +1 -1
- package/dist/ui/conversation-blocks.d.ts.map +1 -1
- package/dist/ui/conversation-blocks.js +6 -0
- package/dist/ui/conversation-blocks.js.map +1 -1
- package/dist/ui/conversation-timeline.d.ts +10 -2
- package/dist/ui/conversation-timeline.d.ts.map +1 -1
- package/dist/ui/conversation-timeline.js +130 -23
- package/dist/ui/conversation-timeline.js.map +1 -1
- package/dist/ui/conversation-viewer.d.ts +20 -5
- package/dist/ui/conversation-viewer.d.ts.map +1 -1
- package/dist/ui/conversation-viewer.js +274 -73
- package/dist/ui/conversation-viewer.js.map +1 -1
- package/dist/ui/fleet-list.d.ts +198 -0
- package/dist/ui/fleet-list.d.ts.map +1 -0
- package/dist/ui/fleet-list.js +487 -0
- package/dist/ui/fleet-list.js.map +1 -0
- package/dist/ui/schedule-menu.d.ts.map +1 -1
- package/dist/ui/schedule-menu.js +6 -7
- package/dist/ui/schedule-menu.js.map +1 -1
- package/dist/ui/select-item.d.ts +28 -0
- package/dist/ui/select-item.d.ts.map +1 -0
- package/dist/ui/select-item.js +35 -0
- package/dist/ui/select-item.js.map +1 -0
- package/dist/ui/workflow-card.d.ts +176 -0
- package/dist/ui/workflow-card.d.ts.map +1 -0
- package/dist/ui/workflow-card.js +333 -0
- package/dist/ui/workflow-card.js.map +1 -0
- package/dist/ui/workflow-dialog.d.ts +306 -0
- package/dist/ui/workflow-dialog.d.ts.map +1 -0
- package/dist/ui/workflow-dialog.js +844 -0
- package/dist/ui/workflow-dialog.js.map +1 -0
- package/dist/ui/workflow-menu.d.ts +61 -0
- package/dist/ui/workflow-menu.d.ts.map +1 -0
- package/dist/ui/workflow-menu.js +148 -0
- package/dist/ui/workflow-menu.js.map +1 -0
- package/dist/usage.d.ts +86 -1
- package/dist/usage.d.ts.map +1 -1
- package/dist/usage.js +72 -1
- package/dist/usage.js.map +1 -1
- package/dist/workflow/collisions.d.ts +96 -0
- package/dist/workflow/collisions.d.ts.map +1 -0
- package/dist/workflow/collisions.js +89 -0
- package/dist/workflow/collisions.js.map +1 -0
- package/dist/workflow/entry.d.ts +33 -0
- package/dist/workflow/entry.d.ts.map +1 -0
- package/dist/workflow/entry.js +30 -0
- package/dist/workflow/entry.js.map +1 -0
- package/dist/workflow/host.d.ts +63 -0
- package/dist/workflow/host.d.ts.map +1 -0
- package/dist/workflow/host.js +363 -0
- package/dist/workflow/host.js.map +1 -0
- package/dist/workflow/journal.d.ts +98 -0
- package/dist/workflow/journal.d.ts.map +1 -0
- package/dist/workflow/journal.js +121 -0
- package/dist/workflow/journal.js.map +1 -0
- package/dist/workflow/json-schema.d.ts +52 -0
- package/dist/workflow/json-schema.d.ts.map +1 -0
- package/dist/workflow/json-schema.js +112 -0
- package/dist/workflow/json-schema.js.map +1 -0
- package/dist/workflow/meta.d.ts +68 -0
- package/dist/workflow/meta.d.ts.map +1 -0
- package/dist/workflow/meta.js +318 -0
- package/dist/workflow/meta.js.map +1 -0
- package/dist/workflow/progress.d.ts +225 -0
- package/dist/workflow/progress.d.ts.map +1 -0
- package/dist/workflow/progress.js +362 -0
- package/dist/workflow/progress.js.map +1 -0
- package/dist/workflow/runtime.d.ts +335 -0
- package/dist/workflow/runtime.d.ts.map +1 -0
- package/dist/workflow/runtime.js +831 -0
- package/dist/workflow/runtime.js.map +1 -0
- package/dist/workflow/saved.d.ts +91 -0
- package/dist/workflow/saved.d.ts.map +1 -0
- package/dist/workflow/saved.js +204 -0
- package/dist/workflow/saved.js.map +1 -0
- package/dist/workflow/task.d.ts +137 -0
- package/dist/workflow/task.d.ts.map +1 -0
- package/dist/workflow/task.js +208 -0
- package/dist/workflow/task.js.map +1 -0
- package/dist/workflow/tool-description.d.ts +39 -0
- package/dist/workflow/tool-description.d.ts.map +1 -0
- package/dist/workflow/tool-description.js +200 -0
- package/dist/workflow/tool-description.js.map +1 -0
- package/dist/workflow/worker-source.d.ts +48 -0
- package/dist/workflow/worker-source.d.ts.map +1 -0
- package/dist/workflow/worker-source.js +779 -0
- package/dist/workflow/worker-source.js.map +1 -0
- package/dist/worktree.d.ts +10 -3
- package/dist/worktree.d.ts.map +1 -1
- package/dist/worktree.js +58 -54
- package/dist/worktree.js.map +1 -1
- package/dist/xml.d.ts +11 -0
- package/dist/xml.d.ts.map +1 -0
- package/dist/xml.js +13 -0
- package/dist/xml.js.map +1 -0
- package/docs/rpc.md +183 -0
- package/docs/superpowers/plans/2026-09-30-conversation-viewer-scrollbar.md +216 -0
- package/docs/superpowers/plans/2026-09-30-upstream-event-workflow-partial-history.md +195 -0
- package/docs/superpowers/specs/2026-09-30-conversation-viewer-scrollbar-design.md +82 -0
- package/docs/superpowers/specs/2026-09-30-upstream-event-workflow-partial-history-design.md +49 -0
- package/docs/workflows.md +437 -0
- package/examples/agent-tool-description.md +7 -7
- package/examples/workflows/compose.js +51 -0
- package/examples/workflows/fan-out-audit.js +47 -0
- package/examples/workflows/gated-fix.js +60 -0
- package/examples/workflows/lib/count-child.js +27 -0
- package/examples/workflows/review-panel.js +63 -0
- package/examples/workflows/structured-findings.js +78 -0
- package/package.json +1 -1
- package/src/abortable.ts +43 -0
- package/src/agent-color.ts +161 -0
- package/src/agent-file-toggle.ts +269 -0
- package/src/agent-history.ts +54 -2
- package/src/agent-manager.ts +1263 -402
- package/src/agent-runner.ts +251 -27
- package/src/agent-types.ts +188 -32
- package/src/child-context.ts +15 -0
- package/src/cross-extension-rpc.ts +96 -20
- package/src/custom-agents.ts +170 -13
- package/src/index.ts +2024 -537
- package/src/invocation-config.ts +118 -3
- package/src/mention-clone.ts +196 -0
- package/src/mention.ts +141 -0
- package/src/model-resolver.ts +18 -0
- package/src/model-scope.ts +70 -0
- package/src/nested-tools.ts +424 -0
- package/src/output-file.ts +61 -6
- package/src/prompts.ts +45 -2
- package/src/schedule.ts +35 -14
- package/src/settings.ts +312 -2
- package/src/status-note.ts +66 -1
- package/src/structured-output.ts +130 -0
- package/src/types.ts +177 -10
- package/src/ui/agent-mention.ts +216 -0
- package/src/ui/agent-widget.ts +389 -441
- package/src/ui/conversation-blocks.ts +6 -0
- package/src/ui/conversation-timeline.ts +139 -25
- package/src/ui/conversation-viewer.ts +284 -69
- package/src/ui/fleet-list.ts +558 -0
- package/src/ui/schedule-menu.ts +9 -8
- package/src/ui/select-item.ts +45 -0
- package/src/ui/workflow-card.ts +470 -0
- package/src/ui/workflow-dialog.ts +1115 -0
- package/src/ui/workflow-menu.ts +193 -0
- package/src/usage.ts +109 -2
- package/src/workflow/collisions.ts +123 -0
- package/src/workflow/entry.ts +47 -0
- package/src/workflow/host.ts +403 -0
- package/src/workflow/journal.ts +164 -0
- package/src/workflow/json-schema.ts +128 -0
- package/src/workflow/meta.ts +325 -0
- package/src/workflow/progress.ts +550 -0
- package/src/workflow/runtime.ts +1219 -0
- package/src/workflow/saved.ts +217 -0
- package/src/workflow/task.ts +302 -0
- package/src/workflow/tool-description.ts +200 -0
- package/src/workflow/worker-source.ts +781 -0
- package/src/worktree.ts +69 -55
- package/src/xml.ts +13 -0
- package/vitest.config.ts +0 -18
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"worker-source.js","sourceRoot":"","sources":["../../src/workflow/worker-source.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6CG;AAEH;;;;;;GAMG;AACH,MAAM,mBAAmB,GACvB,6BAA6B;IAC7B,oCAAoC;IACpC,gCAAgC;IAChC,gFAAgF;IAChF,+EAA+E;IAC/E,KAAK;IACL,8DAA8D;IAC9D,gEAAgE;IAChE,+CAA+C;IAC/C,0FAA0F;IAC1F,KAAK;IACL,OAAO,CAAC;AAEV,MAAM,CAAC,MAAM,aAAa,GAAG;;;;;;;kBAOX,IAAI,CAAC,SAAS,CAAC,mBAAmB,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAisBpD,CAAC"}
|
package/dist/worktree.d.ts
CHANGED
|
@@ -4,7 +4,12 @@
|
|
|
4
4
|
* Creates a temporary git worktree so the agent works on an isolated copy of the repo.
|
|
5
5
|
* On completion, if no changes were made, the worktree is cleaned up.
|
|
6
6
|
* If changes exist, a branch is created and returned in the result.
|
|
7
|
+
*
|
|
8
|
+
* Every git call goes through `pi.exec` (async) rather than `execFileSync`: a
|
|
9
|
+
* worktree copy can take seconds, and a session that spawns several isolated
|
|
10
|
+
* agents at once would otherwise serialize them all on the TUI's event loop.
|
|
7
11
|
*/
|
|
12
|
+
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
8
13
|
export interface WorktreeInfo {
|
|
9
14
|
/** Absolute path to the worktree directory (the copied repo's root). */
|
|
10
15
|
path: string;
|
|
@@ -20,6 +25,8 @@ export interface WorktreeInfo {
|
|
|
20
25
|
*/
|
|
21
26
|
workPath: string;
|
|
22
27
|
}
|
|
28
|
+
export declare function setWorktreeIsolationEnabled(enabled: boolean): void;
|
|
29
|
+
export declare function isWorktreeIsolationEnabled(): boolean;
|
|
23
30
|
export interface WorktreeCleanupResult {
|
|
24
31
|
/** Whether changes were found in the worktree. */
|
|
25
32
|
hasChanges: boolean;
|
|
@@ -32,15 +39,15 @@ export interface WorktreeCleanupResult {
|
|
|
32
39
|
* Create a temporary git worktree for an agent.
|
|
33
40
|
* Returns the worktree path, or undefined if not in a git repo.
|
|
34
41
|
*/
|
|
35
|
-
export declare function createWorktree(cwd: string, agentId: string): WorktreeInfo | undefined
|
|
42
|
+
export declare function createWorktree(pi: ExtensionAPI, cwd: string, agentId: string): Promise<WorktreeInfo | undefined>;
|
|
36
43
|
/**
|
|
37
44
|
* Clean up a worktree after agent completion.
|
|
38
45
|
* - If no changes: remove worktree entirely.
|
|
39
46
|
* - If changes exist: create a branch, commit changes, return branch info.
|
|
40
47
|
*/
|
|
41
|
-
export declare function cleanupWorktree(cwd: string, worktree: WorktreeInfo, agentDescription: string): WorktreeCleanupResult
|
|
48
|
+
export declare function cleanupWorktree(pi: ExtensionAPI, cwd: string, worktree: WorktreeInfo, agentDescription: string): Promise<WorktreeCleanupResult>;
|
|
42
49
|
/**
|
|
43
50
|
* Prune any orphaned worktrees (crash recovery).
|
|
44
51
|
*/
|
|
45
|
-
export declare function pruneWorktrees(cwd: string): void
|
|
52
|
+
export declare function pruneWorktrees(pi: ExtensionAPI, cwd: string): Promise<void>;
|
|
46
53
|
//# sourceMappingURL=worktree.d.ts.map
|
package/dist/worktree.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"worktree.d.ts","sourceRoot":"","sources":["../src/worktree.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"worktree.d.ts","sourceRoot":"","sources":["../src/worktree.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAMH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,iCAAiC,CAAC;AAEpE,MAAM,WAAW,YAAY;IAC3B,wEAAwE;IACxE,IAAI,EAAE,MAAM,CAAC;IACb,gEAAgE;IAChE,MAAM,EAAE,MAAM,CAAC;IACf,qDAAqD;IACrD,OAAO,EAAE,MAAM,CAAC;IAChB;;;;;OAKG;IACH,QAAQ,EAAE,MAAM,CAAC;CAClB;AAcD,wBAAgB,2BAA2B,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAElE;AAED,wBAAgB,0BAA0B,IAAI,OAAO,CAEpD;AAED,MAAM,WAAW,qBAAqB;IACpC,kDAAkD;IAClD,UAAU,EAAE,OAAO,CAAC;IACpB,6CAA6C;IAC7C,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,oCAAoC;IACpC,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAmBD;;;GAGG;AACH,wBAAsB,cAAc,CAClC,EAAE,EAAE,YAAY,EAChB,GAAG,EAAE,MAAM,EACX,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,YAAY,GAAG,SAAS,CAAC,CA6BnC;AAED;;;;GAIG;AACH,wBAAsB,eAAe,CACnC,EAAE,EAAE,YAAY,EAChB,GAAG,EAAE,MAAM,EACX,QAAQ,EAAE,YAAY,EACtB,gBAAgB,EAAE,MAAM,GACvB,OAAO,CAAC,qBAAqB,CAAC,CAoDhC;AAgBD;;GAEG;AACH,wBAAsB,cAAc,CAAC,EAAE,EAAE,YAAY,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAIjF"}
|
package/dist/worktree.js
CHANGED
|
@@ -4,32 +4,64 @@
|
|
|
4
4
|
* Creates a temporary git worktree so the agent works on an isolated copy of the repo.
|
|
5
5
|
* On completion, if no changes were made, the worktree is cleaned up.
|
|
6
6
|
* If changes exist, a branch is created and returned in the result.
|
|
7
|
+
*
|
|
8
|
+
* Every git call goes through `pi.exec` (async) rather than `execFileSync`: a
|
|
9
|
+
* worktree copy can take seconds, and a session that spawns several isolated
|
|
10
|
+
* agents at once would otherwise serialize them all on the TUI's event loop.
|
|
7
11
|
*/
|
|
8
|
-
import { execFileSync } from "node:child_process";
|
|
9
12
|
import { randomUUID } from "node:crypto";
|
|
10
13
|
import { existsSync, realpathSync } from "node:fs";
|
|
11
14
|
import { tmpdir } from "node:os";
|
|
12
15
|
import { join, relative } from "node:path";
|
|
16
|
+
/**
|
|
17
|
+
* Project-wide switch for worktree isolation (`worktreeIsolation` in
|
|
18
|
+
* subagents.json). Default `true` — unchanged behaviour.
|
|
19
|
+
*
|
|
20
|
+
* The `"off"` isolation value gives a model a legal way to decline a worktree,
|
|
21
|
+
* but it still depends on the model choosing it. This is the deterministic half
|
|
22
|
+
* of the same fix: on a large repo where every worktree costs real time and
|
|
23
|
+
* disk (#184), turning it off means no caller can create one, whatever it
|
|
24
|
+
* passes.
|
|
25
|
+
*/
|
|
26
|
+
let worktreeIsolationEnabled = true;
|
|
27
|
+
export function setWorktreeIsolationEnabled(enabled) {
|
|
28
|
+
worktreeIsolationEnabled = enabled;
|
|
29
|
+
}
|
|
30
|
+
export function isWorktreeIsolationEnabled() {
|
|
31
|
+
return worktreeIsolationEnabled;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Run git and return its trimmed stdout, throwing on failure so callers keep
|
|
35
|
+
* the try/catch control flow `execFileSync` gave them.
|
|
36
|
+
*
|
|
37
|
+
* `pi.exec` never rejects — it reports failure in the result — and a command
|
|
38
|
+
* killed by its timeout comes back as `killed` with an exit code of 0, so both
|
|
39
|
+
* have to be checked to reproduce `execFileSync`'s "throws on anything but a
|
|
40
|
+
* clean exit".
|
|
41
|
+
*/
|
|
42
|
+
async function git(pi, cwd, args, timeout) {
|
|
43
|
+
const result = await pi.exec("git", args, { cwd, timeout });
|
|
44
|
+
if (result.killed || result.code !== 0) {
|
|
45
|
+
throw new Error(result.stderr.trim() || `git ${args.join(" ")} failed (exit ${result.code})`);
|
|
46
|
+
}
|
|
47
|
+
return result.stdout.trim();
|
|
48
|
+
}
|
|
13
49
|
/**
|
|
14
50
|
* Create a temporary git worktree for an agent.
|
|
15
51
|
* Returns the worktree path, or undefined if not in a git repo.
|
|
16
52
|
*/
|
|
17
|
-
export function createWorktree(cwd, agentId) {
|
|
53
|
+
export async function createWorktree(pi, cwd, agentId) {
|
|
18
54
|
// Verify we're in a git repo with at least one commit (HEAD must exist)
|
|
19
55
|
let baseSha;
|
|
20
56
|
let subdir;
|
|
21
57
|
try {
|
|
22
|
-
|
|
23
|
-
baseSha =
|
|
24
|
-
.toString()
|
|
25
|
-
.trim();
|
|
58
|
+
await git(pi, cwd, ["rev-parse", "--is-inside-work-tree"], 5000);
|
|
59
|
+
baseSha = await git(pi, cwd, ["rev-parse", "HEAD"], 5000);
|
|
26
60
|
// Where cwd sits inside the repo ("" at the root): the agent must work at
|
|
27
61
|
// the same subdirectory inside the copy, or a monorepo-package cwd would
|
|
28
62
|
// silently widen to the whole repo. realpath both sides — git emits
|
|
29
63
|
// resolved paths while cwd may arrive through a symlink (macOS /tmp).
|
|
30
|
-
const topLevel =
|
|
31
|
-
.toString()
|
|
32
|
-
.trim();
|
|
64
|
+
const topLevel = await git(pi, cwd, ["rev-parse", "--show-toplevel"], 5000);
|
|
33
65
|
subdir = relative(realpathSync(topLevel), realpathSync(cwd));
|
|
34
66
|
}
|
|
35
67
|
catch {
|
|
@@ -40,11 +72,7 @@ export function createWorktree(cwd, agentId) {
|
|
|
40
72
|
const worktreePath = join(tmpdir(), `pi-agent-${agentId}-${suffix}`);
|
|
41
73
|
try {
|
|
42
74
|
// Create detached worktree at HEAD
|
|
43
|
-
|
|
44
|
-
cwd,
|
|
45
|
-
stdio: "pipe",
|
|
46
|
-
timeout: 30000,
|
|
47
|
-
});
|
|
75
|
+
await git(pi, cwd, ["worktree", "add", "--detach", worktreePath, "HEAD"], 30000);
|
|
48
76
|
return { path: worktreePath, branch, baseSha, workPath: subdir ? join(worktreePath, subdir) : worktreePath };
|
|
49
77
|
}
|
|
50
78
|
catch {
|
|
@@ -57,38 +85,26 @@ export function createWorktree(cwd, agentId) {
|
|
|
57
85
|
* - If no changes: remove worktree entirely.
|
|
58
86
|
* - If changes exist: create a branch, commit changes, return branch info.
|
|
59
87
|
*/
|
|
60
|
-
export function cleanupWorktree(cwd, worktree, agentDescription) {
|
|
88
|
+
export async function cleanupWorktree(pi, cwd, worktree, agentDescription) {
|
|
61
89
|
if (!existsSync(worktree.path)) {
|
|
62
90
|
return { hasChanges: false };
|
|
63
91
|
}
|
|
64
92
|
try {
|
|
65
93
|
// Check for uncommitted changes in the worktree
|
|
66
|
-
const status =
|
|
67
|
-
cwd: worktree.path,
|
|
68
|
-
stdio: "pipe",
|
|
69
|
-
timeout: 10000,
|
|
70
|
-
}).toString().trim();
|
|
94
|
+
const status = await git(pi, worktree.path, ["status", "--porcelain"], 10000);
|
|
71
95
|
if (status) {
|
|
72
96
|
// Changes exist — stage, commit, and create a branch
|
|
73
|
-
|
|
74
|
-
// Truncate description for commit message (no shell sanitization needed —
|
|
97
|
+
await git(pi, worktree.path, ["add", "-A"], 10000);
|
|
98
|
+
// Truncate description for commit message (no shell sanitization needed — pi.exec uses argv)
|
|
75
99
|
const safeDesc = agentDescription.slice(0, 200);
|
|
76
100
|
const commitMsg = `pi-agent: ${safeDesc}`;
|
|
77
|
-
|
|
78
|
-
cwd: worktree.path,
|
|
79
|
-
stdio: "pipe",
|
|
80
|
-
timeout: 10000,
|
|
81
|
-
});
|
|
101
|
+
await git(pi, worktree.path, ["commit", "--no-verify", "-m", commitMsg], 10000);
|
|
82
102
|
}
|
|
83
103
|
else {
|
|
84
|
-
const currentSha =
|
|
85
|
-
cwd: worktree.path,
|
|
86
|
-
stdio: "pipe",
|
|
87
|
-
timeout: 5000,
|
|
88
|
-
}).toString().trim();
|
|
104
|
+
const currentSha = await git(pi, worktree.path, ["rev-parse", "HEAD"], 5000);
|
|
89
105
|
if (currentSha === worktree.baseSha) {
|
|
90
106
|
// No changes — remove worktree
|
|
91
|
-
removeWorktree(cwd, worktree.path);
|
|
107
|
+
await removeWorktree(pi, cwd, worktree.path);
|
|
92
108
|
return { hasChanges: false };
|
|
93
109
|
}
|
|
94
110
|
}
|
|
@@ -96,25 +112,17 @@ export function cleanupWorktree(cwd, worktree, agentDescription) {
|
|
|
96
112
|
// If the branch already exists, append a suffix to avoid overwriting previous work.
|
|
97
113
|
let branchName = worktree.branch;
|
|
98
114
|
try {
|
|
99
|
-
|
|
100
|
-
cwd: worktree.path,
|
|
101
|
-
stdio: "pipe",
|
|
102
|
-
timeout: 5000,
|
|
103
|
-
});
|
|
115
|
+
await git(pi, worktree.path, ["branch", branchName], 5000);
|
|
104
116
|
}
|
|
105
117
|
catch {
|
|
106
118
|
// Branch already exists — use a unique suffix
|
|
107
119
|
branchName = `${worktree.branch}-${Date.now()}`;
|
|
108
|
-
|
|
109
|
-
cwd: worktree.path,
|
|
110
|
-
stdio: "pipe",
|
|
111
|
-
timeout: 5000,
|
|
112
|
-
});
|
|
120
|
+
await git(pi, worktree.path, ["branch", branchName], 5000);
|
|
113
121
|
}
|
|
114
122
|
// Update branch name in worktree info for the caller
|
|
115
123
|
worktree.branch = branchName;
|
|
116
124
|
// Remove the worktree (branch persists in main repo)
|
|
117
|
-
removeWorktree(cwd, worktree.path);
|
|
125
|
+
await removeWorktree(pi, cwd, worktree.path);
|
|
118
126
|
return {
|
|
119
127
|
hasChanges: true,
|
|
120
128
|
branch: worktree.branch,
|
|
@@ -124,7 +132,7 @@ export function cleanupWorktree(cwd, worktree, agentDescription) {
|
|
|
124
132
|
catch {
|
|
125
133
|
// Best effort cleanup on error
|
|
126
134
|
try {
|
|
127
|
-
removeWorktree(cwd, worktree.path);
|
|
135
|
+
await removeWorktree(pi, cwd, worktree.path);
|
|
128
136
|
}
|
|
129
137
|
catch { /* ignore */ }
|
|
130
138
|
return { hasChanges: false };
|
|
@@ -133,18 +141,14 @@ export function cleanupWorktree(cwd, worktree, agentDescription) {
|
|
|
133
141
|
/**
|
|
134
142
|
* Force-remove a worktree.
|
|
135
143
|
*/
|
|
136
|
-
function removeWorktree(cwd, worktreePath) {
|
|
144
|
+
async function removeWorktree(pi, cwd, worktreePath) {
|
|
137
145
|
try {
|
|
138
|
-
|
|
139
|
-
cwd,
|
|
140
|
-
stdio: "pipe",
|
|
141
|
-
timeout: 10000,
|
|
142
|
-
});
|
|
146
|
+
await git(pi, cwd, ["worktree", "remove", "--force", worktreePath], 10000);
|
|
143
147
|
}
|
|
144
148
|
catch {
|
|
145
149
|
// If git worktree remove fails, try pruning
|
|
146
150
|
try {
|
|
147
|
-
|
|
151
|
+
await git(pi, cwd, ["worktree", "prune"], 5000);
|
|
148
152
|
}
|
|
149
153
|
catch { /* ignore */ }
|
|
150
154
|
}
|
|
@@ -152,9 +156,9 @@ function removeWorktree(cwd, worktreePath) {
|
|
|
152
156
|
/**
|
|
153
157
|
* Prune any orphaned worktrees (crash recovery).
|
|
154
158
|
*/
|
|
155
|
-
export function pruneWorktrees(cwd) {
|
|
159
|
+
export async function pruneWorktrees(pi, cwd) {
|
|
156
160
|
try {
|
|
157
|
-
|
|
161
|
+
await git(pi, cwd, ["worktree", "prune"], 5000);
|
|
158
162
|
}
|
|
159
163
|
catch { /* ignore */ }
|
|
160
164
|
}
|
package/dist/worktree.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"worktree.js","sourceRoot":"","sources":["../src/worktree.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"worktree.js","sourceRoot":"","sources":["../src/worktree.ts"],"names":[],"mappings":"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"}
|
package/dist/xml.d.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* xml.ts — escaping for the `<task-notification>` payloads.
|
|
3
|
+
*
|
|
4
|
+
* A module of its own because both notification builders need it and they sit
|
|
5
|
+
* on opposite sides of a dependency edge: the agent one lives in `index.ts`,
|
|
6
|
+
* which imports `workflow/task.ts`, so the workflow one cannot reach back for
|
|
7
|
+
* it without a cycle.
|
|
8
|
+
*/
|
|
9
|
+
/** Escape XML special characters to prevent injection in structured notifications. */
|
|
10
|
+
export declare function escapeXml(s: string): string;
|
|
11
|
+
//# sourceMappingURL=xml.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"xml.d.ts","sourceRoot":"","sources":["../src/xml.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,sFAAsF;AACtF,wBAAgB,SAAS,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAE3C"}
|
package/dist/xml.js
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* xml.ts — escaping for the `<task-notification>` payloads.
|
|
3
|
+
*
|
|
4
|
+
* A module of its own because both notification builders need it and they sit
|
|
5
|
+
* on opposite sides of a dependency edge: the agent one lives in `index.ts`,
|
|
6
|
+
* which imports `workflow/task.ts`, so the workflow one cannot reach back for
|
|
7
|
+
* it without a cycle.
|
|
8
|
+
*/
|
|
9
|
+
/** Escape XML special characters to prevent injection in structured notifications. */
|
|
10
|
+
export function escapeXml(s) {
|
|
11
|
+
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
12
|
+
}
|
|
13
|
+
//# sourceMappingURL=xml.js.map
|
package/dist/xml.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"xml.js","sourceRoot":"","sources":["../src/xml.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,sFAAsF;AACtF,MAAM,UAAU,SAAS,CAAC,CAAS;IACjC,OAAO,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;AAC9E,CAAC"}
|
package/docs/rpc.md
ADDED
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
# Driving subagents from another extension
|
|
2
|
+
|
|
3
|
+
Another pi extension can spawn a subagent, listen for subagent completion, read the result and stop the run — all over the `pi.events` bus, without importing this package directly. Four request/reply channels (`subagents:rpc:ping`, `subagents:rpc:spawn`, `subagents:rpc:stop`, `subagents:rpc:consume`), eleven lifecycle events, and one in-process registry at `Symbol.for("pi-subagents:manager")`.
|
|
4
|
+
|
|
5
|
+
The thing worth understanding up front is that **the bus is in-process.** Every "RPC" call here is a synchronous `pi.events.emit` into the same event loop, and every reply comes back the same way. That single fact explains most of what follows: why `signal` and the `on*` callbacks work on a spawn payload at all, why a `consume` fired inside a `subagents:completed` handler lands *before* the notification decision has been made, and why none of this survives a real process boundary.
|
|
6
|
+
|
|
7
|
+
For the channel list, the reply envelope, the per-channel snippets and the event table, see [`README.md`](../README.md#cross-extension-rpc). This document is the reference README does not have room for: the complete spawn-option surface, every error string, the notification race, the registry, and what protocol version `2` does and does not promise.
|
|
8
|
+
|
|
9
|
+
## Spawn options
|
|
10
|
+
|
|
11
|
+
`subagents:rpc:spawn` forwards `options` to `AgentManager.spawn` — but not verbatim. The manager's `spawn` behind the RPC is `spawnTopLevel` (`src/index.ts:698-721`), which deletes internal-only fields first, and then `spawnResolved` (`src/index.ts:666-696`) overwrites the activity-tracker callbacks with its own. The full interface is `SpawnOptions` at `src/agent-manager.ts:169-303`; what a bus caller actually gets is three different things.
|
|
12
|
+
|
|
13
|
+
**Honoured** — set these and they take effect:
|
|
14
|
+
|
|
15
|
+
| Field | Type | Notes |
|
|
16
|
+
|---|---|---|
|
|
17
|
+
| `description` | string | What the agent is doing. Shown in the widget, FleetView and the completion notification |
|
|
18
|
+
| `name` | string | A memorable second handle (`@auth-audit`). Slugged, never validated — anything unusable degrades rather than failing the spawn |
|
|
19
|
+
| `model` | `Model` **or** `"provider/modelId"` | Strings are resolved at the RPC boundary against `ctx.modelRegistry`. `null` means inherit, not override. Resolution is fuzzy — see [Model Scope](../README.md#model-scope) |
|
|
20
|
+
| `maxTurns` | number | Turn ceiling for the run |
|
|
21
|
+
| `isolated` | boolean | Strips extensions, skills and nested tools. **Not** a git worktree — see the trap table below |
|
|
22
|
+
| `inheritContext` | boolean | Fork the parent conversation into the child |
|
|
23
|
+
| `thinkingLevel` | ThinkingLevel | Clamped to what the resolved model supports |
|
|
24
|
+
| `isBackground` | boolean | Occupies a `maxConcurrent` slot and queues behind them. Every RPC spawn runs detached regardless; this is what decides whether it is *pooled* |
|
|
25
|
+
| `bypassQueue` | boolean | Starts immediately even when the concurrency limit would queue it. The slot is still counted once running |
|
|
26
|
+
| `structuredOutput` | CompiledSchema | Makes the child report through a `StructuredOutput` tool |
|
|
27
|
+
| `isolation` | `"worktree"` | Temp git worktree, committed to a `pi-agent-*` branch on completion |
|
|
28
|
+
| `cwd` | absolute path | The agent's tools operate here; `.pi` config still loads from the parent session's project |
|
|
29
|
+
| `invocation` | AgentInvocation | Resolved snapshot used for UI display |
|
|
30
|
+
| `signal` | AbortSignal | Aborting it stops the subagent |
|
|
31
|
+
| `onSpawned` / `onQueued` / `onCompaction` / `onBeforeWorktreeCleanup` | functions | Fire as documented on `SpawnOptions` |
|
|
32
|
+
|
|
33
|
+
**Silently stripped** — set these and nothing happens, with no error and no note. Each deletion is a deliberate guard, and the reasons are worth knowing because they say what the surface refuses to let a caller forge:
|
|
34
|
+
|
|
35
|
+
| Field | Why it is taken away |
|
|
36
|
+
|---|---|
|
|
37
|
+
| `parentAgentId` | Ownership. A forged parent hides your agent under someone else's nested tools |
|
|
38
|
+
| `workflowId` | A forged value would hide an RPC-spawned agent inside someone else's workflow — and take it out of the concurrency pool with it |
|
|
39
|
+
| `depth`, `maxSubagentDepth` | The nesting cap is inherited, not declared |
|
|
40
|
+
| `configCwd` | Config-discovery root; only nested launches may set it |
|
|
41
|
+
| `rootSessionId` | Names a transcript directory, so a forged value is a path-traversal primitive |
|
|
42
|
+
| `resumeSessionFile` | Worse: it names a file to **open and replay** as a conversation. Dispatcher only, and only from a path this extension itself recorded |
|
|
43
|
+
| `reclaim` | Bypasses handle allocation, so a forged value would duplicate a live agent's name and make `@handle` ambiguous |
|
|
44
|
+
| `blocking` | Every spawn through here is detached. A forged `blocking` would charge it to the foreground pool and defer it behind a queue whose gate nobody is holding |
|
|
45
|
+
|
|
46
|
+
**Silently overwritten** — `onToolActivity`, `onTextDelta`, `onTurnEnd`, `onSessionCreated` and `onAssistantUsage` are replaced by the activity tracker's own (`src/index.ts:693`). Every programmatic spawn passes through one funnel so none can supply half-wired callbacks; a half-wired tracker renders worse than none, which is the bug behind a row that reads `thinking…` for an agent's whole life ([#181](https://github.com/tintinweb/pi-subagents/pull/181)).
|
|
47
|
+
|
|
48
|
+
Four things that are not obvious from the tables:
|
|
49
|
+
|
|
50
|
+
- **Nothing is required at runtime.** `description` is non-optional in TypeScript and never validated. A spawn with no `options` at all is legal and is what `test/cross-extension-rpc.test.ts:81-94` pins.
|
|
51
|
+
- **`bypassQueue` is not stripped.** Its own doc comment scopes it to the scheduler and the `/agents` generator, but a bus caller can set it and skip the `maxConcurrent` check.
|
|
52
|
+
- **`structuredOutput` is documented "set only by the workflow host"** (`src/agent-manager.ts:231-234`) and is also not stripped.
|
|
53
|
+
- **`signal` and the `on*` callbacks are function values.** They work only because the bus is in-process. A caller that genuinely serializes its payload cannot use them, and they arrive as `undefined` rather than failing.
|
|
54
|
+
|
|
55
|
+
### Names that look right and are not
|
|
56
|
+
|
|
57
|
+
One of these already shipped as a bug in this project's own README example, so it is worth reading the table even if you are sure.
|
|
58
|
+
|
|
59
|
+
| You might write | What it does | What you meant |
|
|
60
|
+
|---|---|---|
|
|
61
|
+
| `run_in_background` | Forwarded verbatim and ignored — it is the [`Agent`](../README.md#agent) *tool's* parameter name | `isBackground` |
|
|
62
|
+
| `isolated: true` | Disables extensions, skills and nested tools | `isolation: "worktree"` for a git worktree |
|
|
63
|
+
| `isolation: "worktree"` | Creates a git worktree | `isolated: true` to strip capabilities |
|
|
64
|
+
| `configCwd` | Stripped | `cwd` |
|
|
65
|
+
| `max_turns` / `thinking` / `inherit_context` | Ignored — tool and frontmatter spellings | `maxTurns` / `thinkingLevel` / `inheritContext` |
|
|
66
|
+
| `memory` | Nothing. **There is no such option** | Memory scope comes only from the agent definition's frontmatter |
|
|
67
|
+
|
|
68
|
+
**None of these produce an error.** Option keys are not validated on this path at all — unknown ones are accepted and dropped. (Contrast `agent()` inside a [workflow](workflows.md), which rejects unknown keys by name.)
|
|
69
|
+
|
|
70
|
+
## Errors
|
|
71
|
+
|
|
72
|
+
Every failure reaches the caller as `{ success: false, error }`, where `error` is `err?.message ?? String(err)` (`src/cross-extension-rpc.ts:87`) — so these strings are what you will actually see.
|
|
73
|
+
|
|
74
|
+
| Error | Source |
|
|
75
|
+
|---|---|
|
|
76
|
+
| `No active session` | `src/cross-extension-rpc.ts:107` — called before the first bound `session_start`, or in a session that excludes pi-subagents |
|
|
77
|
+
| `Model override "<label>" provided but ctx.modelRegistry is unavailable` | `src/cross-extension-rpc.ts:126` |
|
|
78
|
+
| `Model not found: "<input>".` + available models | `src/model-resolver.ts:117` |
|
|
79
|
+
| `Model not in scope: "<input>".` + allowed models | `src/model-scope.ts:62` — only with `scopeModels` on, and checked against the *resolved* model |
|
|
80
|
+
| `Unknown or disabled agent type: "<raw>". Available: <list>.` | `src/agent-types.ts:187` — only under `fallbackSubagent: none` |
|
|
81
|
+
| `No agent type given. Available: <list>.` | `src/agent-types.ts:187-194` — same condition |
|
|
82
|
+
| `<reason> The configured fallbackSubagent "<x>" is itself unknown or disabled. Available: <list>.` | `src/agent-types.ts:205-207` |
|
|
83
|
+
| `SpawnOptions.cwd must be an absolute path: "<value>"` | `src/agent-manager.ts:85` |
|
|
84
|
+
| `SpawnOptions.cwd does not exist: "<cwd>"` | `src/agent-manager.ts:91` |
|
|
85
|
+
| `SpawnOptions.cwd is not a directory: "<cwd>"` | `src/agent-manager.ts:94` |
|
|
86
|
+
| `Cannot run with isolation: "worktree" — not a git repo, no commits yet, or 'git worktree add' failed.` | `src/agent-manager.ts:716-719`, surfaced through `awaitStartup` |
|
|
87
|
+
| git plumbing failures | `src/worktree.ts:76` |
|
|
88
|
+
| `Agent not found` | stop — `src/cross-extension-rpc.ts:170` |
|
|
89
|
+
| `Agent is owned by another agent or workflow` | stop — `:178` |
|
|
90
|
+
| `Agent is not running` | stop — `:182`. The record exists, so it has already settled |
|
|
91
|
+
| `Agent not found or still running` | consume — `:193` |
|
|
92
|
+
|
|
93
|
+
Three things the table cannot show:
|
|
94
|
+
|
|
95
|
+
- **The failure that is not an error.** With `worktreeIsolation` off project-wide, `isolation: "worktree"` is dropped at `src/agent-manager.ts:712` with no error, no note on the record, and a success envelope on the wire. Your agent runs in the main tree. If you asked for isolation because two agents were going to write the same files, they now collide and nothing told you.
|
|
96
|
+
- **`data` is omitted** when a handler returns nothing, so a successful stop or consume reply is a bare `{ success: true }` and `reply.data.anything` throws.
|
|
97
|
+
- **`requestId` is not validated.** It is interpolated straight into the reply channel, so a caller that omits it gets its reply on the literal channel `subagents:rpc:spawn:reply:undefined` — where every other caller that omitted it is also listening. Send one, and send a unique one.
|
|
98
|
+
|
|
99
|
+
## Ownership
|
|
100
|
+
|
|
101
|
+
`isTopLevelAgent(record)` is `parentAgentId === undefined && workflowId === undefined` (`src/agent-manager.ts:122-126`). `subagents:rpc:stop` enforces it (`src/cross-extension-rpc.ts:178`): a nested child or a workflow's agent is owned by something that is *waiting on it*, and aborting it out from under that owner turns another extension's stop into a failed step. It is defence in depth rather than a live hole — no RPC hands out agent ids, so a caller has no ordinary way to name one it does not own.
|
|
102
|
+
|
|
103
|
+
Two asymmetries to know about, stated as they are:
|
|
104
|
+
|
|
105
|
+
- **Stop takes an id only** (`src/index.ts:806`). Consume takes an id *or* an `@handle`, through `resolveAgentRef` (`src/index.ts:816` → `:731-736`).
|
|
106
|
+
- **Consume checks `parentAgentId` but not `workflowId`** (`src/index.ts:816`). A workflow-owned agent's result can be marked consumed over the bus even though the same agent cannot be stopped.
|
|
107
|
+
|
|
108
|
+
The same predicate silently scopes the events. **Every lifecycle event is top-level only** — `subagents:started`, `:completed`, `:failed` and `:compacted` all return early for nested and workflow-owned agents (`src/index.ts:573`, `:615`, `:631`). A workflow's children are invisible on the bus: you will see the workflow's own agents come and go without a single event.
|
|
109
|
+
|
|
110
|
+
## The notification race
|
|
111
|
+
|
|
112
|
+
When a background agent finishes, pi-subagents sends the user a completion notification. If you have already shown the model that result yourself, that notification arrives on top of an answer that was already given, and it costs the parent a turn to dismiss. `subagents:rpc:consume` is how you say you have handled it — the bus-side half of what `get_subagent_result` does when it returns a result.
|
|
113
|
+
|
|
114
|
+
**When you send it decides whether it works.** The timeline:
|
|
115
|
+
|
|
116
|
+
1. The agent settles and `subagents:completed` is emitted — `src/index.ts:581`.
|
|
117
|
+
2. Eleven lines later, at `src/index.ts:592`, the code checks `record.resultConsumed` and decides whether to notify at all.
|
|
118
|
+
3. `pi.events` dispatch is synchronous and in-process, so a handler that emits `subagents:rpc:consume` **without awaiting anything** has already set that flag before step 2 evaluates.
|
|
119
|
+
|
|
120
|
+
| When you consume | What happens |
|
|
121
|
+
|---|---|
|
|
122
|
+
| Synchronously, inside your `subagents:completed` handler | The notification is never scheduled. This is the clean path |
|
|
123
|
+
| After an `await`, within 200 ms | Still suppressed. The nudge is held for `NUDGE_HOLD_MS` (`src/index.ts:451`), `consume` cancels the pending timer (`:819`), and there is a re-check at send time (`:474`) |
|
|
124
|
+
| After 200 ms | Too late. The follow-up has fired with `triggerTurn: true` and cost the parent a turn |
|
|
125
|
+
|
|
126
|
+
Fire-and-forget is the intended use: the reply carries nothing to act on, and the channel sits outside the `subagents:rpc:ping` version handshake on purpose (`src/cross-extension-rpc.ts:190`), so you can send it unconditionally and an older pi-subagents with no handler simply keeps notifying.
|
|
127
|
+
|
|
128
|
+
Consumption is not terminal. An `@handle` steer un-consumes the record (`src/index.ts:920`) because the agent's reply to that message still needs relaying, and so does a background resume (`src/agent-manager.ts:1135`) because the record is starting a new run.
|
|
129
|
+
|
|
130
|
+
One related thing that lives nowhere else: on every top-level settle, pi-subagents writes a session entry — not an event — via `pi.appendEntry("subagents:record", …)` (`src/index.ts:585`), carrying `id`, `type`, `description`, `status`, `result`, `error`, `startedAt` and `completedAt`. It exists for cross-extension history reconstruction. It is append-only history, not something to react to.
|
|
131
|
+
|
|
132
|
+
## The manager registry
|
|
133
|
+
|
|
134
|
+
`globalThis[Symbol.for("pi-subagents:manager")]` (`src/index.ts:649-659`) is a second integration surface — the standard Node cross-package singleton pattern, no bus involved:
|
|
135
|
+
|
|
136
|
+
| Member | Signature | Notes |
|
|
137
|
+
|---|---|---|
|
|
138
|
+
| `waitForAll()` | `() => Promise<void>` | Resolves when nothing is running. **All** agents, including ones you did not spawn — a shutdown barrier, not a join |
|
|
139
|
+
| `hasRunning()` | `() => boolean` | |
|
|
140
|
+
| `spawn(pi, ctx, type, prompt, options)` | `=> string` | **Is** `spawnTopLevel`, so the strip list above applies identically |
|
|
141
|
+
| `getRecord(id)` | `=> AgentRecord \| undefined` | Filtered through `isTopLevelAgent`, so someone else's child comes back `undefined` rather than leaking |
|
|
142
|
+
|
|
143
|
+
The slot is claimed by the first activation only; subagent sessions re-activate this extension in the same process, and unconditionally overwriting would point the registry at a short-lived child manager whose shutdown would then delete the root session's entry ([#128](https://github.com/tintinweb/pi-subagents/pull/128)). Child activations leave it alone, and shutdown releases it only if this activation claimed it (`src/index.ts:747-750`, `:1105-1107`).
|
|
144
|
+
|
|
145
|
+
Prefer the bus. The registry has no reply envelope, no version, and no availability event — `globalThis[Symbol.for("pi-subagents:manager")] === undefined` is the only probe you get, and it is also `undefined` in a session that filtered pi-subagents out. Reach for it for the two things the bus has no verb for — *is anything still running*, and *give me a settled record back* — or for a headless host that wants to block on `waitForAll()` before exiting.
|
|
146
|
+
|
|
147
|
+
## Protocol versions
|
|
148
|
+
|
|
149
|
+
`subagents:rpc:ping` replies `{ version: PROTOCOL_VERSION }`, currently `2` (`src/cross-extension-rpc.ts:33`). The constant was introduced already equal to `2` in 0.5.0; "v1" is a retroactive name for the pre-envelope contract, where spawn replied with a bare `{ id }` or `{ error }`, stop replied `{ success: boolean }` with no message, and each handler caught its own errors.
|
|
150
|
+
|
|
151
|
+
Everything added since shipped **without a bump**, because all of it is additive: stop's ownership refusal, string-`model` resolution ([#59](https://github.com/tintinweb/pi-subagents/pull/59)/[#60](https://github.com/tintinweb/pi-subagents/issues/60)), `scopeModels` enforcement ([#240](https://github.com/tintinweb/pi-subagents/issues/240)), and the whole `consume` channel.
|
|
152
|
+
|
|
153
|
+
> A `ping` that answers `2` does not tell you whether `consume` exists, whether model scope is enforced, or whether stop checks ownership.
|
|
154
|
+
|
|
155
|
+
So: send `consume` unconditionally and ignore the outcome — an older build has no handler and simply keeps notifying, which is exactly why it was left outside the handshake. And treat every error envelope as authoritative rather than trying to predict which checks are in force.
|
|
156
|
+
|
|
157
|
+
## Availability
|
|
158
|
+
|
|
159
|
+
`subagents:ready` is the discovery signal, and both the RPC handlers and the event itself are wired on the first bound `session_start` (`src/index.ts:789`, `:799`, `:827`) — deliberately not at factory time. pi runs every extension factory *before* applying an agent's `extensions:` filter and only delivers lifecycle events to the survivors, so a factory-time broadcast made a filtered-out session advertise a spawn service it could never provide: `ping` succeeded and every `spawn` answered `No active session` ([#142](https://github.com/tintinweb/pi-subagents/issues/142)).
|
|
160
|
+
|
|
161
|
+
The consequence is worth stating plainly: **a session that excludes pi-subagents is indistinguishable from pi-subagents not being installed.** It emits no `subagents:ready` and answers nothing. Give discovery a timeout and treat expiry as "not available here" rather than waiting indefinitely. The payload is `{}` — read nothing off it. Handlers are torn down and the flag reset on `session_shutdown` (`src/index.ts:1100-1103`), so a later `session_start` re-registers and re-emits.
|
|
162
|
+
|
|
163
|
+
One more trap on the way in: an RPC-spawned agent emits **no `subagents:created`**. The only two emit sites are the `Agent` tool's background branch (`src/index.ts:2104`) and detached resume (`:1350`). Your first event for your own agent is `subagents:started` (`:625`), so key your bookkeeping off the id that `spawn` handed you, not off `subagents:created`.
|
|
164
|
+
|
|
165
|
+
## What the tests pin
|
|
166
|
+
|
|
167
|
+
This document has no test of its own, so it is worth knowing which claims are actually held in place:
|
|
168
|
+
|
|
169
|
+
| Test | Level | Pins |
|
|
170
|
+
|---|---|---|
|
|
171
|
+
| `test/cross-extension-rpc.test.ts` | Mocked `SpawnCapable` | Envelope shape, per-channel error strings, model resolution and scope enforcement |
|
|
172
|
+
| `test/rpc-lifecycle-gating.test.ts` | Real extension factory | Nothing wired at factory time, everything once at `session_start`, and live widget activity for RPC spawns ([#142](https://github.com/tintinweb/pi-subagents/issues/142)/[#181](https://github.com/tintinweb/pi-subagents/pull/181)) |
|
|
173
|
+
| `test/rpc-result-consumption.test.ts` | Real delivery path | The notification firing, and not firing, around `consume` |
|
|
174
|
+
|
|
175
|
+
Not pinned anywhere, so treat them as descriptions rather than contracts: the `SpawnOptions.cwd` error strings, `subagents:ready`'s `{}` payload, consume's handle resolution, and its missing `workflowId` check.
|
|
176
|
+
|
|
177
|
+
## Reference implementation
|
|
178
|
+
|
|
179
|
+
[**`tintinweb/pi-tasks`**](https://github.com/tintinweb/pi-tasks) is the working integration and the one this surface was shaped by. Its `TaskExecute` drives `subagents:rpc:spawn` — including the serialized `"provider/modelId"` string form that the boundary now resolves — and its `TaskOutput` drives `subagents:rpc:consume`, which exists because pi-tasks joins an agent on `subagents:completed` and reports the result itself ([pi-tasks#62](https://github.com/tintinweb/pi-tasks/issues/62)).
|
|
180
|
+
|
|
181
|
+
Read it for the shape of the whole loop: waiting on `subagents:ready`, keeping an id-keyed map of outstanding spawns, resolving each from the `subagents:completed` / `subagents:failed` handler, and consuming the result in the same synchronous handler that reports it.
|
|
182
|
+
|
|
183
|
+
If what you want is many coordinated agents rather than one, hand a script to [`SubagentWorkflow`](workflows.md) instead of fanning out over `subagents:rpc:spawn` — and note that a workflow's agents are not yours: they emit no lifecycle events, and `subagents:rpc:stop` refuses them.
|