@narumitw/pi-subagents 2.1.3 → 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 +181 -1250
- 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 -1598
- 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 -153
- package/docs/pi-subagents-diagrams.md +0 -183
- package/src/adaptive-scheduler.ts +0 -224
- package/src/admission-benchmark.ts +0 -95
- 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/async-subagent-benchmark.ts +0 -532
- 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 -355
- 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
package/src/rpc-transport.ts
DELETED
|
@@ -1,1035 +0,0 @@
|
|
|
1
|
-
import { type ChildProcess, spawn } from "node:child_process";
|
|
2
|
-
import * as fs from "node:fs";
|
|
3
|
-
import * as os from "node:os";
|
|
4
|
-
import * as path from "node:path";
|
|
5
|
-
import type { RpcSessionState } from "@earendil-works/pi-coding-agent";
|
|
6
|
-
import { discoverAgents } from "./agents/discovery.js";
|
|
7
|
-
import type { AgentConfig, SubagentSettings } from "./agents/types.js";
|
|
8
|
-
import { formatPeerMessage } from "./child-peer-tools.js";
|
|
9
|
-
import {
|
|
10
|
-
buildCurrentTurnPrompt,
|
|
11
|
-
type ParentRuntimeSnapshot,
|
|
12
|
-
validateInProcessTools,
|
|
13
|
-
} from "./in-process-transport.js";
|
|
14
|
-
import {
|
|
15
|
-
appendBounded,
|
|
16
|
-
DEFAULT_MAX_OUTPUT_BYTES,
|
|
17
|
-
DEFAULT_MAX_STDERR_BYTES,
|
|
18
|
-
truncateUtf8,
|
|
19
|
-
} from "./limits.js";
|
|
20
|
-
import {
|
|
21
|
-
CHILD_PEER_TOOL_NAMES,
|
|
22
|
-
childPeerBridgePath,
|
|
23
|
-
type PeerTransportRuntime,
|
|
24
|
-
peerBridgeEnvironment,
|
|
25
|
-
} from "./peer-transport.js";
|
|
26
|
-
import { type PiInvocation, resolvePiInvocation } from "./pi-invocation.js";
|
|
27
|
-
import { terminateProcess } from "./process-control.js";
|
|
28
|
-
import { resolvePiPromptResources } from "./prompt-resources.js";
|
|
29
|
-
import { JsonLineDecoder } from "./protocol.js";
|
|
30
|
-
import type { AgentMailboxMessage, ManagedAgent, TurnOutcome } from "./registry.js";
|
|
31
|
-
import { finalizeTimedOutRpcTurn } from "./rpc-timeout-finalization.js";
|
|
32
|
-
import {
|
|
33
|
-
boundedError,
|
|
34
|
-
interruptedRpcOutcome,
|
|
35
|
-
modelIdentity,
|
|
36
|
-
normalizeThinking,
|
|
37
|
-
rpcPolicy,
|
|
38
|
-
} from "./rpc-transport-metadata.js";
|
|
39
|
-
import {
|
|
40
|
-
captureRpcEvent,
|
|
41
|
-
commitRpcInFlightUsage,
|
|
42
|
-
createRpcTurnCapture,
|
|
43
|
-
observeRpcBudgetEvent,
|
|
44
|
-
snapshotRpcUsage,
|
|
45
|
-
} from "./rpc-turn-capture.js";
|
|
46
|
-
import { safeTerminalText } from "./safe-text.js";
|
|
47
|
-
import { readSubagentSettings } from "./settings.js";
|
|
48
|
-
import { buildStatefulTurnPrompt, resolveStatefulTurnTimeout } from "./stateful-prompt.js";
|
|
49
|
-
import {
|
|
50
|
-
formatTimeoutCheckpoint,
|
|
51
|
-
formatTurnTerminationMessage,
|
|
52
|
-
TURN_TERMINATION_VERSION,
|
|
53
|
-
type TurnTerminationReport,
|
|
54
|
-
} from "./timeout-checkpoint.js";
|
|
55
|
-
import type { SubagentTransport } from "./transport.js";
|
|
56
|
-
import {
|
|
57
|
-
PI_SUBAGENTS_RPC_PROTOCOL,
|
|
58
|
-
type TransportProgressCallback,
|
|
59
|
-
type TransportTelemetry,
|
|
60
|
-
} from "./transport-types.js";
|
|
61
|
-
import { TurnBudgetMonitor, type TurnBudgetStop } from "./turn-budget.js";
|
|
62
|
-
|
|
63
|
-
const COMMAND_TIMEOUT_MS = 30_000;
|
|
64
|
-
const STARTUP_TIMEOUT_MS = 30_000;
|
|
65
|
-
const ABORT_GRACE_MS = 5_000;
|
|
66
|
-
const BUILT_IN_TOOL_NAMES = new Set(["read", "bash", "edit", "write", "grep", "find", "ls"]);
|
|
67
|
-
|
|
68
|
-
interface RpcCommandInput {
|
|
69
|
-
type: string;
|
|
70
|
-
[key: string]: unknown;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
interface RpcResponse {
|
|
74
|
-
id?: string;
|
|
75
|
-
type: "response";
|
|
76
|
-
command: string;
|
|
77
|
-
success: boolean;
|
|
78
|
-
data?: unknown;
|
|
79
|
-
error?: string;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
interface PendingRequest {
|
|
83
|
-
command: string;
|
|
84
|
-
resolve(value: RpcResponse): void;
|
|
85
|
-
reject(error: Error): void;
|
|
86
|
-
timer: NodeJS.Timeout;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
export interface RpcProtocolClientOptions {
|
|
90
|
-
cwd: string;
|
|
91
|
-
args: string[];
|
|
92
|
-
env?: NodeJS.ProcessEnv;
|
|
93
|
-
startupTimeoutMs?: number;
|
|
94
|
-
commandTimeoutMs?: number;
|
|
95
|
-
abortTimeoutMs?: number;
|
|
96
|
-
terminationGraceMs?: number;
|
|
97
|
-
invocation?: PiInvocation;
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
export interface RpcProtocolSnapshot {
|
|
101
|
-
state: RpcSessionState;
|
|
102
|
-
stderr: string;
|
|
103
|
-
malformedLines: number;
|
|
104
|
-
oversizedLines: number;
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
export class RpcProtocolClient {
|
|
108
|
-
private process?: ChildProcess;
|
|
109
|
-
private processClosed = false;
|
|
110
|
-
private decoder?: JsonLineDecoder;
|
|
111
|
-
private readonly pending = new Map<string, PendingRequest>();
|
|
112
|
-
private readonly listeners = new Set<(event: unknown) => void>();
|
|
113
|
-
private readonly closeListeners = new Set<(error: Error) => void>();
|
|
114
|
-
private nextRequestId = 0;
|
|
115
|
-
private stderr = "";
|
|
116
|
-
private malformedLines = 0;
|
|
117
|
-
private oversizedLines = 0;
|
|
118
|
-
private closedError?: Error;
|
|
119
|
-
private closePromise?: Promise<void>;
|
|
120
|
-
private cleanupTermination?: () => void;
|
|
121
|
-
|
|
122
|
-
constructor(private readonly options: RpcProtocolClientOptions) {}
|
|
123
|
-
|
|
124
|
-
async start(signal?: AbortSignal): Promise<RpcProtocolSnapshot> {
|
|
125
|
-
if (this.process) throw new Error("RPC subagent client already started");
|
|
126
|
-
if (signal?.aborted) throw abortError("RPC subagent start aborted");
|
|
127
|
-
const invocation = this.options.invocation
|
|
128
|
-
? {
|
|
129
|
-
command: this.options.invocation.command,
|
|
130
|
-
args: [...this.options.invocation.args, ...this.options.args],
|
|
131
|
-
}
|
|
132
|
-
: resolvePiInvocation(this.options.args);
|
|
133
|
-
const proc = spawn(invocation.command, invocation.args, {
|
|
134
|
-
cwd: this.options.cwd,
|
|
135
|
-
detached: process.platform !== "win32",
|
|
136
|
-
shell: false,
|
|
137
|
-
stdio: ["pipe", "pipe", "pipe"],
|
|
138
|
-
env: { ...process.env, ...this.options.env },
|
|
139
|
-
});
|
|
140
|
-
this.process = proc;
|
|
141
|
-
this.processClosed = false;
|
|
142
|
-
this.decoder = new JsonLineDecoder({
|
|
143
|
-
onValue: (value) => this.handleValue(value),
|
|
144
|
-
onMalformed: () => {
|
|
145
|
-
this.malformedLines++;
|
|
146
|
-
this.fail(new Error("RPC subagent emitted malformed JSONL"));
|
|
147
|
-
},
|
|
148
|
-
onOversized: () => {
|
|
149
|
-
this.oversizedLines++;
|
|
150
|
-
this.fail(new Error("RPC subagent emitted an oversized JSONL record"));
|
|
151
|
-
},
|
|
152
|
-
});
|
|
153
|
-
proc.stdout?.on("data", (chunk) => this.decoder?.push(chunk));
|
|
154
|
-
proc.stdin?.on("error", (error) => this.fail(errorMessage("RPC stdin failed", error)));
|
|
155
|
-
proc.stderr?.on("data", (chunk) => {
|
|
156
|
-
this.stderr = appendBounded(
|
|
157
|
-
this.stderr,
|
|
158
|
-
Buffer.isBuffer(chunk) ? chunk.toString("utf8") : String(chunk),
|
|
159
|
-
DEFAULT_MAX_STDERR_BYTES,
|
|
160
|
-
).text;
|
|
161
|
-
});
|
|
162
|
-
proc.once("error", (error) => this.fail(errorMessage("RPC process failed", error)));
|
|
163
|
-
proc.once("close", (code, childSignal) => {
|
|
164
|
-
this.processClosed = true;
|
|
165
|
-
this.decoder?.finish();
|
|
166
|
-
this.fail(
|
|
167
|
-
new Error(
|
|
168
|
-
`RPC subagent exited (code=${code ?? "null"}, signal=${childSignal ?? "null"})${this.stderr ? `: ${safeTerminalText(this.stderr)}` : ""}`,
|
|
169
|
-
),
|
|
170
|
-
);
|
|
171
|
-
});
|
|
172
|
-
try {
|
|
173
|
-
await waitForSpawn(proc, signal, this.options.startupTimeoutMs ?? STARTUP_TIMEOUT_MS);
|
|
174
|
-
if (signal?.aborted) throw abortError("RPC subagent start aborted");
|
|
175
|
-
const state = await raceWithAbort(
|
|
176
|
-
this.requestData<RpcSessionState>(
|
|
177
|
-
{ type: "get_state" },
|
|
178
|
-
this.options.startupTimeoutMs ?? STARTUP_TIMEOUT_MS,
|
|
179
|
-
),
|
|
180
|
-
signal,
|
|
181
|
-
"RPC subagent readiness aborted",
|
|
182
|
-
);
|
|
183
|
-
return {
|
|
184
|
-
state,
|
|
185
|
-
stderr: this.stderr,
|
|
186
|
-
malformedLines: this.malformedLines,
|
|
187
|
-
oversizedLines: this.oversizedLines,
|
|
188
|
-
};
|
|
189
|
-
} catch (error) {
|
|
190
|
-
await this.stop();
|
|
191
|
-
throw error;
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
onEvent(listener: (event: unknown) => void): () => void {
|
|
196
|
-
this.listeners.add(listener);
|
|
197
|
-
return () => this.listeners.delete(listener);
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
onClose(listener: (error: Error) => void): () => void {
|
|
201
|
-
this.closeListeners.add(listener);
|
|
202
|
-
return () => this.closeListeners.delete(listener);
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
async prompt(message: string, timeoutMs?: number): Promise<void> {
|
|
206
|
-
await this.request({ type: "prompt", message }, timeoutMs);
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
async steer(message: string, timeoutMs?: number): Promise<void> {
|
|
210
|
-
await this.request({ type: "steer", message }, timeoutMs);
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
async abort(): Promise<void> {
|
|
214
|
-
await this.request(
|
|
215
|
-
{ type: "abort" },
|
|
216
|
-
this.options.abortTimeoutMs ?? Math.min(COMMAND_TIMEOUT_MS, ABORT_GRACE_MS),
|
|
217
|
-
);
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
async getState(): Promise<RpcSessionState> {
|
|
221
|
-
return this.requestData<RpcSessionState>({ type: "get_state" });
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
getStderr(): string {
|
|
225
|
-
return this.stderr;
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
async stop(): Promise<void> {
|
|
229
|
-
if (this.closePromise) return this.closePromise;
|
|
230
|
-
const proc = this.process;
|
|
231
|
-
if (!proc) return;
|
|
232
|
-
if (this.processClosed) {
|
|
233
|
-
this.cleanupTermination?.();
|
|
234
|
-
this.cleanupTermination = undefined;
|
|
235
|
-
this.process = undefined;
|
|
236
|
-
this.rejectPending(this.closedError ?? new Error("RPC subagent process already exited"));
|
|
237
|
-
this.listeners.clear();
|
|
238
|
-
this.closeListeners.clear();
|
|
239
|
-
return;
|
|
240
|
-
}
|
|
241
|
-
this.closePromise = new Promise<void>((resolve) => {
|
|
242
|
-
let settled = false;
|
|
243
|
-
let terminationDeadline: NodeJS.Timeout | undefined;
|
|
244
|
-
const finish = () => {
|
|
245
|
-
if (settled) return;
|
|
246
|
-
settled = true;
|
|
247
|
-
if (terminationDeadline) clearTimeout(terminationDeadline);
|
|
248
|
-
proc.off("close", finish);
|
|
249
|
-
this.cleanupTermination?.();
|
|
250
|
-
this.cleanupTermination = undefined;
|
|
251
|
-
resolve();
|
|
252
|
-
};
|
|
253
|
-
proc.once("close", finish);
|
|
254
|
-
try {
|
|
255
|
-
proc.stdin?.end();
|
|
256
|
-
} catch {
|
|
257
|
-
// Termination below remains authoritative.
|
|
258
|
-
}
|
|
259
|
-
const graceMs = this.options.terminationGraceMs ?? ABORT_GRACE_MS;
|
|
260
|
-
this.cleanupTermination?.();
|
|
261
|
-
this.cleanupTermination = terminateProcess(proc, graceMs);
|
|
262
|
-
terminationDeadline = setTimeout(() => {
|
|
263
|
-
proc.stdin?.destroy();
|
|
264
|
-
proc.stdout?.destroy();
|
|
265
|
-
proc.stderr?.destroy();
|
|
266
|
-
finish();
|
|
267
|
-
}, graceMs + 1_000);
|
|
268
|
-
});
|
|
269
|
-
await this.closePromise;
|
|
270
|
-
this.process = undefined;
|
|
271
|
-
this.rejectPending(new Error("RPC subagent client stopped"));
|
|
272
|
-
this.listeners.clear();
|
|
273
|
-
this.closeListeners.clear();
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
private async requestData<T>(
|
|
277
|
-
command: RpcCommandInput,
|
|
278
|
-
timeoutMs = this.options.commandTimeoutMs ?? COMMAND_TIMEOUT_MS,
|
|
279
|
-
): Promise<T> {
|
|
280
|
-
const response = await this.request(command, timeoutMs);
|
|
281
|
-
return response.data as T;
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
private request(
|
|
285
|
-
command: RpcCommandInput,
|
|
286
|
-
timeoutMs = this.options.commandTimeoutMs ?? COMMAND_TIMEOUT_MS,
|
|
287
|
-
): Promise<RpcResponse> {
|
|
288
|
-
const proc = this.process;
|
|
289
|
-
if (!proc?.stdin || proc.stdin.destroyed || !proc.stdin.writable) {
|
|
290
|
-
return Promise.reject(this.closedError ?? new Error("RPC subagent stdin is unavailable"));
|
|
291
|
-
}
|
|
292
|
-
const id = `psa_${++this.nextRequestId}`;
|
|
293
|
-
return new Promise<RpcResponse>((resolve, reject) => {
|
|
294
|
-
const timer = setTimeout(() => {
|
|
295
|
-
this.pending.delete(id);
|
|
296
|
-
reject(rpcTimeoutError(`RPC ${command.type} response timed out after ${timeoutMs}ms`));
|
|
297
|
-
}, timeoutMs);
|
|
298
|
-
timer.unref();
|
|
299
|
-
this.pending.set(id, { command: command.type, resolve, reject, timer });
|
|
300
|
-
try {
|
|
301
|
-
proc.stdin?.write(`${JSON.stringify({ ...command, id })}\n`, (error) => {
|
|
302
|
-
if (!error) return;
|
|
303
|
-
this.fail(errorMessage("RPC stdin write failed", error));
|
|
304
|
-
});
|
|
305
|
-
} catch (error) {
|
|
306
|
-
this.pending.delete(id);
|
|
307
|
-
clearTimeout(timer);
|
|
308
|
-
reject(error instanceof Error ? error : new Error(String(error)));
|
|
309
|
-
}
|
|
310
|
-
}).then((response) => {
|
|
311
|
-
if (!response.success) throw new Error(response.error || `RPC ${command.type} failed`);
|
|
312
|
-
return response;
|
|
313
|
-
});
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
private handleValue(value: unknown): void {
|
|
317
|
-
if (!value || typeof value !== "object" || Array.isArray(value)) return;
|
|
318
|
-
const event = value as Record<string, unknown>;
|
|
319
|
-
if (event.type === "response" && typeof event.id === "string") {
|
|
320
|
-
const pending = this.pending.get(event.id);
|
|
321
|
-
if (!pending) return;
|
|
322
|
-
if (typeof event.command !== "string" || event.command !== pending.command) {
|
|
323
|
-
this.fail(
|
|
324
|
-
new Error(`RPC response command mismatch for ${event.id}: expected ${pending.command}`),
|
|
325
|
-
);
|
|
326
|
-
return;
|
|
327
|
-
}
|
|
328
|
-
this.pending.delete(event.id);
|
|
329
|
-
clearTimeout(pending.timer);
|
|
330
|
-
pending.resolve(event as unknown as RpcResponse);
|
|
331
|
-
return;
|
|
332
|
-
}
|
|
333
|
-
if (event.type === "extension_ui_request" && typeof event.id === "string") {
|
|
334
|
-
this.writeFireAndForget({
|
|
335
|
-
type: "extension_ui_response",
|
|
336
|
-
id: event.id,
|
|
337
|
-
cancelled: true,
|
|
338
|
-
});
|
|
339
|
-
}
|
|
340
|
-
for (const listener of this.listeners) listener(value);
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
private writeFireAndForget(value: Record<string, unknown>): void {
|
|
344
|
-
try {
|
|
345
|
-
this.process?.stdin?.write(`${JSON.stringify(value)}\n`);
|
|
346
|
-
} catch {
|
|
347
|
-
// Process failure will be reported through close/error handling.
|
|
348
|
-
}
|
|
349
|
-
}
|
|
350
|
-
|
|
351
|
-
private fail(error: Error): void {
|
|
352
|
-
if (this.closedError) return;
|
|
353
|
-
this.closedError = error;
|
|
354
|
-
this.rejectPending(error);
|
|
355
|
-
for (const listener of this.closeListeners) listener(error);
|
|
356
|
-
if (this.process && !this.closePromise) {
|
|
357
|
-
this.cleanupTermination = terminateProcess(
|
|
358
|
-
this.process,
|
|
359
|
-
this.options.terminationGraceMs ?? ABORT_GRACE_MS,
|
|
360
|
-
);
|
|
361
|
-
}
|
|
362
|
-
}
|
|
363
|
-
|
|
364
|
-
private rejectPending(error: Error): void {
|
|
365
|
-
for (const pending of this.pending.values()) {
|
|
366
|
-
clearTimeout(pending.timer);
|
|
367
|
-
pending.reject(error);
|
|
368
|
-
}
|
|
369
|
-
this.pending.clear();
|
|
370
|
-
}
|
|
371
|
-
}
|
|
372
|
-
|
|
373
|
-
export interface RpcTransportOptions {
|
|
374
|
-
getSettings?: () => SubagentSettings | undefined;
|
|
375
|
-
getParentRuntime: () => ParentRuntimeSnapshot;
|
|
376
|
-
createClient?: (options: RpcProtocolClientOptions) => RpcProtocolClient;
|
|
377
|
-
resolvePromptResources?: typeof resolvePiPromptResources;
|
|
378
|
-
defaultTimeoutMs?: number;
|
|
379
|
-
abortGraceMs?: number;
|
|
380
|
-
timeoutFinalizationMs?: number;
|
|
381
|
-
peerRuntime?: PeerTransportRuntime;
|
|
382
|
-
}
|
|
383
|
-
|
|
384
|
-
interface RpcChildRecord {
|
|
385
|
-
client: RpcProtocolClient;
|
|
386
|
-
state: RpcSessionState;
|
|
387
|
-
started: boolean;
|
|
388
|
-
temporaryPrompt?: { dir: string; filePath: string };
|
|
389
|
-
}
|
|
390
|
-
|
|
391
|
-
export class RpcTransport implements SubagentTransport {
|
|
392
|
-
readonly kind = "rpc" as const;
|
|
393
|
-
private readonly children = new Map<string, RpcChildRecord>();
|
|
394
|
-
private readonly createClient: (options: RpcProtocolClientOptions) => RpcProtocolClient;
|
|
395
|
-
|
|
396
|
-
constructor(private readonly options: RpcTransportOptions) {
|
|
397
|
-
this.createClient =
|
|
398
|
-
options.createClient ?? ((clientOptions) => new RpcProtocolClient(clientOptions));
|
|
399
|
-
}
|
|
400
|
-
|
|
401
|
-
async runTurn(
|
|
402
|
-
agent: ManagedAgent,
|
|
403
|
-
task: string,
|
|
404
|
-
signal: AbortSignal,
|
|
405
|
-
onProgress?: TransportProgressCallback,
|
|
406
|
-
): Promise<TurnOutcome> {
|
|
407
|
-
const startedAt = Date.now();
|
|
408
|
-
let telemetry: TransportTelemetry = {
|
|
409
|
-
transport: "rpc",
|
|
410
|
-
protocol: PI_SUBAGENTS_RPC_PROTOCOL,
|
|
411
|
-
phase: "starting",
|
|
412
|
-
updatedAt: startedAt,
|
|
413
|
-
timing: { startedAt, transportStartedAt: startedAt },
|
|
414
|
-
};
|
|
415
|
-
const publish = (patch: Partial<TransportTelemetry>) => {
|
|
416
|
-
telemetry = {
|
|
417
|
-
...telemetry,
|
|
418
|
-
...patch,
|
|
419
|
-
timing: { ...telemetry.timing, ...patch.timing },
|
|
420
|
-
usage: patch.usage ? { ...patch.usage } : telemetry.usage,
|
|
421
|
-
updatedAt: Date.now(),
|
|
422
|
-
};
|
|
423
|
-
onProgress?.({
|
|
424
|
-
...telemetry,
|
|
425
|
-
timing: { ...telemetry.timing },
|
|
426
|
-
usage: telemetry.usage ? { ...telemetry.usage } : undefined,
|
|
427
|
-
});
|
|
428
|
-
};
|
|
429
|
-
publish({});
|
|
430
|
-
if (signal.aborted) return interruptedRpcOutcome("", telemetry, "starting");
|
|
431
|
-
const settings = this.options.getSettings ? this.options.getSettings() : readSubagentSettings();
|
|
432
|
-
const agentConfig = discoverAgents(agent.cwd, agent.agentScope ?? "user", settings).agents.find(
|
|
433
|
-
(candidate) => candidate.name === agent.agent,
|
|
434
|
-
);
|
|
435
|
-
if (!agentConfig) {
|
|
436
|
-
publish({ phase: "failed", failurePhase: "starting" });
|
|
437
|
-
return { output: "", exitCode: 1, error: `Unknown subagent: ${agent.agent}`, telemetry };
|
|
438
|
-
}
|
|
439
|
-
const effectiveAgentConfig = {
|
|
440
|
-
...agentConfig,
|
|
441
|
-
tools: agent.executionPlan?.effectiveTools ?? agentConfig.tools,
|
|
442
|
-
};
|
|
443
|
-
try {
|
|
444
|
-
validateRpcTools(effectiveAgentConfig.tools);
|
|
445
|
-
} catch (error) {
|
|
446
|
-
publish({ phase: "failed", failurePhase: "starting" });
|
|
447
|
-
return { output: "", exitCode: 1, error: boundedError(error), telemetry };
|
|
448
|
-
}
|
|
449
|
-
let child: RpcChildRecord;
|
|
450
|
-
try {
|
|
451
|
-
child = await this.getOrCreate(agent, effectiveAgentConfig, signal);
|
|
452
|
-
} catch (error) {
|
|
453
|
-
publish({ phase: signal.aborted ? "interrupted" : "failed", failurePhase: "starting" });
|
|
454
|
-
return {
|
|
455
|
-
output: "",
|
|
456
|
-
exitCode: signal.aborted ? 130 : 1,
|
|
457
|
-
aborted: signal.aborted,
|
|
458
|
-
error: boundedError(error),
|
|
459
|
-
telemetry,
|
|
460
|
-
};
|
|
461
|
-
}
|
|
462
|
-
const stateModel = modelIdentity(child.state.model);
|
|
463
|
-
publish({
|
|
464
|
-
phase: "ready",
|
|
465
|
-
provider: stateModel.provider,
|
|
466
|
-
model: stateModel.model,
|
|
467
|
-
thinkingLevel: normalizeThinking(child.state.thinkingLevel),
|
|
468
|
-
timing: { readyAt: Date.now() },
|
|
469
|
-
});
|
|
470
|
-
if (signal.aborted) {
|
|
471
|
-
await this.releaseById(agent.id).catch(() => undefined);
|
|
472
|
-
return interruptedRpcOutcome("", telemetry, "ready");
|
|
473
|
-
}
|
|
474
|
-
const timeoutMs =
|
|
475
|
-
agent.currentTimeoutMs ??
|
|
476
|
-
agent.timeoutMs ??
|
|
477
|
-
agentConfig.timeoutMs ??
|
|
478
|
-
this.options.defaultTimeoutMs ??
|
|
479
|
-
resolveStatefulTurnTimeout(agentConfig);
|
|
480
|
-
const capture = createRpcTurnCapture();
|
|
481
|
-
let resolveBudget!: (stop: TurnBudgetStop) => void;
|
|
482
|
-
let resolvePreAcceptanceIdle!: (stop: TurnBudgetStop) => void;
|
|
483
|
-
const budgetExceeded = new Promise<TurnBudgetStop>((resolve) => {
|
|
484
|
-
resolveBudget = resolve;
|
|
485
|
-
});
|
|
486
|
-
const preAcceptanceIdleExceeded = new Promise<TurnBudgetStop>((resolve) => {
|
|
487
|
-
resolvePreAcceptanceIdle = resolve;
|
|
488
|
-
});
|
|
489
|
-
const budgetMonitor = new TurnBudgetMonitor({
|
|
490
|
-
idleTimeoutMs: agent.currentIdleTimeoutMs ?? agent.idleTimeoutMs,
|
|
491
|
-
maxTurns: agent.currentMaxTurns ?? agent.maxTurns,
|
|
492
|
-
maxToolCalls: agent.currentMaxToolCalls ?? agent.maxToolCalls,
|
|
493
|
-
onExceeded(stop) {
|
|
494
|
-
resolveBudget(stop);
|
|
495
|
-
if (stop.reason === "idle_timeout") resolvePreAcceptanceIdle(stop);
|
|
496
|
-
},
|
|
497
|
-
});
|
|
498
|
-
let settledResolve!: () => void;
|
|
499
|
-
let settledReject!: (error: Error) => void;
|
|
500
|
-
const settled = new Promise<void>((resolve, reject) => {
|
|
501
|
-
settledResolve = resolve;
|
|
502
|
-
settledReject = reject;
|
|
503
|
-
});
|
|
504
|
-
void settled.catch(() => undefined);
|
|
505
|
-
const unsubscribeEvent = child.client.onEvent((event) => {
|
|
506
|
-
const usageChanged = captureRpcEvent(event, capture);
|
|
507
|
-
observeRpcBudgetEvent(event, budgetMonitor);
|
|
508
|
-
if (!capture.firstActivityAt && isActivityEvent(event)) {
|
|
509
|
-
capture.firstActivityAt = Date.now();
|
|
510
|
-
publish({
|
|
511
|
-
phase: "running",
|
|
512
|
-
timing: { firstActivityAt: capture.firstActivityAt },
|
|
513
|
-
...(usageChanged ? { usage: snapshotRpcUsage(capture) } : {}),
|
|
514
|
-
});
|
|
515
|
-
} else if (usageChanged) {
|
|
516
|
-
publish({ usage: snapshotRpcUsage(capture) });
|
|
517
|
-
}
|
|
518
|
-
const type = eventType(event);
|
|
519
|
-
if (type === "auto_retry_start") publish({ phase: "retrying" });
|
|
520
|
-
if (type === "compaction_start" || type === "summarization_retry_attempt_start") {
|
|
521
|
-
publish({ phase: "compacting" });
|
|
522
|
-
}
|
|
523
|
-
if (type === "agent_settled") settledResolve();
|
|
524
|
-
});
|
|
525
|
-
const unsubscribeClose = child.client.onClose(settledReject);
|
|
526
|
-
const prompt = child.started
|
|
527
|
-
? buildCurrentTurnPrompt(agent, task)
|
|
528
|
-
: buildStatefulTurnPrompt(agent, task).text;
|
|
529
|
-
let accepted = false;
|
|
530
|
-
let preAcceptanceStop: TurnBudgetStop | undefined;
|
|
531
|
-
const deadline = Date.now() + timeoutMs;
|
|
532
|
-
try {
|
|
533
|
-
await Promise.race([
|
|
534
|
-
raceWithAbort(
|
|
535
|
-
child.client.prompt(prompt, remainingMs(deadline)),
|
|
536
|
-
signal,
|
|
537
|
-
"RPC subagent prompt acceptance aborted",
|
|
538
|
-
),
|
|
539
|
-
preAcceptanceIdleExceeded.then((stop) => {
|
|
540
|
-
preAcceptanceStop = stop;
|
|
541
|
-
throw new Error(formatTurnTerminationMessage(stop.reason, stop.limit, "RPC subagent"));
|
|
542
|
-
}),
|
|
543
|
-
]);
|
|
544
|
-
accepted = true;
|
|
545
|
-
child.started = true;
|
|
546
|
-
publish({ phase: "accepted", timing: { promptAcceptedAt: Date.now() } });
|
|
547
|
-
const settlement = await Promise.race([
|
|
548
|
-
waitForTurnSettlement(settled, signal, remainingMs(deadline)).then((kind) => ({
|
|
549
|
-
kind,
|
|
550
|
-
})),
|
|
551
|
-
budgetExceeded.then((stop) => ({ kind: "limit" as const, stop })),
|
|
552
|
-
]);
|
|
553
|
-
if (settlement.kind !== "settled") {
|
|
554
|
-
const [, stopped] = await Promise.all([
|
|
555
|
-
child.client.abort().catch(() => undefined),
|
|
556
|
-
settlesWithin(settled, this.options.abortGraceMs ?? ABORT_GRACE_MS),
|
|
557
|
-
]);
|
|
558
|
-
const partial = truncateUtf8(capture.output || capture.partial, DEFAULT_MAX_OUTPUT_BYTES);
|
|
559
|
-
const stop =
|
|
560
|
-
settlement.kind === "limit"
|
|
561
|
-
? settlement.stop
|
|
562
|
-
: ({ reason: "work_timeout", limit: timeoutMs } as const);
|
|
563
|
-
const explicitAbort = settlement.kind === "aborted" || signal.aborted;
|
|
564
|
-
const termination: TurnTerminationReport | undefined = explicitAbort
|
|
565
|
-
? undefined
|
|
566
|
-
: {
|
|
567
|
-
version: TURN_TERMINATION_VERSION,
|
|
568
|
-
reason: stop.reason,
|
|
569
|
-
limit: stop.limit,
|
|
570
|
-
checkpoint: capture.journal.checkpoint(task, partial.text),
|
|
571
|
-
finalization: {
|
|
572
|
-
attempted: false,
|
|
573
|
-
status: "skipped",
|
|
574
|
-
durationMs: 0,
|
|
575
|
-
},
|
|
576
|
-
};
|
|
577
|
-
if (explicitAbort || !stopped) {
|
|
578
|
-
if (!stopped) await this.releaseById(agent.id);
|
|
579
|
-
publish({
|
|
580
|
-
phase: explicitAbort ? "interrupted" : "failed",
|
|
581
|
-
failurePhase: "running",
|
|
582
|
-
timing: { settledAt: Date.now() },
|
|
583
|
-
usage: snapshotRpcUsage(capture),
|
|
584
|
-
});
|
|
585
|
-
const checkpointOutput = termination
|
|
586
|
-
? formatTimeoutCheckpoint(termination.checkpoint)
|
|
587
|
-
: partial.text;
|
|
588
|
-
return {
|
|
589
|
-
output: partial.text || checkpointOutput,
|
|
590
|
-
exitCode: explicitAbort ? 130 : 124,
|
|
591
|
-
aborted: explicitAbort,
|
|
592
|
-
truncated: partial.truncated || agent.contextTruncated,
|
|
593
|
-
error: explicitAbort
|
|
594
|
-
? "RPC subagent was aborted"
|
|
595
|
-
: `${formatTurnTerminationMessage(stop.reason, stop.limit, "RPC subagent")}; abort did not settle`,
|
|
596
|
-
policy: rpcPolicy(agentConfig, agent),
|
|
597
|
-
termination,
|
|
598
|
-
telemetry,
|
|
599
|
-
};
|
|
600
|
-
}
|
|
601
|
-
|
|
602
|
-
publish({
|
|
603
|
-
phase: "finalizing",
|
|
604
|
-
failurePhase: "running",
|
|
605
|
-
usage: snapshotRpcUsage(capture),
|
|
606
|
-
});
|
|
607
|
-
const finalizationStartedAt = Date.now();
|
|
608
|
-
const summary = await finalizeTimedOutRpcTurn({
|
|
609
|
-
client: child.client,
|
|
610
|
-
task,
|
|
611
|
-
partialOutput: partial.text,
|
|
612
|
-
checkpoint: termination?.checkpoint,
|
|
613
|
-
terminationReason: stop.reason,
|
|
614
|
-
resultFormat: agent.resultFormat,
|
|
615
|
-
signal,
|
|
616
|
-
workTimeoutMs: timeoutMs,
|
|
617
|
-
finalizationTimeoutMs: this.options.timeoutFinalizationMs,
|
|
618
|
-
abortGraceMs: this.options.abortGraceMs ?? ABORT_GRACE_MS,
|
|
619
|
-
resetCapture() {
|
|
620
|
-
commitRpcInFlightUsage(capture);
|
|
621
|
-
capture.output = "";
|
|
622
|
-
capture.partial = "";
|
|
623
|
-
capture.stopReason = undefined;
|
|
624
|
-
capture.error = undefined;
|
|
625
|
-
},
|
|
626
|
-
getCapture: () => capture,
|
|
627
|
-
release: () => this.releaseById(agent.id),
|
|
628
|
-
});
|
|
629
|
-
if (termination) {
|
|
630
|
-
termination.finalization = {
|
|
631
|
-
attempted: true,
|
|
632
|
-
status: summary.status,
|
|
633
|
-
durationMs: Date.now() - finalizationStartedAt,
|
|
634
|
-
error: summary.error,
|
|
635
|
-
};
|
|
636
|
-
}
|
|
637
|
-
publish({
|
|
638
|
-
phase: "failed",
|
|
639
|
-
failurePhase: "running",
|
|
640
|
-
timing: { settledAt: Date.now() },
|
|
641
|
-
usage: snapshotRpcUsage(capture),
|
|
642
|
-
});
|
|
643
|
-
const checkpointOutput = termination ? formatTimeoutCheckpoint(termination.checkpoint) : "";
|
|
644
|
-
return {
|
|
645
|
-
output: summary.error ? partial.text || checkpointOutput : summary.output,
|
|
646
|
-
exitCode: 124,
|
|
647
|
-
truncated: partial.truncated || summary.truncated || agent.contextTruncated,
|
|
648
|
-
error: [
|
|
649
|
-
formatTurnTerminationMessage(stop.reason, stop.limit, "RPC subagent"),
|
|
650
|
-
summary.error,
|
|
651
|
-
]
|
|
652
|
-
.filter(Boolean)
|
|
653
|
-
.join("; "),
|
|
654
|
-
policy: rpcPolicy(agentConfig, agent),
|
|
655
|
-
termination,
|
|
656
|
-
telemetry,
|
|
657
|
-
};
|
|
658
|
-
}
|
|
659
|
-
const output = truncateUtf8(capture.output || capture.partial, DEFAULT_MAX_OUTPUT_BYTES);
|
|
660
|
-
const failed = capture.stopReason === "error" || !output.text.trim();
|
|
661
|
-
publish({
|
|
662
|
-
phase: failed ? "failed" : "settled",
|
|
663
|
-
failurePhase: failed ? "running" : undefined,
|
|
664
|
-
timing: { settledAt: Date.now() },
|
|
665
|
-
provider: capture.provider ?? telemetry.provider,
|
|
666
|
-
model: capture.model ?? telemetry.model,
|
|
667
|
-
usage: snapshotRpcUsage(capture),
|
|
668
|
-
});
|
|
669
|
-
return {
|
|
670
|
-
output: output.text,
|
|
671
|
-
exitCode: failed ? 1 : 0,
|
|
672
|
-
truncated: output.truncated || agent.contextTruncated,
|
|
673
|
-
error: failed ? capture.error || "RPC subagent completed without final text" : undefined,
|
|
674
|
-
policy: rpcPolicy(agentConfig, agent),
|
|
675
|
-
telemetry,
|
|
676
|
-
};
|
|
677
|
-
} catch (error) {
|
|
678
|
-
await this.releaseById(agent.id).catch(() => undefined);
|
|
679
|
-
const timedOut = isTimeoutError(error);
|
|
680
|
-
const stop =
|
|
681
|
-
preAcceptanceStop ??
|
|
682
|
-
(timedOut ? ({ reason: "work_timeout", limit: timeoutMs } as const) : undefined);
|
|
683
|
-
const partial = truncateUtf8(capture.output || capture.partial, DEFAULT_MAX_OUTPUT_BYTES);
|
|
684
|
-
const termination: TurnTerminationReport | undefined =
|
|
685
|
-
stop && !signal.aborted
|
|
686
|
-
? {
|
|
687
|
-
version: TURN_TERMINATION_VERSION,
|
|
688
|
-
reason: stop.reason,
|
|
689
|
-
limit: stop.limit,
|
|
690
|
-
checkpoint: capture.journal.checkpoint(task, partial.text),
|
|
691
|
-
finalization: {
|
|
692
|
-
attempted: false,
|
|
693
|
-
status: "skipped",
|
|
694
|
-
durationMs: 0,
|
|
695
|
-
},
|
|
696
|
-
}
|
|
697
|
-
: undefined;
|
|
698
|
-
publish({
|
|
699
|
-
phase: signal.aborted ? "interrupted" : "failed",
|
|
700
|
-
failurePhase: accepted ? "running" : "ready",
|
|
701
|
-
timing: { settledAt: Date.now() },
|
|
702
|
-
usage: snapshotRpcUsage(capture),
|
|
703
|
-
});
|
|
704
|
-
return {
|
|
705
|
-
output:
|
|
706
|
-
partial.text || (termination ? formatTimeoutCheckpoint(termination.checkpoint) : ""),
|
|
707
|
-
exitCode: signal.aborted ? 130 : stop ? 124 : 1,
|
|
708
|
-
aborted: signal.aborted,
|
|
709
|
-
error: stop
|
|
710
|
-
? formatTurnTerminationMessage(stop.reason, stop.limit, "RPC subagent")
|
|
711
|
-
: boundedError(error),
|
|
712
|
-
policy: rpcPolicy(agentConfig, agent),
|
|
713
|
-
termination,
|
|
714
|
-
telemetry,
|
|
715
|
-
};
|
|
716
|
-
} finally {
|
|
717
|
-
budgetMonitor.dispose();
|
|
718
|
-
unsubscribeEvent();
|
|
719
|
-
unsubscribeClose();
|
|
720
|
-
}
|
|
721
|
-
}
|
|
722
|
-
|
|
723
|
-
async deliverMessage(agent: ManagedAgent, message: AgentMailboxMessage): Promise<boolean> {
|
|
724
|
-
const child = this.children.get(agent.id);
|
|
725
|
-
if (!child) return false;
|
|
726
|
-
await child.client.steer(formatPeerMessage(message));
|
|
727
|
-
return true;
|
|
728
|
-
}
|
|
729
|
-
|
|
730
|
-
async release(agent: ManagedAgent): Promise<void> {
|
|
731
|
-
await this.releaseById(agent.id);
|
|
732
|
-
}
|
|
733
|
-
|
|
734
|
-
async shutdown(): Promise<void> {
|
|
735
|
-
const results = await Promise.allSettled(
|
|
736
|
-
[...this.children.keys()].map((agentId) => this.releaseById(agentId)),
|
|
737
|
-
);
|
|
738
|
-
const failures = results.flatMap((result) =>
|
|
739
|
-
result.status === "rejected" ? [result.reason] : [],
|
|
740
|
-
);
|
|
741
|
-
if (failures.length > 0) {
|
|
742
|
-
throw new AggregateError(failures, `Failed to release ${failures.length} RPC subagent(s)`);
|
|
743
|
-
}
|
|
744
|
-
}
|
|
745
|
-
|
|
746
|
-
private async getOrCreate(
|
|
747
|
-
agent: ManagedAgent,
|
|
748
|
-
agentConfig: AgentConfig,
|
|
749
|
-
signal: AbortSignal,
|
|
750
|
-
): Promise<RpcChildRecord> {
|
|
751
|
-
const existing = this.children.get(agent.id);
|
|
752
|
-
if (existing) return existing;
|
|
753
|
-
const hasRolePrompt = agentConfig.systemPrompt.trim().length > 0;
|
|
754
|
-
const resources = hasRolePrompt
|
|
755
|
-
? await (this.options.resolvePromptResources ?? resolvePiPromptResources)(
|
|
756
|
-
agent.cwd,
|
|
757
|
-
agent.target?.trust.projectTrusted ?? false,
|
|
758
|
-
)
|
|
759
|
-
: { appendSystemPromptPaths: [] };
|
|
760
|
-
if (signal.aborted) throw abortError("RPC subagent start aborted");
|
|
761
|
-
const temporaryPrompt = hasRolePrompt
|
|
762
|
-
? await writeRolePrompt(agentConfig.name, agentConfig.systemPrompt)
|
|
763
|
-
: undefined;
|
|
764
|
-
if (signal.aborted) {
|
|
765
|
-
cleanupRolePrompt(temporaryPrompt);
|
|
766
|
-
throw abortError("RPC subagent start aborted");
|
|
767
|
-
}
|
|
768
|
-
let client: RpcProtocolClient | undefined;
|
|
769
|
-
try {
|
|
770
|
-
const credentials = this.options.peerRuntime
|
|
771
|
-
? await this.options.peerRuntime.issueCredentials(
|
|
772
|
-
agent.id,
|
|
773
|
-
agent.currentTurnGeneration ?? agent.turnGeneration ?? 1,
|
|
774
|
-
)
|
|
775
|
-
: undefined;
|
|
776
|
-
if (signal.aborted) throw abortError("RPC subagent start aborted");
|
|
777
|
-
client = this.createClient({
|
|
778
|
-
cwd: agent.cwd,
|
|
779
|
-
abortTimeoutMs: this.options.abortGraceMs ?? ABORT_GRACE_MS,
|
|
780
|
-
terminationGraceMs: this.options.abortGraceMs ?? ABORT_GRACE_MS,
|
|
781
|
-
args: buildRpcArgs(
|
|
782
|
-
agent,
|
|
783
|
-
agentConfig,
|
|
784
|
-
this.options.getParentRuntime(),
|
|
785
|
-
temporaryPrompt?.filePath,
|
|
786
|
-
resources.appendSystemPromptPaths,
|
|
787
|
-
credentials !== undefined,
|
|
788
|
-
),
|
|
789
|
-
env: {
|
|
790
|
-
PI_SUBAGENT_DEPTH: String(
|
|
791
|
-
(Number.parseInt(process.env.PI_SUBAGENT_DEPTH ?? "0", 10) || 0) + 1,
|
|
792
|
-
),
|
|
793
|
-
PI_SUBAGENT_RPC_PROTOCOL: PI_SUBAGENTS_RPC_PROTOCOL,
|
|
794
|
-
...(credentials ? peerBridgeEnvironment(credentials) : {}),
|
|
795
|
-
},
|
|
796
|
-
});
|
|
797
|
-
const snapshot = await client.start(signal);
|
|
798
|
-
if (signal.aborted) throw abortError("RPC subagent start aborted");
|
|
799
|
-
const record: RpcChildRecord = {
|
|
800
|
-
client,
|
|
801
|
-
state: snapshot.state,
|
|
802
|
-
started: false,
|
|
803
|
-
temporaryPrompt,
|
|
804
|
-
};
|
|
805
|
-
this.children.set(agent.id, record);
|
|
806
|
-
return record;
|
|
807
|
-
} catch (error) {
|
|
808
|
-
await client?.stop().catch(() => undefined);
|
|
809
|
-
this.options.peerRuntime?.revoke(agent.id);
|
|
810
|
-
cleanupRolePrompt(temporaryPrompt);
|
|
811
|
-
throw error;
|
|
812
|
-
}
|
|
813
|
-
}
|
|
814
|
-
|
|
815
|
-
private async releaseById(agentId: string): Promise<void> {
|
|
816
|
-
const child = this.children.get(agentId);
|
|
817
|
-
if (!child) return;
|
|
818
|
-
this.children.delete(agentId);
|
|
819
|
-
this.options.peerRuntime?.revoke(agentId);
|
|
820
|
-
try {
|
|
821
|
-
await child.client.abort().catch(() => undefined);
|
|
822
|
-
await child.client.stop();
|
|
823
|
-
} finally {
|
|
824
|
-
cleanupRolePrompt(child.temporaryPrompt);
|
|
825
|
-
}
|
|
826
|
-
}
|
|
827
|
-
}
|
|
828
|
-
|
|
829
|
-
export function validateRpcTools(tools: string[] | undefined): string[] | undefined {
|
|
830
|
-
const validated = validateInProcessTools(tools);
|
|
831
|
-
if (validated?.some((tool) => !BUILT_IN_TOOL_NAMES.has(tool))) {
|
|
832
|
-
throw new Error("RPC subagents support only built-in Pi tools in pi-subagents:v1");
|
|
833
|
-
}
|
|
834
|
-
return validated;
|
|
835
|
-
}
|
|
836
|
-
|
|
837
|
-
export function buildRpcArgs(
|
|
838
|
-
agent: ManagedAgent,
|
|
839
|
-
agentConfig: AgentConfig,
|
|
840
|
-
parentRuntime: ParentRuntimeSnapshot,
|
|
841
|
-
rolePromptPath?: string,
|
|
842
|
-
appendSystemPromptPaths: readonly string[] = [],
|
|
843
|
-
peerBridge = false,
|
|
844
|
-
): string[] {
|
|
845
|
-
const args = ["--mode", "rpc", "--no-session", "--no-extensions"];
|
|
846
|
-
if (peerBridge) args.push("-e", childPeerBridgePath());
|
|
847
|
-
const model =
|
|
848
|
-
agentConfig.model ??
|
|
849
|
-
(parentRuntime.model ? `${parentRuntime.model.provider}/${parentRuntime.model.id}` : undefined);
|
|
850
|
-
if (model) args.push("--model", model);
|
|
851
|
-
const modelSelectsThinking = agentConfig.model
|
|
852
|
-
? /:(?:off|minimal|low|medium|high|xhigh|max)$/u.test(agentConfig.model.trim())
|
|
853
|
-
: false;
|
|
854
|
-
const thinking =
|
|
855
|
-
agent.thinkingLevel ??
|
|
856
|
-
agentConfig.thinkingLevel ??
|
|
857
|
-
(modelSelectsThinking ? undefined : parentRuntime.thinkingLevel);
|
|
858
|
-
if (thinking) args.push("--thinking", thinking);
|
|
859
|
-
if (agent.target?.trust.projectTrusted ?? false) args.push("--approve");
|
|
860
|
-
else args.push("--no-approve");
|
|
861
|
-
if (Array.isArray(agentConfig.tools)) {
|
|
862
|
-
const tools = [...agentConfig.tools, ...(peerBridge ? CHILD_PEER_TOOL_NAMES : [])];
|
|
863
|
-
if (tools.length > 0) args.push("--tools", [...new Set(tools)].join(","));
|
|
864
|
-
else args.push("--no-tools");
|
|
865
|
-
}
|
|
866
|
-
for (const promptPath of appendSystemPromptPaths) {
|
|
867
|
-
args.push("--append-system-prompt", promptPath);
|
|
868
|
-
}
|
|
869
|
-
if (rolePromptPath) args.push("--append-system-prompt", rolePromptPath);
|
|
870
|
-
return args;
|
|
871
|
-
}
|
|
872
|
-
|
|
873
|
-
function eventType(value: unknown): string | undefined {
|
|
874
|
-
if (!value || typeof value !== "object" || Array.isArray(value)) return undefined;
|
|
875
|
-
const type = (value as Record<string, unknown>).type;
|
|
876
|
-
return typeof type === "string" ? type : undefined;
|
|
877
|
-
}
|
|
878
|
-
|
|
879
|
-
function isActivityEvent(value: unknown): boolean {
|
|
880
|
-
return [
|
|
881
|
-
"message_start",
|
|
882
|
-
"message_update",
|
|
883
|
-
"message_end",
|
|
884
|
-
"tool_execution_start",
|
|
885
|
-
"tool_execution_update",
|
|
886
|
-
"auto_retry_start",
|
|
887
|
-
"compaction_start",
|
|
888
|
-
].includes(eventType(value) ?? "");
|
|
889
|
-
}
|
|
890
|
-
|
|
891
|
-
async function writeRolePrompt(
|
|
892
|
-
agentName: string,
|
|
893
|
-
prompt: string,
|
|
894
|
-
): Promise<{ dir: string; filePath: string }> {
|
|
895
|
-
const dir = await fs.promises.mkdtemp(path.join(os.tmpdir(), "pi-subagent-rpc-"));
|
|
896
|
-
const safeName = agentName.replace(/[^\w.-]+/gu, "_");
|
|
897
|
-
const filePath = path.join(dir, `prompt-${safeName}.md`);
|
|
898
|
-
await fs.promises.writeFile(filePath, prompt, {
|
|
899
|
-
encoding: "utf8",
|
|
900
|
-
mode: 0o600,
|
|
901
|
-
});
|
|
902
|
-
return { dir, filePath };
|
|
903
|
-
}
|
|
904
|
-
|
|
905
|
-
function cleanupRolePrompt(value: { dir: string; filePath: string } | undefined): void {
|
|
906
|
-
if (!value) return;
|
|
907
|
-
try {
|
|
908
|
-
fs.rmSync(value.dir, { recursive: true, force: true });
|
|
909
|
-
} catch {
|
|
910
|
-
// Best-effort cleanup is repeated when the owning runtime shuts down.
|
|
911
|
-
}
|
|
912
|
-
}
|
|
913
|
-
|
|
914
|
-
function waitForSpawn(
|
|
915
|
-
proc: ChildProcess,
|
|
916
|
-
signal: AbortSignal | undefined,
|
|
917
|
-
timeoutMs: number,
|
|
918
|
-
): Promise<void> {
|
|
919
|
-
return new Promise<void>((resolve, reject) => {
|
|
920
|
-
let settled = false;
|
|
921
|
-
const finish = (error?: Error) => {
|
|
922
|
-
if (settled) return;
|
|
923
|
-
settled = true;
|
|
924
|
-
clearTimeout(timer);
|
|
925
|
-
proc.off("spawn", onSpawn);
|
|
926
|
-
proc.off("error", onError);
|
|
927
|
-
signal?.removeEventListener("abort", onAbort);
|
|
928
|
-
if (error) reject(error);
|
|
929
|
-
else resolve();
|
|
930
|
-
};
|
|
931
|
-
const onSpawn = () => finish();
|
|
932
|
-
const onError = (error: Error) => finish(error);
|
|
933
|
-
const onAbort = () => finish(abortError("RPC subagent start aborted"));
|
|
934
|
-
const timer = setTimeout(
|
|
935
|
-
() => finish(new Error(`RPC subagent start timed out after ${timeoutMs}ms`)),
|
|
936
|
-
timeoutMs,
|
|
937
|
-
);
|
|
938
|
-
timer.unref();
|
|
939
|
-
proc.once("spawn", onSpawn);
|
|
940
|
-
proc.once("error", onError);
|
|
941
|
-
signal?.addEventListener("abort", onAbort, { once: true });
|
|
942
|
-
if (signal?.aborted) onAbort();
|
|
943
|
-
});
|
|
944
|
-
}
|
|
945
|
-
|
|
946
|
-
async function raceWithAbort<T>(
|
|
947
|
-
promise: Promise<T>,
|
|
948
|
-
signal: AbortSignal | undefined,
|
|
949
|
-
message: string,
|
|
950
|
-
): Promise<T> {
|
|
951
|
-
if (!signal) return promise;
|
|
952
|
-
if (signal.aborted) throw abortError(message);
|
|
953
|
-
let onAbort: (() => void) | undefined;
|
|
954
|
-
try {
|
|
955
|
-
return await Promise.race([
|
|
956
|
-
promise,
|
|
957
|
-
new Promise<T>((_resolve, reject) => {
|
|
958
|
-
onAbort = () => reject(abortError(message));
|
|
959
|
-
signal.addEventListener("abort", onAbort, { once: true });
|
|
960
|
-
if (signal.aborted) onAbort();
|
|
961
|
-
}),
|
|
962
|
-
]);
|
|
963
|
-
} finally {
|
|
964
|
-
if (onAbort) signal.removeEventListener("abort", onAbort);
|
|
965
|
-
}
|
|
966
|
-
}
|
|
967
|
-
|
|
968
|
-
function waitForTurnSettlement(
|
|
969
|
-
settled: Promise<void>,
|
|
970
|
-
signal: AbortSignal,
|
|
971
|
-
timeoutMs: number,
|
|
972
|
-
): Promise<"settled" | "aborted" | "timeout"> {
|
|
973
|
-
if (signal.aborted) return Promise.resolve("aborted");
|
|
974
|
-
return new Promise((resolve, reject) => {
|
|
975
|
-
let finished = false;
|
|
976
|
-
const finish = (value: "settled" | "aborted" | "timeout", error?: unknown) => {
|
|
977
|
-
if (finished) return;
|
|
978
|
-
finished = true;
|
|
979
|
-
clearTimeout(timer);
|
|
980
|
-
signal.removeEventListener("abort", onAbort);
|
|
981
|
-
if (error) reject(error);
|
|
982
|
-
else resolve(value);
|
|
983
|
-
};
|
|
984
|
-
const onAbort = () => finish("aborted");
|
|
985
|
-
const timer = setTimeout(() => finish("timeout"), timeoutMs);
|
|
986
|
-
timer.unref();
|
|
987
|
-
signal.addEventListener("abort", onAbort, { once: true });
|
|
988
|
-
if (signal.aborted) onAbort();
|
|
989
|
-
settled.then(
|
|
990
|
-
() => finish("settled"),
|
|
991
|
-
(error) => finish("settled", error),
|
|
992
|
-
);
|
|
993
|
-
});
|
|
994
|
-
}
|
|
995
|
-
|
|
996
|
-
async function settlesWithin(settled: Promise<void>, timeoutMs: number): Promise<boolean> {
|
|
997
|
-
let timer: NodeJS.Timeout | undefined;
|
|
998
|
-
try {
|
|
999
|
-
return await Promise.race([
|
|
1000
|
-
settled.then(
|
|
1001
|
-
() => true,
|
|
1002
|
-
() => true,
|
|
1003
|
-
),
|
|
1004
|
-
new Promise<boolean>((resolve) => {
|
|
1005
|
-
timer = setTimeout(() => resolve(false), timeoutMs);
|
|
1006
|
-
}),
|
|
1007
|
-
]);
|
|
1008
|
-
} finally {
|
|
1009
|
-
if (timer) clearTimeout(timer);
|
|
1010
|
-
}
|
|
1011
|
-
}
|
|
1012
|
-
|
|
1013
|
-
function remainingMs(deadline: number): number {
|
|
1014
|
-
return Math.max(1, deadline - Date.now());
|
|
1015
|
-
}
|
|
1016
|
-
|
|
1017
|
-
function rpcTimeoutError(message: string): Error {
|
|
1018
|
-
const error = new Error(message);
|
|
1019
|
-
error.name = "RpcTimeoutError";
|
|
1020
|
-
return error;
|
|
1021
|
-
}
|
|
1022
|
-
|
|
1023
|
-
function isTimeoutError(error: unknown): boolean {
|
|
1024
|
-
return error instanceof Error && error.name === "RpcTimeoutError";
|
|
1025
|
-
}
|
|
1026
|
-
|
|
1027
|
-
function abortError(message: string): Error {
|
|
1028
|
-
const error = new Error(message);
|
|
1029
|
-
error.name = "AbortError";
|
|
1030
|
-
return error;
|
|
1031
|
-
}
|
|
1032
|
-
|
|
1033
|
-
function errorMessage(prefix: string, error: Error): Error {
|
|
1034
|
-
return new Error(`${prefix}: ${error.message}`);
|
|
1035
|
-
}
|