@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,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Model-facing Run Control projection.
|
|
3
|
+
* Zones: structured redaction, bounded Control input/error, decision-useful evidence
|
|
4
|
+
* Owns safe Control reads; durable journals and lifecycle transitions remain in runs-controls.
|
|
5
|
+
*/
|
|
6
|
+
import type { RunControlRecord, RunControlStatus } from "./runs-controls.ts";
|
|
7
|
+
export interface ProjectedRunControl {
|
|
8
|
+
action: string;
|
|
9
|
+
claimed_at?: string;
|
|
10
|
+
delivered_at?: string;
|
|
11
|
+
error?: string;
|
|
12
|
+
failed_at?: string;
|
|
13
|
+
handled_at?: string;
|
|
14
|
+
id: string;
|
|
15
|
+
input?: unknown;
|
|
16
|
+
queued_at: string;
|
|
17
|
+
run_instance_id: string;
|
|
18
|
+
status: RunControlStatus;
|
|
19
|
+
}
|
|
20
|
+
export declare function projectRunControl(control: RunControlRecord): ProjectedRunControl;
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Model-facing Run Control projection.
|
|
3
|
+
* Zones: structured redaction, bounded Control input/error, decision-useful evidence
|
|
4
|
+
* Owns safe Control reads; durable journals and lifecycle transitions remain in runs-controls.
|
|
5
|
+
*/
|
|
6
|
+
import * as Limits from "./limits.js";
|
|
7
|
+
import * as SessionEvidence from "./session-evidence.js";
|
|
8
|
+
const OPTIONAL_TIMESTAMP_FIELDS = [
|
|
9
|
+
"claimed_at",
|
|
10
|
+
"delivered_at",
|
|
11
|
+
"failed_at",
|
|
12
|
+
"handled_at",
|
|
13
|
+
];
|
|
14
|
+
function boundedRedactedString(value, maxChars) {
|
|
15
|
+
const redacted = SessionEvidence.redactSessionEvidenceValue(value, maxChars);
|
|
16
|
+
return typeof redacted === "string"
|
|
17
|
+
? redacted
|
|
18
|
+
: String(redacted).slice(0, maxChars);
|
|
19
|
+
}
|
|
20
|
+
function boundedRedactedValue(value) {
|
|
21
|
+
const redacted = SessionEvidence.redactSessionEvidenceValue(value, Limits.INSPECTOR_BODY_PREVIEW_CHARS);
|
|
22
|
+
let serialized;
|
|
23
|
+
try {
|
|
24
|
+
serialized = JSON.stringify(redacted);
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
return "[UNSERIALIZABLE]";
|
|
28
|
+
}
|
|
29
|
+
if (serialized === undefined)
|
|
30
|
+
return "[UNSERIALIZABLE]";
|
|
31
|
+
if (serialized.length <= Limits.INSPECTOR_BODY_PREVIEW_CHARS)
|
|
32
|
+
return redacted;
|
|
33
|
+
let preview = serialized.slice(0, Limits.INSPECTOR_BODY_PREVIEW_CHARS);
|
|
34
|
+
let bounded = { preview: `${preview}…`, truncated: true };
|
|
35
|
+
while (preview &&
|
|
36
|
+
JSON.stringify(bounded).length > Limits.INSPECTOR_BODY_PREVIEW_CHARS) {
|
|
37
|
+
const excess = JSON.stringify(bounded).length - Limits.INSPECTOR_BODY_PREVIEW_CHARS;
|
|
38
|
+
preview = preview.slice(0, Math.max(0, preview.length - Math.max(1, excess)));
|
|
39
|
+
bounded = { preview: `${preview}…`, truncated: true };
|
|
40
|
+
}
|
|
41
|
+
return bounded;
|
|
42
|
+
}
|
|
43
|
+
export function projectRunControl(control) {
|
|
44
|
+
const timestamps = Object.fromEntries(OPTIONAL_TIMESTAMP_FIELDS.flatMap((field) => {
|
|
45
|
+
const value = control[field];
|
|
46
|
+
return typeof value === "string"
|
|
47
|
+
? [[field, boundedRedactedString(value, Limits.COMPACT_PREVIEW_CHARS)]]
|
|
48
|
+
: [];
|
|
49
|
+
}));
|
|
50
|
+
return {
|
|
51
|
+
action: boundedRedactedString(control.action, Limits.COMPACT_PREVIEW_CHARS),
|
|
52
|
+
...timestamps,
|
|
53
|
+
...(typeof control.error === "string"
|
|
54
|
+
? {
|
|
55
|
+
error: boundedRedactedString(control.error, Limits.INSPECTOR_BODY_PREVIEW_CHARS),
|
|
56
|
+
}
|
|
57
|
+
: {}),
|
|
58
|
+
id: boundedRedactedString(control.id, Limits.COMPACT_PREVIEW_CHARS),
|
|
59
|
+
...(control.input !== undefined
|
|
60
|
+
? { input: boundedRedactedValue(control.input) }
|
|
61
|
+
: {}),
|
|
62
|
+
queued_at: boundedRedactedString(control.queued_at, Limits.COMPACT_PREVIEW_CHARS),
|
|
63
|
+
run_instance_id: boundedRedactedString(control.run_instance_id, Limits.COMPACT_PREVIEW_CHARS),
|
|
64
|
+
status: boundedRedactedString(control.status, Limits.COMPACT_PREVIEW_CHARS),
|
|
65
|
+
};
|
|
66
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
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 isControlAction(value: string): boolean;
|
|
13
|
+
export declare function normalizeControlAction(value: unknown): string;
|
|
14
|
+
export declare function normalizeControlInput(input: unknown): unknown;
|
|
15
|
+
export declare function normalizeControlRequest(input: unknown): ControlRequest;
|
|
@@ -0,0 +1,97 @@
|
|
|
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
|
+
export function isControlAction(value) {
|
|
21
|
+
return ACTION_PATTERN.test(value);
|
|
22
|
+
}
|
|
23
|
+
export function normalizeControlAction(value) {
|
|
24
|
+
if (typeof value !== "string" || !value.trim()) {
|
|
25
|
+
throw new Error("control.action is required");
|
|
26
|
+
}
|
|
27
|
+
const action = value.trim();
|
|
28
|
+
if (!isControlAction(action)) {
|
|
29
|
+
throw new Error(`invalid control action: ${action}`);
|
|
30
|
+
}
|
|
31
|
+
if (action.length > Limits.CONTROL_ACTION_MAX_LENGTH) {
|
|
32
|
+
throw new Error(`control.action exceeds ${Limits.CONTROL_ACTION_MAX_LENGTH} ASCII characters`);
|
|
33
|
+
}
|
|
34
|
+
return action;
|
|
35
|
+
}
|
|
36
|
+
export function normalizeControlInput(input) {
|
|
37
|
+
let serialized;
|
|
38
|
+
try {
|
|
39
|
+
serialized = JSON.stringify(input);
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
throw new Error("control.input must be JSON-serializable");
|
|
43
|
+
}
|
|
44
|
+
if (serialized === undefined) {
|
|
45
|
+
throw new Error("control.input must be JSON-serializable");
|
|
46
|
+
}
|
|
47
|
+
const bytes = Buffer.byteLength(serialized);
|
|
48
|
+
if (bytes > Limits.CONTROL_INPUT_MAX_BYTES) {
|
|
49
|
+
throw new Error(`control.input exceeds ${Limits.CONTROL_INPUT_MAX_BYTES} serialized bytes`);
|
|
50
|
+
}
|
|
51
|
+
return JSON.parse(serialized);
|
|
52
|
+
}
|
|
53
|
+
function normalizeTarget(value) {
|
|
54
|
+
if (typeof value !== "string" || !value.trim()) {
|
|
55
|
+
throw new Error("control.target is required");
|
|
56
|
+
}
|
|
57
|
+
const target = value.trim();
|
|
58
|
+
if (target === "runtime")
|
|
59
|
+
return target;
|
|
60
|
+
if (!target.startsWith("run:")) {
|
|
61
|
+
throw new Error(`unsupported control target: ${target}; use run:<id> or runtime`);
|
|
62
|
+
}
|
|
63
|
+
const run = target.slice(4);
|
|
64
|
+
if (!RUN_ID_PATTERN.test(run)) {
|
|
65
|
+
throw new Error(`invalid control Run target: ${target}`);
|
|
66
|
+
}
|
|
67
|
+
return `run:${run}`;
|
|
68
|
+
}
|
|
69
|
+
export function normalizeControlRequest(input) {
|
|
70
|
+
if (!input || typeof input !== "object" || Array.isArray(input)) {
|
|
71
|
+
throw new Error("control request must be an object");
|
|
72
|
+
}
|
|
73
|
+
const record = input;
|
|
74
|
+
const fields = Object.keys(record);
|
|
75
|
+
const removed = fields.filter((field) => REMOVED_FIELDS.has(field));
|
|
76
|
+
if (removed.length > 0) {
|
|
77
|
+
throw new Error(`actor-message fields are removed: ${removed.sort().join(", ")}; use target, action, input, verbose`);
|
|
78
|
+
}
|
|
79
|
+
const unknown = fields.filter((field) => !CONTROL_FIELDS.has(field));
|
|
80
|
+
if (unknown.length > 0) {
|
|
81
|
+
throw new Error(`unsupported control fields: ${unknown.sort().join(", ")}`);
|
|
82
|
+
}
|
|
83
|
+
const target = normalizeTarget(record.target);
|
|
84
|
+
const action = normalizeControlAction(record.action);
|
|
85
|
+
if (record.verbose !== undefined && typeof record.verbose !== "boolean") {
|
|
86
|
+
throw new Error("control.verbose must be a boolean");
|
|
87
|
+
}
|
|
88
|
+
const normalizedInput = record.input === undefined
|
|
89
|
+
? undefined
|
|
90
|
+
: normalizeControlInput(record.input);
|
|
91
|
+
return {
|
|
92
|
+
target,
|
|
93
|
+
action,
|
|
94
|
+
...(normalizedInput !== undefined ? { input: normalizedInput } : {}),
|
|
95
|
+
...(record.verbose !== undefined ? { verbose: record.verbose } : {}),
|
|
96
|
+
};
|
|
97
|
+
}
|
package/dist/lib/draft-sleep.js
CHANGED
|
@@ -391,7 +391,7 @@ export function createDraftSleepScheduler(deps) {
|
|
|
391
391
|
...existing,
|
|
392
392
|
failedStage: processed.stage ?? "result_processing",
|
|
393
393
|
lastError: processed.error ?? "automatic draft review processing failed",
|
|
394
|
-
nextAction: "message
|
|
394
|
+
nextAction: "message target=runtime action=review.retry input={\"scope\":\"draft\"}",
|
|
395
395
|
phase: "processing_failed",
|
|
396
396
|
processingAttempts: (existing.processingAttempts ?? 0) + 1,
|
|
397
397
|
updatedAt: now().toISOString(),
|
|
@@ -402,7 +402,7 @@ export function createDraftSleepScheduler(deps) {
|
|
|
402
402
|
...existing,
|
|
403
403
|
failedStage: processed.stage ?? "review_result",
|
|
404
404
|
lastError: processed.error ?? "automatic draft reviewer failed",
|
|
405
|
-
nextAction: "message
|
|
405
|
+
nextAction: "message target=runtime action=review.retry input={\"scope\":\"draft\"}",
|
|
406
406
|
phase: "failed",
|
|
407
407
|
runId: undefined,
|
|
408
408
|
updatedAt: now().toISOString(),
|
|
@@ -444,7 +444,7 @@ export function createDraftSleepScheduler(deps) {
|
|
|
444
444
|
attempts: (prior?.batchId === batch.batchId ? prior.attempts : 0) + 1,
|
|
445
445
|
failedStage: "review_launch",
|
|
446
446
|
lastError: error instanceof Error ? error.message : String(error),
|
|
447
|
-
nextAction: "message
|
|
447
|
+
nextAction: "message target=runtime action=review.retry input={\"scope\":\"draft\"}",
|
|
448
448
|
batchId: batch.batchId,
|
|
449
449
|
inputPath: batch.inputPath,
|
|
450
450
|
phase: "failed",
|
|
@@ -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
|
@@ -5,10 +5,12 @@
|
|
|
5
5
|
*/
|
|
6
6
|
export declare function mutationLockPath(path: string): string;
|
|
7
7
|
export interface FileMutationLockOptions {
|
|
8
|
+
onBeforeLockPublish?(): void;
|
|
8
9
|
onBeforeReclaimRemove?(): void;
|
|
9
10
|
onContention?(): void;
|
|
10
11
|
onRemovalContention?(): void;
|
|
11
12
|
}
|
|
12
13
|
export declare function acquireFileMutationLock(path: string, options?: FileMutationLockOptions): () => void;
|
|
13
14
|
export declare function withFileMutationLock<T>(path: string, mutate: () => T, options?: FileMutationLockOptions): T;
|
|
15
|
+
export declare function writeTextAtomic(path: string, content: string): void;
|
|
14
16
|
export declare function writeJsonAtomic(path: string, value: unknown): void;
|
package/dist/lib/file-state.js
CHANGED
|
@@ -3,17 +3,27 @@
|
|
|
3
3
|
* Zones: file persistence, atomic writes, runtime state support
|
|
4
4
|
* Owns generic durable JSON file writes shared by registry config and async run state.
|
|
5
5
|
*/
|
|
6
|
+
import { spawnSync } from "node:child_process";
|
|
6
7
|
import { createHash, randomUUID } from "node:crypto";
|
|
7
|
-
import { existsSync, mkdirSync, readFileSync, realpathSync, renameSync, rmSync, statSync, unlinkSync, writeFileSync
|
|
8
|
+
import { existsSync, lstatSync, mkdirSync, readFileSync, realpathSync, renameSync, rmSync, statSync, unlinkSync, writeFileSync } from "node:fs";
|
|
8
9
|
import { tmpdir } from "node:os";
|
|
9
10
|
import { basename, dirname, join, parse, resolve } from "node:path";
|
|
10
|
-
const FILE_MUTATION_LOCK_TIMEOUT_MS = 5000;
|
|
11
|
+
const FILE_MUTATION_LOCK_TIMEOUT_MS = process.platform === "win32" ? 15000 : 5000;
|
|
11
12
|
const FILE_MUTATION_LOCK_STALE_MS = 30000;
|
|
12
13
|
const FILE_MUTATION_LOCK_ROOT = join(tmpdir(), "pi-actors-file-locks");
|
|
13
14
|
function canonicalMutationPath(path) {
|
|
14
15
|
const absolute = resolve(path);
|
|
15
|
-
|
|
16
|
-
|
|
16
|
+
try {
|
|
17
|
+
if (lstatSync(absolute).isSymbolicLink()) {
|
|
18
|
+
const target = realpathSync.native(absolute);
|
|
19
|
+
return process.platform === "win32" ? target.toLowerCase() : target;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
catch {
|
|
23
|
+
/* Materialization may race lock-key derivation; canonicalize through the parent. */
|
|
24
|
+
}
|
|
25
|
+
const suffix = [basename(absolute)];
|
|
26
|
+
let existing = dirname(absolute);
|
|
17
27
|
while (!existsSync(existing)) {
|
|
18
28
|
const parent = dirname(existing);
|
|
19
29
|
if (parent === existing || existing === parse(existing).root)
|
|
@@ -33,6 +43,20 @@ export function mutationLockPath(path) {
|
|
|
33
43
|
.digest("hex");
|
|
34
44
|
return join(FILE_MUTATION_LOCK_ROOT, `${key}.lock`);
|
|
35
45
|
}
|
|
46
|
+
function isZombieProcess(pid) {
|
|
47
|
+
if (process.platform === "linux")
|
|
48
|
+
try {
|
|
49
|
+
const stat = readFileSync(`/proc/${pid}/stat`, "utf8");
|
|
50
|
+
return stat.slice(stat.lastIndexOf(")") + 2).startsWith("Z ");
|
|
51
|
+
}
|
|
52
|
+
catch {
|
|
53
|
+
return false;
|
|
54
|
+
}
|
|
55
|
+
if (process.platform !== "darwin")
|
|
56
|
+
return false;
|
|
57
|
+
const result = spawnSync("ps", ["-p", String(pid), "-o", "stat="], { encoding: "utf8" });
|
|
58
|
+
return result.status === 0 && result.stdout.trim().startsWith("Z");
|
|
59
|
+
}
|
|
36
60
|
function lockOwnerStatus(lockPath) {
|
|
37
61
|
try {
|
|
38
62
|
const owner = JSON.parse(readFileSync(join(lockPath, "owner.json"), "utf8"));
|
|
@@ -41,6 +65,8 @@ function lockOwnerStatus(lockPath) {
|
|
|
41
65
|
return "unknown";
|
|
42
66
|
try {
|
|
43
67
|
process.kill(pid, 0);
|
|
68
|
+
if (Date.now() - statSync(lockPath).mtimeMs > 50 && isZombieProcess(pid))
|
|
69
|
+
return "dead";
|
|
44
70
|
return "alive";
|
|
45
71
|
}
|
|
46
72
|
catch (error) {
|
|
@@ -62,6 +88,46 @@ function readLockToken(lockPath) {
|
|
|
62
88
|
return undefined;
|
|
63
89
|
}
|
|
64
90
|
}
|
|
91
|
+
function prepareLockBoundary(lockPath, token, onBeforePublish) {
|
|
92
|
+
const pendingPath = `${lockPath}.${process.pid}.${token}.pending`;
|
|
93
|
+
try {
|
|
94
|
+
mkdirSync(pendingPath);
|
|
95
|
+
writeFileSync(join(pendingPath, "owner.json"), `${JSON.stringify({ pid: process.pid, token, acquired_at: new Date().toISOString() })}\n`, "utf8");
|
|
96
|
+
onBeforePublish?.();
|
|
97
|
+
return pendingPath;
|
|
98
|
+
}
|
|
99
|
+
catch (error) {
|
|
100
|
+
try {
|
|
101
|
+
rmSync(pendingPath, { recursive: true, force: true });
|
|
102
|
+
}
|
|
103
|
+
catch { }
|
|
104
|
+
throw error;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
function removeLockBoundary(lockPath, token) {
|
|
108
|
+
if (readLockToken(lockPath) !== token)
|
|
109
|
+
return false;
|
|
110
|
+
if (process.platform === "win32")
|
|
111
|
+
try {
|
|
112
|
+
rmSync(lockPath, { recursive: true, force: true, maxRetries: 20, retryDelay: 5 });
|
|
113
|
+
return true;
|
|
114
|
+
}
|
|
115
|
+
catch {
|
|
116
|
+
return false;
|
|
117
|
+
}
|
|
118
|
+
const removingPath = `${lockPath}.${process.pid}.${randomUUID()}.removing`;
|
|
119
|
+
try {
|
|
120
|
+
renameSync(lockPath, removingPath);
|
|
121
|
+
}
|
|
122
|
+
catch {
|
|
123
|
+
return false;
|
|
124
|
+
}
|
|
125
|
+
try {
|
|
126
|
+
rmSync(removingPath, { recursive: true, force: true });
|
|
127
|
+
}
|
|
128
|
+
catch { }
|
|
129
|
+
return true;
|
|
130
|
+
}
|
|
65
131
|
function tryReclaimRemovalBoundary(reclaimPath) {
|
|
66
132
|
if (!existsSync(reclaimPath))
|
|
67
133
|
return;
|
|
@@ -72,7 +138,7 @@ function tryReclaimRemovalBoundary(reclaimPath) {
|
|
|
72
138
|
if ((ownerStatus === "dead" ||
|
|
73
139
|
(ownerStatus === "unknown" && age > FILE_MUTATION_LOCK_STALE_MS)) &&
|
|
74
140
|
readLockToken(reclaimPath) === inspectedToken) {
|
|
75
|
-
|
|
141
|
+
removeLockBoundary(reclaimPath, inspectedToken);
|
|
76
142
|
}
|
|
77
143
|
}
|
|
78
144
|
catch {
|
|
@@ -82,28 +148,28 @@ function tryReclaimRemovalBoundary(reclaimPath) {
|
|
|
82
148
|
function withRemovalBoundary(lockPath, action) {
|
|
83
149
|
const reclaimPath = `${lockPath}.reclaim`;
|
|
84
150
|
const token = randomUUID();
|
|
151
|
+
const pendingPath = prepareLockBoundary(reclaimPath, token);
|
|
85
152
|
try {
|
|
86
|
-
mkdirSync(reclaimPath);
|
|
87
153
|
try {
|
|
88
|
-
|
|
154
|
+
renameSync(pendingPath, reclaimPath);
|
|
89
155
|
}
|
|
90
|
-
catch
|
|
91
|
-
|
|
92
|
-
|
|
156
|
+
catch {
|
|
157
|
+
tryReclaimRemovalBoundary(reclaimPath);
|
|
158
|
+
return false;
|
|
159
|
+
}
|
|
160
|
+
try {
|
|
161
|
+
action();
|
|
162
|
+
return true;
|
|
163
|
+
}
|
|
164
|
+
finally {
|
|
165
|
+
removeLockBoundary(reclaimPath, token);
|
|
93
166
|
}
|
|
94
|
-
}
|
|
95
|
-
catch {
|
|
96
|
-
tryReclaimRemovalBoundary(reclaimPath);
|
|
97
|
-
return false;
|
|
98
|
-
}
|
|
99
|
-
try {
|
|
100
|
-
action();
|
|
101
|
-
return true;
|
|
102
167
|
}
|
|
103
168
|
finally {
|
|
104
|
-
|
|
105
|
-
rmSync(
|
|
169
|
+
try {
|
|
170
|
+
rmSync(pendingPath, { recursive: true, force: true });
|
|
106
171
|
}
|
|
172
|
+
catch { }
|
|
107
173
|
}
|
|
108
174
|
}
|
|
109
175
|
function tryReclaimMutationLock(lockPath, options) {
|
|
@@ -120,8 +186,7 @@ function tryReclaimMutationLock(lockPath, options) {
|
|
|
120
186
|
(ownerStatus === "unknown" && age > FILE_MUTATION_LOCK_STALE_MS)) &&
|
|
121
187
|
readLockToken(lockPath) === inspectedToken) {
|
|
122
188
|
options.onBeforeReclaimRemove?.();
|
|
123
|
-
|
|
124
|
-
reclaimed = true;
|
|
189
|
+
reclaimed = removeLockBoundary(lockPath, inspectedToken);
|
|
125
190
|
}
|
|
126
191
|
})) {
|
|
127
192
|
return false;
|
|
@@ -133,32 +198,34 @@ export function acquireFileMutationLock(path, options = {}) {
|
|
|
133
198
|
const lockPath = mutationLockPath(path);
|
|
134
199
|
const deadline = Date.now() + FILE_MUTATION_LOCK_TIMEOUT_MS;
|
|
135
200
|
const token = randomUUID();
|
|
201
|
+
const pendingPath = prepareLockBoundary(lockPath, token, options.onBeforeLockPublish);
|
|
136
202
|
let contentionReported = false;
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
mkdirSync(lockPath);
|
|
203
|
+
try {
|
|
204
|
+
for (;;) {
|
|
140
205
|
try {
|
|
141
|
-
|
|
206
|
+
renameSync(pendingPath, lockPath);
|
|
207
|
+
break;
|
|
142
208
|
}
|
|
143
209
|
catch (error) {
|
|
144
|
-
|
|
145
|
-
|
|
210
|
+
if (!contentionReported) {
|
|
211
|
+
contentionReported = true;
|
|
212
|
+
options.onContention?.();
|
|
213
|
+
}
|
|
214
|
+
tryReclaimMutationLock(lockPath, options);
|
|
215
|
+
if (Date.now() >= deadline) {
|
|
216
|
+
throw new Error(`Timed out waiting for file mutation lock: ${canonicalMutationPath(path)}`, {
|
|
217
|
+
cause: error,
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 10);
|
|
146
221
|
}
|
|
147
|
-
break;
|
|
148
222
|
}
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
}
|
|
154
|
-
tryReclaimMutationLock(lockPath, options);
|
|
155
|
-
if (Date.now() >= deadline) {
|
|
156
|
-
throw new Error(`Timed out waiting for file mutation lock: ${canonicalMutationPath(path)}`, {
|
|
157
|
-
cause: error,
|
|
158
|
-
});
|
|
159
|
-
}
|
|
160
|
-
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 10);
|
|
223
|
+
}
|
|
224
|
+
finally {
|
|
225
|
+
try {
|
|
226
|
+
rmSync(pendingPath, { recursive: true, force: true });
|
|
161
227
|
}
|
|
228
|
+
catch { }
|
|
162
229
|
}
|
|
163
230
|
let released = false;
|
|
164
231
|
return () => {
|
|
@@ -168,9 +235,7 @@ export function acquireFileMutationLock(path, options = {}) {
|
|
|
168
235
|
const deadline = Date.now() + FILE_MUTATION_LOCK_TIMEOUT_MS;
|
|
169
236
|
let removalContentionReported = false;
|
|
170
237
|
while (!withRemovalBoundary(lockPath, () => {
|
|
171
|
-
|
|
172
|
-
rmSync(lockPath, { recursive: true, force: true });
|
|
173
|
-
}
|
|
238
|
+
removeLockBoundary(lockPath, token);
|
|
174
239
|
})) {
|
|
175
240
|
if (!removalContentionReported) {
|
|
176
241
|
removalContentionReported = true;
|
|
@@ -191,11 +256,11 @@ export function withFileMutationLock(path, mutate, options = {}) {
|
|
|
191
256
|
release();
|
|
192
257
|
}
|
|
193
258
|
}
|
|
194
|
-
export function
|
|
259
|
+
export function writeTextAtomic(path, content) {
|
|
195
260
|
mkdirSync(dirname(path), { recursive: true });
|
|
196
261
|
const tempPath = `${path}.${process.pid}.${Date.now()}.${randomUUID()}.tmp`;
|
|
197
262
|
try {
|
|
198
|
-
writeFileSync(tempPath,
|
|
263
|
+
writeFileSync(tempPath, content, "utf8");
|
|
199
264
|
renameSync(tempPath, path);
|
|
200
265
|
}
|
|
201
266
|
catch (error) {
|
|
@@ -208,3 +273,6 @@ export function writeJsonAtomic(path, value) {
|
|
|
208
273
|
throw error;
|
|
209
274
|
}
|
|
210
275
|
}
|
|
276
|
+
export function writeJsonAtomic(path, value) {
|
|
277
|
+
writeTextAtomic(path, `${JSON.stringify(value, null, 2)}\n`);
|
|
278
|
+
}
|
|
@@ -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;
|
|
@@ -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
|
import { join } from "node:path";
|
|
@@ -11,7 +11,7 @@ function bounded(value) {
|
|
|
11
11
|
? text
|
|
12
12
|
: `${text.slice(0, FEEDBACK_LIMIT - 1)}…`;
|
|
13
13
|
}
|
|
14
|
-
export function
|
|
14
|
+
export function killOwnedRunFromInspector(ownerId, run, stateRoot, expectedRunInstanceId, deps) {
|
|
15
15
|
let stateDir;
|
|
16
16
|
try {
|
|
17
17
|
stateDir = join(stateRoot, safeRunId(run));
|
|
@@ -8,13 +8,13 @@ import * as InspectorActions from "./inspector-actions.js";
|
|
|
8
8
|
import * as InspectorOverlay from "./inspector-overlay.js";
|
|
9
9
|
import * as Paths from "./paths.js";
|
|
10
10
|
export function registerActorInspectorCommand(pi, getRunOwnerId) {
|
|
11
|
-
pi.registerCommand("
|
|
12
|
-
description: "Open the keyboard-driven
|
|
11
|
+
pi.registerCommand("actor-inspector", {
|
|
12
|
+
description: "Open the keyboard-driven Actor Inspector",
|
|
13
13
|
handler: async (_args, ctx) => {
|
|
14
14
|
ctx.ui.setWidget("zz-pi-actors-comms", undefined);
|
|
15
15
|
await ctx.ui.custom((tui, theme, _keybindings, done) => new InspectorOverlay.ActorInspectorOverlay({
|
|
16
16
|
done,
|
|
17
|
-
killRun: (run, runInstanceId) => InspectorActions.
|
|
17
|
+
killRun: (run, runInstanceId) => InspectorActions.killOwnedRunFromInspector(getRunOwnerId(ctx), run, Paths.EXTENSION_RUNTIME_PATHS.runStateRoot, runInstanceId, {
|
|
18
18
|
getRunStatus: AsyncRuns.getRunStatus,
|
|
19
19
|
killRun: AsyncRuns.killRun,
|
|
20
20
|
}),
|