@tintinweb/pi-subagents 0.4.10 → 0.5.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.
@@ -0,0 +1,46 @@
1
+ /**
2
+ * Cross-extension RPC handlers for the subagents extension.
3
+ *
4
+ * Exposes ping, spawn, and stop RPCs over the pi.events event bus,
5
+ * using per-request scoped reply channels.
6
+ *
7
+ * Reply envelope follows pi-mono convention:
8
+ * success → { success: true, data?: T }
9
+ * error → { success: false, error: string }
10
+ */
11
+ /** Minimal event bus interface needed by the RPC handlers. */
12
+ export interface EventBus {
13
+ on(event: string, handler: (data: unknown) => void): () => void;
14
+ emit(event: string, data: unknown): void;
15
+ }
16
+ /** RPC reply envelope — matches pi-mono's RpcResponse shape. */
17
+ export type RpcReply<T = void> = {
18
+ success: true;
19
+ data?: T;
20
+ } | {
21
+ success: false;
22
+ error: string;
23
+ };
24
+ /** RPC protocol version — bumped when the envelope or method contracts change. */
25
+ export declare const PROTOCOL_VERSION = 2;
26
+ /** Minimal AgentManager interface needed by the spawn/stop RPCs. */
27
+ export interface SpawnCapable {
28
+ spawn(pi: unknown, ctx: unknown, type: string, prompt: string, options: any): string;
29
+ abort(id: string): boolean;
30
+ }
31
+ export interface RpcDeps {
32
+ events: EventBus;
33
+ pi: unknown;
34
+ getCtx: () => unknown | undefined;
35
+ manager: SpawnCapable;
36
+ }
37
+ export interface RpcHandle {
38
+ unsubPing: () => void;
39
+ unsubSpawn: () => void;
40
+ unsubStop: () => void;
41
+ }
42
+ /**
43
+ * Register ping, spawn, and stop RPC handlers on the event bus.
44
+ * Returns unsub functions for cleanup.
45
+ */
46
+ export declare function registerRpcHandlers(deps: RpcDeps): RpcHandle;
@@ -0,0 +1,54 @@
1
+ /**
2
+ * Cross-extension RPC handlers for the subagents extension.
3
+ *
4
+ * Exposes ping, spawn, and stop RPCs over the pi.events event bus,
5
+ * using per-request scoped reply channels.
6
+ *
7
+ * Reply envelope follows pi-mono convention:
8
+ * success → { success: true, data?: T }
9
+ * error → { success: false, error: string }
10
+ */
11
+ /** RPC protocol version — bumped when the envelope or method contracts change. */
12
+ export const PROTOCOL_VERSION = 2;
13
+ /**
14
+ * Wire a single RPC handler: listen on `channel`, run `fn(params)`,
15
+ * emit the reply envelope on `channel:reply:${requestId}`.
16
+ */
17
+ function handleRpc(events, channel, fn) {
18
+ return events.on(channel, async (raw) => {
19
+ const params = raw;
20
+ try {
21
+ const data = await fn(params);
22
+ const reply = { success: true };
23
+ if (data !== undefined)
24
+ reply.data = data;
25
+ events.emit(`${channel}:reply:${params.requestId}`, reply);
26
+ }
27
+ catch (err) {
28
+ events.emit(`${channel}:reply:${params.requestId}`, {
29
+ success: false, error: err?.message ?? String(err),
30
+ });
31
+ }
32
+ });
33
+ }
34
+ /**
35
+ * Register ping, spawn, and stop RPC handlers on the event bus.
36
+ * Returns unsub functions for cleanup.
37
+ */
38
+ export function registerRpcHandlers(deps) {
39
+ const { events, pi, getCtx, manager } = deps;
40
+ const unsubPing = handleRpc(events, "subagents:rpc:ping", () => {
41
+ return { version: PROTOCOL_VERSION };
42
+ });
43
+ const unsubSpawn = handleRpc(events, "subagents:rpc:spawn", ({ type, prompt, options }) => {
44
+ const ctx = getCtx();
45
+ if (!ctx)
46
+ throw new Error("No active session");
47
+ return { id: manager.spawn(pi, ctx, type, prompt, options ?? {}) };
48
+ });
49
+ const unsubStop = handleRpc(events, "subagents:rpc:stop", ({ agentId }) => {
50
+ if (!manager.abort(agentId))
51
+ throw new Error("Agent not found");
52
+ });
53
+ return { unsubPing, unsubSpawn, unsubStop };
54
+ }
@@ -1,10 +1,10 @@
1
1
  /**
2
2
  * custom-agents.ts — Load user-defined agents from project (.pi/agents/) and global (~/.pi/agent/agents/) locations.
3
3
  */
4
- import { parseFrontmatter } from "@mariozechner/pi-coding-agent";
5
- import { readFileSync, readdirSync, existsSync } from "node:fs";
6
- import { join, basename } from "node:path";
4
+ import { existsSync, readdirSync, readFileSync } from "node:fs";
7
5
  import { homedir } from "node:os";
6
+ import { basename, join } from "node:path";
7
+ import { parseFrontmatter } from "@mariozechner/pi-coding-agent";
8
8
  import { BUILTIN_TOOL_NAMES } from "./agent-types.js";
9
9
  /**
10
10
  * Scan for custom agent .md files from multiple locations.
@@ -49,6 +49,7 @@ function loadFromDir(dir, agents, source) {
49
49
  displayName: str(fm.display_name),
50
50
  description: str(fm.description) ?? name,
51
51
  builtinToolNames: csvList(fm.tools, BUILTIN_TOOL_NAMES),
52
+ disallowedTools: csvListOptional(fm.disallowed_tools),
52
53
  extensions: inheritField(fm.extensions ?? fm.inherit_extensions),
53
54
  skills: inheritField(fm.skills ?? fm.inherit_skills),
54
55
  model: str(fm.model),
@@ -59,6 +60,8 @@ function loadFromDir(dir, agents, source) {
59
60
  inheritContext: fm.inherit_context === true,
60
61
  runInBackground: fm.run_in_background === true,
61
62
  isolated: fm.isolated === true,
63
+ memory: parseMemory(fm.memory),
64
+ isolation: fm.isolation === "worktree" ? "worktree" : undefined,
62
65
  enabled: fm.enabled !== false, // default true; explicitly false disables
63
66
  source,
64
67
  });
@@ -75,16 +78,41 @@ function positiveInt(val) {
75
78
  return typeof val === "number" && val >= 1 ? val : undefined;
76
79
  }
77
80
  /**
78
- * Parse a comma-separated list field.
81
+ * Parse a raw CSV field value into items, or undefined if absent/empty/"none".
82
+ */
83
+ function parseCsvField(val) {
84
+ if (val === undefined || val === null)
85
+ return undefined;
86
+ const s = String(val).trim();
87
+ if (!s || s === "none")
88
+ return undefined;
89
+ const items = s.split(",").map(t => t.trim()).filter(Boolean);
90
+ return items.length > 0 ? items : undefined;
91
+ }
92
+ /**
93
+ * Parse a comma-separated list field with defaults.
79
94
  * omitted → defaults; "none"/empty → []; csv → listed items.
80
95
  */
81
96
  function csvList(val, defaults) {
82
97
  if (val === undefined || val === null)
83
98
  return defaults;
84
- const s = String(val).trim();
85
- if (!s || s === "none")
86
- return [];
87
- return s.split(",").map(t => t.trim()).filter(Boolean);
99
+ return parseCsvField(val) ?? [];
100
+ }
101
+ /**
102
+ * Parse an optional comma-separated list field.
103
+ * omitted → undefined; "none"/empty → undefined; csv → listed items.
104
+ */
105
+ function csvListOptional(val) {
106
+ return parseCsvField(val);
107
+ }
108
+ /**
109
+ * Parse a memory scope field.
110
+ * omitted → undefined; "user"/"project"/"local" → MemoryScope.
111
+ */
112
+ function parseMemory(val) {
113
+ if (val === "user" || val === "project" || val === "local")
114
+ return val;
115
+ return undefined;
88
116
  }
89
117
  /**
90
118
  * Parse an inherit field (extensions, skills).