@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 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 stop subagents via the `pi.events` event bus (`subagents:rpc:ping`, `subagents:rpc:spawn`, `subagents:rpc:stop`). Standardized reply envelopes with protocol versioning. Emits `subagents:ready` on session start
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/ping via pi.events
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,6 +1,6 @@
1
1
  {
2
2
  "name": "@herbertgao/pi-subagents",
3
- "version": "0.16.0",
3
+ "version": "0.16.1",
4
4
  "description": "Claude Code-style autonomous subagents for Pi, with HerbertGao-maintained UI extensions.",
5
5
  "keywords": [
6
6
  "agent",
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Cross-extension RPC handlers for the subagents extension.
3
3
  *
4
- * Exposes ping, spawn, and stop RPCs over the pi.events event bus,
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 stop RPC handlers on the event bus.
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
- return { unsubPing, unsubSpawn, unsubStop }
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 (matchesKey(data, "escape") || matchesKey(data, "q")) {
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