@gajae-code/coding-agent 0.15.6 → 0.16.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/CHANGELOG.md +59 -0
- package/dist/types/cli/args.d.ts +2 -0
- package/dist/types/cli/root-flags.d.ts +14 -0
- package/dist/types/cli/update-cli.d.ts +13 -0
- package/dist/types/cli.d.ts +7 -0
- package/dist/types/commands/launch.d.ts +10 -0
- package/dist/types/config/autorouting-tier-map.d.ts +24 -0
- package/dist/types/config/file-lock.d.ts +5 -0
- package/dist/types/config/model-registry.d.ts +6 -0
- package/dist/types/config/settings-schema.d.ts +10 -1
- package/dist/types/config/settings.d.ts +2 -0
- package/dist/types/coordinator-mcp/projection-scan.d.ts +8 -0
- package/dist/types/crash/record-loader.d.ts +15 -0
- package/dist/types/gjc-runtime/launch-worktree-reservation.d.ts +21 -0
- package/dist/types/gjc-runtime/managed-owner-supervisor.d.ts +2 -0
- package/dist/types/gjc-runtime/session-state-sidecar.d.ts +66 -1
- package/dist/types/gjc-runtime/tmux-owner-isolation.d.ts +1 -0
- package/dist/types/gjc-runtime/tmux-sessions.d.ts +27 -0
- package/dist/types/master-mode/context.d.ts +21 -0
- package/dist/types/master-mode/first-request.d.ts +34 -0
- package/dist/types/master-mode/snapshot.d.ts +32 -0
- package/dist/types/modes/components/frictionless-onboarding-selector.d.ts +10 -0
- package/dist/types/modes/components/oauth-selector.d.ts +1 -0
- package/dist/types/modes/components/status-line/model-name.d.ts +18 -0
- package/dist/types/modes/components/status-line/priority-row.d.ts +34 -0
- package/dist/types/modes/components/status-line/segments.d.ts +10 -0
- package/dist/types/modes/print-mode.d.ts +17 -0
- package/dist/types/modes/ui-language.d.ts +7 -1
- package/dist/types/sdk/broker/broker.d.ts +77 -1
- package/dist/types/sdk/broker/endpoint-authority.d.ts +24 -0
- package/dist/types/sdk/broker/index.d.ts +3 -0
- package/dist/types/sdk/broker/lifecycle.d.ts +26 -0
- package/dist/types/sdk/broker/master-capability.d.ts +10 -0
- package/dist/types/sdk/broker/session-index.d.ts +40 -11
- package/dist/types/sdk/broker/session-scope.d.ts +72 -0
- package/dist/types/sdk/broker/spawn-authority.d.ts +201 -0
- package/dist/types/sdk/broker/spawn-substrate.d.ts +30 -0
- package/dist/types/sdk/broker/state-version.d.ts +4 -1
- package/dist/types/sdk/broker/worktree-occupancy.d.ts +28 -0
- package/dist/types/sdk/bus/chat-daemon-control.d.ts +3 -0
- package/dist/types/sdk/bus/index.d.ts +6 -0
- package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +1 -1
- package/dist/types/sdk/cli/index.d.ts +1 -0
- package/dist/types/sdk/cli/master-cli.d.ts +63 -0
- package/dist/types/sdk/cli/rows.d.ts +4 -3
- package/dist/types/sdk/cli/session-cli.d.ts +18 -2
- package/dist/types/sdk/client/client.d.ts +3 -2
- package/dist/types/sdk/host/control/dispatch.d.ts +10 -0
- package/dist/types/sdk/host/control/index.d.ts +1 -1
- package/dist/types/sdk/host/control/operations.d.ts +2 -0
- package/dist/types/sdk/host/control/runtime-gate.d.ts +21 -7
- package/dist/types/sdk/host/host.d.ts +10 -0
- package/dist/types/sdk/host/session-runtime.d.ts +52 -0
- package/dist/types/sdk/lifecycle/broker-client.d.ts +6 -0
- package/dist/types/sdk/lifecycle/service.d.ts +15 -3
- package/dist/types/sdk/lifecycle-session.d.ts +2 -0
- package/dist/types/sdk/router/session-router.d.ts +2 -0
- package/dist/types/sdk/session.d.ts +16 -0
- package/dist/types/sdk/startup-capability.d.ts +2 -1
- package/dist/types/session/agent-session.d.ts +5 -0
- package/dist/types/session/credential-store-errors.d.ts +1 -0
- package/dist/types/session/session-manager.d.ts +65 -0
- package/dist/types/session/yield-queue.d.ts +5 -4
- package/dist/types/setup/frictionless-onboarding.d.ts +1 -0
- package/dist/types/tools/bash.d.ts +4 -0
- package/dist/types/tools/gh.d.ts +8 -0
- package/dist/types/tools/index.d.ts +4 -0
- package/package.json +7 -7
- package/scripts/generate-sdk-operation-inventory.ts +6 -0
- package/scripts/verify-gjc-sdk-canonicalization.ts +35 -1
- package/src/cli/accounts-cli.ts +9 -5
- package/src/cli/args.ts +17 -0
- package/src/cli/fast-help.ts +1 -0
- package/src/cli/root-flags.ts +5 -0
- package/src/cli/update-cli.ts +77 -11
- package/src/commands/launch.ts +86 -18
- package/src/commands/sdk.ts +113 -21
- package/src/config/autorouting-tier-map.ts +12 -0
- package/src/config/file-lock.ts +158 -83
- package/src/config/model-preset-registry.ts +1 -5
- package/src/config/model-profiles.ts +5 -5
- package/src/config/model-registry.ts +23 -0
- package/src/config/model-resolver.ts +5 -0
- package/src/config/settings-schema.ts +8 -1
- package/src/config/settings.ts +5 -0
- package/src/coordinator-mcp/projection-scan.ts +21 -0
- package/src/coordinator-mcp/question-state.ts +9 -11
- package/src/coordinator-mcp/server.ts +94 -20
- package/src/crash/index-store.ts +2 -0
- package/src/crash/record-loader.ts +10 -0
- package/src/gjc-runtime/launch-worktree-reservation.ts +242 -0
- package/src/gjc-runtime/managed-owner-supervisor.ts +63 -51
- package/src/gjc-runtime/session-state-lock.ts +79 -6
- package/src/gjc-runtime/session-state-sidecar.ts +929 -23
- package/src/gjc-runtime/tmux-owner-isolation-cli.ts +4 -7
- package/src/gjc-runtime/tmux-owner-isolation.ts +2 -2
- package/src/gjc-runtime/tmux-sessions.ts +252 -13
- package/src/hooks/skill-state.ts +6 -0
- package/src/internal-urls/docs-index.generated.ts +7 -7
- package/src/internal-urls/issue-pr-protocol.ts +4 -2
- package/src/main.ts +57 -2
- package/src/master-mode/context.ts +50 -0
- package/src/master-mode/first-request.ts +65 -0
- package/src/master-mode/snapshot.ts +205 -0
- package/src/modes/acp/acp-agent.ts +605 -156
- package/src/modes/components/footer.ts +4 -2
- package/src/modes/components/frictionless-onboarding-selector.ts +48 -2
- package/src/modes/components/oauth-selector.ts +6 -1
- package/src/modes/components/settings-selector.ts +1 -0
- package/src/modes/components/status-line/model-name.ts +53 -0
- package/src/modes/components/status-line/priority-row.ts +107 -0
- package/src/modes/components/status-line/segments.ts +23 -9
- package/src/modes/components/tool-status-header.ts +151 -15
- package/src/modes/controllers/input-controller.ts +28 -0
- package/src/modes/controllers/selector-controller.ts +191 -96
- package/src/modes/print-mode.ts +64 -0
- package/src/modes/ui-language.ts +49 -2
- package/src/prompts/system/master-mode.md +28 -0
- package/src/sdk/broker/broker.ts +1636 -26
- package/src/sdk/broker/endpoint-authority.ts +86 -0
- package/src/sdk/broker/ensure.ts +5 -0
- package/src/sdk/broker/index.ts +3 -0
- package/src/sdk/broker/lifecycle.ts +186 -41
- package/src/sdk/broker/master-capability.ts +232 -0
- package/src/sdk/broker/session-index.ts +121 -17
- package/src/sdk/broker/session-scope.ts +272 -0
- package/src/sdk/broker/spawn-authority.ts +1031 -0
- package/src/sdk/broker/spawn-substrate.ts +548 -0
- package/src/sdk/broker/state-version.ts +26 -14
- package/src/sdk/broker/transport.ts +1 -0
- package/src/sdk/broker/worktree-occupancy.ts +90 -0
- package/src/sdk/bus/chat-daemon-control.ts +5 -2
- package/src/sdk/bus/chat-daemon-runtime.ts +35 -4
- package/src/sdk/bus/index.ts +51 -4
- package/src/sdk/bus/telegram-daemon-contract.ts +1 -1
- package/src/sdk/cli/index.ts +1 -0
- package/src/sdk/cli/master-cli.ts +259 -0
- package/src/sdk/cli/rows.ts +11 -8
- package/src/sdk/cli/session-cli.ts +264 -5
- package/src/sdk/client/client.ts +14 -8
- package/src/sdk/host/control/dispatch.ts +99 -38
- package/src/sdk/host/control/index.ts +2 -0
- package/src/sdk/host/control/operations.ts +2 -0
- package/src/sdk/host/control/runtime-gate.ts +203 -11
- package/src/sdk/host/host.ts +27 -2
- package/src/sdk/host/session-runtime.test.ts +210 -6
- package/src/sdk/host/session-runtime.ts +291 -49
- package/src/sdk/lifecycle/broker-client.ts +28 -0
- package/src/sdk/lifecycle/service.ts +279 -20
- package/src/sdk/lifecycle-session.ts +10 -2
- package/src/sdk/protocol/operation-inventory.generated.json +51 -0
- package/src/sdk/protocol/operation-registry.ts +13 -3
- package/src/sdk/router/session-router.ts +67 -59
- package/src/sdk/session.ts +357 -155
- package/src/sdk/startup-capability.ts +1 -0
- package/src/session/agent-session.ts +640 -103
- package/src/session/blob-store.ts +15 -5
- package/src/session/credential-store-errors.ts +2 -0
- package/src/session/internal/managed-session-storage.ts +153 -78
- package/src/session/session-manager.ts +337 -24
- package/src/session/yield-queue.ts +24 -10
- package/src/setup/frictionless-onboarding.ts +2 -0
- package/src/telemetry/events.ts +1138 -12
- package/src/tools/bash.ts +256 -42
- package/src/tools/gh.ts +104 -48
- package/src/tools/github-cache.ts +2 -2
- package/src/tools/index.ts +4 -0
|
@@ -83,7 +83,7 @@ import {
|
|
|
83
83
|
requireCoordinatorMutation,
|
|
84
84
|
safeOpenCoordinatorArtifact,
|
|
85
85
|
} from "./policy";
|
|
86
|
-
import {
|
|
86
|
+
import { listCoordinatorJsonFilesWithRetry } from "./projection-scan";
|
|
87
87
|
import {
|
|
88
88
|
answerBindingMatches,
|
|
89
89
|
buildCoordinatorAskAnswerSchema,
|
|
@@ -835,7 +835,7 @@ function toolSchema(name: CoordinatorToolName): {
|
|
|
835
835
|
properties: {
|
|
836
836
|
after_seq: { type: "integer", minimum: 0 },
|
|
837
837
|
session_id: sessionId,
|
|
838
|
-
event_types: { type: "array", items: { type: "string" } },
|
|
838
|
+
event_types: { type: "array", items: { type: "string", enum: [...COORDINATOR_EVENT_KINDS] } },
|
|
839
839
|
timeout_ms: {
|
|
840
840
|
type: "number",
|
|
841
841
|
description: "Bounded event long-poll timeout in milliseconds, capped at 30 seconds.",
|
|
@@ -1498,7 +1498,7 @@ function publicSdkAcknowledgement(result: RuntimePromptAcknowledgement): Record<
|
|
|
1498
1498
|
}
|
|
1499
1499
|
|
|
1500
1500
|
async function listJsonFiles(dir: string): Promise<unknown[]> {
|
|
1501
|
-
const scan = await
|
|
1501
|
+
const scan = await listCoordinatorJsonFilesWithRetry(dir);
|
|
1502
1502
|
if (scan.capped) throw new Error("coordinator_projection_scan_incomplete");
|
|
1503
1503
|
if (scan.skippedEmpty > 0 || scan.skippedDebris > 0) {
|
|
1504
1504
|
logger.warn("Coordinator projection scan skipped debris", {
|
|
@@ -1530,7 +1530,10 @@ function brokerSessionId(record: Record<string, unknown>): string | null {
|
|
|
1530
1530
|
}
|
|
1531
1531
|
|
|
1532
1532
|
function brokerSessionScope(record: Record<string, unknown>): string | null {
|
|
1533
|
-
|
|
1533
|
+
// Locator v2 records the canonical workspace directory as `cwd`; the removed
|
|
1534
|
+
// `repo` field is never translated, so reading it here silently dropped every
|
|
1535
|
+
// broker row and failed open with an empty scoped listing.
|
|
1536
|
+
return firstString(asRecord(record.locator) ?? {}, ["cwd"]);
|
|
1534
1537
|
}
|
|
1535
1538
|
|
|
1536
1539
|
function sameCanonicalPath(left: string, right: string, platform: NodeJS.Platform): boolean {
|
|
@@ -2634,11 +2637,6 @@ async function writeActiveTurn(namespaceDir: string, turn: TurnRecord): Promise<
|
|
|
2634
2637
|
});
|
|
2635
2638
|
}
|
|
2636
2639
|
|
|
2637
|
-
async function clearActiveTurn(namespaceDir: string, turn: TurnRecord): Promise<void> {
|
|
2638
|
-
const active = asRecord(await readJsonFile(activeTurnFile(namespaceDir, turn.session_id)));
|
|
2639
|
-
if (active?.turn_id === turn.turn_id) await removeCoordinatorFile(activeTurnFile(namespaceDir, turn.session_id));
|
|
2640
|
-
}
|
|
2641
|
-
|
|
2642
2640
|
async function readSessionState(namespaceDir: string, sessionId: string): Promise<CoordinatorSessionState | null> {
|
|
2643
2641
|
return (await readJsonFile(sessionStateFile(namespaceDir, sessionId))) as CoordinatorSessionState | null;
|
|
2644
2642
|
}
|
|
@@ -2658,9 +2656,6 @@ async function writeSessionStateUnlocked(
|
|
|
2658
2656
|
} = {},
|
|
2659
2657
|
): Promise<CoordinatorSessionState> {
|
|
2660
2658
|
const persisted = await readSessionState(namespaceDir, sessionId);
|
|
2661
|
-
const session = asRecord(
|
|
2662
|
-
await readJsonFile(path.join(namespaceDir, "sessions", `${safeExternalId("session", sessionId)}.json`)),
|
|
2663
|
-
);
|
|
2664
2659
|
// An overwrite is a canonical lifecycle repair, not proof that tool activity ended.
|
|
2665
2660
|
const previous = options.overwrite ? null : persisted;
|
|
2666
2661
|
const hasCurrentTurn = Object.hasOwn(options, "currentTurnId");
|
|
@@ -5044,6 +5039,58 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
|
|
|
5044
5039
|
const keyDigest = createHash("sha256").update(idempotencyKey).digest("hex");
|
|
5045
5040
|
return path.join(namespaceDir, "idempotency", `${keyDigest}.json`);
|
|
5046
5041
|
}
|
|
5042
|
+
const idempotencyFlights = new Map<string, { requestDigest: string; promise: Promise<Record<string, unknown>> }>();
|
|
5043
|
+
type IdempotencyFlightAdmission =
|
|
5044
|
+
| { kind: "conflict"; response: Record<string, unknown> }
|
|
5045
|
+
| { kind: "joined"; promise: Promise<Record<string, unknown>> }
|
|
5046
|
+
| { kind: "owner"; run: (operation: Promise<Record<string, unknown>>) => Promise<Record<string, unknown>> };
|
|
5047
|
+
function admitIdempotencyFlight(
|
|
5048
|
+
tool: string,
|
|
5049
|
+
idempotencyKey: string,
|
|
5050
|
+
canonicalArgs: Record<string, unknown>,
|
|
5051
|
+
): IdempotencyFlightAdmission {
|
|
5052
|
+
const keyDigest = createHash("sha256").update(idempotencyKey).digest("hex");
|
|
5053
|
+
const requestDigest = createHash("sha256")
|
|
5054
|
+
.update(canonicalJson({ tool, args: canonicalArgs }))
|
|
5055
|
+
.digest("hex");
|
|
5056
|
+
const existingFlight = idempotencyFlights.get(keyDigest);
|
|
5057
|
+
if (existingFlight) {
|
|
5058
|
+
if (existingFlight.requestDigest !== requestDigest)
|
|
5059
|
+
return {
|
|
5060
|
+
kind: "conflict",
|
|
5061
|
+
response: {
|
|
5062
|
+
ok: false,
|
|
5063
|
+
error: { code: "idempotency_conflict", message: "idempotency key was used with a different request" },
|
|
5064
|
+
},
|
|
5065
|
+
};
|
|
5066
|
+
return { kind: "joined", promise: existingFlight.promise };
|
|
5067
|
+
}
|
|
5068
|
+
const result = Promise.withResolvers<Record<string, unknown>>();
|
|
5069
|
+
const flight = { requestDigest, promise: result.promise };
|
|
5070
|
+
idempotencyFlights.set(keyDigest, flight);
|
|
5071
|
+
return {
|
|
5072
|
+
kind: "owner",
|
|
5073
|
+
run: async operation => {
|
|
5074
|
+
void operation.then(result.resolve, result.reject);
|
|
5075
|
+
try {
|
|
5076
|
+
return await result.promise;
|
|
5077
|
+
} finally {
|
|
5078
|
+
if (idempotencyFlights.get(keyDigest) === flight) idempotencyFlights.delete(keyDigest);
|
|
5079
|
+
}
|
|
5080
|
+
},
|
|
5081
|
+
};
|
|
5082
|
+
}
|
|
5083
|
+
async function withIdempotencyFlight(
|
|
5084
|
+
tool: string,
|
|
5085
|
+
idempotencyKey: string,
|
|
5086
|
+
canonicalArgs: Record<string, unknown>,
|
|
5087
|
+
operation: () => Promise<Record<string, unknown>>,
|
|
5088
|
+
): Promise<Record<string, unknown>> {
|
|
5089
|
+
const admission = admitIdempotencyFlight(tool, idempotencyKey, canonicalArgs);
|
|
5090
|
+
if (admission.kind === "conflict") return admission.response;
|
|
5091
|
+
if (admission.kind === "joined") return await admission.promise;
|
|
5092
|
+
return await admission.run(operation());
|
|
5093
|
+
}
|
|
5047
5094
|
async function withOrderedSessionStateLocks<T>(
|
|
5048
5095
|
lockFiles: readonly string[],
|
|
5049
5096
|
operation: () => Promise<T>,
|
|
@@ -5075,6 +5122,7 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
|
|
|
5075
5122
|
recoverInProgress = false,
|
|
5076
5123
|
isNonterminal: (response: Record<string, unknown>) => boolean = isRouterRequestAmbiguous,
|
|
5077
5124
|
lockAlreadyHeld = false,
|
|
5125
|
+
flightAlreadyOwned = false,
|
|
5078
5126
|
): Promise<Record<string, unknown>> {
|
|
5079
5127
|
const keyDigest = createHash("sha256").update(idempotencyKey).digest("hex");
|
|
5080
5128
|
const requestDigest = createHash("sha256")
|
|
@@ -5179,7 +5227,11 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
|
|
|
5179
5227
|
});
|
|
5180
5228
|
return response;
|
|
5181
5229
|
};
|
|
5182
|
-
|
|
5230
|
+
const executeWithLock = async (): Promise<Record<string, unknown>> =>
|
|
5231
|
+
lockAlreadyHeld ? await execute() : await withSessionStateLock(lockFile, execute);
|
|
5232
|
+
return flightAlreadyOwned
|
|
5233
|
+
? await executeWithLock()
|
|
5234
|
+
: await withIdempotencyFlight(tool, idempotencyKey, canonicalArgs, executeWithLock);
|
|
5183
5235
|
}
|
|
5184
5236
|
|
|
5185
5237
|
async function brokerSession(
|
|
@@ -5281,11 +5333,28 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
|
|
|
5281
5333
|
sessionId,
|
|
5282
5334
|
);
|
|
5283
5335
|
} catch (closeError) {
|
|
5284
|
-
|
|
5285
|
-
|
|
5286
|
-
|
|
5287
|
-
|
|
5288
|
-
|
|
5336
|
+
let listed: Record<string, unknown>;
|
|
5337
|
+
try {
|
|
5338
|
+
listed = await paginatedBrokerSessionList(cwd, { cwd });
|
|
5339
|
+
} catch (listError) {
|
|
5340
|
+
throw new SdkClientError(
|
|
5341
|
+
UNOBSERVED_COMPENSATION_CODE,
|
|
5342
|
+
`Coordinator creation failed after remote session ${sessionId} was created, and compensation failed.`,
|
|
5343
|
+
{ primary: error, compensation: closeError, list: listError, session_id: sessionId },
|
|
5344
|
+
);
|
|
5345
|
+
}
|
|
5346
|
+
const rows = jsonRecords(Array.isArray(listed.sessions) ? listed.sessions : []);
|
|
5347
|
+
const row = rows.find(candidate => brokerSessionId(candidate) === sessionId);
|
|
5348
|
+
const deadUncertain =
|
|
5349
|
+
row !== undefined &&
|
|
5350
|
+
row.live !== true &&
|
|
5351
|
+
(row.terminalUncertain === true || (row as Record<string, unknown>).terminal_uncertain === true);
|
|
5352
|
+
if (!deadUncertain)
|
|
5353
|
+
throw new SdkClientError(
|
|
5354
|
+
UNOBSERVED_COMPENSATION_CODE,
|
|
5355
|
+
`Coordinator creation failed after remote session ${sessionId} was created, and compensation failed.`,
|
|
5356
|
+
{ primary: error, compensation: closeError, session_id: sessionId },
|
|
5357
|
+
);
|
|
5289
5358
|
}
|
|
5290
5359
|
throw error;
|
|
5291
5360
|
}
|
|
@@ -6713,7 +6782,7 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
|
|
|
6713
6782
|
// any stale pointer directly: once the turn projection is rewritten
|
|
6714
6783
|
// terminal, readActiveTurn() intentionally rejects it and cannot
|
|
6715
6784
|
// discover the legacy active-turn file to clear.
|
|
6716
|
-
await
|
|
6785
|
+
await removeCoordinatorFile(activeTurnFile(namespaceDir, sessionId));
|
|
6717
6786
|
}
|
|
6718
6787
|
// A runtime sidecar can advance a live session while the canonical WAL still
|
|
6719
6788
|
// needs projection repair. Preserve that observed lifecycle state (and its
|
|
@@ -8780,7 +8849,10 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
|
|
|
8780
8849
|
code === "live_session"
|
|
8781
8850
|
);
|
|
8782
8851
|
};
|
|
8783
|
-
|
|
8852
|
+
const flight = admitIdempotencyFlight(name, retirementKey, canonicalArgs);
|
|
8853
|
+
if (flight.kind === "conflict") return flight.response;
|
|
8854
|
+
if (flight.kind === "joined") return await flight.promise;
|
|
8855
|
+
const retirementOperation = withOrderedSessionStateLocks(
|
|
8784
8856
|
[idempotencyLockFile(creationKey), idempotencyLockFile(retirementKey)],
|
|
8785
8857
|
async () =>
|
|
8786
8858
|
await withToolIdempotency(
|
|
@@ -8964,8 +9036,10 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
|
|
|
8964
9036
|
true,
|
|
8965
9037
|
isRetirementRetryable,
|
|
8966
9038
|
true,
|
|
9039
|
+
true,
|
|
8967
9040
|
),
|
|
8968
9041
|
);
|
|
9042
|
+
return await flight.run(retirementOperation);
|
|
8969
9043
|
}
|
|
8970
9044
|
if (name === "gjc_coordinator_activate_session") {
|
|
8971
9045
|
requireCoordinatorMutation(config, "sessions", args);
|
package/src/crash/index-store.ts
CHANGED
|
@@ -613,6 +613,7 @@ export function applyCrashEvent(index: CrashIndex, event: CrashEvent, now: numbe
|
|
|
613
613
|
}
|
|
614
614
|
|
|
615
615
|
// occurrence
|
|
616
|
+
if (event.provenance === "eval" || event.provenance === "bun_test") return false;
|
|
616
617
|
// Clear before deduplication: a replayed occurrence after main-index publish
|
|
617
618
|
// but before retirement-sidecar publish must still supersede a stale tombstone.
|
|
618
619
|
index.retiredFingerprints = index.retiredFingerprints.filter(fingerprint => fingerprint !== event.fingerprint);
|
|
@@ -698,6 +699,7 @@ async function recoverAndRecomputeRetainedCounts(index: CrashIndex, crashLogPath
|
|
|
698
699
|
const recordsByFingerprint = new Map<string, LoadedCrashRecord[]>();
|
|
699
700
|
const seenRecordIds = new Set<string>();
|
|
700
701
|
for (const record of parseRecoverableCrashRecords(contents)) {
|
|
702
|
+
if (record.provenance === "eval" || record.provenance === "bun_test") continue;
|
|
701
703
|
if (seenRecordIds.has(record.recordId)) continue;
|
|
702
704
|
seenRecordIds.add(record.recordId);
|
|
703
705
|
const records = recordsByFingerprint.get(record.fingerprint) ?? [];
|
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
CRASH_FINGERPRINT_VERSION,
|
|
16
16
|
CRASH_RECORD_MARKER,
|
|
17
17
|
type CrashFingerprint,
|
|
18
|
+
type CrashProvenance,
|
|
18
19
|
computeCrashFingerprint,
|
|
19
20
|
parseCrashRecordMarker,
|
|
20
21
|
} from "@gajae-code/utils";
|
|
@@ -33,6 +34,7 @@ export interface LoadedCrashRecord {
|
|
|
33
34
|
readonly body: string;
|
|
34
35
|
/** The `Name: message` part of the header line. */
|
|
35
36
|
readonly headline: string;
|
|
37
|
+
readonly provenance?: CrashProvenance;
|
|
36
38
|
}
|
|
37
39
|
|
|
38
40
|
interface ParsedCrashRecord {
|
|
@@ -78,6 +80,7 @@ function parseCandidates(contents: string): ParsedCrashRecord[] {
|
|
|
78
80
|
let at = 0;
|
|
79
81
|
let buffer: string[] = [];
|
|
80
82
|
let started = false;
|
|
83
|
+
let provenance: CrashProvenance | undefined;
|
|
81
84
|
let pending: ParsedCrashRecord | undefined;
|
|
82
85
|
for (const line of contents.split("\n")) {
|
|
83
86
|
if (pending) {
|
|
@@ -92,6 +95,12 @@ function parseCandidates(contents: string): ParsedCrashRecord[] {
|
|
|
92
95
|
continue;
|
|
93
96
|
}
|
|
94
97
|
headline = header[2] ?? "";
|
|
98
|
+
const label = line.slice(line.indexOf("[") + 1, line.indexOf("]"));
|
|
99
|
+
const provenanceValue = label
|
|
100
|
+
.split(";")
|
|
101
|
+
.find(part => part.startsWith("provenance="))
|
|
102
|
+
?.slice(11);
|
|
103
|
+
provenance = provenanceValue === "eval" || provenanceValue === "bun_test" ? provenanceValue : undefined;
|
|
95
104
|
at = parsedAt;
|
|
96
105
|
buffer = [];
|
|
97
106
|
started = true;
|
|
@@ -116,6 +125,7 @@ function parseCandidates(contents: string): ParsedCrashRecord[] {
|
|
|
116
125
|
messageClass: fingerprint?.messageClass ?? "",
|
|
117
126
|
body: lines.join("\n").trimEnd(),
|
|
118
127
|
headline,
|
|
128
|
+
...(provenance === undefined ? {} : { provenance }),
|
|
119
129
|
},
|
|
120
130
|
complete: false,
|
|
121
131
|
bound: marker.version === CRASH_FINGERPRINT_VERSION && fingerprint !== undefined,
|
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cross-process reservation for a deterministic launch-worktree path.
|
|
3
|
+
*
|
|
4
|
+
* The durable session index is the long-lived ownership record, but a newly
|
|
5
|
+
* planned worktree has no index row yet. This reservation closes that interval:
|
|
6
|
+
* it is acquired before preparation and removed after `host_registered` is
|
|
7
|
+
* durably appended to the index.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
11
|
+
import type { Dirent } from "node:fs";
|
|
12
|
+
import * as fs from "node:fs/promises";
|
|
13
|
+
import * as path from "node:path";
|
|
14
|
+
import { resolveEquivalentPath } from "@gajae-code/utils";
|
|
15
|
+
import {
|
|
16
|
+
type FileLockGcObservation,
|
|
17
|
+
processStartTime,
|
|
18
|
+
readFileLockObservationForGc,
|
|
19
|
+
removeFileLockDirForGc,
|
|
20
|
+
} from "../config/file-lock";
|
|
21
|
+
import { processIncarnation } from "../sdk/broker/process-incarnation";
|
|
22
|
+
|
|
23
|
+
const RESERVATION_VERSION = 1;
|
|
24
|
+
const RESERVATION_DIRECTORY = "launch-worktree-reservations";
|
|
25
|
+
|
|
26
|
+
interface LaunchWorktreeReservationRecord {
|
|
27
|
+
version: typeof RESERVATION_VERSION;
|
|
28
|
+
worktreePath: string;
|
|
29
|
+
pid: number;
|
|
30
|
+
start_time?: string;
|
|
31
|
+
processIncarnation: string | null;
|
|
32
|
+
timestamp: number;
|
|
33
|
+
reservationId: string;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface LaunchWorktreeReservation {
|
|
37
|
+
release(): Promise<void>;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function canonicalWorktreePath(worktreePath: string): string {
|
|
41
|
+
return resolveEquivalentPath(path.resolve(worktreePath));
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function reservationRoot(agentDir: string): string {
|
|
45
|
+
return path.join(agentDir, "sdk", "sessions", RESERVATION_DIRECTORY);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function launchWorktreeReservationDirectory(agentDir: string, worktreePath: string): string {
|
|
49
|
+
const digest = createHash("sha256").update(canonicalWorktreePath(worktreePath)).digest("hex");
|
|
50
|
+
return path.join(reservationRoot(agentDir), `${digest}.lock`);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** Test seam for seeding a dead-owner reservation at the exact production path. */
|
|
54
|
+
export function launchWorktreeReservationDirectoryForTest(agentDir: string, worktreePath: string): string {
|
|
55
|
+
return launchWorktreeReservationDirectory(agentDir, worktreePath);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function isErrno(error: unknown, code: string): boolean {
|
|
59
|
+
return typeof error === "object" && error !== null && "code" in error && error.code === code;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function isReservationRecord(value: unknown): value is LaunchWorktreeReservationRecord {
|
|
63
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
|
|
64
|
+
const record = value as Partial<LaunchWorktreeReservationRecord>;
|
|
65
|
+
return (
|
|
66
|
+
record.version === RESERVATION_VERSION &&
|
|
67
|
+
typeof record.worktreePath === "string" &&
|
|
68
|
+
record.worktreePath.length > 0 &&
|
|
69
|
+
typeof record.pid === "number" &&
|
|
70
|
+
Number.isSafeInteger(record.pid) &&
|
|
71
|
+
record.pid > 0 &&
|
|
72
|
+
(record.start_time === undefined || (typeof record.start_time === "string" && record.start_time.length > 0)) &&
|
|
73
|
+
(record.processIncarnation === null ||
|
|
74
|
+
(typeof record.processIncarnation === "string" && record.processIncarnation.length > 0)) &&
|
|
75
|
+
typeof record.timestamp === "number" &&
|
|
76
|
+
Number.isFinite(record.timestamp) &&
|
|
77
|
+
typeof record.reservationId === "string" &&
|
|
78
|
+
record.reservationId.length > 0
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
async function readReservation(lockDir: string): Promise<LaunchWorktreeReservationRecord | null> {
|
|
83
|
+
let value: unknown;
|
|
84
|
+
try {
|
|
85
|
+
value = await Bun.file(path.join(lockDir, "info")).json();
|
|
86
|
+
} catch (error) {
|
|
87
|
+
if (isErrno(error, "ENOENT") || error instanceof SyntaxError) return null;
|
|
88
|
+
throw error;
|
|
89
|
+
}
|
|
90
|
+
return isReservationRecord(value) ? value : null;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function ownerDefinitelyExited(record: LaunchWorktreeReservationRecord): boolean {
|
|
94
|
+
try {
|
|
95
|
+
process.kill(record.pid, 0);
|
|
96
|
+
} catch (error) {
|
|
97
|
+
return isErrno(error, "ESRCH");
|
|
98
|
+
}
|
|
99
|
+
if (record.processIncarnation !== null) {
|
|
100
|
+
const actualIncarnation = processIncarnation(record.pid);
|
|
101
|
+
if (actualIncarnation !== undefined) return actualIncarnation !== record.processIncarnation;
|
|
102
|
+
}
|
|
103
|
+
if (record.start_time) {
|
|
104
|
+
const actualStartTime = processStartTime(record.pid);
|
|
105
|
+
if (actualStartTime !== null) return actualStartTime !== record.start_time;
|
|
106
|
+
}
|
|
107
|
+
return false;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
async function removeReservation(
|
|
111
|
+
lockDir: string,
|
|
112
|
+
record: LaunchWorktreeReservationRecord,
|
|
113
|
+
): Promise<"removed" | "kept"> {
|
|
114
|
+
// Capture the exact on-disk identity BEFORE authorizing removal: the generic
|
|
115
|
+
// GC remover only deletes a tree whose pre-verdict identity it was handed,
|
|
116
|
+
// so a successor reservation published at the same path is never reaped.
|
|
117
|
+
let observation: FileLockGcObservation | null;
|
|
118
|
+
try {
|
|
119
|
+
observation = await readFileLockObservationForGc(lockDir);
|
|
120
|
+
} catch {
|
|
121
|
+
return "kept";
|
|
122
|
+
}
|
|
123
|
+
if (!observation) return "kept";
|
|
124
|
+
let onDisk: unknown;
|
|
125
|
+
try {
|
|
126
|
+
onDisk = JSON.parse(observation.bytes);
|
|
127
|
+
} catch {
|
|
128
|
+
return "kept";
|
|
129
|
+
}
|
|
130
|
+
if (
|
|
131
|
+
!isReservationRecord(onDisk) ||
|
|
132
|
+
onDisk.reservationId !== record.reservationId ||
|
|
133
|
+
onDisk.pid !== record.pid ||
|
|
134
|
+
onDisk.timestamp !== record.timestamp ||
|
|
135
|
+
canonicalWorktreePath(onDisk.worktreePath) !== canonicalWorktreePath(record.worktreePath)
|
|
136
|
+
)
|
|
137
|
+
return "kept";
|
|
138
|
+
const result = await removeFileLockDirForGc(lockDir, observation.info, observation.identity);
|
|
139
|
+
return result === "removed" ? "removed" : "kept";
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
async function writeReservation(lockDir: string, record: LaunchWorktreeReservationRecord): Promise<void> {
|
|
143
|
+
try {
|
|
144
|
+
await Bun.write(path.join(lockDir, "info"), JSON.stringify(record));
|
|
145
|
+
} catch (error) {
|
|
146
|
+
await fs.rm(lockDir, { recursive: true, force: true }).catch(() => undefined);
|
|
147
|
+
throw error;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
async function tryPublishReservation(lockDir: string, record: LaunchWorktreeReservationRecord): Promise<boolean> {
|
|
152
|
+
const pendingDir = `${lockDir}.pending.${process.pid}.${randomUUID()}`;
|
|
153
|
+
await fs.mkdir(pendingDir, { mode: 0o700 });
|
|
154
|
+
try {
|
|
155
|
+
await writeReservation(pendingDir, record);
|
|
156
|
+
try {
|
|
157
|
+
await fs.rename(pendingDir, lockDir);
|
|
158
|
+
return true;
|
|
159
|
+
} catch (error) {
|
|
160
|
+
if (isErrno(error, "EEXIST") || isErrno(error, "ENOTEMPTY")) return false;
|
|
161
|
+
if (isErrno(error, "EPERM")) {
|
|
162
|
+
try {
|
|
163
|
+
await fs.stat(lockDir);
|
|
164
|
+
return false;
|
|
165
|
+
} catch (statError) {
|
|
166
|
+
if (!isErrno(statError, "ENOENT")) throw statError;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
throw error;
|
|
170
|
+
}
|
|
171
|
+
} finally {
|
|
172
|
+
await fs.rm(pendingDir, { recursive: true, force: true }).catch(() => undefined);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Atomically claim `worktreePath`, returning `null` only when another live or
|
|
178
|
+
* unverified launch already owns the same deterministic target. A dead or
|
|
179
|
+
* PID-reused owner is reclaimed before retrying the exclusive directory create.
|
|
180
|
+
*/
|
|
181
|
+
export async function reserveLaunchWorktree(
|
|
182
|
+
agentDir: string,
|
|
183
|
+
worktreePath: string,
|
|
184
|
+
): Promise<LaunchWorktreeReservation | null> {
|
|
185
|
+
const canonicalPath = canonicalWorktreePath(worktreePath);
|
|
186
|
+
const lockDir = launchWorktreeReservationDirectory(agentDir, canonicalPath);
|
|
187
|
+
await fs.mkdir(path.dirname(lockDir), { recursive: true, mode: 0o700 });
|
|
188
|
+
const ownStartTime = processStartTime(process.pid) ?? undefined;
|
|
189
|
+
const ownIncarnation = processIncarnation(process.pid) ?? null;
|
|
190
|
+
|
|
191
|
+
for (let attempt = 0; attempt < 2; attempt += 1) {
|
|
192
|
+
const record: LaunchWorktreeReservationRecord = {
|
|
193
|
+
version: RESERVATION_VERSION,
|
|
194
|
+
worktreePath: canonicalPath,
|
|
195
|
+
pid: process.pid,
|
|
196
|
+
...(ownStartTime ? { start_time: ownStartTime } : {}),
|
|
197
|
+
processIncarnation: ownIncarnation,
|
|
198
|
+
timestamp: Date.now(),
|
|
199
|
+
reservationId: randomUUID(),
|
|
200
|
+
};
|
|
201
|
+
if (await tryPublishReservation(lockDir, record)) {
|
|
202
|
+
return {
|
|
203
|
+
async release(): Promise<void> {
|
|
204
|
+
await removeReservation(lockDir, record);
|
|
205
|
+
},
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
const existing = await readReservation(lockDir);
|
|
210
|
+
if (
|
|
211
|
+
!existing ||
|
|
212
|
+
canonicalWorktreePath(existing.worktreePath) !== canonicalPath ||
|
|
213
|
+
!ownerDefinitelyExited(existing)
|
|
214
|
+
)
|
|
215
|
+
return null;
|
|
216
|
+
if ((await removeReservation(lockDir, existing)) !== "removed") return null;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
return null;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/** Remove the transient reservation only after the matching worktree is indexed. */
|
|
223
|
+
export async function releaseLaunchWorktreeReservationAfterRegistration(
|
|
224
|
+
agentDir: string,
|
|
225
|
+
worktreePath: string,
|
|
226
|
+
): Promise<void> {
|
|
227
|
+
const canonicalPath = canonicalWorktreePath(worktreePath);
|
|
228
|
+
let entries: Dirent[];
|
|
229
|
+
try {
|
|
230
|
+
entries = await fs.readdir(reservationRoot(agentDir), { withFileTypes: true });
|
|
231
|
+
} catch (error) {
|
|
232
|
+
if (isErrno(error, "ENOENT")) return;
|
|
233
|
+
throw error;
|
|
234
|
+
}
|
|
235
|
+
for (const entry of entries) {
|
|
236
|
+
if (!entry.isDirectory() || !entry.name.endsWith(".lock")) continue;
|
|
237
|
+
const lockDir = path.join(reservationRoot(agentDir), entry.name);
|
|
238
|
+
const record = await readReservation(lockDir);
|
|
239
|
+
if (!record || canonicalWorktreePath(record.worktreePath) !== canonicalPath) continue;
|
|
240
|
+
await removeReservation(lockDir, record);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
@@ -15,6 +15,8 @@ export const MANAGED_OWNER_GENERATION_ENV = "GJC_TMUX_OWNER_GENERATION";
|
|
|
15
15
|
export const MANAGED_OWNER_STATE_DIR_ENV = "GJC_TMUX_OWNER_STATE_DIR";
|
|
16
16
|
export const MANAGED_OWNER_RUN_ID_ENV = "GJC_MANAGED_OWNER_RUN_ID";
|
|
17
17
|
export const MANAGED_OWNER_INCARNATION_ENV = "GJC_MANAGED_OWNER_INCARNATION";
|
|
18
|
+
/** Suppresses command-derived durable artifacts for Broker-owned opaque spawn children. */
|
|
19
|
+
export const MANAGED_OWNER_REDACT_COMMAND_ENV = "GJC_MANAGED_OWNER_REDACT_COMMAND";
|
|
18
20
|
|
|
19
21
|
let bootstrapSigtermPending = false;
|
|
20
22
|
const captureBootstrapSigterm = () => {
|
|
@@ -140,26 +142,32 @@ export async function runManagedOwnerSupervisor(): Promise<void> {
|
|
|
140
142
|
if (!supervisorStartTime) throw new Error("managed_owner_supervisor_start_time_unavailable");
|
|
141
143
|
await fs.mkdir(root, { recursive: true, mode: 0o700 });
|
|
142
144
|
const childToken = crypto.randomUUID();
|
|
143
|
-
const
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
145
|
+
const redactCommand = process.env[MANAGED_OWNER_REDACT_COMMAND_ENV] === "1";
|
|
146
|
+
const binding: ManagedOwnerBinding | null = redactCommand
|
|
147
|
+
? null
|
|
148
|
+
: {
|
|
149
|
+
schema_version: 2,
|
|
150
|
+
generation,
|
|
151
|
+
session_id: sessionId,
|
|
152
|
+
run_id: runId,
|
|
153
|
+
endpoint_incarnation: incarnation,
|
|
154
|
+
child_token: childToken,
|
|
155
|
+
command,
|
|
156
|
+
command_sha256: commandDigest(command),
|
|
157
|
+
supervisor_pid: process.pid,
|
|
158
|
+
supervisor_start_time: supervisorStartTime,
|
|
159
|
+
created_at: new Date().toISOString(),
|
|
160
|
+
};
|
|
161
|
+
if (binding) await writeDurableExclusive(path.join(root, `child-${childToken}.binding.json`), binding);
|
|
162
|
+
const childEnvironment: NodeJS.ProcessEnv = { ...process.env, [MANAGED_OWNER_CHILD_TOKEN_ENV]: childToken };
|
|
163
|
+
delete childEnvironment[MANAGED_OWNER_COMMAND_ENV];
|
|
164
|
+
delete childEnvironment[MANAGED_OWNER_REDACT_COMMAND_ENV];
|
|
157
165
|
const child = Bun.spawn({
|
|
158
166
|
cmd: command,
|
|
159
167
|
stdin: "inherit",
|
|
160
168
|
stdout: "inherit",
|
|
161
169
|
stderr: "inherit",
|
|
162
|
-
env:
|
|
170
|
+
env: childEnvironment,
|
|
163
171
|
});
|
|
164
172
|
const childStartTime = await managedOwnerProcessProvenance(child.pid);
|
|
165
173
|
if (!childStartTime) throw new Error("managed_owner_child_start_time_unavailable");
|
|
@@ -167,24 +175,26 @@ export async function runManagedOwnerSupervisor(): Promise<void> {
|
|
|
167
175
|
if (!childProcess) {
|
|
168
176
|
const exitCode = await child.exited;
|
|
169
177
|
if (child.signalCode === "SIGABRT") {
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
178
|
+
if (binding) {
|
|
179
|
+
const receipt: ManagedOwnerSigabrtReceipt = {
|
|
180
|
+
schema_version: 2,
|
|
181
|
+
generation,
|
|
182
|
+
session_id: sessionId,
|
|
183
|
+
run_id: runId,
|
|
184
|
+
endpoint_incarnation: incarnation,
|
|
185
|
+
child_token: childToken,
|
|
186
|
+
command_sha256: binding.command_sha256,
|
|
187
|
+
supervisor_pid: process.pid,
|
|
188
|
+
supervisor_start_time: supervisorStartTime,
|
|
189
|
+
child_pid: child.pid,
|
|
190
|
+
child_start_time: childStartTime,
|
|
191
|
+
signal: "SIGABRT",
|
|
192
|
+
signal_number: 6,
|
|
193
|
+
exit_code: exitCode,
|
|
194
|
+
received_at: new Date().toISOString(),
|
|
195
|
+
};
|
|
196
|
+
await writeDurableExclusive(path.join(root, `sigabrt-${childToken}.receipt.json`), receipt);
|
|
197
|
+
}
|
|
188
198
|
process.exitCode = 134;
|
|
189
199
|
return;
|
|
190
200
|
}
|
|
@@ -246,24 +256,26 @@ export async function runManagedOwnerSupervisor(): Promise<void> {
|
|
|
246
256
|
});
|
|
247
257
|
}
|
|
248
258
|
if (child.signalCode === "SIGABRT") {
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
259
|
+
if (binding) {
|
|
260
|
+
const receipt: ManagedOwnerSigabrtReceipt = {
|
|
261
|
+
schema_version: 2,
|
|
262
|
+
generation,
|
|
263
|
+
session_id: sessionId,
|
|
264
|
+
run_id: runId,
|
|
265
|
+
endpoint_incarnation: incarnation,
|
|
266
|
+
child_token: childToken,
|
|
267
|
+
command_sha256: binding.command_sha256,
|
|
268
|
+
supervisor_pid: process.pid,
|
|
269
|
+
supervisor_start_time: supervisorStartTime,
|
|
270
|
+
child_pid: child.pid,
|
|
271
|
+
child_start_time: childStartTime,
|
|
272
|
+
signal: "SIGABRT",
|
|
273
|
+
signal_number: 6,
|
|
274
|
+
exit_code: exitCode,
|
|
275
|
+
received_at: new Date().toISOString(),
|
|
276
|
+
};
|
|
277
|
+
await writeDurableExclusive(path.join(root, `sigabrt-${childToken}.receipt.json`), receipt);
|
|
278
|
+
}
|
|
267
279
|
process.exitCode = 134;
|
|
268
280
|
return;
|
|
269
281
|
}
|