pi-crew 0.10.3 → 0.10.5
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/CHANGELOG.md +40 -0
- package/README.md +5 -1
- package/dist/index.mjs +10684 -6883
- 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 +62 -42
- 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/run-tracker.ts +9 -8
- 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
|
@@ -23,14 +23,10 @@ import { listRecentRuns } from "./run-index.ts";
|
|
|
23
23
|
import type { PiTeamsToolResult } from "./tool-result.ts";
|
|
24
24
|
|
|
25
25
|
type ExecuteTeamRunFn = typeof _executeTeamRunFn;
|
|
26
|
-
let _cachedExecuteTeamRun: ExecuteTeamRunFn | undefined;
|
|
27
26
|
async function executeTeamRun(...args: Parameters<ExecuteTeamRunFn>): Promise<Awaited<ReturnType<ExecuteTeamRunFn>>> {
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
_cachedExecuteTeamRun = mod.executeTeamRun;
|
|
32
|
-
}
|
|
33
|
-
return _cachedExecuteTeamRun(...args);
|
|
27
|
+
// LAZY: heavy runtime — defer 1.4s import cost until team run actually executes.
|
|
28
|
+
const mod = await import("../runtime/team-runner.ts");
|
|
29
|
+
return mod.executeTeamRun(...args);
|
|
34
30
|
}
|
|
35
31
|
|
|
36
32
|
import { directTeamAndWorkflowFromRun } from "../runtime/direct-run.ts";
|
|
@@ -44,14 +40,10 @@ import { buildParentContext, formatScoped, result, type TeamContext } from "./te
|
|
|
44
40
|
import type { handleRun as _handleRunFn } from "./team-tool/run.ts";
|
|
45
41
|
|
|
46
42
|
type HandleRunFn = typeof _handleRunFn;
|
|
47
|
-
let _cachedHandleRun: HandleRunFn | undefined;
|
|
48
43
|
async function handleRun(...args: Parameters<HandleRunFn>): Promise<Awaited<ReturnType<HandleRunFn>>> {
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
_cachedHandleRun = mod.handleRun;
|
|
53
|
-
}
|
|
54
|
-
return _cachedHandleRun(...args);
|
|
44
|
+
// LAZY: run.ts pulls in spawnBackgroundTeamRun + resolveCrewRuntime; also avoids jiti import race in child-process contexts.
|
|
45
|
+
const mod = await import("./team-tool/run.ts");
|
|
46
|
+
return mod.handleRun(...args);
|
|
55
47
|
}
|
|
56
48
|
|
|
57
49
|
import { t } from "../i18n.ts";
|
package/src/hooks/registry.ts
CHANGED
|
@@ -179,6 +179,9 @@ export async function executeHook(name: HookName, ctx: HookContext): Promise<Hoo
|
|
|
179
179
|
}
|
|
180
180
|
|
|
181
181
|
export function appendHookEvent(manifest: TeamRunManifest, report: HookExecutionReport): void {
|
|
182
|
+
// REVIEW FIX (2026-09-10): reverted M2b buffered conversion — hook.executed
|
|
183
|
+
// events are read back synchronously (recovery-hooks tests, hooks audit
|
|
184
|
+
// display) and are low-frequency (per hook execution).
|
|
182
185
|
appendEvent(manifest.eventsPath, {
|
|
183
186
|
type: "hook.executed",
|
|
184
187
|
runId: manifest.runId,
|
|
@@ -32,7 +32,7 @@ import * as path from "node:path";
|
|
|
32
32
|
import { type Static, Type } from "@sinclair/typebox";
|
|
33
33
|
import { defineTool, type ExtensionAPI, type ToolDefinition } from "../extension/pi-api.ts";
|
|
34
34
|
import { EngineManager, type ExecuteResult } from "../runtime/scratchpad/engine.ts";
|
|
35
|
-
import { appendEventFireAndForget } from "../state/event-log/event-log.ts";
|
|
35
|
+
import { appendEventBuffered, appendEventFireAndForget } from "../state/event-log/event-log.ts";
|
|
36
36
|
// D5/MAJOR-S1: PI_CREW_PARENT_PID + PI_CREW_GUEST build the guest's zombie-
|
|
37
37
|
// backstop env (PI_CREW_KIND_ENV is already exported above).
|
|
38
38
|
import { type ArtifactWriteOptions, writeArtifact } from "../state/stores/artifact-store.ts";
|
|
@@ -84,12 +84,12 @@ function emitScratchpadMetric(
|
|
|
84
84
|
// never break the cell. The real appendEventFireAndForget already catches
|
|
85
85
|
// async; this guard also absorbs a sync throw from a bad writer.
|
|
86
86
|
try {
|
|
87
|
-
|
|
87
|
+
appendEventBuffered(eventsPath, {
|
|
88
88
|
type,
|
|
89
89
|
runId,
|
|
90
90
|
taskId: env[PI_CREW_TASK_ID_ENV],
|
|
91
91
|
data,
|
|
92
|
-
});
|
|
92
|
+
}).catch((e) => logInternalError("scratchpad_lifecycle.buffered", e, `type=${type}`));
|
|
93
93
|
} catch {
|
|
94
94
|
// Metric is best-effort — drop the event, never the cell.
|
|
95
95
|
}
|
|
@@ -5,12 +5,11 @@ import { loadConfig } from "../config/config.ts";
|
|
|
5
5
|
import { getCrewEnv } from "../config/env-vars.ts";
|
|
6
6
|
import { atomicWriteFile } from "../state/atomic-write.ts";
|
|
7
7
|
import { withRunLockSync } from "../state/coordination/locks.ts";
|
|
8
|
-
import { appendEvent, appendEventFireAndForget } from "../state/event-log/event-log.ts";
|
|
8
|
+
import { appendEvent, appendEventBuffered, appendEventFireAndForget } from "../state/event-log/event-log.ts";
|
|
9
9
|
import { createRunPaths, loadRunManifestById, saveRunManifestAsync, updateRunStatus } from "../state/stores/state-store.ts";
|
|
10
10
|
import type { TeamRunManifest, TeamTaskState } from "../state/types.ts";
|
|
11
11
|
import { allTeams, discoverTeams } from "../teams/discover-teams.ts";
|
|
12
12
|
import { errorMessage } from "../utils/guards.ts";
|
|
13
|
-
import { projectCrewRoot } from "../utils/paths.ts";
|
|
14
13
|
import { assertSafePathId } from "../utils/safe-paths.ts";
|
|
15
14
|
import { allWorkflows, discoverWorkflows } from "../workflows/discover-workflows.ts";
|
|
16
15
|
// Heavy runtime — lazy-loaded to avoid pulling team-runner into background-runner
|
|
@@ -18,8 +17,6 @@ import { allWorkflows, discoverWorkflows } from "../workflows/discover-workflows
|
|
|
18
17
|
import { primePeerDep } from "./peer-dep.ts";
|
|
19
18
|
import type { executeTeamRun as ExecuteTeamRunFn } from "./team-runner.ts";
|
|
20
19
|
|
|
21
|
-
let _cachedExecuteTeamRun: typeof ExecuteTeamRunFn | undefined;
|
|
22
|
-
|
|
23
20
|
/** Maximum runtime for a single background run before the watchdog force-aborts
|
|
24
21
|
* it. Prevents zombie background-runner processes when a team run hangs forever
|
|
25
22
|
* (e.g. a hung child Pi process, a stuck lock, or a test that spawns a run
|
|
@@ -33,16 +30,13 @@ const MAX_BACKGROUND_RUN_MS = (() => {
|
|
|
33
30
|
return Number.isFinite(env) && env > 0 ? env : 2 * 60 * 60 * 1000;
|
|
34
31
|
})();
|
|
35
32
|
async function executeTeamRun(...args: Parameters<typeof ExecuteTeamRunFn>): Promise<Awaited<ReturnType<typeof ExecuteTeamRunFn>>> {
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
_cachedExecuteTeamRun = mod.executeTeamRun;
|
|
44
|
-
}
|
|
45
|
-
return _cachedExecuteTeamRun(...args);
|
|
33
|
+
// FIX (split-scope install): prime the ESM peer dep BEFORE team-runner is
|
|
34
|
+
// imported, so its transitive skill-instructions.ts can read getAgentDir()
|
|
35
|
+
// from the primed cache instead of crashing on `Cannot find module`.
|
|
36
|
+
await primePeerDep().catch(() => undefined);
|
|
37
|
+
// LAZY: avoid pulling team-runner into background-runner at module load time.
|
|
38
|
+
const mod = await import("./team-runner.ts");
|
|
39
|
+
return mod.executeTeamRun(...args);
|
|
46
40
|
}
|
|
47
41
|
|
|
48
42
|
import { logInternalError } from "../utils/internal-error.ts";
|
|
@@ -165,6 +159,27 @@ export function signalEventType(sig: string): "async.signal" | "async.failed" {
|
|
|
165
159
|
return BENIGN_SIGNALS.has(sig) ? "async.signal" : "async.failed";
|
|
166
160
|
}
|
|
167
161
|
|
|
162
|
+
/**
|
|
163
|
+
* Scope-aware diagnostic paths for the background runner (issue #55, follows
|
|
164
|
+
* the #54 fix in run-tracker.ts). Runs created in a markerless (non-git) cwd
|
|
165
|
+
* live under userCrewRoot() — resolved via the same createRunPaths/
|
|
166
|
+
* scopeBaseRoot chain used at run CREATION — while project cwds keep .crew/ or
|
|
167
|
+
* the .pi/teams/ fallback (issue #29). Pure path math: createRunPaths asserts
|
|
168
|
+
* the runId (R11-2 boundary hardening preserved) and never touches the fs.
|
|
169
|
+
*
|
|
170
|
+
* These must stay (cwd, runId)-derived: both sites run BEFORE the manifest is
|
|
171
|
+
* loaded (the console redirect is the FIRST thing main() does; exitCodePath is
|
|
172
|
+
* computed at module load), so manifest.stateRoot is not available yet.
|
|
173
|
+
*/
|
|
174
|
+
export function backgroundLogPath(cwd: string, runId: string): string {
|
|
175
|
+
return path.join(createRunPaths(cwd, runId).stateRoot, "background.log");
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/** exit-code.txt sibling of background.log — same scope resolution (#55). */
|
|
179
|
+
export function backgroundExitCodePath(cwd: string, runId: string): string {
|
|
180
|
+
return path.join(createRunPaths(cwd, runId).stateRoot, "exit-code.txt");
|
|
181
|
+
}
|
|
182
|
+
|
|
168
183
|
/**
|
|
169
184
|
* Fire-and-forget event log for signal handlers. Extracted to module level
|
|
170
185
|
* (from inside main()) so the exported SIGINT handler installer (test seam)
|
|
@@ -256,11 +271,11 @@ export function startInterruptGuard(
|
|
|
256
271
|
/* best-effort ack — interruptHandled gate prevents re-fire */
|
|
257
272
|
}
|
|
258
273
|
|
|
259
|
-
|
|
274
|
+
appendEventBuffered(manifest.eventsPath, {
|
|
260
275
|
type: "async.interrupt_detected",
|
|
261
276
|
runId: manifest.runId,
|
|
262
277
|
message: "Background runner detected foreground interrupt — killing child processes and exiting.",
|
|
263
|
-
});
|
|
278
|
+
}).catch((e) => logInternalError("background-runner.buffered", e, "type=async.interrupt_detected"));
|
|
264
279
|
// FIX: Terminate ALL child-pi processes IMMEDIATELY before exiting.
|
|
265
280
|
// Previously this was missing, causing orphaned child processes to run forever
|
|
266
281
|
// after the background-runner exited. terminateActiveChildPiProcesses sends
|
|
@@ -311,7 +326,7 @@ function setupUnhandledRejectionGuard(
|
|
|
311
326
|
console.error("[background-runner] Stack:", reason instanceof Error ? reason.stack : "N/A");
|
|
312
327
|
try {
|
|
313
328
|
if (state.eventsPath && state.runId) {
|
|
314
|
-
|
|
329
|
+
appendEventBuffered(state.eventsPath, {
|
|
315
330
|
type: "async.failed",
|
|
316
331
|
runId: state.runId,
|
|
317
332
|
message: `Unhandled rejection: ${message}`,
|
|
@@ -320,7 +335,7 @@ function setupUnhandledRejectionGuard(
|
|
|
320
335
|
stack: reason instanceof Error ? reason.stack : undefined,
|
|
321
336
|
handled: false,
|
|
322
337
|
},
|
|
323
|
-
});
|
|
338
|
+
}).catch((e) => logInternalError("background-runner.buffered", e, "type=async.failed source=unhandledRejection"));
|
|
324
339
|
}
|
|
325
340
|
} catch (appendErr) {
|
|
326
341
|
console.error("[background-runner] Failed to write async.failed event:", appendErr);
|
|
@@ -377,12 +392,12 @@ function runCleanup(
|
|
|
377
392
|
console.log(`[background-runner] runCleanup: unregisterWorker error: ${errorMessage(error)}`);
|
|
378
393
|
if (eventsPath) {
|
|
379
394
|
try {
|
|
380
|
-
|
|
395
|
+
appendEventBuffered(eventsPath, {
|
|
381
396
|
type: "background.unregister_worker_failed",
|
|
382
397
|
runId: argValue("--run-id") ?? "unknown",
|
|
383
398
|
message: `unregisterWorker failed: ${errorMessage(error)}`,
|
|
384
399
|
data: { pid: process.pid },
|
|
385
|
-
});
|
|
400
|
+
}).catch((e) => logInternalError("background-runner.buffered", e, "type=background.unregister_worker_failed"));
|
|
386
401
|
} catch {
|
|
387
402
|
/* best-effort */
|
|
388
403
|
}
|
|
@@ -426,10 +441,12 @@ async function main(): Promise<void> {
|
|
|
426
441
|
// best-effort try/catch so the hardening is never silently swallowed).
|
|
427
442
|
assertSafePathId("runId", _runId);
|
|
428
443
|
try {
|
|
429
|
-
//
|
|
430
|
-
// manifest
|
|
431
|
-
//
|
|
432
|
-
|
|
444
|
+
// Scope-aware (issue #55): resolve via createRunPaths so the log lands
|
|
445
|
+
// next to the manifest for BOTH project cwds (.crew/ or the .pi/teams/
|
|
446
|
+
// fallback, issue #29) and markerless cwds (user scope, issue #54). The
|
|
447
|
+
// previous projectCrewRoot join silently dropped the console redirect
|
|
448
|
+
// for user-scope runs — exactly the log needed to diagnose them.
|
|
449
|
+
const logPath = backgroundLogPath(_cwd, _runId);
|
|
433
450
|
logFd = fs.openSync(logPath, "a");
|
|
434
451
|
const origWrite =
|
|
435
452
|
(_prefix: string) =>
|
|
@@ -483,8 +500,11 @@ async function main(): Promise<void> {
|
|
|
483
500
|
// IIFE runs at MODULE LOAD, so an unsafe runId throws before the runner
|
|
484
501
|
// starts (intended fail-fast, matching run-import.ts:105 pattern).
|
|
485
502
|
assertSafePathId("runId", runId);
|
|
486
|
-
//
|
|
487
|
-
|
|
503
|
+
// Scope-aware (issue #55): same resolution as backgroundLogPath — project
|
|
504
|
+
// scope (.crew/ or .pi/teams/, issue #29) or user scope (#54). The
|
|
505
|
+
// previous projectCrewRoot join never landed exit-code.txt for user-scope
|
|
506
|
+
// runs, hiding non-zero exit diagnostics.
|
|
507
|
+
return backgroundExitCodePath(cwd, runId);
|
|
488
508
|
})();
|
|
489
509
|
if (exitCodePath) {
|
|
490
510
|
process.on("exit", (code) => {
|
|
@@ -538,12 +558,12 @@ async function main(): Promise<void> {
|
|
|
538
558
|
const runId = argValue("--run-id");
|
|
539
559
|
if (runId && manifest.eventsPath) {
|
|
540
560
|
try {
|
|
541
|
-
|
|
561
|
+
appendEventBuffered(manifest.eventsPath, {
|
|
542
562
|
type: "async.sigterm_received_graceful_shutdown",
|
|
543
563
|
runId,
|
|
544
564
|
message: `SIGTERM received, graceful shutdown via abort pid=${process.pid}`,
|
|
545
565
|
data: { pid: process.pid, ppid: process.ppid },
|
|
546
|
-
});
|
|
566
|
+
}).catch((e) => logInternalError("background-runner.buffered", e, "type=async.sigterm_received_graceful_shutdown"));
|
|
547
567
|
} catch {
|
|
548
568
|
/* best-effort */
|
|
549
569
|
}
|
|
@@ -596,12 +616,12 @@ async function main(): Promise<void> {
|
|
|
596
616
|
const codeStr = code === undefined ? "<none>" : String(code);
|
|
597
617
|
if (runId2 && manifest.eventsPath) {
|
|
598
618
|
try {
|
|
599
|
-
|
|
619
|
+
appendEventBuffered(manifest.eventsPath, {
|
|
600
620
|
type: "async.exit",
|
|
601
621
|
runId: runId2,
|
|
602
622
|
message: `Background runner exit(${codeStr}) pid=${process.pid}`,
|
|
603
623
|
data: { code, pid: process.pid },
|
|
604
|
-
});
|
|
624
|
+
}).catch((e) => logInternalError("background-runner.buffered", e, "type=async.exit"));
|
|
605
625
|
} catch {
|
|
606
626
|
/* best-effort */
|
|
607
627
|
}
|
|
@@ -635,11 +655,11 @@ async function main(): Promise<void> {
|
|
|
635
655
|
// NOTE: intentionally no unref() — the guard keeps the event loop alive
|
|
636
656
|
// to prevent premature worker exit. See parent-guard.ts:86 for rationale.
|
|
637
657
|
|
|
638
|
-
|
|
658
|
+
appendEventBuffered(manifest.eventsPath, {
|
|
639
659
|
type: "async.started",
|
|
640
660
|
runId: manifest.runId,
|
|
641
661
|
data: { pid: process.pid },
|
|
642
|
-
});
|
|
662
|
+
}).catch((e) => logInternalError("background-runner.buffered", e, "type=async.started"));
|
|
643
663
|
debugLog(`[background-runner] async.started written, pid=${process.pid}`);
|
|
644
664
|
writeAsyncStartMarker(manifest, {
|
|
645
665
|
pid: process.pid,
|
|
@@ -663,12 +683,12 @@ async function main(): Promise<void> {
|
|
|
663
683
|
const watchdogTimer = setTimeout(() => {
|
|
664
684
|
console.error(`[background-runner] WATCHDOG: run ${runId} exceeded ${MAX_BACKGROUND_RUN_MS}ms — aborting (zombie prevention)`);
|
|
665
685
|
try {
|
|
666
|
-
|
|
686
|
+
appendEventBuffered(manifest.eventsPath, {
|
|
667
687
|
type: "async.watchdog_fired",
|
|
668
688
|
runId,
|
|
669
689
|
message: `Run exceeded ${MAX_BACKGROUND_RUN_MS}ms and was force-aborted to prevent a zombie background-runner process.`,
|
|
670
690
|
data: { maxRunMs: MAX_BACKGROUND_RUN_MS },
|
|
671
|
-
});
|
|
691
|
+
}).catch((e) => logInternalError("background-runner.buffered", e, "type=async.watchdog_fired"));
|
|
672
692
|
} catch {
|
|
673
693
|
/* best-effort event log */
|
|
674
694
|
}
|
|
@@ -825,12 +845,12 @@ async function main(): Promise<void> {
|
|
|
825
845
|
updatedAt: new Date().toISOString(),
|
|
826
846
|
};
|
|
827
847
|
await saveRunManifestAsync(manifest);
|
|
828
|
-
|
|
848
|
+
appendEventBuffered(manifest.eventsPath, {
|
|
829
849
|
type: "runtime.resolved",
|
|
830
850
|
runId: manifest.runId,
|
|
831
851
|
message: `Runtime resolved: ${runtime.kind} safety=${runtime.safety}`,
|
|
832
852
|
data: { runtimeResolution, async: true },
|
|
833
|
-
});
|
|
853
|
+
}).catch((e) => logInternalError("background-runner.buffered", e, "type=runtime.resolved"));
|
|
834
854
|
if (runtime.safety === "blocked")
|
|
835
855
|
throw new Error(runtime.reason ?? "Child worker execution is disabled; refusing to create no-op scaffold subagents.");
|
|
836
856
|
const executeWorkers = runtime.kind !== "scaffold";
|
|
@@ -890,11 +910,11 @@ async function main(): Promise<void> {
|
|
|
890
910
|
} // close if (!earlyResult) — team-run setup+execute done; earlyResult path skips to here
|
|
891
911
|
manifest = result!.manifest;
|
|
892
912
|
tasks = result!.tasks;
|
|
893
|
-
|
|
913
|
+
appendEventBuffered(manifest.eventsPath, {
|
|
894
914
|
type: "async.completed",
|
|
895
915
|
runId: manifest.runId,
|
|
896
916
|
data: { status: manifest.status, tasks: tasks.length },
|
|
897
|
-
});
|
|
917
|
+
}).catch((e) => logInternalError("background-runner.buffered", e, "type=async.completed"));
|
|
898
918
|
console.log(`[background-runner] async.completed written, status=${manifest.status}`);
|
|
899
919
|
if (manifest.status === "failed" || manifest.status === "cancelled" || manifest.status === "blocked") process.exitCode = 1;
|
|
900
920
|
} catch (error) {
|
|
@@ -915,11 +935,11 @@ async function main(): Promise<void> {
|
|
|
915
935
|
const fresh = loaded?.manifest ?? manifest;
|
|
916
936
|
if (fresh) {
|
|
917
937
|
manifest = updateRunStatus(fresh, "failed", message);
|
|
918
|
-
|
|
938
|
+
appendEventBuffered(manifest.eventsPath, {
|
|
919
939
|
type: "async.failed",
|
|
920
940
|
runId: manifest.runId,
|
|
921
941
|
message,
|
|
922
|
-
});
|
|
942
|
+
}).catch((e) => logInternalError("background-runner.buffered", e, "type=async.failed source=updateFailed"));
|
|
923
943
|
}
|
|
924
944
|
return fresh;
|
|
925
945
|
},
|
|
@@ -988,12 +1008,12 @@ try {
|
|
|
988
1008
|
const mRunId = argValue("--run-id");
|
|
989
1009
|
if (mCwd && mRunId) {
|
|
990
1010
|
const mEventsPath = createRunPaths(mCwd, mRunId).eventsPath;
|
|
991
|
-
|
|
1011
|
+
appendEventBuffered(mEventsPath, {
|
|
992
1012
|
type: "async.failed",
|
|
993
1013
|
runId: mRunId,
|
|
994
1014
|
message: errorMessage(err),
|
|
995
1015
|
data: { stack: err instanceof Error ? err.stack : undefined },
|
|
996
|
-
});
|
|
1016
|
+
}).catch((e) => logInternalError("background-runner.buffered", e, "type=async.failed source=catch"));
|
|
997
1017
|
}
|
|
998
1018
|
} catch {
|
|
999
1019
|
/* best-effort — don't let event-write failure mask the original error */
|