@llblab/pi-actors 0.42.3 → 0.43.1
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 +127 -175
- package/BACKLOG.md +189 -1
- package/CHANGELOG.md +183 -292
- package/README.md +115 -276
- package/dist/fixtures/protocol/control-endpoint.json +6 -0
- package/dist/fixtures/protocol/control-record.json +9 -0
- package/dist/fixtures/protocol/recipe-summary.json +4 -12
- package/dist/fixtures/protocol/trace-event.json +9 -0
- package/dist/index.js +1 -1
- package/dist/lib/async-runs.d.ts +15 -38
- package/dist/lib/async-runs.js +173 -111
- package/dist/lib/automatic-review-runtime.d.ts +1 -1
- package/dist/lib/automatic-review-runtime.js +5 -5
- package/dist/lib/command-templates.d.ts +2 -0
- package/dist/lib/command-templates.js +38 -4
- package/dist/lib/control-projection.d.ts +20 -0
- package/dist/lib/control-projection.js +66 -0
- package/dist/lib/control.d.ts +15 -0
- package/dist/lib/control.js +97 -0
- package/dist/lib/draft-sleep.js +3 -3
- package/dist/lib/execution-sessions.d.ts +17 -0
- package/dist/lib/execution-sessions.js +85 -0
- package/dist/lib/file-state.d.ts +2 -0
- package/dist/lib/file-state.js +114 -46
- package/dist/lib/inspector-actions.d.ts +2 -2
- package/dist/lib/inspector-actions.js +2 -2
- package/dist/lib/inspector-command.js +3 -3
- package/dist/lib/inspector-overlay.d.ts +54 -70
- package/dist/lib/inspector-overlay.js +576 -910
- package/dist/lib/inspector.d.ts +3 -71
- package/dist/lib/inspector.js +19 -665
- package/dist/lib/limits.d.ts +7 -3
- package/dist/lib/limits.js +7 -3
- package/dist/lib/observability.d.ts +16 -16
- package/dist/lib/observability.js +43 -82
- package/dist/lib/prompts.d.ts +1 -1
- package/dist/lib/prompts.js +3 -3
- package/dist/lib/recipe-control.d.ts +7 -0
- package/dist/lib/recipe-control.js +43 -0
- package/dist/lib/recipes-discovery.js +2 -0
- package/dist/lib/recipes-references.d.ts +1 -14
- package/dist/lib/recipes-references.js +6 -21
- package/dist/lib/review-control.d.ts +1 -1
- package/dist/lib/review-control.js +4 -5
- package/dist/lib/review-projection.js +1 -5
- package/dist/lib/run-ui-runtime.js +2 -2
- package/dist/lib/runs-control-delivery.d.ts +28 -0
- package/dist/lib/runs-control-delivery.js +150 -0
- package/dist/lib/runs-controls.d.ts +37 -0
- package/dist/lib/runs-controls.js +146 -0
- package/dist/lib/runs-retention.d.ts +7 -0
- package/dist/lib/runs-retention.js +27 -3
- package/dist/lib/runs-start.js +4 -2
- package/dist/lib/runs-status.js +11 -6
- package/dist/lib/runs-trace.d.ts +24 -0
- package/dist/lib/runs-trace.js +102 -0
- package/dist/lib/runtime-identity.d.ts +7 -0
- package/dist/lib/runtime-identity.js +35 -0
- package/dist/lib/runtime-notifier.d.ts +1 -1
- package/dist/lib/runtime-notifier.js +1 -1
- package/dist/lib/runtime-triage.d.ts +29 -0
- package/dist/lib/runtime-triage.js +76 -0
- package/dist/lib/tool-review-scheduler.js +7 -7
- package/dist/lib/tools-inspect.d.ts +3 -3
- package/dist/lib/tools-inspect.js +241 -707
- package/dist/lib/tools-local.js +2 -10
- package/dist/lib/tools-message.d.ts +6 -7
- package/dist/lib/tools-message.js +95 -396
- package/dist/lib/tools-response.d.ts +1 -5
- package/dist/lib/tools-response.js +5 -48
- package/dist/lib/tools-spawn.js +16 -28
- package/dist/lib/tools.d.ts +1 -1
- package/dist/lib/tools.js +2 -3
- package/dist/lib/trace-projection.d.ts +22 -0
- package/dist/lib/trace-projection.js +185 -0
- package/dist/recipes/draft-review.json +0 -10
- package/dist/recipes/lens-swarm.json +0 -14
- package/dist/recipes/music-player.json +10 -19
- package/dist/recipes/pipeline-architect-coordinator.json +0 -11
- package/dist/recipes/pipeline-artifact-bundle.json +1 -22
- package/dist/recipes/pipeline-artifact-report.json +1 -18
- package/dist/recipes/pipeline-artifact-write.json +1 -18
- package/dist/recipes/pipeline-async-run-ops.json +0 -12
- package/dist/recipes/pipeline-checkpoint-continuation.json +0 -14
- package/dist/recipes/pipeline-development-tasking.json +0 -12
- package/dist/recipes/pipeline-docs-maintenance.json +0 -12
- package/dist/recipes/pipeline-media-library.json +0 -12
- package/dist/recipes/pipeline-quorum-review.json +0 -12
- package/dist/recipes/pipeline-release-readiness.json +0 -12
- package/dist/recipes/pipeline-release-summary.json +0 -12
- package/dist/recipes/pipeline-repo-health.json +0 -12
- package/dist/recipes/pipeline-research-synthesis.json +0 -11
- package/dist/recipes/pipeline-review-readiness.json +0 -12
- package/dist/recipes/resource-locker.json +27 -0
- package/dist/recipes/subagent-artifact.json +0 -9
- package/dist/recipes/subagent-checkpoint.json +0 -10
- package/dist/recipes/subagent-conflict-report.json +0 -11
- package/dist/recipes/subagent-contradiction-map.json +0 -11
- package/dist/recipes/subagent-critic.json +0 -11
- package/dist/recipes/subagent-evidence-map.json +0 -11
- package/dist/recipes/subagent-followup.json +0 -10
- package/dist/recipes/subagent-judge.json +0 -11
- package/dist/recipes/subagent-merge.json +0 -11
- package/dist/recipes/subagent-normalize.json +0 -11
- package/dist/recipes/subagent-plan.json +0 -11
- package/dist/recipes/subagent-preflight.json +0 -11
- package/dist/recipes/subagent-prompt.json +0 -10
- package/dist/recipes/subagent-quorum.json +0 -10
- package/dist/recipes/subagent-review-coordinator.json +0 -14
- package/dist/recipes/subagent-review.json +0 -11
- package/dist/recipes/subagent-task-card.json +0 -11
- package/dist/recipes/subagent-tools.json +0 -10
- package/dist/recipes/subagent-verify.json +0 -11
- package/dist/recipes/subagents-prompts.json +0 -10
- package/dist/recipes/tool-review.json +0 -10
- package/dist/scripts/async-runner.mjs +25 -25
- package/dist/scripts/conformance.mjs +4 -2
- package/dist/scripts/locker.mjs +196 -69
- package/dist/scripts/music-player.mjs +162 -159
- package/dist/scripts/recipe-utils.mjs +6 -96
- package/dist/scripts/release-gates.mjs +91 -0
- package/dist/scripts/validate-recipe.mjs +8 -57
- package/dist/skills/actors/SKILL.md +57 -265
- package/dist/skills/swarm/SKILL.md +10 -34
- package/docs/0.43-baseline.md +39 -0
- package/docs/README.md +4 -6
- package/docs/actor-inspector.md +26 -64
- package/docs/async-runs.md +81 -328
- package/docs/command-templates.md +8 -118
- package/docs/recipe-library.md +55 -182
- package/docs/releasing.md +28 -0
- package/docs/template-recipes.md +76 -289
- package/docs/tool-registry.md +41 -161
- package/fixtures/protocol/control-endpoint.json +6 -0
- package/fixtures/protocol/control-record.json +9 -0
- package/fixtures/protocol/recipe-summary.json +4 -12
- package/fixtures/protocol/trace-event.json +9 -0
- package/index.ts +1 -1
- package/lib/async-runs.ts +218 -204
- package/lib/automatic-review-runtime.ts +7 -7
- package/lib/command-templates.ts +44 -4
- package/lib/control-projection.ts +105 -0
- package/lib/control.ts +117 -0
- package/lib/draft-sleep.ts +3 -3
- package/lib/execution-sessions.ts +111 -0
- package/lib/file-state.ts +84 -64
- package/lib/inspector-actions.ts +2 -2
- package/lib/inspector-command.ts +3 -3
- package/lib/inspector-overlay.ts +617 -1126
- package/lib/inspector.ts +46 -979
- package/lib/limits.ts +7 -3
- package/lib/observability.ts +60 -101
- package/lib/prompts.ts +3 -3
- package/lib/recipe-control.ts +52 -0
- package/lib/recipes-discovery.ts +2 -0
- package/lib/recipes-references.ts +9 -45
- package/lib/review-control.ts +4 -5
- package/lib/review-projection.ts +1 -5
- package/lib/run-ui-runtime.ts +2 -2
- package/lib/runs-control-delivery.ts +209 -0
- package/lib/runs-controls.ts +213 -0
- package/lib/runs-retention.ts +38 -3
- package/lib/runs-start.ts +4 -2
- package/lib/runs-status.ts +11 -6
- package/lib/runs-trace.ts +136 -0
- package/lib/runtime-identity.ts +39 -0
- package/lib/runtime-notifier.ts +1 -1
- package/lib/runtime-triage.ts +120 -0
- package/lib/tool-review-scheduler.ts +7 -7
- package/lib/tools-inspect.ts +283 -900
- package/lib/tools-local.ts +2 -12
- package/lib/tools-message.ts +112 -520
- package/lib/tools-response.ts +5 -64
- package/lib/tools-spawn.ts +16 -32
- package/lib/tools.ts +5 -6
- package/lib/trace-projection.ts +244 -0
- package/package.json +2 -1
- package/recipes/draft-review.json +0 -10
- package/recipes/lens-swarm.json +0 -14
- package/recipes/music-player.json +10 -19
- package/recipes/pipeline-architect-coordinator.json +0 -11
- package/recipes/pipeline-artifact-bundle.json +1 -22
- package/recipes/pipeline-artifact-report.json +1 -18
- package/recipes/pipeline-artifact-write.json +1 -18
- package/recipes/pipeline-async-run-ops.json +0 -12
- package/recipes/pipeline-checkpoint-continuation.json +0 -14
- package/recipes/pipeline-development-tasking.json +0 -12
- package/recipes/pipeline-docs-maintenance.json +0 -12
- package/recipes/pipeline-media-library.json +0 -12
- package/recipes/pipeline-quorum-review.json +0 -12
- package/recipes/pipeline-release-readiness.json +0 -12
- package/recipes/pipeline-release-summary.json +0 -12
- package/recipes/pipeline-repo-health.json +0 -12
- package/recipes/pipeline-research-synthesis.json +0 -11
- package/recipes/pipeline-review-readiness.json +0 -12
- package/recipes/resource-locker.json +27 -0
- package/recipes/subagent-artifact.json +0 -9
- package/recipes/subagent-checkpoint.json +0 -10
- package/recipes/subagent-conflict-report.json +0 -11
- package/recipes/subagent-contradiction-map.json +0 -11
- package/recipes/subagent-critic.json +0 -11
- package/recipes/subagent-evidence-map.json +0 -11
- package/recipes/subagent-followup.json +0 -10
- package/recipes/subagent-judge.json +0 -11
- package/recipes/subagent-merge.json +0 -11
- package/recipes/subagent-normalize.json +0 -11
- package/recipes/subagent-plan.json +0 -11
- package/recipes/subagent-preflight.json +0 -11
- package/recipes/subagent-prompt.json +0 -10
- package/recipes/subagent-quorum.json +0 -10
- package/recipes/subagent-review-coordinator.json +0 -14
- package/recipes/subagent-review.json +0 -11
- package/recipes/subagent-task-card.json +0 -11
- package/recipes/subagent-tools.json +0 -10
- package/recipes/subagent-verify.json +0 -11
- package/recipes/subagents-prompts.json +0 -10
- package/recipes/tool-review.json +0 -10
- package/scripts/async-runner.mjs +25 -25
- package/scripts/conformance.mjs +4 -2
- package/scripts/locker.mjs +196 -69
- package/scripts/music-player.mjs +162 -159
- package/scripts/recipe-utils.mjs +6 -96
- package/scripts/release-gates.mjs +91 -0
- package/scripts/validate-recipe.mjs +8 -57
- package/skills/actors/SKILL.md +57 -265
- package/skills/swarm/SKILL.md +10 -34
- package/dist/fixtures/protocol/actor-message-branch.json +0 -13
- package/dist/fixtures/protocol/mailbox-contract.json +0 -15
- package/dist/fixtures/protocol/room-message.json +0 -11
- package/dist/fixtures/protocol/room-roster.json +0 -11
- package/dist/fixtures/protocol/run-inbox-message.json +0 -9
- package/dist/fixtures/protocol/run-outbox-event.json +0 -9
- package/dist/lib/mailbox-loop.d.ts +0 -41
- package/dist/lib/mailbox-loop.js +0 -60
- package/dist/lib/messages.d.ts +0 -25
- package/dist/lib/messages.js +0 -122
- package/dist/lib/rooms.d.ts +0 -104
- package/dist/lib/rooms.js +0 -647
- package/dist/lib/runs-mailbox.d.ts +0 -25
- package/dist/lib/runs-mailbox.js +0 -146
- package/dist/lib/runs-messages.d.ts +0 -15
- package/dist/lib/runs-messages.js +0 -179
- package/dist/lib/runs-outbox.d.ts +0 -41
- package/dist/lib/runs-outbox.js +0 -87
- package/dist/lib/tools-mailbox.d.ts +0 -8
- package/dist/lib/tools-mailbox.js +0 -48
- package/dist/recipes/actor-worker.json +0 -39
- package/dist/recipes/coordinator-locker.json +0 -45
- package/dist/recipes/locker.json +0 -45
- package/dist/recipes/pipeline-room-swarm.json +0 -50
- package/dist/recipes/subagent-message.json +0 -32
- package/dist/recipes/utility-actor-message.json +0 -23
- package/dist/scripts/actor-worker.mjs +0 -214
- package/dist/scripts/coordinator.mjs +0 -799
- package/docs/actor-messages.md +0 -225
- package/docs/actors-deep-reference.md +0 -66
- package/docs/component-recipes.md +0 -148
- package/docs/task-first-recipes.md +0 -263
- package/fixtures/protocol/actor-message-branch.json +0 -13
- package/fixtures/protocol/mailbox-contract.json +0 -15
- package/fixtures/protocol/room-message.json +0 -11
- package/fixtures/protocol/room-roster.json +0 -11
- package/fixtures/protocol/run-inbox-message.json +0 -9
- package/fixtures/protocol/run-outbox-event.json +0 -9
- package/lib/mailbox-loop.ts +0 -144
- package/lib/messages.ts +0 -151
- package/lib/rooms.ts +0 -939
- package/lib/runs-mailbox.ts +0 -208
- package/lib/runs-messages.ts +0 -252
- package/lib/runs-outbox.ts +0 -144
- package/lib/tools-mailbox.ts +0 -56
- package/recipes/actor-worker.json +0 -39
- package/recipes/coordinator-locker.json +0 -45
- package/recipes/locker.json +0 -45
- package/recipes/pipeline-room-swarm.json +0 -50
- package/recipes/subagent-message.json +0 -32
- package/recipes/utility-actor-message.json +0 -23
- package/scripts/actor-worker.mjs +0 -214
- package/scripts/coordinator.mjs +0 -799
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Run Control delivery.
|
|
3
|
+
* Zones: generation-fenced endpoint resolution, FIFO/named-pipe writes, durable outcomes
|
|
4
|
+
* Owns delivery of persisted actor-local Controls to ready service endpoints.
|
|
5
|
+
*/
|
|
6
|
+
export interface RunControlEndpoint {
|
|
7
|
+
path: string;
|
|
8
|
+
type: "fifo" | "named-pipe";
|
|
9
|
+
}
|
|
10
|
+
export interface DeliverRunControlOptions {
|
|
11
|
+
namedPipeSend?: (path: string, payload: string) => Promise<number>;
|
|
12
|
+
platform?: NodeJS.Platform;
|
|
13
|
+
}
|
|
14
|
+
export interface DeliverRunControlRequest {
|
|
15
|
+
action: string;
|
|
16
|
+
input?: unknown;
|
|
17
|
+
run_instance_id: string;
|
|
18
|
+
}
|
|
19
|
+
export declare function encodeRunControlWire(wire: {
|
|
20
|
+
action: string;
|
|
21
|
+
id: string;
|
|
22
|
+
input?: unknown;
|
|
23
|
+
}): {
|
|
24
|
+
bytes: number;
|
|
25
|
+
payload: string;
|
|
26
|
+
};
|
|
27
|
+
export declare function readRunControlEndpoint(stateDir: string, runInstanceId: string): RunControlEndpoint | undefined;
|
|
28
|
+
export declare function deliverRunControl(run: string, stateDir: string, request: DeliverRunControlRequest, options?: DeliverRunControlOptions): Promise<Record<string, unknown>>;
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Run Control delivery.
|
|
3
|
+
* Zones: generation-fenced endpoint resolution, FIFO/named-pipe writes, durable outcomes
|
|
4
|
+
* Owns delivery of persisted actor-local Controls to ready service endpoints.
|
|
5
|
+
*/
|
|
6
|
+
import { closeSync, constants, existsSync, openSync, statSync, writeSync, } from "node:fs";
|
|
7
|
+
import { createConnection } from "node:net";
|
|
8
|
+
import { join } from "node:path";
|
|
9
|
+
import * as Control from "./control.js";
|
|
10
|
+
import * as Limits from "./limits.js";
|
|
11
|
+
import { appendRunControlInStateDir, updateRunControlStatusInStateDir, } from "./runs-controls.js";
|
|
12
|
+
import { readJsonFileResilient } from "./state-readers.js";
|
|
13
|
+
const PREFLIGHT_CONTROL_ID = "00000000-0000-4000-8000-000000000000";
|
|
14
|
+
export function encodeRunControlWire(wire) {
|
|
15
|
+
const payload = `${JSON.stringify({
|
|
16
|
+
id: wire.id,
|
|
17
|
+
action: wire.action,
|
|
18
|
+
...(wire.input !== undefined ? { input: wire.input } : {}),
|
|
19
|
+
})}\n`;
|
|
20
|
+
const bytes = Buffer.byteLength(payload);
|
|
21
|
+
if (bytes > Limits.CONTROL_WIRE_MAX_BYTES) {
|
|
22
|
+
throw new Error(`Run Control wire record exceeds the ${Limits.CONTROL_WIRE_MAX_BYTES}-byte portable bound`);
|
|
23
|
+
}
|
|
24
|
+
return { bytes, payload };
|
|
25
|
+
}
|
|
26
|
+
export function readRunControlEndpoint(stateDir, runInstanceId) {
|
|
27
|
+
const endpoint = readJsonFileResilient(join(stateDir, "control-endpoint.json"), {}).value;
|
|
28
|
+
if (endpoint.run_instance_id !== runInstanceId)
|
|
29
|
+
return undefined;
|
|
30
|
+
if ((endpoint.type === "fifo" || endpoint.type === "named-pipe") &&
|
|
31
|
+
typeof endpoint.path === "string" &&
|
|
32
|
+
endpoint.path.trim()) {
|
|
33
|
+
return { path: endpoint.path, type: endpoint.type };
|
|
34
|
+
}
|
|
35
|
+
return undefined;
|
|
36
|
+
}
|
|
37
|
+
function sendToFifo(endpoint, payload) {
|
|
38
|
+
if (!existsSync(endpoint.path))
|
|
39
|
+
throw new Error(`Run Control FIFO not found: ${endpoint.path}`);
|
|
40
|
+
const stat = statSync(endpoint.path);
|
|
41
|
+
if ((stat.mode & constants.S_IFMT) !== constants.S_IFIFO) {
|
|
42
|
+
throw new Error(`Run Control endpoint is not a FIFO: ${endpoint.path}`);
|
|
43
|
+
}
|
|
44
|
+
let fd;
|
|
45
|
+
try {
|
|
46
|
+
fd = openSync(endpoint.path, constants.O_WRONLY | constants.O_NONBLOCK);
|
|
47
|
+
return writeSync(fd, payload);
|
|
48
|
+
}
|
|
49
|
+
finally {
|
|
50
|
+
if (fd !== undefined)
|
|
51
|
+
closeSync(fd);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
function sendToNamedPipe(endpoint, payload, send) {
|
|
55
|
+
if (send)
|
|
56
|
+
return send(endpoint.path, payload);
|
|
57
|
+
return new Promise((resolve, reject) => {
|
|
58
|
+
const socket = createConnection(endpoint.path);
|
|
59
|
+
let settled = false;
|
|
60
|
+
const timeout = setTimeout(() => {
|
|
61
|
+
if (settled)
|
|
62
|
+
return;
|
|
63
|
+
settled = true;
|
|
64
|
+
socket.destroy();
|
|
65
|
+
reject(new Error("named pipe connection timed out"));
|
|
66
|
+
}, 5000);
|
|
67
|
+
const finish = (error) => {
|
|
68
|
+
if (settled)
|
|
69
|
+
return;
|
|
70
|
+
settled = true;
|
|
71
|
+
clearTimeout(timeout);
|
|
72
|
+
if (error)
|
|
73
|
+
reject(error);
|
|
74
|
+
else
|
|
75
|
+
resolve(Buffer.byteLength(payload));
|
|
76
|
+
};
|
|
77
|
+
socket.on("error", finish);
|
|
78
|
+
socket.on("connect", () => socket.end(payload, () => finish()));
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
export async function deliverRunControl(run, stateDir, request, options = {}) {
|
|
82
|
+
const action = Control.normalizeControlAction(request.action);
|
|
83
|
+
const input = request.input === undefined
|
|
84
|
+
? undefined
|
|
85
|
+
: Control.normalizeControlInput(request.input);
|
|
86
|
+
const normalizedRequest = {
|
|
87
|
+
action,
|
|
88
|
+
...(input !== undefined ? { input } : {}),
|
|
89
|
+
run_instance_id: request.run_instance_id,
|
|
90
|
+
};
|
|
91
|
+
encodeRunControlWire({
|
|
92
|
+
id: PREFLIGHT_CONTROL_ID,
|
|
93
|
+
action,
|
|
94
|
+
...(input !== undefined ? { input } : {}),
|
|
95
|
+
});
|
|
96
|
+
const control = appendRunControlInStateDir(stateDir, normalizedRequest);
|
|
97
|
+
const endpoint = readRunControlEndpoint(stateDir, request.run_instance_id);
|
|
98
|
+
if (!endpoint) {
|
|
99
|
+
updateRunControlStatusInStateDir(stateDir, control.id, "failed", { error: "control endpoint is not ready for this Run generation" }, ["queued", "delivered"]);
|
|
100
|
+
throw Object.assign(new Error("Run Control endpoint is not ready."), {
|
|
101
|
+
action,
|
|
102
|
+
control_id: control.id,
|
|
103
|
+
reason: "endpoint_not_ready",
|
|
104
|
+
run,
|
|
105
|
+
run_instance_id: request.run_instance_id,
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
try {
|
|
109
|
+
const { bytes, payload } = encodeRunControlWire({
|
|
110
|
+
id: control.id,
|
|
111
|
+
action,
|
|
112
|
+
...(input !== undefined ? { input } : {}),
|
|
113
|
+
});
|
|
114
|
+
let written;
|
|
115
|
+
if (endpoint.type === "fifo") {
|
|
116
|
+
if ((options.platform ?? process.platform) === "win32") {
|
|
117
|
+
throw new Error("FIFO Control delivery is unsupported on native Windows");
|
|
118
|
+
}
|
|
119
|
+
written = sendToFifo(endpoint, payload);
|
|
120
|
+
}
|
|
121
|
+
else {
|
|
122
|
+
written = await sendToNamedPipe(endpoint, payload, options.namedPipeSend);
|
|
123
|
+
}
|
|
124
|
+
if (written !== bytes) {
|
|
125
|
+
throw new Error(`Run Control endpoint wrote ${written} of ${bytes} bytes`);
|
|
126
|
+
}
|
|
127
|
+
updateRunControlStatusInStateDir(stateDir, control.id, "delivered");
|
|
128
|
+
return {
|
|
129
|
+
action,
|
|
130
|
+
bytes,
|
|
131
|
+
control_id: control.id,
|
|
132
|
+
delivery: "delivered",
|
|
133
|
+
endpoint_type: endpoint.type,
|
|
134
|
+
run,
|
|
135
|
+
run_instance_id: request.run_instance_id,
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
catch (error) {
|
|
139
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
140
|
+
updateRunControlStatusInStateDir(stateDir, control.id, "failed", { error: reason }, ["queued", "delivered"]);
|
|
141
|
+
throw Object.assign(new Error(`Run Control delivery failed: ${reason}`), {
|
|
142
|
+
action,
|
|
143
|
+
control_id: control.id,
|
|
144
|
+
endpoint_type: endpoint.type,
|
|
145
|
+
reason: "delivery_failed",
|
|
146
|
+
run,
|
|
147
|
+
run_instance_id: request.run_instance_id,
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Run Control journal.
|
|
3
|
+
* Zones: durable Control records, claim locks, status transitions, bounded compaction
|
|
4
|
+
* Owns Run-local Control persistence; owner/generation authorization and transport delivery stay in lifecycle adapters.
|
|
5
|
+
*/
|
|
6
|
+
import { type JsonlReadResult } from "./state-readers.ts";
|
|
7
|
+
export declare const RUN_CONTROL_TERMINAL_LIMIT = 128;
|
|
8
|
+
export type RunControlStatus = "queued" | "delivered" | "claimed" | "handled" | "failed";
|
|
9
|
+
export interface RunControlRecord {
|
|
10
|
+
id: string;
|
|
11
|
+
run_instance_id: string;
|
|
12
|
+
action: string;
|
|
13
|
+
input?: unknown;
|
|
14
|
+
status: RunControlStatus;
|
|
15
|
+
queued_at: string;
|
|
16
|
+
delivered_at?: string;
|
|
17
|
+
claimed_at?: string;
|
|
18
|
+
handled_at?: string;
|
|
19
|
+
failed_at?: string;
|
|
20
|
+
error?: string;
|
|
21
|
+
}
|
|
22
|
+
export interface ProcessRunControlsResult {
|
|
23
|
+
claimed: number;
|
|
24
|
+
failed: number;
|
|
25
|
+
handled: number;
|
|
26
|
+
}
|
|
27
|
+
export declare function runControlsFile(stateDir: string): string;
|
|
28
|
+
export declare function readRunControlJournalFromStateDir(stateDir: string): JsonlReadResult<unknown>;
|
|
29
|
+
export declare function readRunControlsFromStateDir(stateDir: string): RunControlRecord[];
|
|
30
|
+
export declare function appendRunControlInStateDir(stateDir: string, request: {
|
|
31
|
+
run_instance_id: string;
|
|
32
|
+
action: string;
|
|
33
|
+
input?: unknown;
|
|
34
|
+
}): RunControlRecord;
|
|
35
|
+
export declare function updateRunControlStatusInStateDir(stateDir: string, id: string, nextStatus: RunControlStatus, metadata?: Pick<RunControlRecord, "error">, expectedStatuses?: readonly RunControlStatus[]): boolean;
|
|
36
|
+
export declare function claimRunControlInStateDir(stateDir: string, runInstanceId: string): RunControlRecord | undefined;
|
|
37
|
+
export declare function processRunControlsInStateDir(stateDir: string, runInstanceId: string, handler: (control: RunControlRecord) => Promise<void> | void, limit?: number): Promise<ProcessRunControlsResult>;
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Run Control journal.
|
|
3
|
+
* Zones: durable Control records, claim locks, status transitions, bounded compaction
|
|
4
|
+
* Owns Run-local Control persistence; owner/generation authorization and transport delivery stay in lifecycle adapters.
|
|
5
|
+
*/
|
|
6
|
+
import { randomUUID } from "node:crypto";
|
|
7
|
+
import { writeFileSync } from "node:fs";
|
|
8
|
+
import { join } from "node:path";
|
|
9
|
+
import { acquireFileMutationLock, writeTextAtomic } from "./file-state.js";
|
|
10
|
+
import { readJsonlFileResilient, } from "./state-readers.js";
|
|
11
|
+
export const RUN_CONTROL_TERMINAL_LIMIT = 128;
|
|
12
|
+
const TERMINAL_STATUSES = new Set(["handled", "failed"]);
|
|
13
|
+
export function runControlsFile(stateDir) {
|
|
14
|
+
return join(stateDir, "controls.jsonl");
|
|
15
|
+
}
|
|
16
|
+
export function readRunControlJournalFromStateDir(stateDir) {
|
|
17
|
+
return readJsonlFileResilient(runControlsFile(stateDir));
|
|
18
|
+
}
|
|
19
|
+
export function readRunControlsFromStateDir(stateDir) {
|
|
20
|
+
return readRunControlJournalFromStateDir(stateDir).records;
|
|
21
|
+
}
|
|
22
|
+
function acquireRunControlsLock(stateDir) {
|
|
23
|
+
return acquireFileMutationLock(runControlsFile(stateDir));
|
|
24
|
+
}
|
|
25
|
+
function compactRunControls(records) {
|
|
26
|
+
const active = records.filter((record) => !TERMINAL_STATUSES.has(record.status));
|
|
27
|
+
const terminal = records
|
|
28
|
+
.filter((record) => TERMINAL_STATUSES.has(record.status))
|
|
29
|
+
.slice(-RUN_CONTROL_TERMINAL_LIMIT);
|
|
30
|
+
const retained = new Set([...active, ...terminal]);
|
|
31
|
+
return records.filter((record) => retained.has(record));
|
|
32
|
+
}
|
|
33
|
+
function writeRunControls(stateDir, records) {
|
|
34
|
+
const compacted = compactRunControls(records);
|
|
35
|
+
writeTextAtomic(runControlsFile(stateDir), compacted.length
|
|
36
|
+
? `${compacted.map((record) => JSON.stringify(record)).join("\n")}\n`
|
|
37
|
+
: "");
|
|
38
|
+
}
|
|
39
|
+
export function appendRunControlInStateDir(stateDir, request) {
|
|
40
|
+
const record = {
|
|
41
|
+
id: randomUUID(),
|
|
42
|
+
run_instance_id: request.run_instance_id,
|
|
43
|
+
action: request.action,
|
|
44
|
+
...(request.input !== undefined ? { input: request.input } : {}),
|
|
45
|
+
status: "queued",
|
|
46
|
+
queued_at: new Date().toISOString(),
|
|
47
|
+
};
|
|
48
|
+
const releaseLock = acquireRunControlsLock(stateDir);
|
|
49
|
+
try {
|
|
50
|
+
writeFileSync(runControlsFile(stateDir), `${JSON.stringify(record)}\n`, {
|
|
51
|
+
flag: "a",
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
finally {
|
|
55
|
+
releaseLock();
|
|
56
|
+
}
|
|
57
|
+
return record;
|
|
58
|
+
}
|
|
59
|
+
export function updateRunControlStatusInStateDir(stateDir, id, nextStatus, metadata = {}, expectedStatuses) {
|
|
60
|
+
const releaseLock = acquireRunControlsLock(stateDir);
|
|
61
|
+
try {
|
|
62
|
+
const records = readRunControlsFromStateDir(stateDir);
|
|
63
|
+
const index = records.findIndex((record) => record.id === id);
|
|
64
|
+
if (index < 0)
|
|
65
|
+
return false;
|
|
66
|
+
const record = records[index];
|
|
67
|
+
if (expectedStatuses && !expectedStatuses.includes(record.status))
|
|
68
|
+
return false;
|
|
69
|
+
if (record.status === nextStatus)
|
|
70
|
+
return false;
|
|
71
|
+
const timestamp = new Date().toISOString();
|
|
72
|
+
if (nextStatus === "delivered") {
|
|
73
|
+
if (record.delivered_at)
|
|
74
|
+
return false;
|
|
75
|
+
records[index] = {
|
|
76
|
+
...record,
|
|
77
|
+
delivered_at: timestamp,
|
|
78
|
+
...(record.status === "queued" ? { status: "delivered" } : {}),
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
else {
|
|
82
|
+
if (TERMINAL_STATUSES.has(record.status) || nextStatus === "queued")
|
|
83
|
+
return false;
|
|
84
|
+
if ((nextStatus === "claimed" &&
|
|
85
|
+
record.status !== "queued" &&
|
|
86
|
+
record.status !== "delivered") ||
|
|
87
|
+
(nextStatus === "handled" && record.status !== "claimed")) {
|
|
88
|
+
return false;
|
|
89
|
+
}
|
|
90
|
+
const timestampKey = `${nextStatus}_at`;
|
|
91
|
+
records[index] = {
|
|
92
|
+
...record,
|
|
93
|
+
...metadata,
|
|
94
|
+
[timestampKey]: timestamp,
|
|
95
|
+
status: nextStatus,
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
writeRunControls(stateDir, records);
|
|
99
|
+
return true;
|
|
100
|
+
}
|
|
101
|
+
finally {
|
|
102
|
+
releaseLock();
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
export function claimRunControlInStateDir(stateDir, runInstanceId) {
|
|
106
|
+
const releaseLock = acquireRunControlsLock(stateDir);
|
|
107
|
+
try {
|
|
108
|
+
const records = readRunControlsFromStateDir(stateDir);
|
|
109
|
+
const index = records.findIndex((record) => record.run_instance_id === runInstanceId &&
|
|
110
|
+
(record.status === "queued" || record.status === "delivered"));
|
|
111
|
+
if (index < 0)
|
|
112
|
+
return undefined;
|
|
113
|
+
const claimed = {
|
|
114
|
+
...records[index],
|
|
115
|
+
claimed_at: new Date().toISOString(),
|
|
116
|
+
status: "claimed",
|
|
117
|
+
};
|
|
118
|
+
records[index] = claimed;
|
|
119
|
+
writeRunControls(stateDir, records);
|
|
120
|
+
return claimed;
|
|
121
|
+
}
|
|
122
|
+
finally {
|
|
123
|
+
releaseLock();
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
export async function processRunControlsInStateDir(stateDir, runInstanceId, handler, limit = 1) {
|
|
127
|
+
const result = { claimed: 0, failed: 0, handled: 0 };
|
|
128
|
+
for (let index = 0; index < Math.max(1, limit); index += 1) {
|
|
129
|
+
const control = claimRunControlInStateDir(stateDir, runInstanceId);
|
|
130
|
+
if (!control)
|
|
131
|
+
break;
|
|
132
|
+
result.claimed += 1;
|
|
133
|
+
try {
|
|
134
|
+
await handler(control);
|
|
135
|
+
if (updateRunControlStatusInStateDir(stateDir, control.id, "handled", {}, ["claimed"])) {
|
|
136
|
+
result.handled += 1;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
catch (error) {
|
|
140
|
+
if (updateRunControlStatusInStateDir(stateDir, control.id, "failed", { error: error instanceof Error ? error.message : String(error) }, ["claimed"])) {
|
|
141
|
+
result.failed += 1;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
return result;
|
|
146
|
+
}
|
|
@@ -3,6 +3,13 @@
|
|
|
3
3
|
* Owns: terminal-run archive and prune filesystem behavior.
|
|
4
4
|
*/
|
|
5
5
|
import { cpSync } from "node:fs";
|
|
6
|
+
export type RunRetentionAction = "archive" | "prune";
|
|
7
|
+
export type RunRetentionOutcome = "queued" | "handled" | "failed";
|
|
8
|
+
export declare function appendRunRetentionEvidence(status: Record<string, unknown>, action: RunRetentionAction, outcome: RunRetentionOutcome, options?: {
|
|
9
|
+
error?: string;
|
|
10
|
+
id?: string;
|
|
11
|
+
result?: Record<string, unknown>;
|
|
12
|
+
}): string;
|
|
6
13
|
export declare function archiveTerminalRun(status: Record<string, unknown>): Record<string, unknown>;
|
|
7
14
|
export declare function pruneTerminalRun(status: Record<string, unknown>, options?: {
|
|
8
15
|
preserveArtifacts?: boolean;
|
|
@@ -2,13 +2,37 @@
|
|
|
2
2
|
* Async run retention operations.
|
|
3
3
|
* Owns: terminal-run archive and prune filesystem behavior.
|
|
4
4
|
*/
|
|
5
|
-
import { createHash } from "node:crypto";
|
|
6
|
-
import { cpSync, mkdirSync, renameSync, rmSync } from "node:fs";
|
|
5
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
6
|
+
import { appendFileSync, cpSync, mkdirSync, renameSync, rmSync } from "node:fs";
|
|
7
7
|
import { basename, dirname, join } from "node:path";
|
|
8
|
-
import { writeJsonAtomic } from "./file-state.js";
|
|
8
|
+
import { acquireFileMutationLock, writeJsonAtomic } from "./file-state.js";
|
|
9
9
|
import { resolveArtifactManifest, } from "./runs-artifacts.js";
|
|
10
10
|
import { safeRunId } from "./runs-identity.js";
|
|
11
11
|
import { assertOwnedRunStateDirectory } from "./runs-ownership.js";
|
|
12
|
+
export function appendRunRetentionEvidence(status, action, outcome, options = {}) {
|
|
13
|
+
const stateDir = String(status.state_dir);
|
|
14
|
+
const path = join(dirname(stateDir), "retention.jsonl");
|
|
15
|
+
const id = options.id ?? randomUUID();
|
|
16
|
+
const release = acquireFileMutationLock(path);
|
|
17
|
+
try {
|
|
18
|
+
appendFileSync(path, `${JSON.stringify({
|
|
19
|
+
action,
|
|
20
|
+
...(options.error ? { error: options.error } : {}),
|
|
21
|
+
id,
|
|
22
|
+
outcome,
|
|
23
|
+
...(options.result ? { result: options.result } : {}),
|
|
24
|
+
run: String(status.run ?? basename(stateDir)),
|
|
25
|
+
...(typeof status.run_instance_id === "string"
|
|
26
|
+
? { run_instance_id: status.run_instance_id }
|
|
27
|
+
: {}),
|
|
28
|
+
ts: new Date().toISOString(),
|
|
29
|
+
})}\n`);
|
|
30
|
+
}
|
|
31
|
+
finally {
|
|
32
|
+
release();
|
|
33
|
+
}
|
|
34
|
+
return id;
|
|
35
|
+
}
|
|
12
36
|
function retainedArtifactFilename(name, path) {
|
|
13
37
|
const readableName = name.replace(/[^A-Za-z0-9_.-]+/g, "_") || "artifact";
|
|
14
38
|
const identity = createHash("sha256")
|
package/dist/lib/runs-start.js
CHANGED
|
@@ -45,15 +45,17 @@ export function prepareStateDirForStart(stateDir, readJson, _runnerPath) {
|
|
|
45
45
|
}
|
|
46
46
|
}
|
|
47
47
|
for (const file of [
|
|
48
|
+
"control-endpoint.json",
|
|
49
|
+
"controls.jsonl",
|
|
48
50
|
"events.jsonl",
|
|
49
|
-
"
|
|
50
|
-
"outbox.jsonl",
|
|
51
|
+
"execution.json",
|
|
51
52
|
"progress.json",
|
|
52
53
|
"result.json",
|
|
53
54
|
"stderr.log",
|
|
54
55
|
"stdout.log",
|
|
55
56
|
"terminal-delivery-failure.json",
|
|
56
57
|
"terminal-handled.json",
|
|
58
|
+
"trace.jsonl",
|
|
57
59
|
]) {
|
|
58
60
|
rmSync(join(stateDir, file), { force: true });
|
|
59
61
|
}
|
package/dist/lib/runs-status.js
CHANGED
|
@@ -7,8 +7,15 @@ import { join } from "node:path";
|
|
|
7
7
|
import { verifyRunProcessIdentity, } from "./runs-process.js";
|
|
8
8
|
import { readJsonlFileResilient } from "./state-readers.js";
|
|
9
9
|
function getInterruptedRunStatus(stateDir) {
|
|
10
|
-
const
|
|
11
|
-
for (const event of
|
|
10
|
+
const trace = readJsonlFileResilient(join(stateDir, "trace.jsonl")).records.slice(-200);
|
|
11
|
+
for (const event of trace.reverse()) {
|
|
12
|
+
if (event.kind === "run.kill")
|
|
13
|
+
return "killed";
|
|
14
|
+
if (event.kind === "run.cancel")
|
|
15
|
+
return "cancelled";
|
|
16
|
+
}
|
|
17
|
+
const legacyEvents = readJsonlFileResilient(join(stateDir, "events.jsonl")).records.slice(-200);
|
|
18
|
+
for (const event of legacyEvents.reverse()) {
|
|
12
19
|
if (event.event === "run.kill")
|
|
13
20
|
return "killed";
|
|
14
21
|
if (event.event === "run.cancel")
|
|
@@ -33,10 +40,8 @@ export function buildRunStatus(stateDir, runOrDir, meta, readJson, _runnerPath,
|
|
|
33
40
|
const terminalDeliveryFailure = readJson(join(stateDir, "terminal-delivery-failure.json"));
|
|
34
41
|
return {
|
|
35
42
|
...meta,
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
inboxFile: join(stateDir, "inbox.jsonl"),
|
|
39
|
-
outboxFile: join(stateDir, "outbox.jsonl"),
|
|
43
|
+
traceFile: join(stateDir, "trace.jsonl"),
|
|
44
|
+
executionFile: join(stateDir, "execution.json"),
|
|
40
45
|
process_identity_status: processIdentity.status,
|
|
41
46
|
progress: readJson(join(stateDir, "progress.json")) || null,
|
|
42
47
|
result: result || null,
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Run Trace event journal.
|
|
3
|
+
* Zones: structured event validation, token-locked append-only writes, resilient bounded reads
|
|
4
|
+
* Owns canonical Run-local Trace persistence; lifecycle projection and owner attention delivery stay in adapters.
|
|
5
|
+
*/
|
|
6
|
+
export interface TraceEvent {
|
|
7
|
+
id: string;
|
|
8
|
+
ts: string;
|
|
9
|
+
kind: string;
|
|
10
|
+
summary?: string;
|
|
11
|
+
data?: unknown;
|
|
12
|
+
level?: "info" | "warning" | "error";
|
|
13
|
+
attention?: "notify" | "followup";
|
|
14
|
+
}
|
|
15
|
+
export interface AppendTraceEventInput {
|
|
16
|
+
kind: string;
|
|
17
|
+
summary?: string;
|
|
18
|
+
data?: unknown;
|
|
19
|
+
level?: TraceEvent["level"];
|
|
20
|
+
attention?: TraceEvent["attention"];
|
|
21
|
+
}
|
|
22
|
+
export declare function runTraceFile(stateDir: string): string;
|
|
23
|
+
export declare function appendRunTraceEvent(stateDir: string, input: AppendTraceEventInput): TraceEvent;
|
|
24
|
+
export declare function readRunTraceEvents(stateDir: string, limit?: number): TraceEvent[];
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Run Trace event journal.
|
|
3
|
+
* Zones: structured event validation, token-locked append-only writes, resilient bounded reads
|
|
4
|
+
* Owns canonical Run-local Trace persistence; lifecycle projection and owner attention delivery stay in adapters.
|
|
5
|
+
*/
|
|
6
|
+
import { randomUUID } from "node:crypto";
|
|
7
|
+
import { writeFileSync } from "node:fs";
|
|
8
|
+
import { join } from "node:path";
|
|
9
|
+
import { withFileMutationLock } from "./file-state.js";
|
|
10
|
+
import * as Limits from "./limits.js";
|
|
11
|
+
import { readJsonlFileResilient } from "./state-readers.js";
|
|
12
|
+
const KIND_PATTERN = /^[a-z][a-z0-9_-]*(?:\.[a-z0-9_-]+)*$/;
|
|
13
|
+
const TRACE_INPUT_FIELDS = new Set([
|
|
14
|
+
"attention",
|
|
15
|
+
"data",
|
|
16
|
+
"kind",
|
|
17
|
+
"level",
|
|
18
|
+
"summary",
|
|
19
|
+
]);
|
|
20
|
+
const FORBIDDEN_FIELDS = new Set([
|
|
21
|
+
"body",
|
|
22
|
+
"correlation_id",
|
|
23
|
+
"from",
|
|
24
|
+
"metadata",
|
|
25
|
+
"reply_to",
|
|
26
|
+
"to",
|
|
27
|
+
"type",
|
|
28
|
+
]);
|
|
29
|
+
export function runTraceFile(stateDir) {
|
|
30
|
+
return join(stateDir, "trace.jsonl");
|
|
31
|
+
}
|
|
32
|
+
function normalizeTraceEventInput(input) {
|
|
33
|
+
if (!input || typeof input !== "object" || Array.isArray(input)) {
|
|
34
|
+
throw new Error("Trace event must be an object");
|
|
35
|
+
}
|
|
36
|
+
const record = input;
|
|
37
|
+
const forbidden = Object.keys(record).filter((key) => FORBIDDEN_FIELDS.has(key));
|
|
38
|
+
if (forbidden.length > 0) {
|
|
39
|
+
throw new Error(`Trace event fields are removed: ${forbidden.sort().join(", ")}`);
|
|
40
|
+
}
|
|
41
|
+
const unknown = Object.keys(record).filter((key) => !TRACE_INPUT_FIELDS.has(key));
|
|
42
|
+
if (unknown.length > 0) {
|
|
43
|
+
throw new Error(`Unsupported Trace event fields: ${unknown.sort().join(", ")}`);
|
|
44
|
+
}
|
|
45
|
+
if (typeof record.kind !== "string" || !KIND_PATTERN.test(record.kind)) {
|
|
46
|
+
throw new Error("Trace event kind must be a lowercase semantic token");
|
|
47
|
+
}
|
|
48
|
+
if (record.summary !== undefined && typeof record.summary !== "string") {
|
|
49
|
+
throw new Error("Trace event summary must be a string");
|
|
50
|
+
}
|
|
51
|
+
if (record.level !== undefined &&
|
|
52
|
+
record.level !== "info" &&
|
|
53
|
+
record.level !== "warning" &&
|
|
54
|
+
record.level !== "error") {
|
|
55
|
+
throw new Error("Trace event level must be info, warning, or error");
|
|
56
|
+
}
|
|
57
|
+
if (record.attention !== undefined &&
|
|
58
|
+
record.attention !== "notify" &&
|
|
59
|
+
record.attention !== "followup") {
|
|
60
|
+
throw new Error("Trace event attention must be notify or followup");
|
|
61
|
+
}
|
|
62
|
+
return {
|
|
63
|
+
kind: record.kind,
|
|
64
|
+
...(record.summary !== undefined
|
|
65
|
+
? { summary: record.summary.slice(0, 1_000) }
|
|
66
|
+
: {}),
|
|
67
|
+
...(record.data !== undefined ? { data: record.data } : {}),
|
|
68
|
+
...(record.level !== undefined
|
|
69
|
+
? { level: record.level }
|
|
70
|
+
: {}),
|
|
71
|
+
...(record.attention !== undefined
|
|
72
|
+
? { attention: record.attention }
|
|
73
|
+
: {}),
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
export function appendRunTraceEvent(stateDir, input) {
|
|
77
|
+
const path = runTraceFile(stateDir);
|
|
78
|
+
return withFileMutationLock(path, () => {
|
|
79
|
+
const normalized = normalizeTraceEventInput(input);
|
|
80
|
+
const event = {
|
|
81
|
+
id: randomUUID(),
|
|
82
|
+
ts: new Date().toISOString(),
|
|
83
|
+
...normalized,
|
|
84
|
+
};
|
|
85
|
+
let encoded;
|
|
86
|
+
try {
|
|
87
|
+
encoded = JSON.stringify(event);
|
|
88
|
+
}
|
|
89
|
+
catch {
|
|
90
|
+
throw new Error("Trace event data must be JSON-serializable");
|
|
91
|
+
}
|
|
92
|
+
if (Buffer.byteLength(encoded) > Limits.TRACE_EVENT_MAX_BYTES) {
|
|
93
|
+
throw new Error(`Trace event exceeds ${Limits.TRACE_EVENT_MAX_BYTES} bytes`);
|
|
94
|
+
}
|
|
95
|
+
writeFileSync(path, `${encoded}\n`, { flag: "a" });
|
|
96
|
+
return event;
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
export function readRunTraceEvents(stateDir, limit = Limits.TRACE_EVENT_MAX_READ) {
|
|
100
|
+
const records = readJsonlFileResilient(runTraceFile(stateDir)).records;
|
|
101
|
+
return records.slice(-Math.max(1, Math.min(limit, Limits.TRACE_EVENT_MAX_READ)));
|
|
102
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Immutable runtime package identity.
|
|
3
|
+
* Zones: source/installed package version resolution and Run-state schema marker
|
|
4
|
+
* Owns package-local identity reads; runtime status and Run persistence remain in their domains.
|
|
5
|
+
*/
|
|
6
|
+
export declare const RUN_STATE_SCHEMA: "run-kernel-v1";
|
|
7
|
+
export declare function getPackageVersion(): string;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Immutable runtime package identity.
|
|
3
|
+
* Zones: source/installed package version resolution and Run-state schema marker
|
|
4
|
+
* Owns package-local identity reads; runtime status and Run persistence remain in their domains.
|
|
5
|
+
*/
|
|
6
|
+
import { readFileSync } from "node:fs";
|
|
7
|
+
import { dirname, join } from "node:path";
|
|
8
|
+
import { fileURLToPath } from "node:url";
|
|
9
|
+
export const RUN_STATE_SCHEMA = "run-kernel-v1";
|
|
10
|
+
let cachedVersion;
|
|
11
|
+
export function getPackageVersion() {
|
|
12
|
+
if (cachedVersion)
|
|
13
|
+
return cachedVersion;
|
|
14
|
+
const moduleDir = dirname(fileURLToPath(import.meta.url));
|
|
15
|
+
for (const path of [
|
|
16
|
+
join(moduleDir, "..", "package.json"),
|
|
17
|
+
join(moduleDir, "..", "..", "package.json"),
|
|
18
|
+
]) {
|
|
19
|
+
try {
|
|
20
|
+
const pkg = JSON.parse(readFileSync(path, "utf8"));
|
|
21
|
+
if (pkg.name === "@llblab/pi-actors" &&
|
|
22
|
+
typeof pkg.version === "string" &&
|
|
23
|
+
pkg.version.length > 0 &&
|
|
24
|
+
pkg.version.length <= 128 &&
|
|
25
|
+
pkg.version.trim() === pkg.version) {
|
|
26
|
+
cachedVersion = pkg.version;
|
|
27
|
+
return cachedVersion;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
// Source and installed-dist modes resolve through different package ancestors.
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
throw new Error("pi-actors package identity is unavailable");
|
|
35
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Runtime wake notifications for actor state.
|
|
3
3
|
* Zones: advisory wake layer, file-backed runtime state, cross-platform notification boundary
|
|
4
|
-
* Owns best-effort live wake signals while durable
|
|
4
|
+
* Owns best-effort live wake signals while durable Run state remains canonical.
|
|
5
5
|
*/
|
|
6
6
|
export interface RuntimeWakeEvent {
|
|
7
7
|
actor: string;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Runtime wake notifications for actor state.
|
|
3
3
|
* Zones: advisory wake layer, file-backed runtime state, cross-platform notification boundary
|
|
4
|
-
* Owns best-effort live wake signals while durable
|
|
4
|
+
* Owns best-effort live wake signals while durable Run state remains canonical.
|
|
5
5
|
*/
|
|
6
6
|
import { randomUUID } from "node:crypto";
|
|
7
7
|
import { appendFileSync, existsSync, mkdirSync, readFileSync, statSync, watch, } from "node:fs";
|