@narumitw/pi-subagents 2.1.4 → 3.0.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 +187 -1228
- package/dist/child-communication-bridge.ts +136 -0
- package/dist/child-communication-bridge.ts.map +7 -0
- package/dist/chunks/chunk-UKTWUQRM.js +667 -0
- package/dist/chunks/chunk-UKTWUQRM.js.map +7 -0
- package/dist/index.ts +1448 -1614
- package/dist/index.ts.map +4 -4
- package/docs/design-principles.md +31 -0
- package/docs/ipc-transport-idea.md +103 -0
- package/docs/tools.md +109 -0
- package/package.json +11 -19
- package/src/broker-credentials.ts +71 -0
- package/src/child-communication-bridge.ts +153 -0
- package/src/child-communication-tools.ts +147 -0
- package/src/completion-renderer.ts +60 -0
- package/src/index.ts +1 -1
- package/src/message-broker.ts +628 -0
- package/src/model-output.ts +42 -0
- package/src/process.ts +688 -0
- package/src/runtime.ts +538 -0
- package/src/subagents.ts +33 -34
- package/src/tools.ts +416 -0
- package/src/types.ts +85 -0
- package/src/widget.ts +130 -0
- package/dist/chunks/auto-transport-FUUKFDIG.ts +0 -109
- package/dist/chunks/auto-transport-FUUKFDIG.ts.map +0 -7
- package/dist/chunks/capability-grant-PR72SWWS.ts +0 -19
- package/dist/chunks/capability-grant-PR72SWWS.ts.map +0 -7
- package/dist/chunks/child-peer-bridge.ts +0 -108
- package/dist/chunks/child-peer-bridge.ts.map +0 -7
- package/dist/chunks/chunk-2LMJU25E.ts +0 -1641
- package/dist/chunks/chunk-2LMJU25E.ts.map +0 -7
- package/dist/chunks/chunk-47SK2QYC.ts +0 -56
- package/dist/chunks/chunk-47SK2QYC.ts.map +0 -7
- package/dist/chunks/chunk-4AQSF7AS.ts +0 -29
- package/dist/chunks/chunk-4AQSF7AS.ts.map +0 -7
- package/dist/chunks/chunk-5DGRMKNV.ts +0 -126
- package/dist/chunks/chunk-5DGRMKNV.ts.map +0 -7
- package/dist/chunks/chunk-6H6TBBED.ts +0 -108
- package/dist/chunks/chunk-6H6TBBED.ts.map +0 -7
- package/dist/chunks/chunk-6KJ34M6S.ts +0 -159
- package/dist/chunks/chunk-6KJ34M6S.ts.map +0 -7
- package/dist/chunks/chunk-6NSJVPXX.ts +0 -480
- package/dist/chunks/chunk-6NSJVPXX.ts.map +0 -7
- package/dist/chunks/chunk-7AAJEUSL.ts +0 -238
- package/dist/chunks/chunk-7AAJEUSL.ts.map +0 -7
- package/dist/chunks/chunk-BFK2Z4ZF.ts +0 -189
- package/dist/chunks/chunk-BFK2Z4ZF.ts.map +0 -7
- package/dist/chunks/chunk-BOAXY55Y.ts +0 -153
- package/dist/chunks/chunk-BOAXY55Y.ts.map +0 -7
- package/dist/chunks/chunk-D4CR7T73.ts +0 -353
- package/dist/chunks/chunk-D4CR7T73.ts.map +0 -7
- package/dist/chunks/chunk-FEVPWRMU.ts +0 -300
- package/dist/chunks/chunk-FEVPWRMU.ts.map +0 -7
- package/dist/chunks/chunk-G6VZTCFH.ts +0 -70
- package/dist/chunks/chunk-G6VZTCFH.ts.map +0 -7
- package/dist/chunks/chunk-H3FP6DLR.ts +0 -160
- package/dist/chunks/chunk-H3FP6DLR.ts.map +0 -7
- package/dist/chunks/chunk-HA36LPCO.ts +0 -130
- package/dist/chunks/chunk-HA36LPCO.ts.map +0 -7
- package/dist/chunks/chunk-HOP5FXDT.ts +0 -27
- package/dist/chunks/chunk-HOP5FXDT.ts.map +0 -7
- package/dist/chunks/chunk-IGAMVWFN.ts +0 -139
- package/dist/chunks/chunk-IGAMVWFN.ts.map +0 -7
- package/dist/chunks/chunk-ILEQ27AL.ts +0 -45
- package/dist/chunks/chunk-ILEQ27AL.ts.map +0 -7
- package/dist/chunks/chunk-ITVWPNU4.ts +0 -819
- package/dist/chunks/chunk-ITVWPNU4.ts.map +0 -7
- package/dist/chunks/chunk-IWC32VPY.ts +0 -172
- package/dist/chunks/chunk-IWC32VPY.ts.map +0 -7
- package/dist/chunks/chunk-JSZIP73U.ts +0 -362
- package/dist/chunks/chunk-JSZIP73U.ts.map +0 -7
- package/dist/chunks/chunk-JU6LUNLP.ts +0 -380
- package/dist/chunks/chunk-JU6LUNLP.ts.map +0 -7
- package/dist/chunks/chunk-KMGKCEO4.ts +0 -38
- package/dist/chunks/chunk-KMGKCEO4.ts.map +0 -7
- package/dist/chunks/chunk-LASD73CM.ts +0 -339
- package/dist/chunks/chunk-LASD73CM.ts.map +0 -7
- package/dist/chunks/chunk-LEOYDZI3.ts +0 -22
- package/dist/chunks/chunk-LEOYDZI3.ts.map +0 -7
- package/dist/chunks/chunk-LL4LP2T7.ts +0 -737
- package/dist/chunks/chunk-LL4LP2T7.ts.map +0 -7
- package/dist/chunks/chunk-N2T5IN4X.ts +0 -18
- package/dist/chunks/chunk-N2T5IN4X.ts.map +0 -7
- package/dist/chunks/chunk-N7BLVXKK.ts +0 -73
- package/dist/chunks/chunk-N7BLVXKK.ts.map +0 -7
- package/dist/chunks/chunk-NLT67IZS.ts +0 -322
- package/dist/chunks/chunk-NLT67IZS.ts.map +0 -7
- package/dist/chunks/chunk-NTRPLF46.ts +0 -63
- package/dist/chunks/chunk-NTRPLF46.ts.map +0 -7
- package/dist/chunks/chunk-ONDTY4EL.ts +0 -91
- package/dist/chunks/chunk-ONDTY4EL.ts.map +0 -7
- package/dist/chunks/chunk-OVPHFGKO.ts +0 -1769
- package/dist/chunks/chunk-OVPHFGKO.ts.map +0 -7
- package/dist/chunks/chunk-P7OH4XMF.ts +0 -60
- package/dist/chunks/chunk-P7OH4XMF.ts.map +0 -7
- package/dist/chunks/chunk-PBZMBTNJ.ts +0 -422
- package/dist/chunks/chunk-PBZMBTNJ.ts.map +0 -7
- package/dist/chunks/chunk-PGLSFLYW.ts +0 -52
- package/dist/chunks/chunk-PGLSFLYW.ts.map +0 -7
- package/dist/chunks/chunk-RRR66UWR.ts +0 -28
- package/dist/chunks/chunk-RRR66UWR.ts.map +0 -7
- package/dist/chunks/chunk-RSUXZD6S.ts +0 -275
- package/dist/chunks/chunk-RSUXZD6S.ts.map +0 -7
- package/dist/chunks/chunk-RTCVYIZA.ts +0 -221
- package/dist/chunks/chunk-RTCVYIZA.ts.map +0 -7
- package/dist/chunks/chunk-RY2AEGMZ.ts +0 -34
- package/dist/chunks/chunk-RY2AEGMZ.ts.map +0 -7
- package/dist/chunks/chunk-SWGQLFSD.ts +0 -60
- package/dist/chunks/chunk-SWGQLFSD.ts.map +0 -7
- package/dist/chunks/chunk-TM2R67J3.ts +0 -52
- package/dist/chunks/chunk-TM2R67J3.ts.map +0 -7
- package/dist/chunks/chunk-TMZRHIIK.ts +0 -497
- package/dist/chunks/chunk-TMZRHIIK.ts.map +0 -7
- package/dist/chunks/chunk-TZ34IQ3M.ts +0 -59
- package/dist/chunks/chunk-TZ34IQ3M.ts.map +0 -7
- package/dist/chunks/chunk-UMBPMVJW.ts +0 -22
- package/dist/chunks/chunk-UMBPMVJW.ts.map +0 -7
- package/dist/chunks/chunk-VDG7LTYE.ts +0 -80
- package/dist/chunks/chunk-VDG7LTYE.ts.map +0 -7
- package/dist/chunks/chunk-X4NMONPE.ts +0 -73
- package/dist/chunks/chunk-X4NMONPE.ts.map +0 -7
- package/dist/chunks/chunk-YPJEN6NU.ts +0 -65
- package/dist/chunks/chunk-YPJEN6NU.ts.map +0 -7
- package/dist/chunks/chunk-YU53SHA7.ts +0 -328
- package/dist/chunks/chunk-YU53SHA7.ts.map +0 -7
- package/dist/chunks/completion-delivery-RSJU6BXL.ts +0 -17
- package/dist/chunks/completion-delivery-RSJU6BXL.ts.map +0 -7
- package/dist/chunks/config-status-FKGDECZ3.ts +0 -35
- package/dist/chunks/config-status-FKGDECZ3.ts.map +0 -7
- package/dist/chunks/config-ui-ABHYNGQ7.ts +0 -1212
- package/dist/chunks/config-ui-ABHYNGQ7.ts.map +0 -7
- package/dist/chunks/consult-LJU3IQY5.ts +0 -519
- package/dist/chunks/consult-LJU3IQY5.ts.map +0 -7
- package/dist/chunks/context-QDKLVQXE.ts +0 -11
- package/dist/chunks/context-QDKLVQXE.ts.map +0 -7
- package/dist/chunks/create-stateful-transport-JWC2EFYL.ts +0 -130
- package/dist/chunks/create-stateful-transport-JWC2EFYL.ts.map +0 -7
- package/dist/chunks/cwd-policy-NB6XZ5GF.ts +0 -17
- package/dist/chunks/cwd-policy-NB6XZ5GF.ts.map +0 -7
- package/dist/chunks/delegation-contract-LA56I5DT.ts +0 -24
- package/dist/chunks/delegation-contract-LA56I5DT.ts.map +0 -7
- package/dist/chunks/discovery-MIFB2U4Y.ts +0 -12
- package/dist/chunks/discovery-MIFB2U4Y.ts.map +0 -7
- package/dist/chunks/execution-Q2JZLKJA.ts +0 -4037
- package/dist/chunks/execution-Q2JZLKJA.ts.map +0 -7
- package/dist/chunks/in-process-transport-HJ6TZXC3.ts +0 -39
- package/dist/chunks/in-process-transport-HJ6TZXC3.ts.map +0 -7
- package/dist/chunks/inspect-UH2TKH6E.ts +0 -634
- package/dist/chunks/inspect-UH2TKH6E.ts.map +0 -7
- package/dist/chunks/peer-communication-KL36Z6CO.ts +0 -294
- package/dist/chunks/peer-communication-KL36Z6CO.ts.map +0 -7
- package/dist/chunks/persistence-UY3PY6E5.ts +0 -348
- package/dist/chunks/persistence-UY3PY6E5.ts.map +0 -7
- package/dist/chunks/registry-BT54L6CY.ts +0 -1445
- package/dist/chunks/registry-BT54L6CY.ts.map +0 -7
- package/dist/chunks/retained-semantic-state-GM75FZGE.ts +0 -81
- package/dist/chunks/retained-semantic-state-GM75FZGE.ts.map +0 -7
- package/dist/chunks/rpc-transport-7R7DVCEB.ts +0 -1222
- package/dist/chunks/rpc-transport-7R7DVCEB.ts.map +0 -7
- package/dist/chunks/runtime-policy-5YELCOVC.ts +0 -15
- package/dist/chunks/runtime-policy-5YELCOVC.ts.map +0 -7
- package/dist/chunks/semantic-snapshot-WPJ3UAFW.ts +0 -19
- package/dist/chunks/semantic-snapshot-WPJ3UAFW.ts.map +0 -7
- package/dist/chunks/spawn-idempotency-BNHOSMZW.ts +0 -13
- package/dist/chunks/spawn-idempotency-BNHOSMZW.ts.map +0 -7
- package/dist/chunks/stateful-lifecycle-JAR6K55H.ts +0 -15
- package/dist/chunks/stateful-lifecycle-JAR6K55H.ts.map +0 -7
- package/dist/chunks/subprocess-transport-VHZJRBTW.ts +0 -160
- package/dist/chunks/subprocess-transport-VHZJRBTW.ts.map +0 -7
- package/dist/chunks/usage-recording-store-CW3EH2SZ.ts +0 -164
- package/dist/chunks/usage-recording-store-CW3EH2SZ.ts.map +0 -7
- package/dist/chunks/workspace-WDKLN72J.ts +0 -11
- package/dist/chunks/workspace-WDKLN72J.ts.map +0 -7
- package/docs/async-runtime-protocol.md +0 -84
- package/docs/implementation-notes/pi-subagents-capability-matrix.md +0 -62
- package/docs/implementation-notes/pi-subagents-current-direction.md +0 -99
- package/docs/implementation-notes/pi-subagents-rpc-v1.md +0 -139
- package/docs/pi-subagents-diagrams.md +0 -183
- package/src/adaptive-scheduler.ts +0 -224
- package/src/admission-policy.ts +0 -78
- package/src/agent-projection.ts +0 -53
- package/src/agents/built-ins.ts +0 -71
- package/src/agents/catalog.ts +0 -241
- package/src/agents/discovery.ts +0 -265
- package/src/agents/types.ts +0 -100
- package/src/agents.ts +0 -51
- package/src/auto-transport.ts +0 -121
- package/src/blocking-status.ts +0 -63
- package/src/cached-module-loader.ts +0 -18
- package/src/capabilities.ts +0 -145
- package/src/capability-grant.ts +0 -115
- package/src/capability-router.ts +0 -107
- package/src/child-peer-bridge.ts +0 -124
- package/src/child-peer-tools.ts +0 -132
- package/src/completion-delivery.ts +0 -409
- package/src/completion-render.ts +0 -190
- package/src/completion-requirement.ts +0 -479
- package/src/completion-routing.ts +0 -24
- package/src/config-registration.ts +0 -114
- package/src/config-status.ts +0 -217
- package/src/config-ui.ts +0 -983
- package/src/consult-policy.ts +0 -15
- package/src/consult-registration.ts +0 -125
- package/src/consult-render.ts +0 -194
- package/src/consult-resources.ts +0 -51
- package/src/consult-tool.ts +0 -95
- package/src/consult.ts +0 -566
- package/src/context.ts +0 -126
- package/src/create-stateful-transport.ts +0 -157
- package/src/cwd-policy.ts +0 -183
- package/src/delegation-contract.ts +0 -463
- package/src/execution/budget.ts +0 -56
- package/src/execution/runtime-policy.ts +0 -19
- package/src/execution-plan.ts +0 -322
- package/src/execution-ui.ts +0 -259
- package/src/execution.ts +0 -1679
- package/src/in-process-transport.ts +0 -941
- package/src/inspect-registration.ts +0 -64
- package/src/inspect-render.ts +0 -334
- package/src/inspect-tool.ts +0 -45
- package/src/inspect.ts +0 -769
- package/src/integration-controller.ts +0 -98
- package/src/limits.ts +0 -75
- package/src/orchestration-metrics.ts +0 -116
- package/src/outcome.ts +0 -61
- package/src/panel-child-group.ts +0 -35
- package/src/panel-contract.ts +0 -343
- package/src/panel-evidence.ts +0 -59
- package/src/panel-execution.ts +0 -769
- package/src/panel-failure.ts +0 -56
- package/src/panel-planning.ts +0 -175
- package/src/panel-presets.ts +0 -3
- package/src/panel-prompts.ts +0 -132
- package/src/panel-reconciliation.ts +0 -57
- package/src/panel-render.ts +0 -103
- package/src/parallel-limit-ui.ts +0 -113
- package/src/params.ts +0 -278
- package/src/peer-communication.ts +0 -352
- package/src/peer-transport.ts +0 -49
- package/src/persistence.ts +0 -522
- package/src/pi-args.ts +0 -43
- package/src/pi-invocation.ts +0 -168
- package/src/process-control.ts +0 -43
- package/src/prompt-resources.ts +0 -38
- package/src/prompt-source-safety.ts +0 -48
- package/src/protocol.ts +0 -76
- package/src/registry-types.ts +0 -204
- package/src/registry.ts +0 -1698
- package/src/render-common.ts +0 -252
- package/src/render.ts +0 -704
- package/src/result-contract.ts +0 -431
- package/src/retained-semantic-state.ts +0 -100
- package/src/rpc-timeout-finalization.ts +0 -207
- package/src/rpc-transport-metadata.ts +0 -65
- package/src/rpc-transport.ts +0 -1035
- package/src/rpc-turn-capture.ts +0 -217
- package/src/runner-outcome.ts +0 -31
- package/src/runner-result.ts +0 -55
- package/src/runner-types.ts +0 -102
- package/src/runner-usage.ts +0 -48
- package/src/runner.ts +0 -866
- package/src/safe-text.ts +0 -67
- package/src/semantic-snapshot.ts +0 -214
- package/src/session-guidance-contract.ts +0 -399
- package/src/settings/inspection.ts +0 -302
- package/src/settings/schema.ts +0 -195
- package/src/settings-reader.ts +0 -215
- package/src/settings.ts +0 -452
- package/src/spawn-idempotency.ts +0 -68
- package/src/stateful-agent-view.ts +0 -85
- package/src/stateful-config.ts +0 -13
- package/src/stateful-guidance.ts +0 -29
- package/src/stateful-lifecycle.ts +0 -74
- package/src/stateful-limit-ui.ts +0 -249
- package/src/stateful-limits.ts +0 -96
- package/src/stateful-prompt.ts +0 -49
- package/src/stateful-registration.ts +0 -1320
- package/src/stateful-render.ts +0 -318
- package/src/stateful-safety.ts +0 -47
- package/src/stateful-tool-params.ts +0 -215
- package/src/stateful.ts +0 -12
- package/src/subagent-details.ts +0 -43
- package/src/subagents-extension.ts +0 -372
- package/src/subprocess-transport.ts +0 -145
- package/src/supervision.ts +0 -104
- package/src/task-path.ts +0 -65
- package/src/timeout-checkpoint.ts +0 -305
- package/src/timeout-finalization.ts +0 -75
- package/src/tool-schema-compatibility.ts +0 -73
- package/src/transport-types.ts +0 -74
- package/src/transport-ui.ts +0 -135
- package/src/transport.ts +0 -43
- package/src/turn-budget.ts +0 -109
- package/src/usage-format.ts +0 -42
- package/src/usage-recording-config.ts +0 -13
- package/src/usage-recording-store.ts +0 -183
- package/src/usage-recording.ts +0 -478
- package/src/verification-harness.ts +0 -516
- package/src/verification-policy.ts +0 -67
- package/src/verification-receipt.ts +0 -275
- package/src/verified-execution-benchmark.ts +0 -86
- package/src/verified-execution-contract.ts +0 -191
- package/src/verified-execution-schema.ts +0 -32
- package/src/work-item-ledger.ts +0 -1404
- package/src/work-item-persistence.ts +0 -254
- package/src/workflow-completion-controller.ts +0 -397
- package/src/workflow-planning.ts +0 -172
- package/src/workflow-tree-identity.ts +0 -289
- package/src/workflow-ui.ts +0 -69
- package/src/workflow-verification.ts +0 -296
- package/src/workspace.ts +0 -174
|
@@ -0,0 +1,667 @@
|
|
|
1
|
+
// @generated by scripts/build-runtime.mjs; do not edit.
|
|
2
|
+
// @ts-nocheck -- generated entries use .ts extensions for Pi's Jiti loader.
|
|
3
|
+
|
|
4
|
+
// src/message-broker.ts
|
|
5
|
+
import { randomBytes, randomUUID } from "node:crypto";
|
|
6
|
+
import net from "node:net";
|
|
7
|
+
var BROKER_HOST = "127.0.0.1";
|
|
8
|
+
var MAX_MESSAGE_BYTES = 48 * 1024;
|
|
9
|
+
var MAX_MESSAGE_LINES = 1992;
|
|
10
|
+
var MAX_FRAME_BYTES = 384 * 1024;
|
|
11
|
+
var MAX_ERROR_BYTES = 8 * 1024;
|
|
12
|
+
var MAX_OUTSTANDING_REQUESTS = 4;
|
|
13
|
+
var MAX_IDENTIFIER_LENGTH = 128;
|
|
14
|
+
var MAX_CONNECTIONS = 32;
|
|
15
|
+
var REQUEST_FRAME_TIMEOUT_MS = 2e3;
|
|
16
|
+
var MAX_TIMEOUT_MS = 2147483647;
|
|
17
|
+
var MAX_RETAINED_CONSUMED_REQUESTS = 4;
|
|
18
|
+
var CHILD_WAIT_INTERRUPTED_ERROR = "Subagent wait was interrupted by an incoming main-agent request. The original request remains active.";
|
|
19
|
+
var MessageBroker = class {
|
|
20
|
+
constructor(options) {
|
|
21
|
+
this.options = options;
|
|
22
|
+
this.createServer = options.createServer ?? ((listener) => net.createServer(listener));
|
|
23
|
+
this.now = options.now ?? Date.now;
|
|
24
|
+
this.requestFrameTimeoutMs = options.requestFrameTimeoutMs ?? REQUEST_FRAME_TIMEOUT_MS;
|
|
25
|
+
if (!Number.isFinite(this.requestFrameTimeoutMs) || this.requestFrameTimeoutMs <= 0) {
|
|
26
|
+
throw new Error("Subagent broker frame timeout must be positive.");
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
options;
|
|
30
|
+
server;
|
|
31
|
+
starting;
|
|
32
|
+
address;
|
|
33
|
+
failure;
|
|
34
|
+
generation = 0;
|
|
35
|
+
sockets = /* @__PURE__ */ new Set();
|
|
36
|
+
jobsByToken = /* @__PURE__ */ new Map();
|
|
37
|
+
tokenByJob = /* @__PURE__ */ new Map();
|
|
38
|
+
requests = /* @__PURE__ */ new Map();
|
|
39
|
+
inboundMessageListeners = /* @__PURE__ */ new Set();
|
|
40
|
+
pendingInboundResponse = false;
|
|
41
|
+
createServer;
|
|
42
|
+
now;
|
|
43
|
+
requestFrameTimeoutMs;
|
|
44
|
+
async start() {
|
|
45
|
+
if (this.server && this.address && !this.failure) return;
|
|
46
|
+
if (this.starting) return this.starting;
|
|
47
|
+
const ownerGeneration = ++this.generation;
|
|
48
|
+
this.failure = void 0;
|
|
49
|
+
this.starting = new Promise((resolve, reject) => {
|
|
50
|
+
const server = this.createServer((socket) => this.accept(socket));
|
|
51
|
+
server.maxConnections = MAX_CONNECTIONS;
|
|
52
|
+
const onStartError = (error) => {
|
|
53
|
+
server.close(() => void 0);
|
|
54
|
+
reject(error);
|
|
55
|
+
};
|
|
56
|
+
server.once("error", onStartError);
|
|
57
|
+
server.listen({ host: BROKER_HOST, port: 0 }, () => {
|
|
58
|
+
server.off("error", onStartError);
|
|
59
|
+
if (ownerGeneration !== this.generation) {
|
|
60
|
+
server.close(() => void 0);
|
|
61
|
+
reject(new Error("Subagent message broker start was superseded."));
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
const address = server.address();
|
|
65
|
+
if (!isLoopbackAddress(address)) {
|
|
66
|
+
server.close(() => void 0);
|
|
67
|
+
reject(new Error("Subagent message broker did not bind to loopback."));
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
this.server = server;
|
|
71
|
+
this.address = { host: BROKER_HOST, port: address.port };
|
|
72
|
+
server.on("error", (error) => this.fail(error));
|
|
73
|
+
server.unref();
|
|
74
|
+
resolve();
|
|
75
|
+
});
|
|
76
|
+
}).catch((error) => {
|
|
77
|
+
const message = truncateError(error instanceof Error ? error.message : String(error));
|
|
78
|
+
this.failure = message;
|
|
79
|
+
throw new Error(`Subagent message broker failed to start: ${message}`);
|
|
80
|
+
}).finally(() => {
|
|
81
|
+
this.starting = void 0;
|
|
82
|
+
});
|
|
83
|
+
return this.starting;
|
|
84
|
+
}
|
|
85
|
+
assertReady() {
|
|
86
|
+
if (this.server && this.address && !this.failure) return;
|
|
87
|
+
throw new Error(
|
|
88
|
+
this.failure ? `Subagent messaging is unavailable: ${this.failure}` : "Subagent messaging is unavailable because the session broker is not ready."
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
issueCredentials(input) {
|
|
92
|
+
this.assertReady();
|
|
93
|
+
if (this.tokenByJob.has(input.jobId)) {
|
|
94
|
+
throw new Error(`Subagent messaging credentials already exist for ${input.jobId}.`);
|
|
95
|
+
}
|
|
96
|
+
const token = randomBytes(32).toString("hex");
|
|
97
|
+
const record = { ...input, token };
|
|
98
|
+
this.jobsByToken.set(token, record);
|
|
99
|
+
this.tokenByJob.set(input.jobId, token);
|
|
100
|
+
return { host: BROKER_HOST, port: this.address?.port ?? 0, token };
|
|
101
|
+
}
|
|
102
|
+
createMainRequest(jobId, message) {
|
|
103
|
+
this.assertReady();
|
|
104
|
+
const job = this.requireJob(jobId);
|
|
105
|
+
return this.createRequest(job, "main", message);
|
|
106
|
+
}
|
|
107
|
+
replyFromMain(requestId, message) {
|
|
108
|
+
this.assertReady();
|
|
109
|
+
return this.acceptResponse("main", void 0, requestId, message);
|
|
110
|
+
}
|
|
111
|
+
rollbackMainRequest(requestId) {
|
|
112
|
+
const request = this.requests.get(requestId);
|
|
113
|
+
if (request?.origin === "main" && request.response === void 0) {
|
|
114
|
+
this.clearWaiter(request);
|
|
115
|
+
this.requests.delete(requestId);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
revokeJob(jobId, reason = "Subagent job is no longer active.") {
|
|
119
|
+
const token = this.tokenByJob.get(jobId);
|
|
120
|
+
if (token) {
|
|
121
|
+
this.tokenByJob.delete(jobId);
|
|
122
|
+
this.jobsByToken.delete(token);
|
|
123
|
+
}
|
|
124
|
+
for (const request of [...this.requests.values()]) {
|
|
125
|
+
if (request.jobId !== jobId) continue;
|
|
126
|
+
if (request.waiter) this.respondError(request.waiter.socket, reason);
|
|
127
|
+
this.clearWaiter(request);
|
|
128
|
+
this.requests.delete(request.requestId);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
hasPendingMainRequest() {
|
|
132
|
+
return [...this.requests.values()].some(
|
|
133
|
+
(request) => request.origin === "child" && request.response === void 0
|
|
134
|
+
);
|
|
135
|
+
}
|
|
136
|
+
takePendingInboundResponse() {
|
|
137
|
+
const pending = this.pendingInboundResponse;
|
|
138
|
+
this.pendingInboundResponse = false;
|
|
139
|
+
return pending;
|
|
140
|
+
}
|
|
141
|
+
markMainRequestQueued(requestId) {
|
|
142
|
+
const request = this.requests.get(requestId);
|
|
143
|
+
if (request?.origin !== "main") {
|
|
144
|
+
throw new Error("Unknown or expired main-agent subagent request.");
|
|
145
|
+
}
|
|
146
|
+
request.deliveryQueued = true;
|
|
147
|
+
return request.response === void 0;
|
|
148
|
+
}
|
|
149
|
+
interruptChildWaits(jobId) {
|
|
150
|
+
let interrupted = 0;
|
|
151
|
+
for (const request of this.requests.values()) {
|
|
152
|
+
if (request.jobId !== jobId || request.origin !== "child" || !request.waiter) continue;
|
|
153
|
+
const socket = request.waiter.socket;
|
|
154
|
+
this.clearWaiter(request);
|
|
155
|
+
this.respondError(socket, CHILD_WAIT_INTERRUPTED_ERROR);
|
|
156
|
+
interrupted++;
|
|
157
|
+
}
|
|
158
|
+
return interrupted;
|
|
159
|
+
}
|
|
160
|
+
subscribeInboundMessage(listener) {
|
|
161
|
+
this.inboundMessageListeners.add(listener);
|
|
162
|
+
if (this.hasPendingMainRequest()) listener();
|
|
163
|
+
return () => this.inboundMessageListeners.delete(listener);
|
|
164
|
+
}
|
|
165
|
+
async shutdown() {
|
|
166
|
+
++this.generation;
|
|
167
|
+
const starting = this.starting;
|
|
168
|
+
if (starting) await starting.catch(() => void 0);
|
|
169
|
+
for (const jobId of [...this.tokenByJob.keys()]) {
|
|
170
|
+
this.revokeJob(jobId, "Subagent session shut down.");
|
|
171
|
+
}
|
|
172
|
+
this.requests.clear();
|
|
173
|
+
this.inboundMessageListeners.clear();
|
|
174
|
+
this.pendingInboundResponse = false;
|
|
175
|
+
for (const socket of this.sockets) socket.destroy();
|
|
176
|
+
this.sockets.clear();
|
|
177
|
+
const server = this.server;
|
|
178
|
+
this.server = void 0;
|
|
179
|
+
this.address = void 0;
|
|
180
|
+
this.failure = void 0;
|
|
181
|
+
if (!server?.listening) return;
|
|
182
|
+
await new Promise((resolve) => server.close(() => resolve()));
|
|
183
|
+
}
|
|
184
|
+
requireJob(jobId) {
|
|
185
|
+
const token = this.tokenByJob.get(jobId);
|
|
186
|
+
const job = token ? this.jobsByToken.get(token) : void 0;
|
|
187
|
+
if (!job) throw new Error("Unknown or inactive subagent job.");
|
|
188
|
+
return job;
|
|
189
|
+
}
|
|
190
|
+
hasQueuedRequestForChild(jobId) {
|
|
191
|
+
return [...this.requests.values()].some(
|
|
192
|
+
(request) => request.jobId === jobId && request.origin === "main" && request.deliveryQueued === true && request.response === void 0
|
|
193
|
+
);
|
|
194
|
+
}
|
|
195
|
+
createRequest(job, origin, message) {
|
|
196
|
+
validateMessage(message, "Subagent request");
|
|
197
|
+
const outstanding = [...this.requests.values()].filter(
|
|
198
|
+
(request2) => request2.jobId === job.jobId && request2.consumedAt === void 0
|
|
199
|
+
).length;
|
|
200
|
+
if (outstanding >= MAX_OUTSTANDING_REQUESTS) {
|
|
201
|
+
throw new Error(
|
|
202
|
+
`Subagent job may have at most ${MAX_OUTSTANDING_REQUESTS} outstanding requests.`
|
|
203
|
+
);
|
|
204
|
+
}
|
|
205
|
+
const request = {
|
|
206
|
+
requestId: `req_${randomUUID()}`,
|
|
207
|
+
jobId: job.jobId,
|
|
208
|
+
generation: job.generation,
|
|
209
|
+
origin,
|
|
210
|
+
expectedResponder: origin === "main" ? "child" : "main",
|
|
211
|
+
message,
|
|
212
|
+
createdAt: this.now()
|
|
213
|
+
};
|
|
214
|
+
this.requests.set(request.requestId, request);
|
|
215
|
+
if (origin === "child") {
|
|
216
|
+
try {
|
|
217
|
+
this.deliverInbound({
|
|
218
|
+
kind: "request",
|
|
219
|
+
requestId: request.requestId,
|
|
220
|
+
jobId: request.jobId,
|
|
221
|
+
message: request.message
|
|
222
|
+
});
|
|
223
|
+
} catch (error) {
|
|
224
|
+
this.requests.delete(request.requestId);
|
|
225
|
+
throw error;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
return { requestId: request.requestId, accepted: true, duplicate: false };
|
|
229
|
+
}
|
|
230
|
+
acceptResponse(responder, job, requestId, message) {
|
|
231
|
+
validateRequestId(requestId);
|
|
232
|
+
validateMessage(message, "Subagent response");
|
|
233
|
+
const request = this.requests.get(requestId);
|
|
234
|
+
if (!request || request.expectedResponder !== responder || job && (request.jobId !== job.jobId || request.generation !== job.generation)) {
|
|
235
|
+
throw new Error("Unknown, expired, or unauthorized subagent request.");
|
|
236
|
+
}
|
|
237
|
+
if (request.response !== void 0) {
|
|
238
|
+
return { requestId, accepted: false, duplicate: true };
|
|
239
|
+
}
|
|
240
|
+
request.response = message;
|
|
241
|
+
if (responder === "child") {
|
|
242
|
+
try {
|
|
243
|
+
this.deliverInbound({
|
|
244
|
+
kind: "response",
|
|
245
|
+
requestId,
|
|
246
|
+
jobId: request.jobId,
|
|
247
|
+
message
|
|
248
|
+
});
|
|
249
|
+
this.markConsumed(request);
|
|
250
|
+
} catch (error) {
|
|
251
|
+
request.response = void 0;
|
|
252
|
+
throw error;
|
|
253
|
+
}
|
|
254
|
+
} else if (request.waiter) {
|
|
255
|
+
const socket = request.waiter.socket;
|
|
256
|
+
this.clearWaiter(request);
|
|
257
|
+
this.respond(socket, { ok: true, response: message }, () => this.markConsumed(request));
|
|
258
|
+
}
|
|
259
|
+
return { requestId, accepted: true, duplicate: false };
|
|
260
|
+
}
|
|
261
|
+
deliverInbound(message) {
|
|
262
|
+
this.options.onMessage(message);
|
|
263
|
+
if (message.kind === "response" && this.inboundMessageListeners.size === 0) {
|
|
264
|
+
this.pendingInboundResponse = true;
|
|
265
|
+
}
|
|
266
|
+
for (const listener of [...this.inboundMessageListeners]) {
|
|
267
|
+
try {
|
|
268
|
+
listener();
|
|
269
|
+
} catch {
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
accept(socket) {
|
|
274
|
+
if (!this.server || this.failure || this.sockets.size >= MAX_CONNECTIONS) {
|
|
275
|
+
socket.destroy();
|
|
276
|
+
return;
|
|
277
|
+
}
|
|
278
|
+
this.sockets.add(socket);
|
|
279
|
+
socket.setNoDelay(true);
|
|
280
|
+
let frame = Buffer.alloc(0);
|
|
281
|
+
let handled = false;
|
|
282
|
+
const frameTimer = setTimeout(() => {
|
|
283
|
+
if (handled) return;
|
|
284
|
+
handled = true;
|
|
285
|
+
this.respondError(socket, "Subagent broker request frame timed out.");
|
|
286
|
+
}, this.requestFrameTimeoutMs);
|
|
287
|
+
frameTimer.unref();
|
|
288
|
+
const cleanup = () => {
|
|
289
|
+
clearTimeout(frameTimer);
|
|
290
|
+
this.sockets.delete(socket);
|
|
291
|
+
for (const request of this.requests.values()) {
|
|
292
|
+
if (request.waiter?.socket === socket) this.clearWaiter(request);
|
|
293
|
+
}
|
|
294
|
+
};
|
|
295
|
+
socket.on("data", (chunk) => {
|
|
296
|
+
if (handled) return;
|
|
297
|
+
frame = Buffer.concat([frame, chunk]);
|
|
298
|
+
if (frame.byteLength > MAX_FRAME_BYTES) {
|
|
299
|
+
handled = true;
|
|
300
|
+
clearTimeout(frameTimer);
|
|
301
|
+
this.respondError(socket, "Subagent broker request exceeded its size limit.");
|
|
302
|
+
return;
|
|
303
|
+
}
|
|
304
|
+
const newline = frame.indexOf(10);
|
|
305
|
+
if (newline < 0) return;
|
|
306
|
+
handled = true;
|
|
307
|
+
clearTimeout(frameTimer);
|
|
308
|
+
socket.removeAllListeners("data");
|
|
309
|
+
const trailing = frame.subarray(newline + 1).toString("utf8").trim();
|
|
310
|
+
if (trailing) {
|
|
311
|
+
this.respondError(socket, "Subagent broker accepts one request per connection.");
|
|
312
|
+
return;
|
|
313
|
+
}
|
|
314
|
+
this.handleFrame(socket, frame.subarray(0, newline).toString("utf8"));
|
|
315
|
+
});
|
|
316
|
+
socket.once("close", cleanup);
|
|
317
|
+
socket.once("error", cleanup);
|
|
318
|
+
}
|
|
319
|
+
handleFrame(socket, frame) {
|
|
320
|
+
let request;
|
|
321
|
+
try {
|
|
322
|
+
const parsed = JSON.parse(frame);
|
|
323
|
+
if (!isRecord(parsed)) throw new Error();
|
|
324
|
+
request = parsed;
|
|
325
|
+
} catch {
|
|
326
|
+
this.respondError(socket, "Malformed subagent broker request.");
|
|
327
|
+
return;
|
|
328
|
+
}
|
|
329
|
+
const job = typeof request.token === "string" ? this.jobsByToken.get(request.token) : void 0;
|
|
330
|
+
if (!job) {
|
|
331
|
+
this.respondError(socket, "Unauthenticated subagent broker request.");
|
|
332
|
+
return;
|
|
333
|
+
}
|
|
334
|
+
try {
|
|
335
|
+
if (request.type === "send") {
|
|
336
|
+
this.handleSend(socket, job, request);
|
|
337
|
+
return;
|
|
338
|
+
}
|
|
339
|
+
if (request.type === "wait") {
|
|
340
|
+
this.handleWait(socket, job, request);
|
|
341
|
+
return;
|
|
342
|
+
}
|
|
343
|
+
throw new Error("Unsupported subagent broker request.");
|
|
344
|
+
} catch (error) {
|
|
345
|
+
this.respondError(socket, error instanceof Error ? error.message : String(error));
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
handleSend(socket, job, request) {
|
|
349
|
+
if (typeof request.message !== "string") {
|
|
350
|
+
throw new Error("subagent_send requires a message string.");
|
|
351
|
+
}
|
|
352
|
+
const hasRecipient = typeof request.recipient === "string";
|
|
353
|
+
const hasRequestId = typeof request.requestId === "string";
|
|
354
|
+
if (hasRecipient === hasRequestId) {
|
|
355
|
+
throw new Error("subagent_send requires exactly one of recipient or requestId.");
|
|
356
|
+
}
|
|
357
|
+
const acknowledgement = hasRecipient ? request.recipient === "main" ? this.createRequest(job, "child", request.message) : (() => {
|
|
358
|
+
throw new Error('A subagent may send new requests only to recipient "main".');
|
|
359
|
+
})() : this.acceptResponse("child", job, request.requestId ?? "", request.message);
|
|
360
|
+
this.respond(socket, { ok: true, ...acknowledgement });
|
|
361
|
+
}
|
|
362
|
+
handleWait(socket, job, request) {
|
|
363
|
+
if (typeof request.requestId !== "string") {
|
|
364
|
+
throw new Error("Subagent wait requires a request ID.");
|
|
365
|
+
}
|
|
366
|
+
validateRequestId(request.requestId);
|
|
367
|
+
if (request.timeoutMs !== void 0) validateTimeout(request.timeoutMs);
|
|
368
|
+
const record = this.requests.get(request.requestId);
|
|
369
|
+
if (record?.origin !== "child" || record.jobId !== job.jobId || record.generation !== job.generation) {
|
|
370
|
+
throw new Error("Unknown or expired subagent request.");
|
|
371
|
+
}
|
|
372
|
+
if (record.consumedAt !== void 0 && record.response !== void 0) {
|
|
373
|
+
this.respond(socket, { ok: true, response: record.response });
|
|
374
|
+
return;
|
|
375
|
+
}
|
|
376
|
+
if (record.response !== void 0) {
|
|
377
|
+
this.respond(
|
|
378
|
+
socket,
|
|
379
|
+
{ ok: true, response: record.response },
|
|
380
|
+
() => this.markConsumed(record)
|
|
381
|
+
);
|
|
382
|
+
return;
|
|
383
|
+
}
|
|
384
|
+
if (this.hasQueuedRequestForChild(job.jobId)) {
|
|
385
|
+
this.respondError(socket, CHILD_WAIT_INTERRUPTED_ERROR);
|
|
386
|
+
return;
|
|
387
|
+
}
|
|
388
|
+
if (record.waiter) {
|
|
389
|
+
throw new Error(`A wait is already active for subagent request ${request.requestId}.`);
|
|
390
|
+
}
|
|
391
|
+
const waiter = { socket };
|
|
392
|
+
record.waiter = waiter;
|
|
393
|
+
if (request.timeoutMs !== void 0) {
|
|
394
|
+
waiter.timer = setTimeout(() => {
|
|
395
|
+
if (record.waiter !== waiter) return;
|
|
396
|
+
this.clearWaiter(record);
|
|
397
|
+
this.respondError(socket, "Subagent response wait timed out.");
|
|
398
|
+
}, request.timeoutMs);
|
|
399
|
+
waiter.timer.unref();
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
markConsumed(request) {
|
|
403
|
+
if (this.requests.get(request.requestId) !== request || request.response === void 0) return;
|
|
404
|
+
request.consumedAt ??= this.now();
|
|
405
|
+
const consumed = [...this.requests.values()].filter(
|
|
406
|
+
(candidate) => candidate.jobId === request.jobId && candidate.consumedAt !== void 0
|
|
407
|
+
).sort((left, right) => (left.consumedAt ?? 0) - (right.consumedAt ?? 0));
|
|
408
|
+
for (const expired of consumed.slice(
|
|
409
|
+
0,
|
|
410
|
+
Math.max(0, consumed.length - MAX_RETAINED_CONSUMED_REQUESTS)
|
|
411
|
+
)) {
|
|
412
|
+
this.requests.delete(expired.requestId);
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
clearWaiter(request) {
|
|
416
|
+
if (request.waiter?.timer) clearTimeout(request.waiter.timer);
|
|
417
|
+
request.waiter = void 0;
|
|
418
|
+
}
|
|
419
|
+
respondError(socket, error) {
|
|
420
|
+
this.respond(socket, { ok: false, error: truncateError(error) });
|
|
421
|
+
}
|
|
422
|
+
respond(socket, value, onFlushed) {
|
|
423
|
+
if (socket.destroyed) return;
|
|
424
|
+
let content = `${JSON.stringify(value)}
|
|
425
|
+
`;
|
|
426
|
+
if (Buffer.byteLength(content, "utf8") > MAX_FRAME_BYTES) {
|
|
427
|
+
content = `${JSON.stringify({ ok: false, error: "Subagent broker response exceeded its size limit." })}
|
|
428
|
+
`;
|
|
429
|
+
}
|
|
430
|
+
socket.end(content, onFlushed);
|
|
431
|
+
}
|
|
432
|
+
fail(error) {
|
|
433
|
+
this.failure = truncateError(error.message);
|
|
434
|
+
for (const jobId of [...this.tokenByJob.keys()]) {
|
|
435
|
+
this.revokeJob(jobId, "Subagent message broker failed.");
|
|
436
|
+
}
|
|
437
|
+
for (const socket of this.sockets) socket.destroy();
|
|
438
|
+
this.sockets.clear();
|
|
439
|
+
this.address = void 0;
|
|
440
|
+
}
|
|
441
|
+
};
|
|
442
|
+
function validateMessage(message, label) {
|
|
443
|
+
if (!message.trim()) throw new Error(`${label} is required.`);
|
|
444
|
+
if (message.includes("\0")) throw new Error(`${label} must not contain NUL bytes.`);
|
|
445
|
+
if (Buffer.byteLength(message, "utf8") > MAX_MESSAGE_BYTES) {
|
|
446
|
+
throw new Error(`${label} must be at most ${MAX_MESSAGE_BYTES} UTF-8 bytes.`);
|
|
447
|
+
}
|
|
448
|
+
if (lineCount(message) > MAX_MESSAGE_LINES) {
|
|
449
|
+
throw new Error(`${label} must contain at most ${MAX_MESSAGE_LINES} lines.`);
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
function sanitizeTerminalText(value) {
|
|
453
|
+
return [...value].filter((character) => {
|
|
454
|
+
const codePoint = character.codePointAt(0) ?? 0;
|
|
455
|
+
if (character === "\n" || character === " ") return true;
|
|
456
|
+
if (codePoint <= 31 || codePoint >= 127 && codePoint <= 159) return false;
|
|
457
|
+
return !(codePoint >= 8234 && codePoint <= 8238 || codePoint >= 8294 && codePoint <= 8297);
|
|
458
|
+
}).join("");
|
|
459
|
+
}
|
|
460
|
+
function validateRequestId(requestId) {
|
|
461
|
+
if (requestId.length > MAX_IDENTIFIER_LENGTH || !/^req_[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/u.test(requestId)) {
|
|
462
|
+
throw new Error("Invalid subagent request ID.");
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
function validateTimeout(timeoutMs) {
|
|
466
|
+
if (!Number.isFinite(timeoutMs) || timeoutMs <= 0 || timeoutMs > MAX_TIMEOUT_MS) {
|
|
467
|
+
throw new Error("Subagent wait timeout is outside the supported range.");
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
function lineCount(value) {
|
|
471
|
+
let count = 1;
|
|
472
|
+
for (const character of value) if (character === "\n") count++;
|
|
473
|
+
return count;
|
|
474
|
+
}
|
|
475
|
+
function truncateError(error) {
|
|
476
|
+
const bytes = Buffer.from(error, "utf8");
|
|
477
|
+
if (bytes.length <= MAX_ERROR_BYTES) return error;
|
|
478
|
+
return `${bytes.subarray(0, Math.max(0, MAX_ERROR_BYTES - 18)).toString("utf8").replace(/�+$/gu, "")}
|
|
479
|
+
\u2026 [truncated]`;
|
|
480
|
+
}
|
|
481
|
+
function isRecord(value) {
|
|
482
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
483
|
+
}
|
|
484
|
+
function isLoopbackAddress(address) {
|
|
485
|
+
return Boolean(address && typeof address !== "string" && address.address === BROKER_HOST);
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
// src/broker-credentials.ts
|
|
489
|
+
import * as fs from "node:fs";
|
|
490
|
+
var BROKER_CREDENTIAL_FD = 3;
|
|
491
|
+
var BROKER_CREDENTIAL_FD_ENV = "PI_SUBAGENT_BROKER_FD";
|
|
492
|
+
var MAX_CREDENTIAL_BYTES = 1024;
|
|
493
|
+
function brokerCredentialEnvironment() {
|
|
494
|
+
return { [BROKER_CREDENTIAL_FD_ENV]: String(BROKER_CREDENTIAL_FD) };
|
|
495
|
+
}
|
|
496
|
+
function serializeBrokerCredentials(credentials) {
|
|
497
|
+
return JSON.stringify(credentials);
|
|
498
|
+
}
|
|
499
|
+
function captureBrokerCredentials(readCredentials = readCredentialPipe) {
|
|
500
|
+
const descriptor = process.env[BROKER_CREDENTIAL_FD_ENV];
|
|
501
|
+
delete process.env[BROKER_CREDENTIAL_FD_ENV];
|
|
502
|
+
if (descriptor === void 0) return void 0;
|
|
503
|
+
if (descriptor !== String(BROKER_CREDENTIAL_FD)) {
|
|
504
|
+
throw new Error("Invalid pi-subagents broker credential descriptor.");
|
|
505
|
+
}
|
|
506
|
+
let serialized;
|
|
507
|
+
try {
|
|
508
|
+
serialized = readCredentials();
|
|
509
|
+
} catch {
|
|
510
|
+
throw new Error("Unable to read pi-subagents broker credentials.");
|
|
511
|
+
}
|
|
512
|
+
if (Buffer.byteLength(serialized, "utf8") > MAX_CREDENTIAL_BYTES) {
|
|
513
|
+
throw new Error("Invalid pi-subagents broker credentials.");
|
|
514
|
+
}
|
|
515
|
+
let value;
|
|
516
|
+
try {
|
|
517
|
+
value = JSON.parse(serialized);
|
|
518
|
+
} catch {
|
|
519
|
+
throw new Error("Invalid pi-subagents broker credentials.");
|
|
520
|
+
}
|
|
521
|
+
if (!isBrokerCredentials(value)) {
|
|
522
|
+
throw new Error("Invalid pi-subagents broker credentials.");
|
|
523
|
+
}
|
|
524
|
+
return value;
|
|
525
|
+
}
|
|
526
|
+
function readCredentialPipe() {
|
|
527
|
+
try {
|
|
528
|
+
return fs.readFileSync(BROKER_CREDENTIAL_FD, "utf8");
|
|
529
|
+
} finally {
|
|
530
|
+
try {
|
|
531
|
+
fs.closeSync(BROKER_CREDENTIAL_FD);
|
|
532
|
+
} catch {
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
function isBrokerCredentials(value) {
|
|
537
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return false;
|
|
538
|
+
const candidate = value;
|
|
539
|
+
return Object.keys(candidate).length === 3 && candidate.host === "127.0.0.1" && Number.isSafeInteger(candidate.port) && candidate.port >= 1 && candidate.port <= 65535 && typeof candidate.token === "string" && /^[a-f0-9]{64}$/u.test(candidate.token);
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
// src/child-communication-tools.ts
|
|
543
|
+
import { Type } from "typebox";
|
|
544
|
+
var CHILD_COMMUNICATION_TOOL_NAMES = ["subagent_send", "subagent_wait"];
|
|
545
|
+
var MAX_TIMEOUT_MS2 = 2147483647;
|
|
546
|
+
var MAX_TIMEOUT_SECONDS = MAX_TIMEOUT_MS2 / 1e3;
|
|
547
|
+
var SendParameters = Type.Object(
|
|
548
|
+
{
|
|
549
|
+
requestId: Type.Optional(
|
|
550
|
+
Type.String({
|
|
551
|
+
description: "Pending main-agent request to answer. Omit when starting a new request.",
|
|
552
|
+
minLength: 1,
|
|
553
|
+
maxLength: MAX_IDENTIFIER_LENGTH
|
|
554
|
+
})
|
|
555
|
+
),
|
|
556
|
+
message: Type.String({
|
|
557
|
+
description: "Plain-text request or response. Maximum 48 KiB of UTF-8 text and 1,992 lines.",
|
|
558
|
+
minLength: 1,
|
|
559
|
+
maxLength: MAX_MESSAGE_BYTES
|
|
560
|
+
})
|
|
561
|
+
},
|
|
562
|
+
{ additionalProperties: false }
|
|
563
|
+
);
|
|
564
|
+
var WaitParameters = Type.Object(
|
|
565
|
+
{
|
|
566
|
+
requestId: Type.String({
|
|
567
|
+
description: "Request ID returned by subagent_send.",
|
|
568
|
+
minLength: 1,
|
|
569
|
+
maxLength: MAX_IDENTIFIER_LENGTH
|
|
570
|
+
}),
|
|
571
|
+
timeout: Type.Optional(
|
|
572
|
+
Type.Number({ description: "Timeout in seconds (optional, no default timeout)" })
|
|
573
|
+
)
|
|
574
|
+
},
|
|
575
|
+
{ additionalProperties: false }
|
|
576
|
+
);
|
|
577
|
+
function createChildCommunicationExtension(client) {
|
|
578
|
+
return (pi) => {
|
|
579
|
+
pi.registerTool({
|
|
580
|
+
name: "subagent_send",
|
|
581
|
+
label: "Subagent \xB7 Send to Main",
|
|
582
|
+
description: "Use subagent_send to send one request to the main agent or answer one pending main-agent request. Omit requestId to start a request. Provide requestId to answer that request.",
|
|
583
|
+
promptSnippet: "Use subagent_send to send or answer one main-agent message",
|
|
584
|
+
parameters: SendParameters,
|
|
585
|
+
async execute(_toolCallId, params, signal) {
|
|
586
|
+
validateMessage(params.message, "Subagent message");
|
|
587
|
+
const requestId = optionalIdentifier(params.requestId, "requestId");
|
|
588
|
+
const acknowledgement = await client.send(
|
|
589
|
+
requestId === void 0 ? { recipient: "main", message: params.message } : { requestId, message: params.message },
|
|
590
|
+
signal
|
|
591
|
+
);
|
|
592
|
+
return {
|
|
593
|
+
content: [{ type: "text", text: JSON.stringify(acknowledgement) }],
|
|
594
|
+
details: acknowledgement
|
|
595
|
+
};
|
|
596
|
+
}
|
|
597
|
+
});
|
|
598
|
+
pi.registerTool({
|
|
599
|
+
name: "subagent_wait",
|
|
600
|
+
label: "Subagent \xB7 Wait for Main Agent",
|
|
601
|
+
description: "Use subagent_wait with a request ID from subagent_send to wait for the main agent's plain-text response. A timeout, caller cancellation, or incoming main-agent request stops only this wait and does not cancel the original request. Retry the wait when its response is still needed.",
|
|
602
|
+
promptSnippet: "Use subagent_wait to receive a requested main-agent response",
|
|
603
|
+
parameters: WaitParameters,
|
|
604
|
+
prepareArguments: prepareWaitArguments,
|
|
605
|
+
async execute(_toolCallId, params, signal) {
|
|
606
|
+
const requestId = requiredIdentifier(params.requestId, "requestId");
|
|
607
|
+
const response = await client.wait(requestId, resolveTimeoutMs(params.timeout), signal);
|
|
608
|
+
return {
|
|
609
|
+
content: [{ type: "text", text: sanitizeTerminalText(response) }],
|
|
610
|
+
details: { requestId }
|
|
611
|
+
};
|
|
612
|
+
}
|
|
613
|
+
});
|
|
614
|
+
};
|
|
615
|
+
}
|
|
616
|
+
function resolveTimeoutMs(timeout) {
|
|
617
|
+
if (timeout === void 0) return void 0;
|
|
618
|
+
if (!Number.isFinite(timeout) || timeout <= 0) {
|
|
619
|
+
throw new Error("Invalid timeout: must be a finite number of seconds");
|
|
620
|
+
}
|
|
621
|
+
const timeoutMs = timeout * 1e3;
|
|
622
|
+
if (timeoutMs > MAX_TIMEOUT_MS2) {
|
|
623
|
+
throw new Error(`Invalid timeout: maximum is ${MAX_TIMEOUT_SECONDS} seconds`);
|
|
624
|
+
}
|
|
625
|
+
return timeoutMs;
|
|
626
|
+
}
|
|
627
|
+
function prepareWaitArguments(args) {
|
|
628
|
+
if (!args || typeof args !== "object") return args;
|
|
629
|
+
if (!Object.hasOwn(args, "timeoutMs")) return args;
|
|
630
|
+
const record = args;
|
|
631
|
+
if (typeof record.timeoutMs !== "number") return record;
|
|
632
|
+
const { timeoutMs, ...prepared } = record;
|
|
633
|
+
if (prepared.timeout === void 0) {
|
|
634
|
+
return { ...prepared, timeout: timeoutMs / 1e3 };
|
|
635
|
+
}
|
|
636
|
+
return prepared;
|
|
637
|
+
}
|
|
638
|
+
function optionalIdentifier(value, field) {
|
|
639
|
+
return value === void 0 ? void 0 : requiredIdentifier(value, field);
|
|
640
|
+
}
|
|
641
|
+
function requiredIdentifier(value, field) {
|
|
642
|
+
if (typeof value !== "string" || !value.trim()) throw new Error(`Subagent ${field} is required.`);
|
|
643
|
+
const identifier = value.trim();
|
|
644
|
+
if (identifier.length > MAX_IDENTIFIER_LENGTH || [...identifier].some((character) => {
|
|
645
|
+
const codePoint = character.codePointAt(0) ?? 0;
|
|
646
|
+
return codePoint <= 31 || codePoint >= 127 && codePoint <= 159;
|
|
647
|
+
})) {
|
|
648
|
+
throw new Error(`Subagent ${field} is invalid.`);
|
|
649
|
+
}
|
|
650
|
+
return identifier;
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
export {
|
|
654
|
+
MAX_MESSAGE_BYTES,
|
|
655
|
+
MAX_FRAME_BYTES,
|
|
656
|
+
MAX_IDENTIFIER_LENGTH,
|
|
657
|
+
MessageBroker,
|
|
658
|
+
validateMessage,
|
|
659
|
+
sanitizeTerminalText,
|
|
660
|
+
BROKER_CREDENTIAL_FD,
|
|
661
|
+
brokerCredentialEnvironment,
|
|
662
|
+
serializeBrokerCredentials,
|
|
663
|
+
captureBrokerCredentials,
|
|
664
|
+
CHILD_COMMUNICATION_TOOL_NAMES,
|
|
665
|
+
createChildCommunicationExtension
|
|
666
|
+
};
|
|
667
|
+
//# sourceMappingURL=chunk-UKTWUQRM.js.map
|