pi-crew 0.10.3 → 0.10.4
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/AGENTS.md +2 -1
- package/README.md +5 -1
- package/dist/index.mjs +10685 -6882
- package/docs/architecture.md +4 -4
- package/docs/commands-reference.md +3 -0
- package/docs/publishing.md +15 -3
- package/install.mjs +90 -39
- package/package.json +8 -3
- package/scripts/README.md +4 -3
- package/skills/real-test-pi-crew/REPORT-TEMPLATE.md +1 -0
- package/skills/real-test-pi-crew/SKILL.md +153 -6
- package/src/config/migration-validator.ts +113 -0
- package/src/extension/cross-extension-rpc.ts +3 -7
- package/src/extension/register.ts +13 -0
- package/src/extension/registration/observability.ts +3 -7
- package/src/extension/registration/subagent-tools.ts +3 -7
- package/src/extension/registration/team-tool.ts +3 -7
- package/src/extension/registration/ui.ts +3 -8
- package/src/extension/registration/viewers.ts +3 -10
- package/src/extension/team-manager-command.ts +3 -7
- package/src/extension/team-tool/api/agent-control.ts +17 -10
- package/src/extension/team-tool/api/heartbeat.ts +4 -3
- package/src/extension/team-tool/api/mailbox.ts +33 -20
- package/src/extension/team-tool/api/plan-approval.ts +5 -5
- package/src/extension/team-tool/api/task-claims.ts +8 -7
- package/src/extension/team-tool/cancel.ts +6 -0
- package/src/extension/team-tool/handle-settings.ts +4 -1
- package/src/extension/team-tool/run.ts +3 -7
- package/src/extension/team-tool/status.ts +5 -0
- package/src/extension/team-tool.ts +6 -14
- package/src/hooks/registry.ts +3 -0
- package/src/prompt/scratchpad-lifecycle.ts +3 -3
- package/src/runtime/background-runner.ts +30 -35
- package/src/runtime/broker/crew-broker.ts +112 -441
- package/src/runtime/broker/delegate/delegate-event.ts +37 -0
- package/src/runtime/broker/mailbox-observer/mailbox-fanout.ts +59 -0
- package/src/runtime/broker/protocol/connection-state.ts +103 -0
- package/src/runtime/broker/protocol/events-replay.ts +68 -0
- package/src/runtime/broker/protocol/manifest-loader.ts +20 -0
- package/src/runtime/broker/protocol/msg-inbox.ts +69 -0
- package/src/runtime/broker/protocol/request-parsers.ts +175 -0
- package/src/runtime/broker/protocol/wait-auth.ts +46 -0
- package/src/runtime/child-pi/child-pi.ts +15 -0
- package/src/runtime/finalize-run.ts +15 -7
- package/src/runtime/foreground-control.ts +19 -6
- package/src/runtime/goal-workflow/dynamic-workflow-context.ts +6 -0
- package/src/runtime/goal-workflow/dynamic-workflow-runner.ts +3 -0
- package/src/runtime/goal-workflow/goal-loop-runner.ts +29 -27
- package/src/runtime/goal-workflow/goal-state-store.ts +3 -0
- package/src/runtime/heartbeat/heartbeat-watcher.ts +3 -3
- package/src/runtime/model/pi-args.ts +6 -1
- package/src/runtime/plan-replan.ts +3 -0
- package/src/runtime/stale-reconciler.ts +28 -3
- package/src/runtime/supervisor-contact.ts +3 -0
- package/src/runtime/task-runner/child-executor.ts +33 -0
- package/src/runtime/team-runner.ts +3 -3
- package/src/state/stores/ownership-map.ts +5 -4
- package/src/state/stores/plan-store.ts +12 -0
- package/src/state/stores/state-store.ts +5 -0
- package/src/ui/powerbar-publisher.ts +3 -7
- package/src/ui/run-action-dispatcher.ts +7 -10
- package/src/ui/settings-overlay.ts +4 -1
|
@@ -25,37 +25,56 @@ import { randomUUID } from "node:crypto";
|
|
|
25
25
|
import * as fsp from "node:fs/promises";
|
|
26
26
|
import * as net from "node:net";
|
|
27
27
|
import { withRunLockSync } from "../../state/coordination/locks.ts";
|
|
28
|
-
import {
|
|
29
|
-
|
|
30
|
-
type MailboxMessage,
|
|
31
|
-
type MailboxMessageKind,
|
|
32
|
-
type MailboxMessagePriority,
|
|
33
|
-
readMailbox,
|
|
34
|
-
registerMailboxAppendObserver,
|
|
35
|
-
} from "../../state/coordination/mailbox.ts";
|
|
36
|
-
import { appendEventAsync, readEventsCursor } from "../../state/event-log/event-log.ts";
|
|
28
|
+
import { appendMailboxMessageAsync, type MailboxMessage, registerMailboxAppendObserver } from "../../state/coordination/mailbox.ts";
|
|
29
|
+
import { appendEventAsync } from "../../state/event-log/event-log.ts";
|
|
37
30
|
import { loadRunManifestById, saveRunManifest, saveRunTasks } from "../../state/stores/state-store.ts";
|
|
38
|
-
import type {
|
|
31
|
+
import type { TeamTaskState } from "../../state/types.ts";
|
|
39
32
|
import { runEventBus } from "../../ui/run-event-bus.ts";
|
|
40
33
|
import { logInternalError } from "../../utils/internal-error.ts";
|
|
41
34
|
import { BrokerError, encodeBrokerFrame, MAX_BROKER_FRAME_BYTES, NdjsonDecoder } from "../../utils/ndjson.ts";
|
|
42
35
|
import { redactSecretString } from "../../utils/redaction.ts";
|
|
43
36
|
import { resolveRealContainedPath } from "../../utils/safe-paths.ts";
|
|
44
37
|
import { getBrokerSocketPath, prepareBrokerSocketDir, removeStaleBrokerSocket } from "../../utils/socket-path.ts";
|
|
45
|
-
import { type
|
|
38
|
+
import { type GrandchildSpawnResult, spawnDelegateGrandchild } from "../delegate-spawn.ts";
|
|
46
39
|
import { resolveCrewMaxDepth } from "../model/pi-args.ts";
|
|
47
40
|
import { NestedSlotBudget } from "../scheduling/nested-slots.ts";
|
|
48
41
|
import { evaluateDelegateAdmission } from "../spawn-policy.ts";
|
|
49
42
|
import { type BrokerToken, BrokerTokenRegistry } from "./crew-broker-tokens.ts";
|
|
43
|
+
import { recordDelegateEvent } from "./delegate/delegate-event.ts";
|
|
44
|
+
import { fanoutMailboxMessage } from "./mailbox-observer/mailbox-fanout.ts";
|
|
45
|
+
import type { CrewBrokerOptions, ServerConnection } from "./protocol/connection-state.ts";
|
|
46
|
+
import { handleEventsSince } from "./protocol/events-replay.ts";
|
|
47
|
+
import { loadRunForHello } from "./protocol/manifest-loader.ts";
|
|
48
|
+
import { handleMsgInbox } from "./protocol/msg-inbox.ts";
|
|
49
|
+
import {
|
|
50
|
+
BROKER_PROTOCOL,
|
|
51
|
+
isHelloParams,
|
|
52
|
+
isRequestObject,
|
|
53
|
+
parseMsgSendParams,
|
|
54
|
+
parseWaitRequestParams,
|
|
55
|
+
parseWaitResolveParams,
|
|
56
|
+
safeStringify,
|
|
57
|
+
WAIT_REQUEST_TIMEOUT_SEC_DEFAULT,
|
|
58
|
+
WAIT_REQUEST_TIMEOUT_SEC_MAX,
|
|
59
|
+
} from "./protocol/request-parsers.ts";
|
|
60
|
+
import { recordWaitPolicyRejection, waitAuthError } from "./protocol/wait-auth.ts";
|
|
50
61
|
import { WaitStatusCache } from "./wait-status-cache.ts";
|
|
51
62
|
|
|
52
|
-
/** Protocol version negotiated at `hello` time. Bump on breaking change.
|
|
53
|
-
|
|
63
|
+
/** Protocol version negotiated at `hello` time. Bump on breaking change.
|
|
64
|
+
* (Re-export removed 2026-09-10 — zero consumers; defined + exported in
|
|
65
|
+
* request-parsers.ts.) */
|
|
54
66
|
|
|
55
67
|
/** Hard hello deadline (per spec). After 1s, the connection is closed with a
|
|
56
|
-
* generic auth/protocol code. */
|
|
68
|
+
* generic auth/protocol code. (Unexported 2026-09-10 — zero consumers.) */
|
|
57
69
|
const HELLO_DEADLINE_MS = 1_000;
|
|
58
70
|
|
|
71
|
+
/** Per-connection server-side state.
|
|
72
|
+
* Moved to ./protocol/connection-state.ts (M4 / WI-4.1):
|
|
73
|
+
* - interface CrewBrokerOptions
|
|
74
|
+
* - interface ServerConnection
|
|
75
|
+
* Both re-exported from connection-state.ts; the class body is unchanged.
|
|
76
|
+
*/
|
|
77
|
+
|
|
59
78
|
/** Task 10 (mux-surface A1 §5.2): run statuses after which every hello token
|
|
60
79
|
* is by definition stale — the run will never issue work again, so the error
|
|
61
80
|
* is "stale-token" instead of generic auth. NARROWER than
|
|
@@ -66,95 +85,6 @@ const STALE_RUN_STATUSES: ReadonlySet<string> = new Set(["completed", "failed",
|
|
|
66
85
|
/** Default per-connection outbound queue cap (events). */
|
|
67
86
|
const DEFAULT_OUTBOUND_QUEUE_CAP = 256;
|
|
68
87
|
|
|
69
|
-
export interface CrewBrokerOptions {
|
|
70
|
-
/** Root session ID used to derive the socket path. */
|
|
71
|
-
sessionId: string;
|
|
72
|
-
/** Pre-resolved socket path (skips re-derivation; useful for tests). */
|
|
73
|
-
socketPath?: string;
|
|
74
|
-
/** Frame cap in UTF-8 bytes. Default 256 KiB. */
|
|
75
|
-
maxFrameBytes?: number;
|
|
76
|
-
/** Per-connection outbound queue cap. Default 256. */
|
|
77
|
-
outboundQueueCap?: number;
|
|
78
|
-
/** Required: when false, start() is a no-op and the server never binds.
|
|
79
|
-
* Lets the lifecycle controller install the broker unconditionally and
|
|
80
|
-
* have a single kill switch. */
|
|
81
|
-
enabled: boolean;
|
|
82
|
-
/** CWD for `loadRunManifestById` (Phase 1 msg.send / msg.inbox resolution).
|
|
83
|
-
* When omitted, manifest-touching methods return no-manifest errors. */
|
|
84
|
-
cwd?: string;
|
|
85
|
-
/** Optional test seam: override the `net` module (allows fake-server tests). */
|
|
86
|
-
netModule?: typeof net;
|
|
87
|
-
/** Optional test seam: inject a pre-configured WaitStatusCache (e.g. one
|
|
88
|
-
* wrapping a loader spy). Production uses a plain cache — see
|
|
89
|
-
* wait-status-cache.ts (R10-3). */
|
|
90
|
-
waitStatusCache?: WaitStatusCache;
|
|
91
|
-
/** WP-2/R2 (ADR-0 2026-08-17-waiting-producer-ask item 7): capability
|
|
92
|
-
* gate for the `wait.*` methods. DEFAULT FALSE — fail-closed. When not
|
|
93
|
-
* explicitly true, wait.request/wait.resolve are rejected with a
|
|
94
|
-
* `policy-disabled` error AND a `policy.action` event is appended to the
|
|
95
|
-
* run's events.jsonl (never silent). The production wiring threads
|
|
96
|
-
* `config.broker.waitMethodsEnabled` here; tests pass it explicitly. */
|
|
97
|
-
waitMethodsEnabled?: boolean;
|
|
98
|
-
/** T3/R5 (ADR-5 §10): capability gate for the `delegate` surface. DEFAULT
|
|
99
|
-
* TRUE since D8 (spec v0.7) — nested spawning is open out of the box; the
|
|
100
|
-
* broker still fail-closes when the flag is anything but true. The
|
|
101
|
-
* production wiring threads `config.nesting.enabled` (loadConfig layers
|
|
102
|
-
* DEFAULT_NESTING.enabled=true; a user `false` closes the surface); tests
|
|
103
|
-
* pass it explicitly. Rejections are NEVER silent (delegate.rejected). */
|
|
104
|
-
nestingEnabled?: boolean;
|
|
105
|
-
/** Optional override for the nested-slot budget size (config nesting.maxSlots). */
|
|
106
|
-
nestingMaxSlots?: number;
|
|
107
|
-
nestingMaxDepth?: number;
|
|
108
|
-
nestingTrustedEscalation?: boolean;
|
|
109
|
-
/** Global worker semaphore size, used to size the nested-slot budget. */
|
|
110
|
-
globalWorkerSemaphore?: number;
|
|
111
|
-
/** Test seam / alternative spawner for delegate grandchildren. Production
|
|
112
|
-
* uses spawnDelegateGrandchild (direct runChildPi call-site, ADR-5 §2). */
|
|
113
|
-
grandchildSpawner?: (input: GrandchildSpawnInput) => Promise<GrandchildSpawnResult>;
|
|
114
|
-
/** Resolved model catalog (canonical provider/id strings) for admission-time
|
|
115
|
-
* model validation (ADR-5 §7). When omitted, model validation is skipped
|
|
116
|
-
* (documented gap — the production wiring must always supply it). */
|
|
117
|
-
modelCatalog?: () => string[] | undefined;
|
|
118
|
-
/** ADR-5 §9: mirrors config limits.serializeOnPathOverlap for the workspace
|
|
119
|
-
* admission gate. Default false. */
|
|
120
|
-
serializeOnPathOverlap?: boolean;
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
/** Per-connection server-side state. */
|
|
124
|
-
interface ServerConnection {
|
|
125
|
-
socket: net.Socket;
|
|
126
|
-
decoder: NdjsonDecoder;
|
|
127
|
-
/** Whether the connection has completed `hello` successfully. */
|
|
128
|
-
authed: boolean;
|
|
129
|
-
/** Run id bound by hello. */
|
|
130
|
-
runId?: string;
|
|
131
|
-
/** Task id bound by hello. */
|
|
132
|
-
taskId?: string;
|
|
133
|
-
/** Role bound by hello: orchestrator can steer/msg-send; workers default. */
|
|
134
|
-
role?: "orchestrator" | "worker";
|
|
135
|
-
/** How the hello token matched the registry (ADR-0 item 6). Derived,
|
|
136
|
-
* non-secret metadata recorded at hello time so `wait.*` can reject a
|
|
137
|
-
* legacy bare-runId fallback match WITHOUT keeping the raw token on the
|
|
138
|
-
* connection (tokens stay confined to the heap-only registry). */
|
|
139
|
-
authMatchKind?: "compound" | "runId-fallback";
|
|
140
|
-
/** Task 10 fix round 2 (BUG #3): sha256 of the secret this connection
|
|
141
|
-
* authenticated with. Derived, one-way — never the plaintext token. The
|
|
142
|
-
* post-hello revocation check evaluates THIS digest so a revoke →
|
|
143
|
-
* re-issue window cannot let an old connection ride the freshly issued
|
|
144
|
-
* token for the same key. */
|
|
145
|
-
authedSecretHash?: string;
|
|
146
|
-
/** Outbound queue of encoded frames awaiting drain. */
|
|
147
|
-
outbound: Buffer[];
|
|
148
|
-
/** Set when the queue has hit the cap and a frame was dropped. */
|
|
149
|
-
needsResync: boolean;
|
|
150
|
-
/** Set when the connection is closing (idempotent). */
|
|
151
|
-
closed: boolean;
|
|
152
|
-
/** Timer for the hello deadline. */
|
|
153
|
-
helloTimer: NodeJS.Timeout | null;
|
|
154
|
-
/** Monotonic seq counter for outbound events (diagnostic). */
|
|
155
|
-
outboundSeq: number;
|
|
156
|
-
}
|
|
157
|
-
|
|
158
88
|
export class CrewBroker {
|
|
159
89
|
private readonly options: Required<Pick<CrewBrokerOptions, "sessionId" | "enabled" | "waitMethodsEnabled" | "nestingEnabled">> &
|
|
160
90
|
Pick<
|
|
@@ -478,9 +408,7 @@ export class CrewBroker {
|
|
|
478
408
|
this.resolvedSocketPath = null;
|
|
479
409
|
}
|
|
480
410
|
|
|
481
|
-
// ------------------------------------------------------------------------
|
|
482
411
|
// Connection lifecycle
|
|
483
|
-
// ------------------------------------------------------------------------
|
|
484
412
|
|
|
485
413
|
private async handleConnection(sock: net.Socket): Promise<void> {
|
|
486
414
|
// B1 (Round 14): a connection event queued after stop() must not be
|
|
@@ -583,38 +511,11 @@ export class CrewBroker {
|
|
|
583
511
|
}
|
|
584
512
|
|
|
585
513
|
/**
|
|
586
|
-
* Phase 1.3:
|
|
587
|
-
*
|
|
588
|
-
* recipients that are offline (they recover via msg.inbox). Never throws.
|
|
514
|
+
* Phase 1.3: see ./mailbox-observer/mailbox-fanout.ts (M4 / WI-4.1 moved).
|
|
515
|
+
* Class method delegates with 1-line binding of connectionsByRun + writers.
|
|
589
516
|
*/
|
|
590
517
|
private fanoutMailboxMessage(msg: MailboxMessage): void {
|
|
591
|
-
|
|
592
|
-
if (!set || set.size === 0) return;
|
|
593
|
-
// Recipient delivery dedup lives in src/prompt/prompt-runtime.ts and is
|
|
594
|
-
// keyed by the same message id in this mailbox event and the steering JSONL.
|
|
595
|
-
const eventFrame = encodeBrokerFrame({
|
|
596
|
-
event: "mailbox.message",
|
|
597
|
-
data: { id: msg.id, from: msg.from, to: msg.to, body: msg.body, kind: msg.kind, priority: msg.priority },
|
|
598
|
-
seq: 0, // mailbox messages don't carry a TeamEvent seq; dedup by msg.id
|
|
599
|
-
});
|
|
600
|
-
for (const conn of set) {
|
|
601
|
-
if (conn.closed || !conn.authed) continue;
|
|
602
|
-
// Recipient filter: deliver to the addressed task, or to all if 'all'.
|
|
603
|
-
// Task 5b (§15.2 wake): "parent"-addressed messages land in the
|
|
604
|
-
// run-level inbox, whose live consumer is the run's orchestrator
|
|
605
|
-
// connection (role from the orchestrator token — its taskId never
|
|
606
|
-
// equals "parent"), so without this branch the wake frame would be
|
|
607
|
-
// filtered out and the orchestrator would only see the message on
|
|
608
|
-
// its next inbox poll.
|
|
609
|
-
const isRecipient =
|
|
610
|
-
!msg.to || msg.to === "all" || conn.taskId === msg.to || (msg.to === "parent" && conn.role === "orchestrator");
|
|
611
|
-
if (!isRecipient) continue;
|
|
612
|
-
try {
|
|
613
|
-
this.writeOrQueue(conn, eventFrame, false);
|
|
614
|
-
} catch {
|
|
615
|
-
/* a slow/dead recipient must not break fanout to others */
|
|
616
|
-
}
|
|
617
|
-
}
|
|
518
|
+
fanoutMailboxMessage(this.connectionsByRun, { writeOrQueue: (conn, buf, force) => this.writeOrQueue(conn, buf, force) }, msg);
|
|
618
519
|
}
|
|
619
520
|
|
|
620
521
|
private async handleData(conn: ServerConnection, chunk: Buffer): Promise<void> {
|
|
@@ -758,7 +659,7 @@ export class CrewBroker {
|
|
|
758
659
|
// is real, that is a stale token — reject, but say so, because the
|
|
759
660
|
// A2 remedy is a re-issue, not a retry. An unknown run/task keeps
|
|
760
661
|
// the generic auth error (no disclosure of which id was valid).
|
|
761
|
-
const loaded = this.
|
|
662
|
+
const loaded = loadRunForHello(this.options.cwd, runId);
|
|
762
663
|
if (loaded && (loaded.tasks ?? []).some((t) => t.id === taskId)) {
|
|
763
664
|
this.sendErrorAndClose(
|
|
764
665
|
conn,
|
|
@@ -787,7 +688,7 @@ export class CrewBroker {
|
|
|
787
688
|
return;
|
|
788
689
|
}
|
|
789
690
|
if (resolved.role === "worker") {
|
|
790
|
-
const loaded = this.
|
|
691
|
+
const loaded = loadRunForHello(this.options.cwd, runId);
|
|
791
692
|
if (loaded && STALE_RUN_STATUSES.has(loaded.manifest.status)) {
|
|
792
693
|
this.sendErrorAndClose(conn, id, "stale-token", "hello rejected: run is already terminal (stale token)");
|
|
793
694
|
return;
|
|
@@ -838,19 +739,9 @@ export class CrewBroker {
|
|
|
838
739
|
* is not on disk — callers treat that as "cannot classify" and keep the
|
|
839
740
|
* legacy generic-auth behavior (the heap registry stays the source of
|
|
840
741
|
* truth for authentication). */
|
|
841
|
-
|
|
842
|
-
const cwd = this.options.cwd;
|
|
843
|
-
if (!cwd) return undefined;
|
|
844
|
-
try {
|
|
845
|
-
return loadRunManifestById(cwd, runId) ?? undefined;
|
|
846
|
-
} catch {
|
|
847
|
-
return undefined;
|
|
848
|
-
}
|
|
849
|
-
}
|
|
742
|
+
// loadRunForHello: inlined at the 2 call sites (was a 5-line method; M4/WI-4.1).
|
|
850
743
|
|
|
851
|
-
// ------------------------------------------------------------------------
|
|
852
744
|
// Outbound queue + drop-newest + needsResync
|
|
853
|
-
// ------------------------------------------------------------------------
|
|
854
745
|
|
|
855
746
|
private sendResult(conn: ServerConnection, id: string, result: unknown): void {
|
|
856
747
|
this.enqueueFrame(conn, { id, result });
|
|
@@ -941,9 +832,7 @@ export class CrewBroker {
|
|
|
941
832
|
}
|
|
942
833
|
}
|
|
943
834
|
|
|
944
|
-
// ------------------------------------------------------------------------
|
|
945
835
|
// Phase 1: msg.send + msg.inbox handlers
|
|
946
|
-
// ------------------------------------------------------------------------
|
|
947
836
|
|
|
948
837
|
/** Phase 1.1: direct or broadcast mailbox write via the durable append path. */
|
|
949
838
|
private async handleMsgSend(conn: ServerConnection, id: string, params: unknown): Promise<void> {
|
|
@@ -1111,96 +1000,35 @@ export class CrewBroker {
|
|
|
1111
1000
|
});
|
|
1112
1001
|
}
|
|
1113
1002
|
|
|
1114
|
-
/** Phase 1.2: paginated inbox pull
|
|
1003
|
+
/** Phase 1.2: paginated inbox pull — see ./protocol/msg-inbox.ts
|
|
1004
|
+
* (M4 / WI-4.1 moved; label corrected 2026-09-10: Phase 1.1 = msg.send). */
|
|
1115
1005
|
private async handleMsgInbox(conn: ServerConnection, id: string, params: unknown): Promise<void> {
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
if (!cwd) {
|
|
1127
|
-
this.sendError(conn, id, "no-manifest", "broker has no cwd configured");
|
|
1128
|
-
return;
|
|
1129
|
-
}
|
|
1130
|
-
let manifest: Parameters<typeof readMailbox>[0];
|
|
1131
|
-
try {
|
|
1132
|
-
const loaded = loadRunManifestById(cwd, conn.runId);
|
|
1133
|
-
if (!loaded) {
|
|
1134
|
-
this.sendError(conn, id, "no-manifest", `run '${conn.runId}' not found`);
|
|
1135
|
-
return;
|
|
1136
|
-
}
|
|
1137
|
-
manifest = loaded.manifest;
|
|
1138
|
-
} catch (err) {
|
|
1139
|
-
this.sendError(conn, id, "no-manifest", (err as Error).message);
|
|
1140
|
-
return;
|
|
1141
|
-
}
|
|
1142
|
-
const limit = Math.min(Math.max(parsed.limit ?? 100, 1), 1000);
|
|
1143
|
-
const taskId = conn.taskId ?? undefined;
|
|
1144
|
-
const all = readMailbox(manifest, "inbox", taskId);
|
|
1145
|
-
const filtered = all.filter((m) => m.status !== "acknowledged");
|
|
1146
|
-
const offset = parsed.cursor ? parseInt(parsed.cursor, 10) || 0 : 0;
|
|
1147
|
-
const page = filtered.slice(offset, offset + limit);
|
|
1148
|
-
const nextOffset = offset + page.length;
|
|
1149
|
-
const hasMore = nextOffset < filtered.length;
|
|
1150
|
-
this.sendResult(conn, id, {
|
|
1151
|
-
messages: page,
|
|
1152
|
-
nextCursor: hasMore ? String(nextOffset) : undefined,
|
|
1153
|
-
hasMore,
|
|
1154
|
-
total: filtered.length,
|
|
1155
|
-
});
|
|
1006
|
+
await handleMsgInbox(
|
|
1007
|
+
conn,
|
|
1008
|
+
id,
|
|
1009
|
+
params,
|
|
1010
|
+
{
|
|
1011
|
+
sendError: (c, i, code, msg) => this.sendError(c, i, code, msg),
|
|
1012
|
+
sendResult: (c, i, r) => this.sendResult(c, i, r),
|
|
1013
|
+
},
|
|
1014
|
+
this.options.cwd,
|
|
1015
|
+
);
|
|
1156
1016
|
}
|
|
1157
1017
|
|
|
1158
|
-
/**
|
|
1159
|
-
*
|
|
1160
|
-
*
|
|
1161
|
-
* live frame (e.g. after a queue overflow or reconnect). Reuses the same
|
|
1162
|
-
* readEventsCursor + seq semantics as runEventBus.onWithReplay.
|
|
1163
|
-
*/
|
|
1018
|
+
/** Phase 1.5: events.since — bounded replay; clients resync after a missed
|
|
1019
|
+
* live frame (queue overflow / reconnect). See ./protocol/events-replay.ts
|
|
1020
|
+
* (M4 / WI-4.1 moved; Phase 2 = events.subscribe, not this). */
|
|
1164
1021
|
private async handleEventsSince(conn: ServerConnection, id: string, params: unknown): Promise<void> {
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
try {
|
|
1176
|
-
const loaded = loadRunManifestById(cwd, conn.runId);
|
|
1177
|
-
if (!loaded) {
|
|
1178
|
-
this.sendError(conn, id, "no-manifest", `run '${conn.runId}' not found`);
|
|
1179
|
-
return;
|
|
1180
|
-
}
|
|
1181
|
-
eventsPath = loaded.manifest.eventsPath;
|
|
1182
|
-
} catch (err) {
|
|
1183
|
-
this.sendError(conn, id, "no-manifest", (err as Error).message);
|
|
1184
|
-
return;
|
|
1185
|
-
}
|
|
1186
|
-
const v = params && typeof params === "object" && !Array.isArray(params) ? (params as Record<string, unknown>) : {};
|
|
1187
|
-
const sinceSeq = typeof v.sinceSeq === "number" && Number.isFinite(v.sinceSeq) ? Math.max(0, Math.floor(v.sinceSeq)) : 0;
|
|
1188
|
-
const limit = typeof v.limit === "number" && Number.isFinite(v.limit) ? Math.min(Math.max(1, Math.floor(v.limit)), 1000) : 1000;
|
|
1189
|
-
try {
|
|
1190
|
-
const result = readEventsCursor(eventsPath, { sinceSeq, limit });
|
|
1191
|
-
// hasMore is true iff the total filtered count exceeds the page we
|
|
1192
|
-
// returned. When `total === events.length` we are at the exact end
|
|
1193
|
-
// of the stream (caller will discover this on the next call when
|
|
1194
|
-
// `nextSeq` is unchanged from `sinceSeq`).
|
|
1195
|
-
const hasMore = result.total > result.events.length;
|
|
1196
|
-
this.sendResult(conn, id, {
|
|
1197
|
-
events: result.events,
|
|
1198
|
-
nextSeq: result.nextSeq,
|
|
1199
|
-
hasMore,
|
|
1200
|
-
});
|
|
1201
|
-
} catch (err) {
|
|
1202
|
-
this.sendError(conn, id, "replay-failed", (err as Error).message);
|
|
1203
|
-
}
|
|
1022
|
+
await handleEventsSince(
|
|
1023
|
+
conn,
|
|
1024
|
+
id,
|
|
1025
|
+
params,
|
|
1026
|
+
{
|
|
1027
|
+
sendError: (c, i, code, msg) => this.sendError(c, i, code, msg),
|
|
1028
|
+
sendResult: (c, i, r) => this.sendResult(c, i, r),
|
|
1029
|
+
},
|
|
1030
|
+
this.options.cwd,
|
|
1031
|
+
);
|
|
1204
1032
|
}
|
|
1205
1033
|
|
|
1206
1034
|
/**
|
|
@@ -1510,39 +1338,19 @@ export class CrewBroker {
|
|
|
1510
1338
|
}
|
|
1511
1339
|
}
|
|
1512
1340
|
|
|
1513
|
-
// ------------------------------------------------------------------------
|
|
1514
1341
|
// WP-2/R2: wait.request / wait.resolve (ADR-0 2026-08-17-waiting-producer-ask)
|
|
1515
|
-
// ------------------------------------------------------------------------
|
|
1516
1342
|
|
|
1517
|
-
|
|
1518
|
-
* ONLY (ADR item 6). A legacy bare-runId fallback match is REJECTED with
|
|
1519
|
-
* a migrate hint; the orchestrator token is rejected by role. Returns the
|
|
1520
|
-
* error to send, or null when auth passes. */
|
|
1343
|
+
// waitAuthError: protocol/wait-auth.ts (M4/WI-4.1).
|
|
1521
1344
|
private waitAuthError(conn: ServerConnection): { code: string; message: string } | null {
|
|
1522
|
-
|
|
1523
|
-
return { code: "forbidden", message: "wait.* requires a worker task-scoped token" };
|
|
1524
|
-
}
|
|
1525
|
-
if (conn.authMatchKind !== "compound") {
|
|
1526
|
-
return {
|
|
1527
|
-
code: "forbidden",
|
|
1528
|
-
message: "wait.* requires a task-scoped token; re-dispatch with PI_CREW_BROKER_TASK_ID",
|
|
1529
|
-
};
|
|
1530
|
-
}
|
|
1531
|
-
return null;
|
|
1345
|
+
return waitAuthError(conn);
|
|
1532
1346
|
}
|
|
1533
1347
|
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
* event-log lock); an append failure is logged, never thrown. */
|
|
1348
|
+
// (The "ADR item 7" doc that used to dangle here documents
|
|
1349
|
+
// recordWaitPolicyRejection — see wait-auth.ts, where it belongs.
|
|
1350
|
+
// Removed 2026-09-10, review F6.)
|
|
1538
1351
|
// T3/R5 (ADR-5): delegate.request — governed-nesting admission + background
|
|
1539
1352
|
// grandchild spawn with durable mailbox delivery (WP-5 step 5).
|
|
1540
|
-
|
|
1541
|
-
if (!this.nestedSlots) {
|
|
1542
|
-
this.nestedSlots = new NestedSlotBudget(this.options.globalWorkerSemaphore ?? 4, this.options.nestingMaxSlots);
|
|
1543
|
-
}
|
|
1544
|
-
return this.nestedSlots;
|
|
1545
|
-
}
|
|
1353
|
+
// getDelegateNestedSlots: inlined at 4 call sites (5-line method; M4/WI-4.1).
|
|
1546
1354
|
|
|
1547
1355
|
private recordDelegateEvent(
|
|
1548
1356
|
manifest: { eventsPath: string; runId: string },
|
|
@@ -1556,15 +1364,7 @@ export class CrewBroker {
|
|
|
1556
1364
|
taskId: string,
|
|
1557
1365
|
data: Record<string, unknown>,
|
|
1558
1366
|
): void {
|
|
1559
|
-
|
|
1560
|
-
type,
|
|
1561
|
-
runId: manifest.runId,
|
|
1562
|
-
taskId,
|
|
1563
|
-
message: `${type}: ${JSON.stringify(data).slice(0, 200)}`,
|
|
1564
|
-
data,
|
|
1565
|
-
}).catch((err) =>
|
|
1566
|
-
logInternalError("crew-broker.delegate.event", err instanceof Error ? err : new Error(String(err)), `runId=${manifest.runId}`),
|
|
1567
|
-
);
|
|
1367
|
+
recordDelegateEvent(manifest, type, taskId, data);
|
|
1568
1368
|
}
|
|
1569
1369
|
|
|
1570
1370
|
private async handleDelegateRequest(conn: ServerConnection, id: string, params: unknown): Promise<void> {
|
|
@@ -1677,7 +1477,11 @@ export class CrewBroker {
|
|
|
1677
1477
|
...(task.depth !== undefined ? { depth: task.depth } : {}),
|
|
1678
1478
|
...(task.allocation !== undefined ? { allocation: task.allocation } : {}),
|
|
1679
1479
|
},
|
|
1680
|
-
slots:
|
|
1480
|
+
slots: (() => {
|
|
1481
|
+
if (!this.nestedSlots)
|
|
1482
|
+
this.nestedSlots = new NestedSlotBudget(this.options.globalWorkerSemaphore ?? 4, this.options.nestingMaxSlots);
|
|
1483
|
+
return this.nestedSlots;
|
|
1484
|
+
})().snapshot(),
|
|
1681
1485
|
requested,
|
|
1682
1486
|
...(effectiveCatalog !== undefined ? { modelCatalog: effectiveCatalog } : {}),
|
|
1683
1487
|
// ADR-5 §12: the delegate surface is an escalation — trusted only by the
|
|
@@ -1697,11 +1501,28 @@ export class CrewBroker {
|
|
|
1697
1501
|
return { code: "policy-denied" as const, message: decision.message ?? decision.reason ?? "delegate denied" };
|
|
1698
1502
|
}
|
|
1699
1503
|
// Slot acquisition INSIDE the lock (no reserve-then-race refund window).
|
|
1700
|
-
if (
|
|
1504
|
+
if (
|
|
1505
|
+
!(() => {
|
|
1506
|
+
if (!this.nestedSlots)
|
|
1507
|
+
this.nestedSlots = new NestedSlotBudget(this.options.globalWorkerSemaphore ?? 4, this.options.nestingMaxSlots);
|
|
1508
|
+
return this.nestedSlots;
|
|
1509
|
+
})().tryAcquire(subId)
|
|
1510
|
+
) {
|
|
1701
1511
|
this.recordDelegateEvent(fresh.manifest, "delegate.rejected", parentTaskId, { subId, reason: "slots-exhausted" });
|
|
1702
1512
|
return {
|
|
1703
1513
|
code: "policy-denied" as const,
|
|
1704
|
-
message: `delegate rejected: nested spawn budget exhausted; ${
|
|
1514
|
+
message: `delegate rejected: nested spawn budget exhausted; ${
|
|
1515
|
+
(
|
|
1516
|
+
() => {
|
|
1517
|
+
if (!this.nestedSlots)
|
|
1518
|
+
this.nestedSlots = new NestedSlotBudget(
|
|
1519
|
+
this.options.globalWorkerSemaphore ?? 4,
|
|
1520
|
+
this.options.nestingMaxSlots,
|
|
1521
|
+
);
|
|
1522
|
+
return this.nestedSlots;
|
|
1523
|
+
}
|
|
1524
|
+
)().statusLine
|
|
1525
|
+
}`,
|
|
1705
1526
|
};
|
|
1706
1527
|
}
|
|
1707
1528
|
// Reserve the requested budget pessimistically (ADR-5 §5): tokensSpent
|
|
@@ -1892,7 +1713,11 @@ export class CrewBroker {
|
|
|
1892
1713
|
}
|
|
1893
1714
|
}
|
|
1894
1715
|
} finally {
|
|
1895
|
-
|
|
1716
|
+
(() => {
|
|
1717
|
+
if (!this.nestedSlots)
|
|
1718
|
+
this.nestedSlots = new NestedSlotBudget(this.options.globalWorkerSemaphore ?? 4, this.options.nestingMaxSlots);
|
|
1719
|
+
return this.nestedSlots;
|
|
1720
|
+
})().release(subId);
|
|
1896
1721
|
}
|
|
1897
1722
|
this.recordDelegateEvent(loaded.manifest, outcome.timedOut ? "delegate.timed_out" : "delegate.completed", parentTaskId, {
|
|
1898
1723
|
subId,
|
|
@@ -1902,16 +1727,7 @@ export class CrewBroker {
|
|
|
1902
1727
|
}
|
|
1903
1728
|
|
|
1904
1729
|
private recordWaitPolicyRejection(manifest: { eventsPath: string; runId: string }, taskId: string, method: string): void {
|
|
1905
|
-
|
|
1906
|
-
void appendEventAsync(manifest.eventsPath, {
|
|
1907
|
-
type: "policy.action",
|
|
1908
|
-
runId,
|
|
1909
|
-
taskId,
|
|
1910
|
-
message: `${method} rejected: waitMethodsEnabled=false (fail-closed)`,
|
|
1911
|
-
data: { action: method, reason: "wait-methods-disabled", policy: "broker.waitMethodsEnabled=false" },
|
|
1912
|
-
}).catch((err) =>
|
|
1913
|
-
logInternalError("crew-broker.wait.policy-event", err instanceof Error ? err : new Error(String(err)), `runId=${runId}`),
|
|
1914
|
-
);
|
|
1730
|
+
recordWaitPolicyRejection(manifest, taskId, method);
|
|
1915
1731
|
}
|
|
1916
1732
|
|
|
1917
1733
|
/** WP-2/R2 step 4: park the calling task while its `ask` tool awaits a
|
|
@@ -2170,159 +1986,14 @@ export class CrewBroker {
|
|
|
2170
1986
|
// Type guards (no `any`)
|
|
2171
1987
|
// ============================================================================
|
|
2172
1988
|
|
|
2173
|
-
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
|
|
2183
|
-
|
|
2184
|
-
|
|
2185
|
-
function isHelloParams(value: unknown): value is {
|
|
2186
|
-
protocol: number;
|
|
2187
|
-
runId: string;
|
|
2188
|
-
taskId: string;
|
|
2189
|
-
token: string;
|
|
2190
|
-
role?: string;
|
|
2191
|
-
} {
|
|
2192
|
-
if (!value || typeof value !== "object" || Array.isArray(value)) return false;
|
|
2193
|
-
const v = value as Record<string, unknown>;
|
|
2194
|
-
if (v.protocol !== BROKER_PROTOCOL) {
|
|
2195
|
-
// Force exact-type comparison (must be the number 1, not "1").
|
|
2196
|
-
if (typeof v.protocol !== "number" || !Number.isInteger(v.protocol)) return false;
|
|
2197
|
-
}
|
|
2198
|
-
if (typeof v.runId !== "string" || v.runId.length === 0 || v.runId.length > 256) return false;
|
|
2199
|
-
if (typeof v.taskId !== "string" || v.taskId.length === 0 || v.taskId.length > 256) return false;
|
|
2200
|
-
if (typeof v.token !== "string" || v.token.length === 0 || v.token.length > 256) return false;
|
|
2201
|
-
return true;
|
|
2202
|
-
}
|
|
2203
|
-
|
|
2204
|
-
// ============================================================================
|
|
2205
|
-
// Phase 1 parameter parsers (module-level; no `any`)
|
|
2206
|
-
// ============================================================================
|
|
2207
|
-
|
|
2208
|
-
interface MsgSendParams {
|
|
2209
|
-
to: string | string[] | "all";
|
|
2210
|
-
body: unknown;
|
|
2211
|
-
kind?: MailboxMessageKind;
|
|
2212
|
-
priority?: MailboxMessagePriority;
|
|
2213
|
-
replyTo?: string;
|
|
2214
|
-
/** Task 5b (§15.2): short subject echoed into the worker.message wake
|
|
2215
|
-
* event (bounded like the tool-side MSG_SUBJECT_MAX_CHARS). */
|
|
2216
|
-
subject?: string;
|
|
2217
|
-
}
|
|
2218
|
-
|
|
2219
|
-
function parseMsgSendParams(value: unknown): MsgSendParams | undefined {
|
|
2220
|
-
if (!value || typeof value !== "object" || Array.isArray(value)) return undefined;
|
|
2221
|
-
const v = value as Record<string, unknown>;
|
|
2222
|
-
const to = v.to;
|
|
2223
|
-
if (typeof to !== "string" && !Array.isArray(to)) return undefined;
|
|
2224
|
-
if (Array.isArray(to) && !to.every((s) => typeof s === "string" && s.length > 0)) return undefined;
|
|
2225
|
-
if (typeof to === "string" && to.length === 0) return undefined;
|
|
2226
|
-
if (v.body === undefined) return undefined;
|
|
2227
|
-
const kind = v.kind as MailboxMessageKind | undefined;
|
|
2228
|
-
if (kind !== undefined && !["message", "notify", "steer", "follow-up", "response", "group_join"].includes(kind)) {
|
|
2229
|
-
return undefined;
|
|
2230
|
-
}
|
|
2231
|
-
const priority = v.priority as MailboxMessagePriority | undefined;
|
|
2232
|
-
if (priority !== undefined && !["urgent", "normal", "low"].includes(priority)) {
|
|
2233
|
-
return undefined;
|
|
2234
|
-
}
|
|
2235
|
-
const replyTo = typeof v.replyTo === "string" ? v.replyTo : undefined;
|
|
2236
|
-
const subject = typeof v.subject === "string" && v.subject.length > 0 && v.subject.length <= 256 ? v.subject : undefined;
|
|
2237
|
-
return { to: to as string | string[] | "all", body: v.body, kind, priority, replyTo, subject };
|
|
2238
|
-
}
|
|
2239
|
-
|
|
2240
|
-
interface MsgInboxParams {
|
|
2241
|
-
limit?: number;
|
|
2242
|
-
cursor?: string;
|
|
2243
|
-
}
|
|
2244
|
-
|
|
2245
|
-
function parseMsgInboxParams(value: unknown): MsgInboxParams | undefined {
|
|
2246
|
-
if (value === undefined || value === null) return { limit: 100, cursor: undefined };
|
|
2247
|
-
if (typeof value !== "object" || Array.isArray(value)) return undefined;
|
|
2248
|
-
const v = value as Record<string, unknown>;
|
|
2249
|
-
const limit = v.limit;
|
|
2250
|
-
if (limit !== undefined && (typeof limit !== "number" || !Number.isFinite(limit) || limit < 1)) {
|
|
2251
|
-
return undefined;
|
|
2252
|
-
}
|
|
2253
|
-
const cursor = v.cursor;
|
|
2254
|
-
if (cursor !== undefined && typeof cursor !== "string") return undefined;
|
|
2255
|
-
return { limit: limit as number | undefined, cursor: cursor as string | undefined };
|
|
2256
|
-
}
|
|
2257
|
-
|
|
2258
|
-
function safeStringify(value: unknown): string {
|
|
2259
|
-
try {
|
|
2260
|
-
return JSON.stringify(value) ?? "{}";
|
|
2261
|
-
} catch {
|
|
2262
|
-
return "{}";
|
|
2263
|
-
}
|
|
2264
|
-
}
|
|
2265
|
-
|
|
2266
|
-
// ============================================================================
|
|
2267
|
-
// WP-2/R2 wait.* parameter parsers (ADR-0 2026-08-17-waiting-producer-ask)
|
|
2268
|
-
// ============================================================================
|
|
2269
|
-
|
|
2270
|
-
/** Server-side ceiling for the ask deadline (ADR P2-7): worker-controlled
|
|
2271
|
-
* timeoutSec may NEVER exceed 1h — an unbounded timeout would pin slots and
|
|
2272
|
-
* amplify I/O. Applied as deadline = now + min(timeoutSec, 3600). */
|
|
2273
|
-
const WAIT_REQUEST_TIMEOUT_SEC_MAX = 3600;
|
|
2274
|
-
/** Default ask timeout when the caller omits timeoutSec (ADR item 1). */
|
|
2275
|
-
const WAIT_REQUEST_TIMEOUT_SEC_DEFAULT = 600;
|
|
2276
|
-
/** Bounded question payload (defense-in-depth under the 256 KiB frame cap). */
|
|
2277
|
-
const WAIT_QUESTION_MAX_CHARS = 8192;
|
|
2278
|
-
/** Bounded answer-choice list: at most 16 options, 256 chars each. */
|
|
2279
|
-
const WAIT_OPTIONS_MAX = 16;
|
|
2280
|
-
const WAIT_OPTION_MAX_CHARS = 256;
|
|
2281
|
-
|
|
2282
|
-
interface WaitRequestParams {
|
|
2283
|
-
to: string;
|
|
2284
|
-
question: string;
|
|
2285
|
-
options?: string[];
|
|
2286
|
-
timeoutSec?: number;
|
|
2287
|
-
}
|
|
2288
|
-
|
|
2289
|
-
function parseWaitRequestParams(value: unknown): WaitRequestParams | undefined {
|
|
2290
|
-
if (!value || typeof value !== "object" || Array.isArray(value)) return undefined;
|
|
2291
|
-
const v = value as Record<string, unknown>;
|
|
2292
|
-
if (typeof v.to !== "string" || v.to.length === 0 || v.to.length > 256) return undefined;
|
|
2293
|
-
if (typeof v.question !== "string" || v.question.length === 0 || v.question.length > WAIT_QUESTION_MAX_CHARS) {
|
|
2294
|
-
return undefined;
|
|
2295
|
-
}
|
|
2296
|
-
let options: string[] | undefined;
|
|
2297
|
-
if (v.options !== undefined) {
|
|
2298
|
-
if (!Array.isArray(v.options) || v.options.length === 0 || v.options.length > WAIT_OPTIONS_MAX) return undefined;
|
|
2299
|
-
for (const o of v.options) {
|
|
2300
|
-
if (typeof o !== "string" || o.length === 0 || o.length > WAIT_OPTION_MAX_CHARS) return undefined;
|
|
2301
|
-
}
|
|
2302
|
-
options = v.options as string[];
|
|
2303
|
-
}
|
|
2304
|
-
// timeoutSec is clamped server-side in the handler (max 3600); the parser
|
|
2305
|
-
// only rejects non-finite values. Non-positive values clamp to 1s.
|
|
2306
|
-
if (v.timeoutSec !== undefined && (typeof v.timeoutSec !== "number" || !Number.isFinite(v.timeoutSec))) {
|
|
2307
|
-
return undefined;
|
|
2308
|
-
}
|
|
2309
|
-
return {
|
|
2310
|
-
to: v.to,
|
|
2311
|
-
question: v.question,
|
|
2312
|
-
options,
|
|
2313
|
-
timeoutSec: v.timeoutSec as number | undefined,
|
|
2314
|
-
};
|
|
2315
|
-
}
|
|
2316
|
-
|
|
2317
|
-
interface WaitResolveParams {
|
|
2318
|
-
to: string;
|
|
2319
|
-
questionId: string;
|
|
2320
|
-
}
|
|
2321
|
-
|
|
2322
|
-
function parseWaitResolveParams(value: unknown): WaitResolveParams | undefined {
|
|
2323
|
-
if (!value || typeof value !== "object" || Array.isArray(value)) return undefined;
|
|
2324
|
-
const v = value as Record<string, unknown>;
|
|
2325
|
-
if (typeof v.to !== "string" || v.to.length === 0 || v.to.length > 256) return undefined;
|
|
2326
|
-
if (typeof v.questionId !== "string" || v.questionId.length === 0 || v.questionId.length > 128) return undefined;
|
|
2327
|
-
return { to: v.to, questionId: v.questionId };
|
|
2328
|
-
}
|
|
1989
|
+
/** Moved to ./protocol/request-parsers.ts (M4 / WI-4.1):
|
|
1990
|
+
* - isRequestObject
|
|
1991
|
+
* - isHelloParams + BROKER_PROTOCOL
|
|
1992
|
+
* - parseMsgSendParams + MsgSendParams
|
|
1993
|
+
* - parseMsgInboxParams + MsgInboxParams
|
|
1994
|
+
* - parseWaitRequestParams + WaitRequestParams
|
|
1995
|
+
* - parseWaitResolveParams + WaitResolveParams
|
|
1996
|
+
* - safeStringify
|
|
1997
|
+
* - WAIT_* constants
|
|
1998
|
+
* Removed from this file; re-exported via "./protocol/request-parsers.ts".
|
|
1999
|
+
*/
|