@narumitw/pi-subagents 3.0.2 → 3.1.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/README.md CHANGED
@@ -7,8 +7,10 @@ Pi Subagents runs Pi jobs in separate child processes and supports authenticated
7
7
  ## ✨ Features
8
8
 
9
9
  - Runs each job in an isolated Pi child process and returns its job ID immediately.
10
- - Uses the task to define the child's specialization and the tool list to limit its capabilities.
10
+ - Uses the task to define the child's specialization and explicit tools, skills, and extensions to define its capabilities.
11
11
  - Defaults work tools to `read`, `grep`, `find`, and `ls`.
12
+ - Can attach validated local skills and trusted local extensions to one job without enabling automatic discovery.
13
+ - Verifies requested extension tools before sending the task to the child model.
12
14
  - Inherits the main agent's effective model and uses its thinking level by default.
13
15
  - Gives the main agent and every child a context-specific `subagent_send` contract for bidirectional requests and responses.
14
16
  - Gives every child `subagent_wait` for an answer to a child-originated request.
@@ -68,8 +70,8 @@ Use messaging only when needed:
68
70
 
69
71
  Completion messages follow Pi's global tool-output expansion state and the `app.tools.expand` binding (`Ctrl+O` by default).
70
72
 
71
- In TUI mode, the above-editor widget shows each queued or running job's ID, state, elapsed time, timeout, and selected work tools.
72
- The widget omits the fixed communication tools, disappears when no jobs remain active, and clears when the session ends.
73
+ In TUI mode, the above-editor widget shows each queued or running job's ID, state, elapsed time, timeout, and selected core and extension tool names.
74
+ It omits fixed communication tools, attachment paths, and resource totals, disappears when no jobs remain active, and clears when the session ends.
73
75
 
74
76
  ## 🛠️ Tools
75
77
 
@@ -77,7 +79,7 @@ The main Pi session exposes five fixed tools:
77
79
 
78
80
  | Tool | Parameters | Purpose |
79
81
  | --- | --- | --- |
80
- | `subagent_spawn` | `task`, optional `tools`, `thinkingLevel`, `timeout` | Start one subagent job and return its `jobId`. |
82
+ | `subagent_spawn` | `task`, optional `tools`, `skills`, `extensions`, `thinkingLevel`, `timeout` | Start one subagent job and return its `jobId`. |
81
83
  | `subagent_inspect` | none | List privacy-filtered retained-job metadata. |
82
84
  | `subagent_cancel` | `jobId` | Idempotently cancel one queued or running job. |
83
85
  | `subagent_wait` | `jobId`, optional `timeout` | Wait for a job or return early for an incoming child message. |
@@ -107,9 +109,9 @@ Tasks are limited to 50 KiB of UTF-8 text.
107
109
  Requests and responses are limited to 48 KiB and 1,992 lines so their protocol envelopes fit Pi's 50 KiB and 2,000-line model-text bounds without truncating accepted content.
108
110
  Each job may have up to four unresolved or answered-but-not-consumed requests across both directions.
109
111
  The terminal states are `completed`, `partial`, `failed`, `timed_out`, and `cancelled`.
110
- `subagent_inspect` never returns complete task text, child output, prompts, selected tools, context, credentials, environment variables, requests, responses, or secrets.
112
+ `subagent_inspect` never returns complete task text, child output, prompts, selected tools, attachment paths or totals, context, credentials, environment variables, requests, responses, or secrets.
111
113
 
112
- See [`docs/tools.md`](./docs/tools.md) for the concise schema reference.
114
+ See [`docs/tools.md`](./docs/tools.md) for the concise schema reference, [`docs/attachments.md`](./docs/attachments.md) for attachment validation, and [`docs/messaging.md`](./docs/messaging.md) for messaging behavior.
113
115
 
114
116
  ## ⚙️ Job configuration
115
117
 
@@ -118,50 +120,50 @@ For reusable delegation policy, you can create your own project skill under `.pi
118
120
  Choose its name, trigger, tool policy, task format, and verification workflow for your use case.
119
121
  The package intentionally registers and publishes no skill; the repository-only [`using-pi-subagents` example](https://github.com/narumiruna/pi-extensions/tree/main/packages/pi-subagents/skills/using-pi-subagents) is an optional starting point.
120
122
 
121
- The optional `tools` list limits what the child can do:
123
+ `subagent_spawn` accepts this additive configuration:
124
+
125
+ ```ts
126
+ {
127
+ task: string;
128
+ tools?: Array<"read" | "bash" | "powershell" | "edit" | "write" | "grep" | "find" | "ls">;
129
+ skills?: string[];
130
+ extensions?: Array<{ path: string; tools: string[] }>;
131
+ thinkingLevel?: "off" | "minimal" | "low" | "medium" | "high" | "xhigh" | "max";
132
+ timeout?: number;
133
+ }
134
+ ```
135
+
136
+ The optional `tools` list selects Pi core work tools.
137
+ Omitting it selects `read`, `grep`, `find`, and `ls`, while an empty list gives the child no core work tools.
138
+ The runtime always adds `subagent_send` and child `subagent_wait` and removes duplicate names.
139
+ Adding `edit` or `write` lets the child modify files, while `bash` or `powershell` grants unrestricted command execution.
122
140
 
123
- - Accepted names are `read`, `bash`, `powershell`, `edit`, `write`, `grep`, `find`, and `ls`.
124
- - Unavailable or extension-only tool names are rejected before a job is queued.
125
- - Omitting `tools` selects `read`, `grep`, `find`, and `ls`.
126
- - Passing an empty list gives the child no work tools.
127
- - The runtime always adds `subagent_send` and child `subagent_wait` and removes duplicate names.
141
+ The optional `skills` list attaches local Pi skills through progressive disclosure without injecting their full bodies or adding tools.
142
+ Every attached skill path must contain at least one loadable skill, every non-ignored declared skill must load successfully, and skill names must be unique across attachments.
128
143
 
129
- Adding `edit` or `write` lets the child modify files.
130
- Adding `bash` or `powershell` grants unrestricted command execution and can also modify the workspace.
144
+ Each `extensions` entry loads a trusted local extension file or directory and selects its initial extension tools.
145
+ An empty `tools` list loads provider or lifecycle behavior without initially exposing extension tools.
146
+ The parent checks that each requested tool comes from its specified attachment before the child model receives the task.
147
+ Attached extensions run with the child's user permissions and are not a sandbox.
131
148
 
132
- The optional `thinkingLevel` accepts `off`, `minimal`, `low`, `medium`, `high`, `xhigh`, or `max`.
133
- Omitting `thinkingLevel` captures the main agent's effective level when `subagent_spawn` executes.
134
- The child inherits the main agent's effective provider and model when `subagent_spawn` executes.
149
+ Attachments must be existing local paths, not npm, Git, or URL sources.
150
+ Project-local attachments and resources resolved from their packages require project trust, including paths that symlink into the project.
151
+ A job accepts up to 16 skill paths, 16 extension entries, and 64 selected work tools.
152
+ The child inherits the main agent's effective model and thinking level by default; there is no per-job model override.
153
+ Parent-only extension providers require an attached extension that registers them in the child.
154
+ Process-local runtime API keys, including a parent-only `--api-key`, are unavailable to the child; use stored or inherited environment credentials.
135
155
 
136
- Spawn rejects providers registered by a parent extension because child processes disable unrelated extensions.
137
- Spawn also rejects process-local runtime API keys, including a parent-only `--api-key` value.
138
- Use stored or environment credentials that child processes can read.
139
- The extension does not expose a per-job model override.
156
+ See [Attachment behavior](./docs/attachments.md) for detailed path checks, package preflight, tool ownership, and resource limits.
140
157
 
141
158
  ## 🔄 Messaging, lifecycle, and retention
142
159
 
143
- The session starts one TCP broker on `127.0.0.1` with an operating-system-assigned ephemeral port.
144
- Each job receives one cryptographically random token bound to its job identity and session generation.
145
- The parent passes the broker credentials once through a private inherited pipe instead of placing them in the child's initial environment or command line.
146
- The child bridge reads and closes that descriptor before model tool execution.
147
-
148
- Each child runs in Pi RPC mode so the parent can inject a main-originated request through `steer` after the initial prompt is accepted.
149
- Each child broker call uses one request-scoped connection, while a response wait uses an abortable long poll.
150
- A main-originated request to a queued job waits for RPC readiness before delivery is accepted.
151
- After RPC accepts the steering message, the runtime interrupts active child response waits so the queued request can reach the child model.
152
- Caller cancellation before RPC delivery starts rolls the request back.
153
- Once RPC delivery starts, cancellation stops only the caller's wait; the request may still arrive and remains answerable until delivery fails or the job terminates.
154
- The interrupted child-originated requests remain active and retryable.
155
-
156
- The first accepted `subagent_send` response wins.
157
- Repeated responses acknowledge the existing answer without replacing it.
158
- A child may retry `subagent_wait` after a wait timeout because the underlying request remains active.
159
-
160
- A new job starts as `queued`, transitions to `running`, and reaches exactly one terminal state.
161
- The runtime retains up to 32 recent terminal records for up to 24 hours within the current extension session.
162
- Inspection reports older records removed by retention bounds through `omitted.jobs`.
163
- Cancelling or terminalizing a job revokes its token and rejects pending child waits before stale output can replace the terminal state.
164
- Session replacement and shutdown cancel active work, suppress stale completion delivery, revoke credentials, close sockets, and stop the broker.
160
+ A main-originated request to a queued job waits for Pi RPC readiness before delivery. After delivery starts, cancellation stops only the caller's wait; the request may still arrive. An interrupted child-originated request remains active and can be waited on again.
161
+
162
+ The first accepted `subagent_send` response wins; repeated responses do not replace it. The execution timeout starts only after Pi accepts the RPC prompt. Jobs reach exactly one terminal state, and inspection retains up to 32 terminal records for up to 24 hours within the current session.
163
+
164
+ The broker uses loopback connections and per-job credentials passed through a private pipe, not the child's initial environment or command line. Cancelling a job or replacing the session revokes those credentials, stops pending work, and prevents stale completion delivery.
165
+
166
+ See [Messaging and lifecycle](./docs/messaging.md) for delivery, retry, retention, and shutdown details.
165
167
 
166
168
  ## 🔀 Migrating from 2.x
167
169
 
@@ -190,9 +192,10 @@ The default list contains no shell or file-mutation tool.
190
192
  It is not a filesystem sandbox because its read tools can inspect files available to the user account.
191
193
  Selecting `bash`, `powershell`, `edit`, or `write` permits workspace mutation with the Pi process environment and user permissions.
192
194
 
193
- Every child disables session persistence, unrelated extensions, skills, and prompt templates.
194
- Provider selection therefore supports Pi's child-visible built-in and configured providers, not providers registered only by a parent extension.
195
- Credentials must be available independently to the child through Pi's stored credentials or its inherited environment.
195
+ Every child disables session persistence and automatic discovery of extensions, skills, and prompt templates.
196
+ Only the communication bridge and explicitly attached resources are added back for that job.
197
+ Provider selection supports Pi's child-visible built-in and configured providers plus providers registered by an attached extension.
198
+ Credentials must be available independently to the child through Pi's stored credentials or inherited environment.
196
199
 
197
200
  The broker accepts only loopback TCP connections with an active per-job token.
198
201
  The token is bootstrapped through a private inherited pipe and is absent from the child's initial environment and command line.
@@ -201,14 +204,20 @@ A child request or response is visible main-agent model context, but its envelop
201
204
  A child message cannot grant permission for writes, shell commands, credential access, or other privileged actions.
202
205
  A main-agent request is visible child model context, but it cannot expand the child's selected tools or grant capabilities the child did not receive at spawn time.
203
206
 
207
+ An attached skill contributes model instructions and may direct already selected tools to bundled helpers.
208
+ An attached extension is fully privileged executable code with the child process's user permissions, can run during factory and lifecycle hooks, can alter prompts or tool behavior, and can change active tools after startup.
209
+ Its requested tool list controls the initial provider-visible loadout but is not an operating-system sandbox.
210
+ Canonical attachment paths are passed to Pi in the child command line, but parent-generated inspection, completion, and broker metadata omit them and child diagnostic errors redact requested attachment roots before publication.
211
+ Attached code and model output remain untrusted and can disclose paths they can access, so attach only code you trust.
212
+
204
213
  Terminal controls and bidirectional controls are stripped before untrusted child text is displayed.
205
214
  Tasks, repository context, requests, responses, and inspected file content may be sent to the selected model provider.
206
215
  Parallel writers require disjoint ownership or workspace isolation outside this extension.
207
216
 
208
217
  ## 🚧 Limitations
209
218
 
210
- - The extension does not load arbitrary extension tools or parent-registered model providers in child processes.
211
- - Process-local runtime API keys are not forwarded to children.
219
+ - Attachments must be existing local files or directories; package, Git, URL, and automatic parent-resource inheritance are unsupported.
220
+ - An attached extension may recreate a parent-registered provider, but parent in-memory extension state and runtime-only API keys are not forwarded.
212
221
  - The extension does not provide custom agents, per-job models, custom system prompts, peer-to-peer child messaging, retained conversations, user-directed follow-up work, mailboxes, Agent Teams, chains, fan-in aggregators, panels, workflow DAGs, dynamic scheduling, verification orchestration, nested subagents, or extension-owned semantic memory.
213
222
  - Bidirectional messages use request-response coordination, not a retained conversational session.
214
223
  - The main agent must verify child claims against the actual diff and deterministic checks.
@@ -222,7 +231,7 @@ packages/pi-subagents/
222
231
  ├── src/ # Authoritative implementation and helpers
223
232
  │ ├── index.ts # Thin Pi entrypoint
224
233
  │ └── subagents.ts # Job, broker, and child lifecycle
225
- ├── dist/ # Generated Jiti runtime and child bridge
234
+ ├── dist/ # Generated Jiti runtime, child bridge, and readiness probe
226
235
  ├── scripts/build-runtime.mjs # Runtime builder
227
236
  ├── docs/ # Published reference documentation
228
237
  ├── skills/using-pi-subagents/ # Repository-only example; not published
@@ -1,18 +1,20 @@
1
1
  // @generated by scripts/build-runtime.mjs; do not edit.
2
2
  // @ts-nocheck -- generated entries use .ts extensions for Pi's Jiti loader.
3
+ import {
4
+ createChildCommunicationExtension
5
+ } from "./chunks/chunk-ZOAFWLDJ.ts";
3
6
  import {
4
7
  MAX_FRAME_BYTES,
5
8
  MAX_IDENTIFIER_LENGTH,
6
- captureBrokerCredentials,
7
- createChildCommunicationExtension
8
- } from "./chunks/chunk-7JQOB375.ts";
9
+ captureChildBootstrap
10
+ } from "./chunks/chunk-YMVSJ5GE.ts";
9
11
 
10
12
  // src/child-communication-bridge.ts
11
13
  import net from "node:net";
12
14
  var CONNECT_TIMEOUT_MS = 2e3;
13
15
  var SEND_RESPONSE_TIMEOUT_MS = 5e3;
14
- var captured = captureBrokerCredentials();
15
- var childCommunicationBridge = captured ? createChildCommunicationExtension(createBrokerClient(captured)) : () => void 0;
16
+ var captured = captureChildBootstrap();
17
+ var childCommunicationBridge = captured ? createChildCommunicationExtension(createBrokerClient(captured.communication)) : () => void 0;
16
18
  var child_communication_bridge_default = childCommunicationBridge;
17
19
  function createBrokerClient(credentials) {
18
20
  return {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../src/child-communication-bridge.ts"],
4
- "sourcesContent": ["import net from \"node:net\";\nimport type { ExtensionFactory } from \"@earendil-works/pi-coding-agent\";\nimport { captureBrokerCredentials } from \"./broker-credentials.js\";\nimport { type ChildCommunicationClient, createChildCommunicationExtension } from \"./child-communication-tools.js\";\nimport { MAX_FRAME_BYTES, MAX_IDENTIFIER_LENGTH } from \"./message-broker.js\";\nimport type { BrokerCredentials } from \"./types.js\";\n\nconst CONNECT_TIMEOUT_MS = 2_000;\nconst SEND_RESPONSE_TIMEOUT_MS = 5_000;\n\nconst captured = captureBrokerCredentials();\n\nconst childCommunicationBridge: ExtensionFactory = captured\n ? createChildCommunicationExtension(createBrokerClient(captured))\n : () => undefined;\n\nexport default childCommunicationBridge;\n\nexport function createBrokerClient(credentials: BrokerCredentials): ChildCommunicationClient {\n return {\n async send(params, signal) {\n const response = await requestBroker(\n credentials,\n { type: \"send\", token: credentials.token, ...params },\n signal,\n SEND_RESPONSE_TIMEOUT_MS,\n );\n if (response.ok !== true) throw brokerError(response);\n if (\n typeof response.requestId !== \"string\" ||\n !response.requestId ||\n response.requestId.length > MAX_IDENTIFIER_LENGTH ||\n typeof response.accepted !== \"boolean\" ||\n typeof response.duplicate !== \"boolean\"\n ) {\n throw new Error(\"Subagent broker returned an invalid send acknowledgement.\");\n }\n return {\n requestId: response.requestId,\n accepted: response.accepted,\n duplicate: response.duplicate,\n };\n },\n async wait(requestId, timeoutMs, signal) {\n const response = await requestBroker(\n credentials,\n {\n type: \"wait\",\n token: credentials.token,\n requestId,\n ...(timeoutMs !== undefined ? { timeoutMs } : {}),\n },\n signal,\n );\n if (response.ok !== true) throw brokerError(response);\n if (typeof response.response !== \"string\") {\n throw new Error(\"Subagent broker returned an invalid plain-text response.\");\n }\n return response.response;\n },\n };\n}\n\nfunction requestBroker(\n credentials: BrokerCredentials,\n request: Record<string, unknown>,\n signal?: AbortSignal,\n responseTimeoutMs?: number,\n): Promise<Record<string, unknown>> {\n if (signal?.aborted) return Promise.reject(abortError(\"Subagent broker request was cancelled.\"));\n return new Promise((resolve, reject) => {\n const socket = net.createConnection({ host: credentials.host, port: credentials.port });\n let response = Buffer.alloc(0);\n let settled = false;\n let responseTimer: NodeJS.Timeout | undefined;\n const connectTimer = setTimeout(\n () => finish(new Error(\"Subagent broker connection timed out.\")),\n CONNECT_TIMEOUT_MS,\n );\n connectTimer.unref();\n const onAbort = () => finish(abortError(\"Subagent broker request was cancelled.\"));\n const finish = (error?: Error, value?: Record<string, unknown>) => {\n if (settled) return;\n settled = true;\n clearTimeout(connectTimer);\n if (responseTimer) clearTimeout(responseTimer);\n signal?.removeEventListener(\"abort\", onAbort);\n socket.destroy();\n if (error) reject(error);\n else resolve(value ?? {});\n };\n signal?.addEventListener(\"abort\", onAbort, { once: true });\n socket.once(\"connect\", () => {\n clearTimeout(connectTimer);\n if (responseTimeoutMs !== undefined) {\n responseTimer = setTimeout(() => finish(new Error(\"Subagent broker response timed out.\")), responseTimeoutMs);\n responseTimer.unref();\n }\n socket.write(`${JSON.stringify(request)}\\n`);\n });\n socket.on(\"data\", (chunk: Buffer) => {\n if (settled) return;\n response = Buffer.concat([response, chunk]);\n if (response.byteLength > MAX_FRAME_BYTES) {\n finish(new Error(\"Subagent broker response exceeded its size limit.\"));\n return;\n }\n const newline = response.indexOf(0x0a);\n if (newline < 0) return;\n const trailing = response\n .subarray(newline + 1)\n .toString(\"utf8\")\n .trim();\n if (trailing) {\n finish(new Error(\"Subagent broker returned more than one response frame.\"));\n return;\n }\n try {\n const parsed = JSON.parse(response.subarray(0, newline).toString(\"utf8\")) as unknown;\n if (!isRecord(parsed)) throw new Error();\n finish(undefined, parsed);\n } catch {\n finish(new Error(\"Subagent broker returned malformed JSON.\"));\n }\n });\n socket.once(\"error\", (error) => finish(error));\n socket.once(\"close\", () => {\n if (!settled) finish(new Error(\"Subagent broker closed without a response.\"));\n });\n });\n}\n\nfunction brokerError(response: Record<string, unknown>): Error {\n return new Error(typeof response.error === \"string\" ? response.error : \"Subagent broker rejected the request.\");\n}\n\nfunction abortError(message: string): Error {\n const error = new Error(message);\n error.name = \"AbortError\";\n return error;\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import net from \"node:net\";\nimport type { ExtensionFactory } from \"@earendil-works/pi-coding-agent\";\nimport { captureChildBootstrap } from \"./broker-credentials.js\";\nimport { type ChildCommunicationClient, createChildCommunicationExtension } from \"./child-communication-tools.js\";\nimport { MAX_FRAME_BYTES, MAX_IDENTIFIER_LENGTH } from \"./message-broker.js\";\nimport type { BrokerCredentials } from \"./types.js\";\n\nconst CONNECT_TIMEOUT_MS = 2_000;\nconst SEND_RESPONSE_TIMEOUT_MS = 5_000;\n\nconst captured = captureChildBootstrap();\n\nconst childCommunicationBridge: ExtensionFactory = captured\n ? createChildCommunicationExtension(createBrokerClient(captured.communication))\n : () => undefined;\n\nexport default childCommunicationBridge;\n\nexport function createBrokerClient(credentials: BrokerCredentials): ChildCommunicationClient {\n return {\n async send(params, signal) {\n const response = await requestBroker(\n credentials,\n { type: \"send\", token: credentials.token, ...params },\n signal,\n SEND_RESPONSE_TIMEOUT_MS,\n );\n if (response.ok !== true) throw brokerError(response);\n if (\n typeof response.requestId !== \"string\" ||\n !response.requestId ||\n response.requestId.length > MAX_IDENTIFIER_LENGTH ||\n typeof response.accepted !== \"boolean\" ||\n typeof response.duplicate !== \"boolean\"\n ) {\n throw new Error(\"Subagent broker returned an invalid send acknowledgement.\");\n }\n return {\n requestId: response.requestId,\n accepted: response.accepted,\n duplicate: response.duplicate,\n };\n },\n async wait(requestId, timeoutMs, signal) {\n const response = await requestBroker(\n credentials,\n {\n type: \"wait\",\n token: credentials.token,\n requestId,\n ...(timeoutMs !== undefined ? { timeoutMs } : {}),\n },\n signal,\n );\n if (response.ok !== true) throw brokerError(response);\n if (typeof response.response !== \"string\") {\n throw new Error(\"Subagent broker returned an invalid plain-text response.\");\n }\n return response.response;\n },\n };\n}\n\nfunction requestBroker(\n credentials: BrokerCredentials,\n request: Record<string, unknown>,\n signal?: AbortSignal,\n responseTimeoutMs?: number,\n): Promise<Record<string, unknown>> {\n if (signal?.aborted) return Promise.reject(abortError(\"Subagent broker request was cancelled.\"));\n return new Promise((resolve, reject) => {\n const socket = net.createConnection({ host: credentials.host, port: credentials.port });\n let response = Buffer.alloc(0);\n let settled = false;\n let responseTimer: NodeJS.Timeout | undefined;\n const connectTimer = setTimeout(\n () => finish(new Error(\"Subagent broker connection timed out.\")),\n CONNECT_TIMEOUT_MS,\n );\n connectTimer.unref();\n const onAbort = () => finish(abortError(\"Subagent broker request was cancelled.\"));\n const finish = (error?: Error, value?: Record<string, unknown>) => {\n if (settled) return;\n settled = true;\n clearTimeout(connectTimer);\n if (responseTimer) clearTimeout(responseTimer);\n signal?.removeEventListener(\"abort\", onAbort);\n socket.destroy();\n if (error) reject(error);\n else resolve(value ?? {});\n };\n signal?.addEventListener(\"abort\", onAbort, { once: true });\n socket.once(\"connect\", () => {\n clearTimeout(connectTimer);\n if (responseTimeoutMs !== undefined) {\n responseTimer = setTimeout(() => finish(new Error(\"Subagent broker response timed out.\")), responseTimeoutMs);\n responseTimer.unref();\n }\n socket.write(`${JSON.stringify(request)}\\n`);\n });\n socket.on(\"data\", (chunk: Buffer) => {\n if (settled) return;\n response = Buffer.concat([response, chunk]);\n if (response.byteLength > MAX_FRAME_BYTES) {\n finish(new Error(\"Subagent broker response exceeded its size limit.\"));\n return;\n }\n const newline = response.indexOf(0x0a);\n if (newline < 0) return;\n const trailing = response\n .subarray(newline + 1)\n .toString(\"utf8\")\n .trim();\n if (trailing) {\n finish(new Error(\"Subagent broker returned more than one response frame.\"));\n return;\n }\n try {\n const parsed = JSON.parse(response.subarray(0, newline).toString(\"utf8\")) as unknown;\n if (!isRecord(parsed)) throw new Error();\n finish(undefined, parsed);\n } catch {\n finish(new Error(\"Subagent broker returned malformed JSON.\"));\n }\n });\n socket.once(\"error\", (error) => finish(error));\n socket.once(\"close\", () => {\n if (!settled) finish(new Error(\"Subagent broker closed without a response.\"));\n });\n });\n}\n\nfunction brokerError(response: Record<string, unknown>): Error {\n return new Error(typeof response.error === \"string\" ? response.error : \"Subagent broker rejected the request.\");\n}\n\nfunction abortError(message: string): Error {\n const error = new Error(message);\n error.name = \"AbortError\";\n return error;\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -0,0 +1,68 @@
1
+ // @generated by scripts/build-runtime.mjs; do not edit.
2
+ // @ts-nocheck -- generated entries use .ts extensions for Pi's Jiti loader.
3
+ import {
4
+ toolSourceId
5
+ } from "./chunks/chunk-3FCHXUKZ.ts";
6
+ import {
7
+ MAX_READINESS_FRAME_BYTES,
8
+ takeCapturedReadiness
9
+ } from "./chunks/chunk-YMVSJ5GE.ts";
10
+
11
+ // src/child-readiness-probe.ts
12
+ import * as fs from "node:fs";
13
+ var captured = takeCapturedReadiness();
14
+ var childReadinessProbe = captured ? createChildReadinessProbe(captured) : () => void 0;
15
+ var child_readiness_probe_default = childReadinessProbe;
16
+ function createChildReadinessProbe(readiness, writeFrame = writeReadinessFrame, close = closeDescriptor) {
17
+ return (pi) => {
18
+ let settled = false;
19
+ const publish = (value) => {
20
+ if (settled) return;
21
+ settled = true;
22
+ const frame = `${JSON.stringify(value)}
23
+ `;
24
+ if (Buffer.byteLength(frame, "utf8") > MAX_READINESS_FRAME_BYTES) {
25
+ close(readiness.fd);
26
+ throw new Error("Pi-subagents readiness frame exceeded its size limit.");
27
+ }
28
+ try {
29
+ writeFrame(readiness.fd, frame);
30
+ } finally {
31
+ close(readiness.fd);
32
+ }
33
+ };
34
+ pi.on("resources_discover", () => {
35
+ const active = new Set(pi.getActiveTools());
36
+ const missing = readiness.expectedTools.filter((tool) => !active.has(tool));
37
+ if (missing.length > 0) {
38
+ publish({ ok: false, error: `Unavailable subagent tools: ${missing.join(", ")}.` });
39
+ return;
40
+ }
41
+ const sources = new Map(pi.getAllTools().map((tool) => [tool.name, tool.sourceInfo.path]));
42
+ publish({
43
+ ok: true,
44
+ sources: readiness.expectedTools.map((tool) => {
45
+ const source = sources.get(tool);
46
+ return source ? toolSourceId(source) : "";
47
+ })
48
+ });
49
+ });
50
+ pi.on("session_shutdown", () => {
51
+ publish({ ok: false, error: "Subagent child shut down before readiness completed." });
52
+ });
53
+ };
54
+ }
55
+ function writeReadinessFrame(fd, frame) {
56
+ fs.writeFileSync(fd, frame, "utf8");
57
+ }
58
+ function closeDescriptor(fd) {
59
+ try {
60
+ fs.closeSync(fd);
61
+ } catch {
62
+ }
63
+ }
64
+ export {
65
+ createChildReadinessProbe,
66
+ child_readiness_probe_default as default
67
+ };
68
+ //# sourceMappingURL=child-readiness-probe.ts.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/child-readiness-probe.ts"],
4
+ "sourcesContent": ["import * as fs from \"node:fs\";\nimport type { ExtensionFactory } from \"@earendil-works/pi-coding-agent\";\nimport { toolSourceId } from \"./attachment-utils.js\";\nimport { MAX_READINESS_FRAME_BYTES, takeCapturedReadiness } from \"./broker-credentials.js\";\n\nexport interface ChildReadinessState {\n fd: number;\n expectedTools: string[];\n}\n\nconst captured = takeCapturedReadiness();\n\nconst childReadinessProbe: ExtensionFactory = captured ? createChildReadinessProbe(captured) : () => undefined;\n\nexport default childReadinessProbe;\n\nexport function createChildReadinessProbe(\n readiness: ChildReadinessState,\n writeFrame: (fd: number, frame: string) => void = writeReadinessFrame,\n close: (fd: number) => void = closeDescriptor,\n): ExtensionFactory {\n return (pi) => {\n let settled = false;\n const publish = (value: { ok: true; sources: string[] } | { ok: false; error: string }) => {\n if (settled) return;\n settled = true;\n const frame = `${JSON.stringify(value)}\\n`;\n if (Buffer.byteLength(frame, \"utf8\") > MAX_READINESS_FRAME_BYTES) {\n close(readiness.fd);\n throw new Error(\"Pi-subagents readiness frame exceeded its size limit.\");\n }\n try {\n writeFrame(readiness.fd, frame);\n } finally {\n close(readiness.fd);\n }\n };\n\n pi.on(\"resources_discover\", () => {\n const active = new Set(pi.getActiveTools());\n const missing = readiness.expectedTools.filter((tool) => !active.has(tool));\n if (missing.length > 0) {\n publish({ ok: false, error: `Unavailable subagent tools: ${missing.join(\", \")}.` });\n return;\n }\n const sources = new Map(pi.getAllTools().map((tool) => [tool.name, tool.sourceInfo.path]));\n publish({\n ok: true,\n sources: readiness.expectedTools.map((tool) => {\n const source = sources.get(tool);\n return source ? toolSourceId(source) : \"\";\n }),\n });\n });\n\n pi.on(\"session_shutdown\", () => {\n publish({ ok: false, error: \"Subagent child shut down before readiness completed.\" });\n });\n };\n}\n\nfunction writeReadinessFrame(fd: number, frame: string): void {\n fs.writeFileSync(fd, frame, \"utf8\");\n}\n\nfunction closeDescriptor(fd: number): void {\n try {\n fs.closeSync(fd);\n } catch {\n // The descriptor may already be closed after a failed write.\n }\n}\n"],
5
+ "mappings": ";;;;;;;;;;;AAAA,YAAY,QAAQ;AAUpB,IAAM,WAAW,sBAAsB;AAEvC,IAAM,sBAAwC,WAAW,0BAA0B,QAAQ,IAAI,MAAM;AAErG,IAAO,gCAAQ;AAER,SAAS,0BACd,WACA,aAAkD,qBAClD,QAA8B,iBACZ;AAClB,SAAO,CAAC,OAAO;AACb,QAAI,UAAU;AACd,UAAM,UAAU,CAAC,UAA0E;AACzF,UAAI,QAAS;AACb,gBAAU;AACV,YAAM,QAAQ,GAAG,KAAK,UAAU,KAAK,CAAC;AAAA;AACtC,UAAI,OAAO,WAAW,OAAO,MAAM,IAAI,2BAA2B;AAChE,cAAM,UAAU,EAAE;AAClB,cAAM,IAAI,MAAM,uDAAuD;AAAA,MACzE;AACA,UAAI;AACF,mBAAW,UAAU,IAAI,KAAK;AAAA,MAChC,UAAE;AACA,cAAM,UAAU,EAAE;AAAA,MACpB;AAAA,IACF;AAEA,OAAG,GAAG,sBAAsB,MAAM;AAChC,YAAM,SAAS,IAAI,IAAI,GAAG,eAAe,CAAC;AAC1C,YAAM,UAAU,UAAU,cAAc,OAAO,CAAC,SAAS,CAAC,OAAO,IAAI,IAAI,CAAC;AAC1E,UAAI,QAAQ,SAAS,GAAG;AACtB,gBAAQ,EAAE,IAAI,OAAO,OAAO,+BAA+B,QAAQ,KAAK,IAAI,CAAC,IAAI,CAAC;AAClF;AAAA,MACF;AACA,YAAM,UAAU,IAAI,IAAI,GAAG,YAAY,EAAE,IAAI,CAAC,SAAS,CAAC,KAAK,MAAM,KAAK,WAAW,IAAI,CAAC,CAAC;AACzF,cAAQ;AAAA,QACN,IAAI;AAAA,QACJ,SAAS,UAAU,cAAc,IAAI,CAAC,SAAS;AAC7C,gBAAM,SAAS,QAAQ,IAAI,IAAI;AAC/B,iBAAO,SAAS,aAAa,MAAM,IAAI;AAAA,QACzC,CAAC;AAAA,MACH,CAAC;AAAA,IACH,CAAC;AAED,OAAG,GAAG,oBAAoB,MAAM;AAC9B,cAAQ,EAAE,IAAI,OAAO,OAAO,uDAAuD,CAAC;AAAA,IACtF,CAAC;AAAA,EACH;AACF;AAEA,SAAS,oBAAoB,IAAY,OAAqB;AAC5D,EAAG,iBAAc,IAAI,OAAO,MAAM;AACpC;AAEA,SAAS,gBAAgB,IAAkB;AACzC,MAAI;AACF,IAAG,aAAU,EAAE;AAAA,EACjB,QAAQ;AAAA,EAER;AACF;",
6
+ "names": []
7
+ }
@@ -0,0 +1,98 @@
1
+ // @generated by scripts/build-runtime.mjs; do not edit.
2
+ // @ts-nocheck -- generated entries use .ts extensions for Pi's Jiti loader.
3
+ import {
4
+ sanitizeTerminalText
5
+ } from "./chunk-YMVSJ5GE.ts";
6
+
7
+ // src/attachment-utils.ts
8
+ import { createHash } from "node:crypto";
9
+ import { realpathSync } from "node:fs";
10
+ import { readFile, stat } from "node:fs/promises";
11
+ import * as path from "node:path";
12
+ var ATTACHMENT_IGNORE_FILE_NAMES = [".gitignore", ".ignore", ".fdignore"];
13
+ function throwInvalidAttachmentIgnoreFile() {
14
+ throw new Error("Subagent attachment ignore files must be regular files.");
15
+ }
16
+ function throwIfAttachmentAborted(signal) {
17
+ if (!signal?.aborted) return;
18
+ const error = new Error("Subagent attachment validation was cancelled.");
19
+ error.name = "AbortError";
20
+ throw error;
21
+ }
22
+ function isAbortError(error) {
23
+ return error instanceof Error && error.name === "AbortError";
24
+ }
25
+ function prefixIgnorePattern(line, prefix) {
26
+ const trimmed = line.trim();
27
+ if (!trimmed || trimmed.startsWith("#") && !trimmed.startsWith("\\#")) return null;
28
+ let pattern = line;
29
+ let negated = false;
30
+ if (pattern.startsWith("!")) {
31
+ negated = true;
32
+ pattern = pattern.slice(1);
33
+ } else if (pattern.startsWith("\\!")) {
34
+ pattern = pattern.slice(1);
35
+ }
36
+ if (pattern.startsWith("/")) pattern = pattern.slice(1);
37
+ const prefixed = prefix ? `${prefix}${pattern}` : pattern;
38
+ return negated ? `!${prefixed}` : prefixed;
39
+ }
40
+ async function addAttachmentIgnoreRules(ignoreMatcher, directory, rootDirectory, signal, accountForFile) {
41
+ const relativeDirectory = path.relative(rootDirectory, directory);
42
+ const prefix = relativeDirectory ? `${toPosixPath(relativeDirectory)}/` : "";
43
+ for (const filename of ATTACHMENT_IGNORE_FILE_NAMES) {
44
+ throwIfAttachmentAborted(signal);
45
+ const ignorePath = path.join(directory, filename);
46
+ let ignoreStats;
47
+ try {
48
+ ignoreStats = await stat(ignorePath);
49
+ } catch (error) {
50
+ if (isAbortError(error)) throw error;
51
+ throwIfAttachmentAborted(signal);
52
+ continue;
53
+ }
54
+ if (!ignoreStats.isFile()) throwInvalidAttachmentIgnoreFile();
55
+ accountForFile(ignorePath, ignoreStats.size);
56
+ try {
57
+ const content = await readFile(ignorePath, { encoding: "utf8", signal });
58
+ throwIfAttachmentAborted(signal);
59
+ const patterns = content.split(/\r?\n/u).map((line) => prefixIgnorePattern(line, prefix)).filter((line) => line !== null);
60
+ if (patterns.length > 0) ignoreMatcher.add(patterns);
61
+ } catch (error) {
62
+ if (isAbortError(error)) throw error;
63
+ throwIfAttachmentAborted(signal);
64
+ }
65
+ }
66
+ }
67
+ function toPosixPath(value) {
68
+ return value.split(path.sep).join("/");
69
+ }
70
+ function toolSourceId(value) {
71
+ return createHash("sha256").update(value).digest("hex");
72
+ }
73
+ function realpath(value, label) {
74
+ try {
75
+ return realpathSync(value);
76
+ } catch {
77
+ throw new Error(`${label} path does not exist: ${sanitizeTerminalText(value).slice(0, 512)}`);
78
+ }
79
+ }
80
+ function isWithin(root, candidate) {
81
+ const relative2 = path.relative(root, candidate);
82
+ return relative2 === "" || !relative2.startsWith(`..${path.sep}`) && relative2 !== ".." && !path.isAbsolute(relative2);
83
+ }
84
+ function isRecord(value) {
85
+ return typeof value === "object" && value !== null && !Array.isArray(value);
86
+ }
87
+
88
+ export {
89
+ throwIfAttachmentAborted,
90
+ isAbortError,
91
+ addAttachmentIgnoreRules,
92
+ toPosixPath,
93
+ toolSourceId,
94
+ realpath,
95
+ isWithin,
96
+ isRecord
97
+ };
98
+ //# sourceMappingURL=chunk-3FCHXUKZ.ts.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/attachment-utils.ts"],
4
+ "sourcesContent": ["import { createHash } from \"node:crypto\";\nimport { realpathSync } from \"node:fs\";\nimport { readFile, stat } from \"node:fs/promises\";\nimport * as path from \"node:path\";\nimport type ignore from \"ignore\";\nimport { sanitizeTerminalText } from \"./message-broker.js\";\n\nconst ATTACHMENT_IGNORE_FILE_NAMES = [\".gitignore\", \".ignore\", \".fdignore\"];\n\nexport type IgnoreMatcher = ReturnType<typeof ignore>;\n\nfunction throwInvalidAttachmentIgnoreFile(): never {\n throw new Error(\"Subagent attachment ignore files must be regular files.\");\n}\n\nexport function throwIfAttachmentAborted(signal: AbortSignal | undefined): void {\n if (!signal?.aborted) return;\n const error = new Error(\"Subagent attachment validation was cancelled.\");\n error.name = \"AbortError\";\n throw error;\n}\n\nexport function isAbortError(error: unknown): boolean {\n return error instanceof Error && error.name === \"AbortError\";\n}\n\nfunction prefixIgnorePattern(line: string, prefix: string): string | null {\n const trimmed = line.trim();\n if (!trimmed || (trimmed.startsWith(\"#\") && !trimmed.startsWith(\"\\\\#\"))) return null;\n let pattern = line;\n let negated = false;\n if (pattern.startsWith(\"!\")) {\n negated = true;\n pattern = pattern.slice(1);\n } else if (pattern.startsWith(\"\\\\!\")) {\n pattern = pattern.slice(1);\n }\n if (pattern.startsWith(\"/\")) pattern = pattern.slice(1);\n const prefixed = prefix ? `${prefix}${pattern}` : pattern;\n return negated ? `!${prefixed}` : prefixed;\n}\n\nexport async function addAttachmentIgnoreRules(\n ignoreMatcher: IgnoreMatcher,\n directory: string,\n rootDirectory: string,\n signal: AbortSignal | undefined,\n accountForFile: (ignorePath: string, bytes: number) => void,\n): Promise<void> {\n const relativeDirectory = path.relative(rootDirectory, directory);\n const prefix = relativeDirectory ? `${toPosixPath(relativeDirectory)}/` : \"\";\n for (const filename of ATTACHMENT_IGNORE_FILE_NAMES) {\n throwIfAttachmentAborted(signal);\n const ignorePath = path.join(directory, filename);\n let ignoreStats: Awaited<ReturnType<typeof stat>>;\n try {\n ignoreStats = await stat(ignorePath);\n } catch (error) {\n if (isAbortError(error)) throw error;\n throwIfAttachmentAborted(signal);\n continue;\n }\n if (!ignoreStats.isFile()) throwInvalidAttachmentIgnoreFile();\n accountForFile(ignorePath, ignoreStats.size);\n try {\n const content = await readFile(ignorePath, { encoding: \"utf8\", signal });\n throwIfAttachmentAborted(signal);\n const patterns = content\n .split(/\\r?\\n/u)\n .map((line) => prefixIgnorePattern(line, prefix))\n .filter((line): line is string => line !== null);\n if (patterns.length > 0) ignoreMatcher.add(patterns);\n } catch (error) {\n if (isAbortError(error)) throw error;\n throwIfAttachmentAborted(signal);\n // Pi ignores unreadable ignore files.\n }\n }\n}\n\nexport function toPosixPath(value: string): string {\n return value.split(path.sep).join(\"/\");\n}\n\nexport function toolSourceId(value: string): string {\n return createHash(\"sha256\").update(value).digest(\"hex\");\n}\n\nexport function realpath(value: string, label: string): string {\n try {\n return realpathSync(value);\n } catch {\n throw new Error(`${label} path does not exist: ${sanitizeTerminalText(value).slice(0, 512)}`);\n }\n}\n\nexport function isWithin(root: string, candidate: string): boolean {\n const relative = path.relative(root, candidate);\n return relative === \"\" || (!relative.startsWith(`..${path.sep}`) && relative !== \"..\" && !path.isAbsolute(relative));\n}\n\nexport function isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n"],
5
+ "mappings": "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",
6
+ "names": ["relative"]
7
+ }