pi-crew 0.11.55 → 0.11.57
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/CHANGELOG.md +136 -0
- package/dist/index.mjs +530 -529
- package/package.json +1 -1
- package/src/extension/registration/lifecycle-handlers.ts +10 -1
- package/src/extension/team-tool/respond-delivery.ts +429 -0
- package/src/extension/team-tool/respond.ts +135 -50
- package/src/extension/team-tool.ts +41 -1
- package/src/runtime/async-runner.ts +10 -1
- package/src/runtime/background-runner.ts +93 -17
- package/src/runtime/broker/crew-broker.ts +146 -1
- package/src/runtime/broker/dispatcher-key-registry.ts +39 -0
- package/src/runtime/broker/protocol/broker-respond.ts +365 -0
- package/src/runtime/broker/protocol/broker-steer.ts +151 -0
- package/src/runtime/broker/protocol/connection-state.ts +10 -0
- package/src/runtime/broker/protocol/request-parsers.ts +137 -1
- package/src/runtime/broker/runner-broker.ts +189 -0
- package/src/runtime/broker/stdin-handshake.ts +39 -4
- package/src/runtime/run-worker.ts +17 -2
- package/src/runtime/spawn-phase-trace.ts +53 -0
- package/src/runtime/task-runner/child-executor.ts +14 -0
- package/src/runtime/task-runner/state-helpers.ts +32 -0
- package/src/state/contracts.ts +15 -0
- package/src/state/coordination/mailbox.ts +15 -0
- package/src/state/types.ts +11 -0
|
@@ -74,6 +74,7 @@ import { checkProcessLiveness } from "../process-status.ts";
|
|
|
74
74
|
import { DEFAULT_RETRY_POLICY } from "../recovery/retry-executor.ts";
|
|
75
75
|
import { runWorker } from "../run-worker.ts";
|
|
76
76
|
import { parseSessionUsage, usageFromValue } from "../session-usage.ts";
|
|
77
|
+
import { createSpawnPhaseTracer } from "../spawn-phase-trace.ts";
|
|
77
78
|
import { recordSupervisorContact, supervisorContactFromEvent } from "../supervisor-contact.ts";
|
|
78
79
|
import type { ResultSource, TaskExecutionResult } from "./post-execution.ts";
|
|
79
80
|
import type { StreamBridgeHandle, TaskExecutionContext } from "./pre-execution.ts";
|
|
@@ -827,7 +828,14 @@ export async function runChildProcessTask(ctx: TaskExecutionContext): Promise<Ta
|
|
|
827
828
|
touch: () => persistHeartbeat(),
|
|
828
829
|
});
|
|
829
830
|
let childResult: ChildPiRunResult;
|
|
831
|
+
// spawn.trace (2026-11-01, run team_20261101074334 forensics): a
|
|
832
|
+
// 7-minute log-silent hang between task.started and worker.spawned
|
|
833
|
+
// left no bisectable trace. This tracer prints a phase breadcrumb every
|
|
834
|
+
// 30s ONLY while the pre-spawn/spawn window is stuck that long — healthy
|
|
835
|
+
// spawns stay silent.
|
|
836
|
+
const spawnTrace = createSpawnPhaseTracer(task.id, i);
|
|
830
837
|
try {
|
|
838
|
+
spawnTrace.phase("runWorker-await");
|
|
831
839
|
childResult = await runWorker({
|
|
832
840
|
cwd: task.cwd,
|
|
833
841
|
task: userPrompt,
|
|
@@ -878,6 +886,9 @@ export async function runChildProcessTask(ctx: TaskExecutionContext): Promise<Ta
|
|
|
878
886
|
attempt: i,
|
|
879
887
|
steeringFile: resolveRealContainedPath(`${manifest.artifactsRoot}/steering`, `${task.id}.jsonl`),
|
|
880
888
|
onSpawn: (pid) => {
|
|
889
|
+
// spawn.trace: the worker process exists — pre-spawn window closed.
|
|
890
|
+
spawnTrace.phase("worker-running");
|
|
891
|
+
spawnTrace.end();
|
|
881
892
|
// NEW-4/G25: pin this incarnation's pid FIRST so the liveness pulse
|
|
882
893
|
// corroborates against the real process from its first tick on.
|
|
883
894
|
attemptWorkerPid = pid;
|
|
@@ -1112,6 +1123,9 @@ export async function runChildProcessTask(ctx: TaskExecutionContext): Promise<Ta
|
|
|
1112
1123
|
// heartbeat anymore, including across the model-fallback respawn gap
|
|
1113
1124
|
// (where nothing is alive and the heartbeat must be allowed to age).
|
|
1114
1125
|
heartbeatPulse.stop();
|
|
1126
|
+
// spawn.trace: attempt settled — always clear the tracer (onSpawn may
|
|
1127
|
+
// never fire for a failed pre-spawn attempt).
|
|
1128
|
+
spawnTrace.end();
|
|
1115
1129
|
if (timeoutHandle) clearTimeout(timeoutHandle);
|
|
1116
1130
|
// W2 fix — release the listener so it doesn't leak. {once:true}
|
|
1117
1131
|
// only auto-removes when the listener FIRES; if the timeout
|
|
@@ -91,6 +91,38 @@ function mergeTaskUpdateWithDisk(diskTasks: TeamTaskState[], incoming: TeamTaskS
|
|
|
91
91
|
},
|
|
92
92
|
};
|
|
93
93
|
}
|
|
94
|
+
|
|
95
|
+
// R6-5 WAITING-PRESERVE (FINDING D root fix, live probes 10/11/12,
|
|
96
|
+
// 2026-11-01): a runner-side snapshot (child-executor persistHeartbeat's
|
|
97
|
+
// ~1s whole-record persist is the caught clobberer — stack pinned via
|
|
98
|
+
// saveRunTasksCoalesced instrumentation, probe12 run
|
|
99
|
+
// team_20261101040510) carries a dispatch-time task object that NEVER
|
|
100
|
+
// learned the broker parked the task (the park is written disk-side by the
|
|
101
|
+
// broker's wait.request handler under the run lock; the runner's in-memory
|
|
102
|
+
// copy keeps status "running", no `waiting`). Without this guard the first
|
|
103
|
+
// post-park heartbeat persist ERASED the park (tasks.json → running,
|
|
104
|
+
// waiting:null), which broke respond.force's live-park gate ("no-park"
|
|
105
|
+
// while the worker was verifiably parked — probe10) — the FINDING A
|
|
106
|
+
// residual class, now closed at the merge chokepoint every
|
|
107
|
+
// persistSingleTaskUpdate caller funnels through.
|
|
108
|
+
//
|
|
109
|
+
// Rule (mirror of R6-1's "the verdict is not ours to overwrite"): when
|
|
110
|
+
// DISK holds a live park (`waiting` payload, non-terminal status) and the
|
|
111
|
+
// incoming snapshot does not itself carry park-awareness
|
|
112
|
+
// (waiting === undefined) and is NON-terminal, the disk park survives —
|
|
113
|
+
// the heartbeat was never authoritative over an ask park. Legitimate
|
|
114
|
+
// clears do NOT pass through here: respond.ts requeueWaitingTask and the
|
|
115
|
+
// broker's wait-resolve victim flip write FULL task arrays directly via
|
|
116
|
+
// saveRunTasks under the run lock (verified 2026-11-01), and terminal
|
|
117
|
+
// settles (post-execution) are let through by the non-terminal condition.
|
|
118
|
+
if (
|
|
119
|
+
diskTask.status === "waiting" &&
|
|
120
|
+
diskTask.waiting !== undefined &&
|
|
121
|
+
incoming.waiting === undefined &&
|
|
122
|
+
!isTerminalTaskStatus(effective.status)
|
|
123
|
+
) {
|
|
124
|
+
effective = { ...effective, status: "waiting" as const, waiting: diskTask.waiting };
|
|
125
|
+
}
|
|
94
126
|
return effective;
|
|
95
127
|
}
|
|
96
128
|
|
package/src/state/contracts.ts
CHANGED
|
@@ -267,6 +267,21 @@ export const TEAM_EVENT_TYPES = [
|
|
|
267
267
|
"mailbox.send",
|
|
268
268
|
"mailbox.pickup",
|
|
269
269
|
"mailbox.pickup_deduped",
|
|
270
|
+
// HƯỚNG A (ADR Option 3, 2026-11-01): broker-side respond delivery audit —
|
|
271
|
+
// fired by crew-broker's respond.append (challenge-response) and
|
|
272
|
+
// respond.force (one-shot live-park) handlers when a leader response row
|
|
273
|
+
// is appended BY THE BROKER PROCESS (w1a: broker-signed rows verify in
|
|
274
|
+
// every worker). data: {taskId, questionId, auth, requestId, messageId,
|
|
275
|
+
// deduped} (+ tag/brokerSocket on the forced path). Diagnostic only —
|
|
276
|
+
// never transitions run/task state; the mailbox row is the source of truth.
|
|
277
|
+
"respond.broker_append",
|
|
278
|
+
// HƯỚNG A follow-up (probe19, 2026-11-01): broker-side forced-steer audit
|
|
279
|
+
// — fired by crew-broker's steer.force handler when a foreign/restarted
|
|
280
|
+
// session's steer is delivered BY THE BROKER PROCESS (mailbox row +
|
|
281
|
+
// broker-signed steering line; w1a: broker-signed rows verify in every
|
|
282
|
+
// worker). data: {taskId, auth:"forced"}. Diagnostic only — never
|
|
283
|
+
// transitions run/task state.
|
|
284
|
+
"steer.broker_force",
|
|
270
285
|
// Recovery
|
|
271
286
|
"recovery.rerun_task",
|
|
272
287
|
// Run-level budget/effectiveness/export/lifecycle bookkeeping
|
|
@@ -230,6 +230,21 @@ export function signMailboxMessage(message: MailboxMessage): MailboxMessage & {
|
|
|
230
230
|
return { ...message, sig: signPayloadHash(coordinationPayloadHash(canonicalMailboxPayload(row))) };
|
|
231
231
|
}
|
|
232
232
|
|
|
233
|
+
/** HƯỚNG A (ADR Option 3, 2026-11-01): challenge-response proof-signing seam
|
|
234
|
+
* for broker `respond.append`. Signs ARBITRARY challenge bytes (nonce ∥
|
|
235
|
+
* canonical payload — NO sha256 wrap, unlike the row signers above; the
|
|
236
|
+
* broker verifies with node:crypto Ed25519 against its dispatch-time heap
|
|
237
|
+
* binding of the dispatcher PUBLIC key) with THIS process's coordination
|
|
238
|
+
* private key. Only the dispatcher process holds that key — the keypair
|
|
239
|
+
* whose public half it stamped as manifest.dispatcherVerifyKey at dispatch
|
|
240
|
+
* time — so only the dispatcher can produce a passing signature; workers
|
|
241
|
+
* and other processes hold at most the public verify key. Private key
|
|
242
|
+
* stays heap-only like every other signer here. */
|
|
243
|
+
export function signCoordinationChallenge(payload: string): string {
|
|
244
|
+
const { privateKey } = getMailboxSigningKeypair();
|
|
245
|
+
return ed25519Sign(null, Buffer.from(payload, "utf8"), privateKey).toString("base64");
|
|
246
|
+
}
|
|
247
|
+
|
|
233
248
|
/** Steering-JSONL line shape shared by every writer (team-tool, broker
|
|
234
249
|
* steer.push, child-executor seed, wrap-up advisories). */
|
|
235
250
|
export interface CoordinationSteerEntry {
|
package/src/state/types.ts
CHANGED
|
@@ -455,6 +455,17 @@ export interface TeamRunManifest {
|
|
|
455
455
|
* models.json happens to list first.
|
|
456
456
|
*/
|
|
457
457
|
modelContext?: RunModelContext;
|
|
458
|
+
/** HƯỚNG A (2026-11-01, probe8): the ACTUAL broker socket path of the
|
|
459
|
+
* run's host process, stamped by the background runner at stdin-creds
|
|
460
|
+
* accept time (async topology: the runner's socket is NOT derivable from
|
|
461
|
+
* this manifest's sessionId — that hash belongs to the dispatcher-side
|
|
462
|
+
* synthetic session; live probe derived 7e4df036 while the run's broker
|
|
463
|
+
* listened on 9529a08a). Cross-process responders (respond-delivery.ts)
|
|
464
|
+
* prefer this field over derivation. Absent on pre-Á-wave manifests and
|
|
465
|
+
* on runs whose runner received no stdin creds — responders fall back to
|
|
466
|
+
* derivation, then to the local signed append (hướng C). Not a secret:
|
|
467
|
+
* same-uid readable either way; connecting still requires auth. */
|
|
468
|
+
brokerSocketPath?: string;
|
|
458
469
|
/** FINDING B (2026-10-31) — w1a coordination verify-key distribution: the
|
|
459
470
|
* base64 SPKI DER PUBLIC verify key of the DISPATCHING session process
|
|
460
471
|
* (the leader that created this run; stamped via
|