@llblab/pi-actors 0.42.2 → 0.43.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/AGENTS.md +121 -175
- package/CHANGELOG.md +16 -0
- package/README.md +113 -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/lib/async-runs.d.ts +14 -38
- package/dist/lib/async-runs.js +158 -108
- package/dist/lib/control.d.ts +12 -0
- package/dist/lib/control.js +84 -0
- package/dist/lib/execution-sessions.d.ts +17 -0
- package/dist/lib/execution-sessions.js +85 -0
- package/dist/lib/file-state.d.ts +1 -0
- package/dist/lib/file-state.js +17 -5
- 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 +52 -70
- package/dist/lib/inspector-overlay.js +532 -905
- package/dist/lib/inspector.d.ts +3 -71
- package/dist/lib/inspector.js +19 -665
- package/dist/lib/limits.d.ts +4 -2
- package/dist/lib/limits.js +4 -2
- package/dist/lib/observability.d.ts +17 -17
- package/dist/lib/observability.js +45 -84
- package/dist/lib/pi.d.ts +1 -1
- package/dist/lib/prompts.d.ts +1 -1
- package/dist/lib/prompts.js +2 -2
- package/dist/lib/recipe-control.d.ts +7 -0
- package/dist/lib/recipe-control.js +39 -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-projection.js +1 -5
- package/dist/lib/run-ui-runtime.js +2 -2
- package/dist/lib/runs-control-delivery.d.ts +21 -0
- package/dist/lib/runs-control-delivery.js +127 -0
- package/dist/lib/runs-controls.d.ts +35 -0
- package/dist/lib/runs-controls.js +144 -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 +98 -0
- package/dist/lib/runtime-notifier.d.ts +1 -1
- package/dist/lib/runtime-notifier.js +1 -1
- package/dist/lib/tools-inspect.d.ts +3 -3
- package/dist/lib/tools-inspect.js +203 -708
- package/dist/lib/tools-local.js +2 -10
- package/dist/lib/tools-message.d.ts +7 -7
- package/dist/lib/tools-message.js +95 -396
- package/dist/lib/tools-response.d.ts +1 -4
- package/dist/lib/tools-response.js +5 -39
- package/dist/lib/tools-spawn.js +16 -28
- package/dist/lib/tools.js +1 -2
- package/dist/lib/trace-projection.d.ts +22 -0
- package/dist/lib/trace-projection.js +165 -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 +200 -66
- package/dist/scripts/music-player.mjs +159 -150
- package/dist/scripts/recipe-utils.mjs +6 -96
- package/dist/scripts/release-gates.mjs +60 -0
- package/dist/scripts/validate-recipe.mjs +3 -53
- package/dist/skills/actors/SKILL.md +53 -266
- package/dist/skills/swarm/SKILL.md +11 -33
- package/docs/0.43-baseline.md +44 -0
- package/docs/README.md +3 -3
- package/docs/actor-inspector.md +26 -64
- package/docs/actors-deep-reference.md +92 -50
- package/docs/async-runs.md +81 -328
- package/docs/command-templates.md +2 -2
- package/docs/component-recipes.md +30 -133
- package/docs/recipe-library.md +57 -182
- package/docs/task-first-recipes.md +10 -12
- 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/lib/async-runs.ts +202 -201
- package/lib/control.ts +102 -0
- package/lib/execution-sessions.ts +111 -0
- package/lib/file-state.ts +17 -4
- package/lib/inspector-actions.ts +2 -2
- package/lib/inspector-command.ts +3 -3
- package/lib/inspector-overlay.ts +577 -1121
- package/lib/inspector.ts +46 -979
- package/lib/limits.ts +4 -2
- package/lib/observability.ts +63 -104
- package/lib/pi.ts +1 -1
- package/lib/prompts.ts +2 -2
- package/lib/recipe-control.ts +45 -0
- package/lib/recipes-discovery.ts +2 -0
- package/lib/recipes-references.ts +9 -45
- package/lib/review-projection.ts +1 -5
- package/lib/run-ui-runtime.ts +2 -2
- package/lib/runs-control-delivery.ts +181 -0
- package/lib/runs-controls.ts +204 -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 +132 -0
- package/lib/runtime-notifier.ts +1 -1
- package/lib/tools-inspect.ts +240 -901
- package/lib/tools-local.ts +2 -12
- package/lib/tools-message.ts +112 -519
- package/lib/tools-response.ts +5 -52
- package/lib/tools-spawn.ts +16 -32
- package/lib/tools.ts +1 -2
- package/lib/trace-projection.ts +221 -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 +200 -66
- package/scripts/music-player.mjs +159 -150
- package/scripts/recipe-utils.mjs +6 -96
- package/scripts/release-gates.mjs +60 -0
- package/scripts/validate-recipe.mjs +3 -53
- package/skills/actors/SKILL.md +53 -266
- package/skills/swarm/SKILL.md +11 -33
- 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/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
package/dist/lib/async-runs.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
import { spawn } from "node:child_process";
|
|
6
6
|
import { randomUUID } from "node:crypto";
|
|
7
|
-
import { closeSync, existsSync, mkdirSync, openSync, readdirSync, statSync,
|
|
7
|
+
import { closeSync, existsSync, mkdirSync, openSync, readdirSync, statSync, } from "node:fs";
|
|
8
8
|
import { basename, dirname, extname, isAbsolute, join, relative, resolve } from "node:path";
|
|
9
9
|
import { fileURLToPath } from "node:url";
|
|
10
10
|
import { writeJsonAtomic } from "./file-state.js";
|
|
@@ -15,16 +15,16 @@ import * as RecipesUsage from "./recipes-usage.js";
|
|
|
15
15
|
import { resolveArtifactPaths, } from "./runs-artifacts.js";
|
|
16
16
|
import { safeRunId } from "./runs-identity.js";
|
|
17
17
|
import { buildTerminalProgress, markTerminalHandled, signalOwnedRunProcess, } from "./runs-control.js";
|
|
18
|
-
import { buildRunOutboxEventPayload, parseRunOutboxEventLine, } from "./runs-outbox.js";
|
|
19
18
|
import { claimRunStateDirectory } from "./runs-ownership.js";
|
|
20
|
-
import { archiveTerminalRun, pruneTerminalRun } from "./runs-retention.js";
|
|
19
|
+
import { appendRunRetentionEvidence, archiveTerminalRun, pruneTerminalRun, } from "./runs-retention.js";
|
|
21
20
|
import { captureRunProcessIdentity, verifyRunProcessIdentity, } from "./runs-process.js";
|
|
22
21
|
import * as RunsStart from "./runs-start.js";
|
|
22
|
+
import { appendRunTraceEvent } from "./runs-trace.js";
|
|
23
23
|
import * as RunsIndex from "./runs-index.js";
|
|
24
24
|
import * as RunsParentTeardown from "./runs-parent-teardown.js";
|
|
25
|
-
import {
|
|
26
|
-
import {
|
|
27
|
-
import { buildRunStatus, tailFile,
|
|
25
|
+
import { appendRunControlInStateDir, updateRunControlStatusInStateDir, } from "./runs-controls.js";
|
|
26
|
+
import { deliverRunControl, } from "./runs-control-delivery.js";
|
|
27
|
+
import { buildRunStatus, tailFile, } from "./runs-status.js";
|
|
28
28
|
import { readJsonFileResilient } from "./state-readers.js";
|
|
29
29
|
const RUNNER_IDENTITY_GRACE_MS = 5000;
|
|
30
30
|
export function normalizeRunTransportContext(value) {
|
|
@@ -238,11 +238,9 @@ export function startRun(params, cwd) {
|
|
|
238
238
|
const stateDir = resolveStateDir(startParams, run);
|
|
239
239
|
const values = {
|
|
240
240
|
...(startParams.values || {}),
|
|
241
|
-
actor_address: `run:${run}`,
|
|
242
|
-
communication_file: join(stateDir, "communication.json"),
|
|
243
|
-
default_room: `room:${run}`,
|
|
244
241
|
run_id: run,
|
|
245
242
|
state_dir: stateDir,
|
|
243
|
+
trace_file: join(stateDir, "trace.jsonl"),
|
|
246
244
|
};
|
|
247
245
|
const modelPolicy = describeCurrentPolicyProvenance({
|
|
248
246
|
defaults: startParams.defaults,
|
|
@@ -300,6 +298,7 @@ export function startRun(params, cwd) {
|
|
|
300
298
|
run,
|
|
301
299
|
run_instance_id: randomUUID(),
|
|
302
300
|
state_dir: stateDir,
|
|
301
|
+
state_schema: "run-kernel-v1",
|
|
303
302
|
status: "running",
|
|
304
303
|
...(startParams.tool ? { tool: startParams.tool } : {}),
|
|
305
304
|
template: resolved.template,
|
|
@@ -307,7 +306,9 @@ export function startRun(params, cwd) {
|
|
|
307
306
|
model_policy: modelPolicy,
|
|
308
307
|
...(artifacts ? { artifacts } : {}),
|
|
309
308
|
...(startParams.control ? { control: startParams.control } : {}),
|
|
310
|
-
...(startParams.
|
|
309
|
+
...(startParams.control_endpoint
|
|
310
|
+
? { control_endpoint: startParams.control_endpoint }
|
|
311
|
+
: {}),
|
|
311
312
|
...(startParams.notification_policy === "silent"
|
|
312
313
|
? { notification_policy: "silent" }
|
|
313
314
|
: {}),
|
|
@@ -340,7 +341,14 @@ export function startRun(params, cwd) {
|
|
|
340
341
|
if (processIdentity)
|
|
341
342
|
meta.process_identity = processIdentity;
|
|
342
343
|
writeJsonAtomic(join(stateDir, "run.json"), meta);
|
|
343
|
-
|
|
344
|
+
appendRunTraceEvent(stateDir, {
|
|
345
|
+
kind: "run.start",
|
|
346
|
+
summary: `Run ${run} started`,
|
|
347
|
+
data: {
|
|
348
|
+
pid: meta.pid,
|
|
349
|
+
run_instance_id: meta.run_instance_id,
|
|
350
|
+
},
|
|
351
|
+
});
|
|
344
352
|
child.unref();
|
|
345
353
|
return meta;
|
|
346
354
|
}
|
|
@@ -348,7 +356,6 @@ export function startRun(params, cwd) {
|
|
|
348
356
|
releaseStartLock();
|
|
349
357
|
}
|
|
350
358
|
}
|
|
351
|
-
export { parseRunOutboxEventLine } from "./runs-outbox.js";
|
|
352
359
|
function resolveRunStateDir(runOrDir) {
|
|
353
360
|
return resolve(/[\\/]/u.test(runOrDir)
|
|
354
361
|
? runOrDir
|
|
@@ -403,17 +410,19 @@ export function teardownRunsOwnedByParent(ownerId, stateRoot = DEFAULT_STATE_ROO
|
|
|
403
410
|
current.run_instance_id !== attempt.runInstanceId) {
|
|
404
411
|
throw new Error("run generation changed before teardown evidence");
|
|
405
412
|
}
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
413
|
+
appendRunTraceEvent(attempt.stateDir, {
|
|
414
|
+
kind: "run.parent_teardown",
|
|
415
|
+
summary: `Parent teardown ${attempt.outcome}`,
|
|
416
|
+
data: {
|
|
417
|
+
outcome: attempt.outcome,
|
|
418
|
+
owner_id: attempt.ownerId,
|
|
419
|
+
reason: attempt.reason,
|
|
420
|
+
run: attempt.run,
|
|
421
|
+
run_instance_id: attempt.runInstanceId,
|
|
422
|
+
trigger: options.trigger ?? "parent_shutdown",
|
|
423
|
+
},
|
|
424
|
+
...(attempt.outcome === "failed" ? { level: "error" } : {}),
|
|
425
|
+
});
|
|
417
426
|
},
|
|
418
427
|
});
|
|
419
428
|
const summaryPath = join(stateRoot, "teardown", `${Date.now()}-${randomUUID()}.json`);
|
|
@@ -437,73 +446,43 @@ export function teardownRunsOwnedByParent(ownerId, stateRoot = DEFAULT_STATE_ROO
|
|
|
437
446
|
export function tailRun(runOrDir, lines = 40) {
|
|
438
447
|
const status = getRunStatus(runOrDir);
|
|
439
448
|
const stateDir = String(status.state_dir);
|
|
440
|
-
const
|
|
441
|
-
if (
|
|
442
|
-
return
|
|
449
|
+
const trace = tailFile(join(stateDir, "trace.jsonl"), lines);
|
|
450
|
+
if (trace)
|
|
451
|
+
return trace;
|
|
443
452
|
return (tailFile(join(stateDir, "stdout.log"), lines) ||
|
|
444
453
|
tailFile(join(stateDir, "stderr.log"), lines));
|
|
445
454
|
}
|
|
446
|
-
export function
|
|
447
|
-
const
|
|
448
|
-
const
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
.
|
|
453
|
-
}
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
}
|
|
461
|
-
export function updateRunInboxMessageStatus(runOrDir, id, nextStatus, metadata = {}) {
|
|
462
|
-
const status = getRunStatus(runOrDir);
|
|
463
|
-
const stateDir = String(status.state_dir);
|
|
464
|
-
return updateRunInboxMessageStatusInStateDir(stateDir, id, nextStatus, metadata);
|
|
465
|
-
}
|
|
466
|
-
export function claimRunInboxMessage(runOrDir, owner = "runtime", statuses = ["queued"]) {
|
|
467
|
-
const status = getRunStatus(runOrDir);
|
|
468
|
-
const stateDir = String(status.state_dir);
|
|
469
|
-
return claimRunInboxMessageInStateDir(stateDir, owner, statuses);
|
|
470
|
-
}
|
|
471
|
-
export async function processRunInboxMessages(runOrDir, handler, options = {}) {
|
|
472
|
-
const status = getRunStatus(runOrDir);
|
|
473
|
-
return processRunInboxMessagesInStateDir(String(status.state_dir), handler, options);
|
|
474
|
-
}
|
|
475
|
-
export function appendRunOutboxEvent(runOrDir, event) {
|
|
476
|
-
const status = getRunStatus(runOrDir);
|
|
477
|
-
const stateDir = String(status.state_dir);
|
|
478
|
-
const run = String(status.run ?? runOrDir);
|
|
479
|
-
const payload = buildRunOutboxEventPayload(run, event);
|
|
480
|
-
const line = `${JSON.stringify(payload)}\n`;
|
|
481
|
-
writeFileSync(join(stateDir, "outbox.jsonl"), line, { flag: "a" });
|
|
482
|
-
return {
|
|
483
|
-
bytes: Buffer.byteLength(line),
|
|
484
|
-
outbox: "outbox.jsonl",
|
|
485
|
-
run,
|
|
486
|
-
sent: true,
|
|
487
|
-
state_dir: stateDir,
|
|
488
|
-
};
|
|
489
|
-
}
|
|
490
|
-
export async function sendRunMessage(runOrDir, message, options = {}) {
|
|
491
|
-
const status = getRunStatus(runOrDir);
|
|
492
|
-
const stateDir = String(status.state_dir);
|
|
493
|
-
const run = String(status.run ?? runOrDir);
|
|
494
|
-
const pid = Number(status.pid || 0);
|
|
495
|
-
const identity = verifyRunProcessIdentity(pid, status.process_identity);
|
|
496
|
-
if (status.status !== "running") {
|
|
497
|
-
if (identity.status === "owner_mismatch" ||
|
|
498
|
-
identity.status === "unsupported_proof") {
|
|
499
|
-
throw new Error(`Run process identity ${identity.status}: ${run}`);
|
|
455
|
+
export async function sendRunControl(runOrDir, request, options = {}) {
|
|
456
|
+
const stateDir = resolveRunStateDir(runOrDir);
|
|
457
|
+
const releaseControlLock = RunsStart.acquireStateStartLock(stateDir);
|
|
458
|
+
try {
|
|
459
|
+
const status = getRunStatus(stateDir);
|
|
460
|
+
const run = String(status.run ?? runOrDir);
|
|
461
|
+
if (options.ownerId !== undefined && status.ownerId !== options.ownerId) {
|
|
462
|
+
throw Object.assign(new Error(`Run ownership changed: ${run}`), {
|
|
463
|
+
reason: "owner_mismatch",
|
|
464
|
+
});
|
|
465
|
+
}
|
|
466
|
+
if (status.run_instance_id !== request.run_instance_id) {
|
|
467
|
+
throw Object.assign(new Error(`Run generation changed: ${run}`), {
|
|
468
|
+
reason: "generation_mismatch",
|
|
469
|
+
});
|
|
500
470
|
}
|
|
501
|
-
|
|
471
|
+
if (status.status !== "running") {
|
|
472
|
+
throw Object.assign(new Error(`Run is not running: ${run}`), {
|
|
473
|
+
reason: "terminal_state",
|
|
474
|
+
});
|
|
475
|
+
}
|
|
476
|
+
const pid = Number(status.pid || 0);
|
|
477
|
+
const identity = verifyRunProcessIdentity(pid, status.process_identity);
|
|
478
|
+
if (!identity.valid) {
|
|
479
|
+
throw Object.assign(new Error(`Run process identity ${identity.status}: ${run}`), { reason: `process_identity_${identity.status}` });
|
|
480
|
+
}
|
|
481
|
+
return await deliverRunControl(run, stateDir, request, options);
|
|
502
482
|
}
|
|
503
|
-
|
|
504
|
-
|
|
483
|
+
finally {
|
|
484
|
+
releaseControlLock();
|
|
505
485
|
}
|
|
506
|
-
return deliverRunMessage(status, run, stateDir, message, options);
|
|
507
486
|
}
|
|
508
487
|
export { getRunProcessSignalPlan } from "./runs-control.js";
|
|
509
488
|
function markTerminalProgress(stateDir, phase) {
|
|
@@ -513,9 +492,9 @@ function markTerminalProgress(stateDir, phase) {
|
|
|
513
492
|
: undefined;
|
|
514
493
|
writeJsonAtomic(join(stateDir, "progress.json"), buildTerminalProgress(progress, phase));
|
|
515
494
|
}
|
|
516
|
-
function
|
|
517
|
-
const
|
|
518
|
-
const manifest = readJson(
|
|
495
|
+
function finalizeInterruptedExecution(stateDir, phase, signal) {
|
|
496
|
+
const executionPath = join(stateDir, "execution.json");
|
|
497
|
+
const manifest = readJson(executionPath);
|
|
519
498
|
if (!manifest || typeof manifest !== "object" || Array.isArray(manifest))
|
|
520
499
|
return;
|
|
521
500
|
const record = manifest;
|
|
@@ -564,7 +543,7 @@ function finalizeInterruptedReviewEvidence(stateDir, phase, signal) {
|
|
|
564
543
|
: {}),
|
|
565
544
|
};
|
|
566
545
|
});
|
|
567
|
-
writeJsonAtomic(
|
|
546
|
+
writeJsonAtomic(executionPath, {
|
|
568
547
|
...record,
|
|
569
548
|
status: phase,
|
|
570
549
|
commands,
|
|
@@ -585,43 +564,80 @@ function stopRun(runOrDir, signal, event, expected = {}) {
|
|
|
585
564
|
status.run_instance_id !== expected.runInstanceId) {
|
|
586
565
|
return { stopped: false, reason: "run generation changed", status };
|
|
587
566
|
}
|
|
567
|
+
const control = event === "run.kill" && typeof status.run_instance_id === "string"
|
|
568
|
+
? appendRunControlInStateDir(stateDir, {
|
|
569
|
+
action: "kill",
|
|
570
|
+
run_instance_id: status.run_instance_id,
|
|
571
|
+
})
|
|
572
|
+
: undefined;
|
|
573
|
+
if (control) {
|
|
574
|
+
updateRunControlStatusInStateDir(stateDir, control.id, "claimed", {}, ["queued"]);
|
|
575
|
+
}
|
|
576
|
+
const finish = (result) => {
|
|
577
|
+
if (!control)
|
|
578
|
+
return result;
|
|
579
|
+
const handled = result.stopped === true;
|
|
580
|
+
updateRunControlStatusInStateDir(stateDir, control.id, handled ? "handled" : "failed", handled ? {} : { error: String(result.reason ?? "kill rejected") }, ["claimed"]);
|
|
581
|
+
return { ...result, control_id: control.id };
|
|
582
|
+
};
|
|
588
583
|
const pid = Number(status.pid || 0);
|
|
589
584
|
if (status.status !== "running" && status.status !== "exited") {
|
|
590
|
-
return { stopped: false, reason: "not running", status };
|
|
585
|
+
return finish({ stopped: false, reason: "not running", status });
|
|
591
586
|
}
|
|
592
587
|
const identity = verifyRunProcessIdentity(pid, status.process_identity);
|
|
593
588
|
if (status.status === "exited") {
|
|
594
589
|
if (identity.status === "owner_mismatch" ||
|
|
595
590
|
identity.status === "unsupported_proof") {
|
|
596
|
-
return {
|
|
591
|
+
return finish({
|
|
597
592
|
stopped: false,
|
|
598
593
|
reason: identity.status.replaceAll("_", " "),
|
|
599
594
|
process_identity_status: identity.status,
|
|
600
595
|
status,
|
|
601
|
-
};
|
|
596
|
+
});
|
|
602
597
|
}
|
|
603
|
-
return { stopped: false, reason: "not running", status };
|
|
598
|
+
return finish({ stopped: false, reason: "not running", status });
|
|
604
599
|
}
|
|
605
600
|
if (!identity.valid) {
|
|
606
|
-
return {
|
|
601
|
+
return finish({
|
|
607
602
|
stopped: false,
|
|
608
603
|
reason: identity.status.replaceAll("_", " "),
|
|
609
604
|
process_identity_status: identity.status,
|
|
610
605
|
status,
|
|
611
|
-
};
|
|
606
|
+
});
|
|
607
|
+
}
|
|
608
|
+
let signalResult;
|
|
609
|
+
try {
|
|
610
|
+
signalResult = signalOwnedRunProcess(pid, signal, status.process_identity);
|
|
611
|
+
}
|
|
612
|
+
catch (error) {
|
|
613
|
+
if (control) {
|
|
614
|
+
updateRunControlStatusInStateDir(stateDir, control.id, "failed", {
|
|
615
|
+
error: error instanceof Error ? error.message : String(error),
|
|
616
|
+
});
|
|
617
|
+
}
|
|
618
|
+
throw error;
|
|
612
619
|
}
|
|
613
|
-
|
|
614
|
-
|
|
620
|
+
appendRunTraceEvent(stateDir, {
|
|
621
|
+
kind: event,
|
|
622
|
+
summary: `${event === "run.kill" ? "Killed" : "Cancelled"} Run process`,
|
|
623
|
+
data: { pid, signal, ...signalResult },
|
|
624
|
+
});
|
|
615
625
|
markTerminalHandled(stateDir, { event, signal });
|
|
616
626
|
if (event === "run.kill") {
|
|
617
|
-
|
|
627
|
+
finalizeInterruptedExecution(stateDir, "killed", signal);
|
|
618
628
|
markTerminalProgress(stateDir, "killed");
|
|
619
629
|
}
|
|
620
630
|
if (event === "run.cancel") {
|
|
621
|
-
|
|
631
|
+
finalizeInterruptedExecution(stateDir, "cancelled", signal);
|
|
622
632
|
markTerminalProgress(stateDir, "cancelled");
|
|
623
633
|
}
|
|
624
|
-
return {
|
|
634
|
+
return finish({
|
|
635
|
+
stopped: true,
|
|
636
|
+
pid,
|
|
637
|
+
signal,
|
|
638
|
+
...signalResult,
|
|
639
|
+
state_dir: stateDir,
|
|
640
|
+
});
|
|
625
641
|
}
|
|
626
642
|
finally {
|
|
627
643
|
releaseControlLock();
|
|
@@ -653,18 +669,52 @@ export function cancelRun(runOrDir, expected = {}) {
|
|
|
653
669
|
? { cancelled: result.stopped, ...result }
|
|
654
670
|
: result;
|
|
655
671
|
}
|
|
656
|
-
function
|
|
657
|
-
const
|
|
658
|
-
|
|
659
|
-
|
|
672
|
+
function retainRun(runOrDir, action, expected, options = {}) {
|
|
673
|
+
const stateDir = resolveRunStateDir(runOrDir);
|
|
674
|
+
const releaseControlLock = RunsStart.acquireStateStartLock(stateDir);
|
|
675
|
+
let status;
|
|
676
|
+
let evidenceId;
|
|
677
|
+
try {
|
|
678
|
+
expected.onLocked?.();
|
|
679
|
+
status = getRunStatus(stateDir);
|
|
680
|
+
if (expected.ownerId !== undefined && status.ownerId !== expected.ownerId) {
|
|
681
|
+
return { [`${action}d`]: false, reason: "ownership changed", status };
|
|
682
|
+
}
|
|
683
|
+
if (expected.runInstanceId !== undefined &&
|
|
684
|
+
status.run_instance_id !== expected.runInstanceId) {
|
|
685
|
+
return { [`${action}d`]: false, reason: "run generation changed", status };
|
|
686
|
+
}
|
|
687
|
+
if (status.status === "running") {
|
|
688
|
+
throw new Error("Only terminal runs can be archived or pruned.");
|
|
689
|
+
}
|
|
690
|
+
evidenceId = appendRunRetentionEvidence(status, action, "queued");
|
|
691
|
+
const result = action === "archive"
|
|
692
|
+
? archiveTerminalRun(status)
|
|
693
|
+
: pruneTerminalRun(status, options);
|
|
694
|
+
appendRunRetentionEvidence(status, action, "handled", {
|
|
695
|
+
id: evidenceId,
|
|
696
|
+
result,
|
|
697
|
+
});
|
|
698
|
+
return { ...result, retention_id: evidenceId };
|
|
699
|
+
}
|
|
700
|
+
catch (error) {
|
|
701
|
+
if (status && evidenceId) {
|
|
702
|
+
appendRunRetentionEvidence(status, action, "failed", {
|
|
703
|
+
error: error instanceof Error ? error.message : String(error),
|
|
704
|
+
id: evidenceId,
|
|
705
|
+
});
|
|
706
|
+
}
|
|
707
|
+
throw error;
|
|
708
|
+
}
|
|
709
|
+
finally {
|
|
710
|
+
releaseControlLock();
|
|
660
711
|
}
|
|
661
|
-
return status;
|
|
662
712
|
}
|
|
663
|
-
export function archiveRun(runOrDir) {
|
|
664
|
-
return
|
|
713
|
+
export function archiveRun(runOrDir, expected = {}) {
|
|
714
|
+
return retainRun(runOrDir, "archive", expected);
|
|
665
715
|
}
|
|
666
|
-
export function pruneRun(runOrDir, options = {}) {
|
|
667
|
-
return
|
|
716
|
+
export function pruneRun(runOrDir, options = {}, expected = {}) {
|
|
717
|
+
return retainRun(runOrDir, "prune", expected, options);
|
|
668
718
|
}
|
|
669
719
|
export function killRun(runOrDir, expected = {}) {
|
|
670
720
|
const result = stopRun(runOrDir, "SIGKILL", "run.kill", expected);
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Run Control request contract.
|
|
3
|
+
* Zones: public request validation, Run/runtime target normalization, input bounds
|
|
4
|
+
* Owns pure Control validation; journaling, delivery, and lifecycle mutation stay in adapters.
|
|
5
|
+
*/
|
|
6
|
+
export interface ControlRequest {
|
|
7
|
+
target: `run:${string}` | "runtime";
|
|
8
|
+
action: string;
|
|
9
|
+
input?: unknown;
|
|
10
|
+
verbose?: boolean;
|
|
11
|
+
}
|
|
12
|
+
export declare function normalizeControlRequest(input: unknown): ControlRequest;
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Run Control request contract.
|
|
3
|
+
* Zones: public request validation, Run/runtime target normalization, input bounds
|
|
4
|
+
* Owns pure Control validation; journaling, delivery, and lifecycle mutation stay in adapters.
|
|
5
|
+
*/
|
|
6
|
+
import * as Limits from "./limits.js";
|
|
7
|
+
const RUN_ID_PATTERN = /^[A-Za-z0-9_.-]+$/;
|
|
8
|
+
const ACTION_PATTERN = /^[a-z][a-z0-9_-]*(?:\.[a-z0-9_-]+)*$/;
|
|
9
|
+
const CONTROL_FIELDS = new Set(["action", "input", "target", "verbose"]);
|
|
10
|
+
const REMOVED_FIELDS = new Set([
|
|
11
|
+
"body",
|
|
12
|
+
"correlation_id",
|
|
13
|
+
"from",
|
|
14
|
+
"metadata",
|
|
15
|
+
"reply_to",
|
|
16
|
+
"summary",
|
|
17
|
+
"to",
|
|
18
|
+
"type",
|
|
19
|
+
]);
|
|
20
|
+
function serializedInputBytes(input) {
|
|
21
|
+
let serialized;
|
|
22
|
+
try {
|
|
23
|
+
serialized = JSON.stringify(input);
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
throw new Error("control.input must be JSON-serializable");
|
|
27
|
+
}
|
|
28
|
+
if (serialized === undefined) {
|
|
29
|
+
throw new Error("control.input must be JSON-serializable");
|
|
30
|
+
}
|
|
31
|
+
return Buffer.byteLength(serialized);
|
|
32
|
+
}
|
|
33
|
+
function normalizeTarget(value) {
|
|
34
|
+
if (typeof value !== "string" || !value.trim()) {
|
|
35
|
+
throw new Error("control.target is required");
|
|
36
|
+
}
|
|
37
|
+
const target = value.trim();
|
|
38
|
+
if (target === "runtime")
|
|
39
|
+
return target;
|
|
40
|
+
if (!target.startsWith("run:")) {
|
|
41
|
+
throw new Error(`unsupported control target: ${target}; use run:<id> or runtime`);
|
|
42
|
+
}
|
|
43
|
+
const run = target.slice(4);
|
|
44
|
+
if (!RUN_ID_PATTERN.test(run)) {
|
|
45
|
+
throw new Error(`invalid control Run target: ${target}`);
|
|
46
|
+
}
|
|
47
|
+
return `run:${run}`;
|
|
48
|
+
}
|
|
49
|
+
export function normalizeControlRequest(input) {
|
|
50
|
+
if (!input || typeof input !== "object" || Array.isArray(input)) {
|
|
51
|
+
throw new Error("control request must be an object");
|
|
52
|
+
}
|
|
53
|
+
const record = input;
|
|
54
|
+
const fields = Object.keys(record);
|
|
55
|
+
const removed = fields.filter((field) => REMOVED_FIELDS.has(field));
|
|
56
|
+
if (removed.length > 0) {
|
|
57
|
+
throw new Error(`actor-message fields are removed: ${removed.sort().join(", ")}; use target, action, input, verbose`);
|
|
58
|
+
}
|
|
59
|
+
const unknown = fields.filter((field) => !CONTROL_FIELDS.has(field));
|
|
60
|
+
if (unknown.length > 0) {
|
|
61
|
+
throw new Error(`unsupported control fields: ${unknown.sort().join(", ")}`);
|
|
62
|
+
}
|
|
63
|
+
const target = normalizeTarget(record.target);
|
|
64
|
+
if (typeof record.action !== "string" || !record.action.trim()) {
|
|
65
|
+
throw new Error("control.action is required");
|
|
66
|
+
}
|
|
67
|
+
const action = record.action.trim();
|
|
68
|
+
if (!ACTION_PATTERN.test(action)) {
|
|
69
|
+
throw new Error(`invalid control action: ${action}`);
|
|
70
|
+
}
|
|
71
|
+
if (record.verbose !== undefined && typeof record.verbose !== "boolean") {
|
|
72
|
+
throw new Error("control.verbose must be a boolean");
|
|
73
|
+
}
|
|
74
|
+
if (record.input !== undefined &&
|
|
75
|
+
serializedInputBytes(record.input) > Limits.CONTROL_INPUT_MAX_BYTES) {
|
|
76
|
+
throw new Error(`control.input exceeds ${Limits.CONTROL_INPUT_MAX_BYTES} bytes`);
|
|
77
|
+
}
|
|
78
|
+
return {
|
|
79
|
+
target,
|
|
80
|
+
action,
|
|
81
|
+
...(record.input !== undefined ? { input: record.input } : {}),
|
|
82
|
+
...(record.verbose !== undefined ? { verbose: record.verbose } : {}),
|
|
83
|
+
};
|
|
84
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Run-owned execution session evidence.
|
|
3
|
+
* Zones: execution manifest session discovery, canonical containment, rich turn correlation
|
|
4
|
+
* Owns reusable agent-turn loading; Trace and TUI projection stay in their adapters.
|
|
5
|
+
*/
|
|
6
|
+
import * as SessionEvidence from "./session-evidence.ts";
|
|
7
|
+
export interface ExecutionTurnItem extends SessionEvidence.SessionEvidenceTurn {
|
|
8
|
+
commandId: string;
|
|
9
|
+
diagnostics: string[];
|
|
10
|
+
promptBytes?: number;
|
|
11
|
+
promptFile?: string;
|
|
12
|
+
recipeContext?: unknown;
|
|
13
|
+
sessionFile: string;
|
|
14
|
+
sessionTruncated: boolean;
|
|
15
|
+
stage?: string;
|
|
16
|
+
}
|
|
17
|
+
export declare function readExecutionTurns(stateDir: string): ExecutionTurnItem[];
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Run-owned execution session evidence.
|
|
3
|
+
* Zones: execution manifest session discovery, canonical containment, rich turn correlation
|
|
4
|
+
* Owns reusable agent-turn loading; Trace and TUI projection stay in their adapters.
|
|
5
|
+
*/
|
|
6
|
+
import * as fs from "node:fs";
|
|
7
|
+
import * as path from "node:path";
|
|
8
|
+
import * as SessionEvidence from "./session-evidence.js";
|
|
9
|
+
import { readJsonFileResilient } from "./state-readers.js";
|
|
10
|
+
function ownedSessionPath(stateDir, sessionFile) {
|
|
11
|
+
if (path.isAbsolute(sessionFile))
|
|
12
|
+
return undefined;
|
|
13
|
+
const sessionsRoot = path.resolve(stateDir, "sessions");
|
|
14
|
+
const resolved = path.resolve(stateDir, sessionFile);
|
|
15
|
+
if (!resolved.startsWith(`${sessionsRoot}${path.sep}`))
|
|
16
|
+
return undefined;
|
|
17
|
+
try {
|
|
18
|
+
const canonicalRoot = fs.realpathSync(sessionsRoot);
|
|
19
|
+
const canonicalFile = fs.realpathSync(resolved);
|
|
20
|
+
return canonicalFile.startsWith(`${canonicalRoot}${path.sep}`)
|
|
21
|
+
? canonicalFile
|
|
22
|
+
: undefined;
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
return undefined;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
export function readExecutionTurns(stateDir) {
|
|
29
|
+
const execution = readJsonFileResilient(path.join(stateDir, "execution.json"), {}).value;
|
|
30
|
+
const commands = Array.isArray(execution.commands)
|
|
31
|
+
? execution.commands
|
|
32
|
+
: [];
|
|
33
|
+
const recordedFiles = new Set(commands.flatMap((command) => (Array.isArray(command.session_files) ? command.session_files : []).filter((file) => typeof file === "string")));
|
|
34
|
+
let discoveredCommands = [];
|
|
35
|
+
try {
|
|
36
|
+
const sessionsDir = path.join(stateDir, "sessions");
|
|
37
|
+
discoveredCommands = fs
|
|
38
|
+
.readdirSync(sessionsDir, { withFileTypes: true })
|
|
39
|
+
.filter((entry) => entry.isDirectory())
|
|
40
|
+
.flatMap((entry) => {
|
|
41
|
+
const files = fs
|
|
42
|
+
.readdirSync(path.join(sessionsDir, entry.name), {
|
|
43
|
+
withFileTypes: true,
|
|
44
|
+
})
|
|
45
|
+
.filter((file) => file.isFile() && file.name.endsWith(".jsonl"))
|
|
46
|
+
.map((file) => path.posix.join("sessions", entry.name, file.name))
|
|
47
|
+
.filter((file) => !recordedFiles.has(file));
|
|
48
|
+
return files.length > 0
|
|
49
|
+
? [{ id: entry.name, session_files: files, stage: "subagent" }]
|
|
50
|
+
: [];
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
discoveredCommands = [];
|
|
55
|
+
}
|
|
56
|
+
return [...commands, ...discoveredCommands]
|
|
57
|
+
.flatMap((command) => (Array.isArray(command.session_files) ? command.session_files : [])
|
|
58
|
+
.filter((file) => typeof file === "string")
|
|
59
|
+
.flatMap((file) => {
|
|
60
|
+
const sessionPath = ownedSessionPath(stateDir, file);
|
|
61
|
+
if (!sessionPath)
|
|
62
|
+
return [];
|
|
63
|
+
const session = SessionEvidence.readSessionEvidence(sessionPath);
|
|
64
|
+
return session.turns.map((turn) => ({
|
|
65
|
+
...turn,
|
|
66
|
+
commandId: String(command.id ?? "unknown"),
|
|
67
|
+
diagnostics: session.diagnostics.map((item) => `${item.line === undefined ? "" : `line ${item.line}: `}${item.message}`),
|
|
68
|
+
...(typeof command.prompt_bytes === "number"
|
|
69
|
+
? { promptBytes: command.prompt_bytes }
|
|
70
|
+
: {}),
|
|
71
|
+
...(typeof command.prompt_file === "string"
|
|
72
|
+
? { promptFile: command.prompt_file }
|
|
73
|
+
: {}),
|
|
74
|
+
...(command.recipe_context !== undefined
|
|
75
|
+
? { recipeContext: command.recipe_context }
|
|
76
|
+
: {}),
|
|
77
|
+
sessionFile: file,
|
|
78
|
+
sessionTruncated: session.truncated,
|
|
79
|
+
...(typeof command.stage === "string"
|
|
80
|
+
? { stage: command.stage }
|
|
81
|
+
: {}),
|
|
82
|
+
}));
|
|
83
|
+
}))
|
|
84
|
+
.map((turn, index) => ({ ...turn, index: index + 1 }));
|
|
85
|
+
}
|
package/dist/lib/file-state.d.ts
CHANGED
|
@@ -11,4 +11,5 @@ export interface FileMutationLockOptions {
|
|
|
11
11
|
}
|
|
12
12
|
export declare function acquireFileMutationLock(path: string, options?: FileMutationLockOptions): () => void;
|
|
13
13
|
export declare function withFileMutationLock<T>(path: string, mutate: () => T, options?: FileMutationLockOptions): T;
|
|
14
|
+
export declare function writeTextAtomic(path: string, content: string): void;
|
|
14
15
|
export declare function writeJsonAtomic(path: string, value: unknown): void;
|
package/dist/lib/file-state.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Owns generic durable JSON file writes shared by registry config and async run state.
|
|
5
5
|
*/
|
|
6
6
|
import { createHash, randomUUID } from "node:crypto";
|
|
7
|
-
import { existsSync, mkdirSync, readFileSync, realpathSync, renameSync, rmSync, statSync, unlinkSync, writeFileSync, } from "node:fs";
|
|
7
|
+
import { existsSync, lstatSync, mkdirSync, readFileSync, realpathSync, renameSync, rmSync, statSync, unlinkSync, writeFileSync, } from "node:fs";
|
|
8
8
|
import { tmpdir } from "node:os";
|
|
9
9
|
import { basename, dirname, join, parse, resolve } from "node:path";
|
|
10
10
|
const FILE_MUTATION_LOCK_TIMEOUT_MS = 5000;
|
|
@@ -12,8 +12,17 @@ const FILE_MUTATION_LOCK_STALE_MS = 30000;
|
|
|
12
12
|
const FILE_MUTATION_LOCK_ROOT = join(tmpdir(), "pi-actors-file-locks");
|
|
13
13
|
function canonicalMutationPath(path) {
|
|
14
14
|
const absolute = resolve(path);
|
|
15
|
-
|
|
16
|
-
|
|
15
|
+
try {
|
|
16
|
+
if (lstatSync(absolute).isSymbolicLink()) {
|
|
17
|
+
const target = realpathSync.native(absolute);
|
|
18
|
+
return process.platform === "win32" ? target.toLowerCase() : target;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
/* Materialization may race lock-key derivation; canonicalize through the parent. */
|
|
23
|
+
}
|
|
24
|
+
const suffix = [basename(absolute)];
|
|
25
|
+
let existing = dirname(absolute);
|
|
17
26
|
while (!existsSync(existing)) {
|
|
18
27
|
const parent = dirname(existing);
|
|
19
28
|
if (parent === existing || existing === parse(existing).root)
|
|
@@ -191,11 +200,11 @@ export function withFileMutationLock(path, mutate, options = {}) {
|
|
|
191
200
|
release();
|
|
192
201
|
}
|
|
193
202
|
}
|
|
194
|
-
export function
|
|
203
|
+
export function writeTextAtomic(path, content) {
|
|
195
204
|
mkdirSync(dirname(path), { recursive: true });
|
|
196
205
|
const tempPath = `${path}.${process.pid}.${Date.now()}.${randomUUID()}.tmp`;
|
|
197
206
|
try {
|
|
198
|
-
writeFileSync(tempPath,
|
|
207
|
+
writeFileSync(tempPath, content, "utf8");
|
|
199
208
|
renameSync(tempPath, path);
|
|
200
209
|
}
|
|
201
210
|
catch (error) {
|
|
@@ -208,3 +217,6 @@ export function writeJsonAtomic(path, value) {
|
|
|
208
217
|
throw error;
|
|
209
218
|
}
|
|
210
219
|
}
|
|
220
|
+
export function writeJsonAtomic(path, value) {
|
|
221
|
+
writeTextAtomic(path, `${JSON.stringify(value, null, 2)}\n`);
|
|
222
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Actor Inspector
|
|
2
|
+
* Actor Inspector actions for concrete Run instances.
|
|
3
3
|
* Owns: exact-session run-action authorization, canonical kill routing, and bounded operator feedback.
|
|
4
4
|
*/
|
|
5
5
|
export interface ActorInspectorKillResult {
|
|
@@ -13,4 +13,4 @@ export interface ActorInspectorKillDeps {
|
|
|
13
13
|
runInstanceId: string;
|
|
14
14
|
}) => Record<string, unknown>;
|
|
15
15
|
}
|
|
16
|
-
export declare function
|
|
16
|
+
export declare function killOwnedRunFromInspector(ownerId: string, run: string, stateRoot: string, expectedRunInstanceId: string, deps: ActorInspectorKillDeps): ActorInspectorKillResult;
|