@herbertgao/pi-subagents 0.16.0 → 0.16.1
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 +8 -0
- package/README.md +16 -3
- package/package.json +1 -1
- package/src/cross-extension-rpc.ts +15 -4
- package/src/index.ts +16 -0
- package/src/ui/conversation-viewer.ts +5 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.16.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- [#116](https://github.com/HerbertGao/pi-extensions/pull/116) [`ecda398`](https://github.com/HerbertGao/pi-extensions/commit/ecda398b9a95212c512d53325c617e485e6ded8d) Thanks [@HerbertGao](https://github.com/HerbertGao)! - Port Ctrl+C conversation-viewer dismissal and cross-extension consumption of settled subagent results while preserving local spawn and ownership safeguards.
|
|
8
|
+
|
|
3
9
|
## 0.16.0
|
|
4
10
|
|
|
5
11
|
### Minor Changes
|
|
@@ -49,6 +55,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
49
55
|
|
|
50
56
|
### Fixed
|
|
51
57
|
|
|
58
|
+
- **Ctrl+C closes the conversation viewer** when it is not composing a steering message.
|
|
59
|
+
- **Cross-extension callers can consume settled RPC-spawned results** with `subagents:rpc:consume`, suppressing a duplicate completion notification after another extension has already shown the result.
|
|
52
60
|
- **One malformed agent file no longer aborts extension activation** ([#212](https://github.com/tintinweb/pi-subagents/issues/212) — thanks [@daromaj](https://github.com/daromaj)). Unreadable and unparseable files are skipped with a path-specific warning, including the earlier source that remains active when a broken file was an override.
|
|
53
61
|
|
|
54
62
|
## [0.15.1] - 2026-08-10
|
package/README.md
CHANGED
|
@@ -32,7 +32,7 @@ Spawn specialized agents that run in isolated sessions — each with its own too
|
|
|
32
32
|
- **Tool denylist** — block specific tools via `disallowed_tools` frontmatter
|
|
33
33
|
- **Styled completion notifications** — background agent results render as themed, compact notification boxes (icon, stats, result preview) instead of raw XML. Expandable to show full output. Group completions render each agent individually
|
|
34
34
|
- **Event bus** — lifecycle events (`subagents:created`, `started`, `completed`, `failed`, `steered`, `compacted`) emitted via `pi.events`, enabling other extensions to react to sub-agent activity
|
|
35
|
-
- **Cross-extension RPC** — other pi extensions can spawn and
|
|
35
|
+
- **Cross-extension RPC** — other pi extensions can spawn, stop, and consume subagent results via the `pi.events` event bus (`subagents:rpc:ping`, `subagents:rpc:spawn`, `subagents:rpc:stop`, `subagents:rpc:consume`). Standardized reply envelopes with protocol versioning. Emits `subagents:ready` on session start
|
|
36
36
|
- **Schedule subagents** — pass `schedule` to the `Agent` tool to fire on cron / interval / one-shot. Session-scoped jobs with PID-locked persistence; results land via the same `subagent-notification` followUp path as manual background completions; manage via `/agents → Scheduled jobs`
|
|
37
37
|
- **Model scope enforcement** — opt-in validation that subagent model choices stay within your pi `enabledModels` allowlist (sourced from `/scoped-models`, with both global and project-local pi settings honored). Caller-supplied out-of-scope → hard error to orchestrator; frontmatter-pinned out-of-scope → warning + runs anyway (frontmatter authoritative). Toggle via `/agents → Settings → Scope models`
|
|
38
38
|
|
|
@@ -520,7 +520,7 @@ Listen for `subagents:ready` to know when RPC handlers are available:
|
|
|
520
520
|
|
|
521
521
|
```typescript
|
|
522
522
|
pi.events.on("subagents:ready", () => {
|
|
523
|
-
// RPC handlers are registered — safe to call ping/spawn/stop
|
|
523
|
+
// RPC handlers are registered — safe to call ping/spawn/stop/consume
|
|
524
524
|
})
|
|
525
525
|
```
|
|
526
526
|
|
|
@@ -581,6 +581,19 @@ const unsub = pi.events.on(`subagents:rpc:stop:reply:${requestId}`, (reply) => {
|
|
|
581
581
|
pi.events.emit("subagents:rpc:stop", { requestId, agentId: "agent-id-here" })
|
|
582
582
|
```
|
|
583
583
|
|
|
584
|
+
### Consume
|
|
585
|
+
|
|
586
|
+
Mark a settled agent result as already shown so its completion notification is suppressed:
|
|
587
|
+
|
|
588
|
+
```typescript
|
|
589
|
+
pi.events.emit("subagents:rpc:consume", {
|
|
590
|
+
requestId: crypto.randomUUID(),
|
|
591
|
+
agentId: "agent-id-here",
|
|
592
|
+
})
|
|
593
|
+
```
|
|
594
|
+
|
|
595
|
+
This is the event-bus equivalent of `get_subagent_result` consuming a result. Running, queued, unknown, and nested agents are refused; the channel is additive and remains outside the protocol-version handshake.
|
|
596
|
+
|
|
584
597
|
Reply channels are scoped per `requestId`, so concurrent requests don't interfere.
|
|
585
598
|
|
|
586
599
|
## Persistent Agent Memory
|
|
@@ -677,7 +690,7 @@ src/
|
|
|
677
690
|
agent-color.ts # Claude Code/Agency Agents name color parsing and badge rendering
|
|
678
691
|
agent-runner.ts # Session creation, execution, graceful max_turns, steer/resume
|
|
679
692
|
agent-manager.ts # Agent lifecycle, concurrency queue, completion notifications
|
|
680
|
-
cross-extension-rpc.ts # RPC handlers for cross-extension spawn/
|
|
693
|
+
cross-extension-rpc.ts # RPC handlers for cross-extension spawn/stop/consume via pi.events
|
|
681
694
|
group-join.ts # Group join manager: batched completion notifications with timeout
|
|
682
695
|
custom-agents.ts # Load user-defined agents from .pi/agents/, .agents/agents/, and global agents
|
|
683
696
|
memory.ts # Persistent agent memory (resolve, read, build prompt blocks)
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Cross-extension RPC handlers for the subagents extension.
|
|
3
3
|
*
|
|
4
|
-
* Exposes ping, spawn, and
|
|
4
|
+
* Exposes ping, spawn, stop, and consume RPCs over the pi.events event bus,
|
|
5
5
|
* using per-request scoped reply channels.
|
|
6
6
|
*
|
|
7
7
|
* Reply envelope follows pi-mono convention:
|
|
@@ -25,7 +25,7 @@ export type RpcReply<T = void> =
|
|
|
25
25
|
/** RPC protocol version — bumped when the envelope or method contracts change. */
|
|
26
26
|
export const PROTOCOL_VERSION = 2
|
|
27
27
|
|
|
28
|
-
/** Minimal AgentManager interface needed by the spawn/stop RPCs. */
|
|
28
|
+
/** Minimal AgentManager interface needed by the spawn/stop/consume RPCs. */
|
|
29
29
|
export interface SpawnCapable {
|
|
30
30
|
spawn(
|
|
31
31
|
pi: unknown,
|
|
@@ -35,6 +35,7 @@ export interface SpawnCapable {
|
|
|
35
35
|
options: any,
|
|
36
36
|
): string
|
|
37
37
|
abort(id: string): boolean
|
|
38
|
+
consumeResult(id: string): boolean
|
|
38
39
|
}
|
|
39
40
|
|
|
40
41
|
export interface RpcDeps {
|
|
@@ -48,6 +49,7 @@ export interface RpcHandle {
|
|
|
48
49
|
unsubPing: () => void
|
|
49
50
|
unsubSpawn: () => void
|
|
50
51
|
unsubStop: () => void
|
|
52
|
+
unsubConsume: () => void
|
|
51
53
|
}
|
|
52
54
|
|
|
53
55
|
/**
|
|
@@ -76,7 +78,7 @@ function handleRpc<P extends { requestId: string }>(
|
|
|
76
78
|
}
|
|
77
79
|
|
|
78
80
|
/**
|
|
79
|
-
* Register ping, spawn, and
|
|
81
|
+
* Register ping, spawn, stop, and consume RPC handlers on the event bus.
|
|
80
82
|
* Returns unsub functions for cleanup.
|
|
81
83
|
*/
|
|
82
84
|
export function registerRpcHandlers(deps: RpcDeps): RpcHandle {
|
|
@@ -130,5 +132,14 @@ export function registerRpcHandlers(deps: RpcDeps): RpcHandle {
|
|
|
130
132
|
},
|
|
131
133
|
)
|
|
132
134
|
|
|
133
|
-
|
|
135
|
+
const unsubConsume = handleRpc<{
|
|
136
|
+
requestId: string
|
|
137
|
+
agentId: string
|
|
138
|
+
}>(events, "subagents:rpc:consume", ({ agentId }) => {
|
|
139
|
+
if (!manager.consumeResult(agentId)) {
|
|
140
|
+
throw new Error("Agent not found or still running")
|
|
141
|
+
}
|
|
142
|
+
})
|
|
143
|
+
|
|
144
|
+
return { unsubPing, unsubSpawn, unsubStop, unsubConsume }
|
|
134
145
|
}
|
package/src/index.ts
CHANGED
|
@@ -890,6 +890,20 @@ export default function (pi: ExtensionAPI) {
|
|
|
890
890
|
const record = manager.getRecord(id)
|
|
891
891
|
return !record?.parentAgentId && manager.abort(id)
|
|
892
892
|
},
|
|
893
|
+
consumeResult: (id) => {
|
|
894
|
+
const record = manager.getRecord(id)
|
|
895
|
+
if (
|
|
896
|
+
!record ||
|
|
897
|
+
record.parentAgentId ||
|
|
898
|
+
record.status === "running" ||
|
|
899
|
+
record.status === "queued"
|
|
900
|
+
) {
|
|
901
|
+
return false
|
|
902
|
+
}
|
|
903
|
+
record.resultConsumed = true
|
|
904
|
+
cancelNudge(record.id)
|
|
905
|
+
return true
|
|
906
|
+
},
|
|
893
907
|
},
|
|
894
908
|
})
|
|
895
909
|
// Broadcast readiness so extensions loaded alongside us can discover us.
|
|
@@ -911,6 +925,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
911
925
|
rpcHandle?.unsubSpawn()
|
|
912
926
|
rpcHandle?.unsubStop()
|
|
913
927
|
rpcHandle?.unsubPing()
|
|
928
|
+
rpcHandle?.unsubConsume()
|
|
914
929
|
rpcHandle = undefined
|
|
915
930
|
currentCtx = undefined
|
|
916
931
|
// Only release the global slot if this activation claimed it — a child
|
|
@@ -1068,6 +1083,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
1068
1083
|
// Grab UI context from first tool execution + clear lingering widget on new turn
|
|
1069
1084
|
pi.on("tool_execution_start", async (_event, ctx) => {
|
|
1070
1085
|
widget.setUICtx(ctx.ui as UICtx)
|
|
1086
|
+
// SAFETY: both UI adapters receive the same Pi ExtensionContext UI surface.
|
|
1071
1087
|
fleet.setUICtx(ctx.ui as unknown as FleetUICtx)
|
|
1072
1088
|
widget.onTurnStart()
|
|
1073
1089
|
})
|
|
@@ -94,7 +94,11 @@ export class ConversationViewer implements Component {
|
|
|
94
94
|
return
|
|
95
95
|
}
|
|
96
96
|
|
|
97
|
-
if (
|
|
97
|
+
if (
|
|
98
|
+
matchesKey(data, "escape") ||
|
|
99
|
+
matchesKey(data, "ctrl+c") ||
|
|
100
|
+
matchesKey(data, "q")
|
|
101
|
+
) {
|
|
98
102
|
this.closed = true
|
|
99
103
|
this.done(undefined)
|
|
100
104
|
return
|