@llblab/pi-actors 0.43.0 → 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 +12 -6
- package/BACKLOG.md +189 -1
- package/CHANGELOG.md +180 -301
- package/README.md +9 -7
- package/dist/index.js +1 -1
- package/dist/lib/async-runs.d.ts +2 -1
- package/dist/lib/async-runs.js +16 -4
- 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 +3 -0
- package/dist/lib/control.js +27 -14
- package/dist/lib/draft-sleep.js +3 -3
- package/dist/lib/file-state.d.ts +1 -0
- package/dist/lib/file-state.js +98 -42
- package/dist/lib/inspector-overlay.d.ts +2 -0
- package/dist/lib/inspector-overlay.js +92 -53
- package/dist/lib/limits.d.ts +5 -3
- package/dist/lib/limits.js +5 -3
- package/dist/lib/prompts.d.ts +1 -1
- package/dist/lib/prompts.js +1 -1
- package/dist/lib/recipe-control.js +6 -2
- package/dist/lib/review-control.d.ts +1 -1
- package/dist/lib/review-control.js +4 -5
- package/dist/lib/runs-control-delivery.d.ts +8 -1
- package/dist/lib/runs-control-delivery.js +38 -15
- package/dist/lib/runs-controls.d.ts +2 -0
- package/dist/lib/runs-controls.js +5 -3
- package/dist/lib/runs-trace.d.ts +2 -2
- package/dist/lib/runs-trace.js +24 -20
- package/dist/lib/runtime-identity.d.ts +7 -0
- package/dist/lib/runtime-identity.js +35 -0
- 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.js +53 -14
- package/dist/lib/tools-message.d.ts +1 -2
- package/dist/lib/tools-message.js +6 -6
- package/dist/lib/tools-response.d.ts +0 -1
- package/dist/lib/tools-response.js +0 -9
- package/dist/lib/tools.d.ts +1 -1
- package/dist/lib/tools.js +1 -1
- package/dist/lib/trace-projection.js +30 -10
- package/dist/scripts/locker.mjs +9 -16
- package/dist/scripts/music-player.mjs +7 -13
- package/dist/scripts/release-gates.mjs +33 -2
- package/dist/scripts/validate-recipe.mjs +5 -4
- package/dist/skills/actors/SKILL.md +12 -7
- package/dist/skills/swarm/SKILL.md +0 -2
- package/docs/0.43-baseline.md +18 -23
- package/docs/README.md +2 -4
- package/docs/actor-inspector.md +5 -5
- package/docs/async-runs.md +2 -2
- package/docs/command-templates.md +6 -116
- package/docs/recipe-library.md +2 -4
- package/docs/releasing.md +28 -0
- package/docs/template-recipes.md +1 -1
- package/docs/tool-registry.md +2 -2
- package/index.ts +1 -1
- package/lib/async-runs.ts +18 -5
- 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 +33 -18
- package/lib/draft-sleep.ts +3 -3
- package/lib/file-state.ts +68 -61
- package/lib/inspector-overlay.ts +93 -58
- package/lib/limits.ts +5 -3
- package/lib/prompts.ts +1 -1
- package/lib/recipe-control.ts +9 -2
- package/lib/review-control.ts +4 -5
- package/lib/runs-control-delivery.ts +45 -17
- package/lib/runs-controls.ts +12 -3
- package/lib/runs-trace.ts +23 -19
- package/lib/runtime-identity.ts +39 -0
- package/lib/runtime-triage.ts +120 -0
- package/lib/tool-review-scheduler.ts +7 -7
- package/lib/tools-inspect.ts +60 -16
- package/lib/tools-message.ts +7 -8
- package/lib/tools-response.ts +0 -12
- package/lib/tools.ts +4 -4
- package/lib/trace-projection.ts +33 -10
- package/package.json +1 -1
- package/scripts/locker.mjs +9 -16
- package/scripts/music-player.mjs +7 -13
- package/scripts/release-gates.mjs +33 -2
- package/scripts/validate-recipe.mjs +5 -4
- package/skills/actors/SKILL.md +12 -7
- package/skills/swarm/SKILL.md +0 -2
- package/docs/actors-deep-reference.md +0 -108
- package/docs/component-recipes.md +0 -45
- package/docs/task-first-recipes.md +0 -261
package/dist/lib/runs-trace.js
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Run Trace event journal.
|
|
3
|
-
* Zones: structured event validation,
|
|
4
|
-
* Owns Run-local
|
|
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
5
|
*/
|
|
6
6
|
import { randomUUID } from "node:crypto";
|
|
7
7
|
import { writeFileSync } from "node:fs";
|
|
8
8
|
import { join } from "node:path";
|
|
9
|
+
import { withFileMutationLock } from "./file-state.js";
|
|
9
10
|
import * as Limits from "./limits.js";
|
|
10
11
|
import { readJsonlFileResilient } from "./state-readers.js";
|
|
11
12
|
const KIND_PATTERN = /^[a-z][a-z0-9_-]*(?:\.[a-z0-9_-]+)*$/;
|
|
@@ -73,24 +74,27 @@ function normalizeTraceEventInput(input) {
|
|
|
73
74
|
};
|
|
74
75
|
}
|
|
75
76
|
export function appendRunTraceEvent(stateDir, input) {
|
|
76
|
-
const
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
encoded
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
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
|
+
});
|
|
94
98
|
}
|
|
95
99
|
export function readRunTraceEvents(stateDir, limit = Limits.TRACE_EVENT_MAX_READ) {
|
|
96
100
|
const records = readJsonlFileResilient(runTraceFile(stateDir)).records;
|
|
@@ -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
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure runtime Control triage policy.
|
|
3
|
+
* Zones: pending admission, status-age classification, terminal/replacement staleness
|
|
4
|
+
* Owns Control triage decisions; journal reads and owner filtering stay in adapters, while safe fields come from control-projection.
|
|
5
|
+
*/
|
|
6
|
+
import * as ControlProjection from "./control-projection.ts";
|
|
7
|
+
export declare const RUNTIME_CONTROL_STALE_AFTER_MS: number;
|
|
8
|
+
export interface RuntimeTriageRun {
|
|
9
|
+
run: string;
|
|
10
|
+
runInstanceId?: string;
|
|
11
|
+
status: string;
|
|
12
|
+
}
|
|
13
|
+
export interface RuntimePendingControl extends ControlProjection.ProjectedRunControl {
|
|
14
|
+
age_ms: number;
|
|
15
|
+
reason: "age_threshold_reached" | "run_generation_replaced" | "run_generation_unavailable" | "run_terminal" | "within_age_threshold";
|
|
16
|
+
run: string;
|
|
17
|
+
status: "queued" | "delivered" | "claimed";
|
|
18
|
+
status_at: string;
|
|
19
|
+
}
|
|
20
|
+
export interface RuntimeControlDiagnostic {
|
|
21
|
+
reason: "invalid_action" | "invalid_control_id" | "invalid_generation" | "invalid_record" | "invalid_status" | "invalid_status_timestamp";
|
|
22
|
+
run: string;
|
|
23
|
+
}
|
|
24
|
+
export interface RuntimeControlClassification {
|
|
25
|
+
diagnostic?: RuntimeControlDiagnostic;
|
|
26
|
+
pending?: RuntimePendingControl;
|
|
27
|
+
stale?: RuntimePendingControl;
|
|
28
|
+
}
|
|
29
|
+
export declare function classifyRuntimeControl(run: RuntimeTriageRun, value: unknown, nowMs: number): RuntimeControlClassification;
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure runtime Control triage policy.
|
|
3
|
+
* Zones: pending admission, status-age classification, terminal/replacement staleness
|
|
4
|
+
* Owns Control triage decisions; journal reads and owner filtering stay in adapters, while safe fields come from control-projection.
|
|
5
|
+
*/
|
|
6
|
+
import * as ControlProjection from "./control-projection.js";
|
|
7
|
+
export const RUNTIME_CONTROL_STALE_AFTER_MS = 5 * 60 * 1_000;
|
|
8
|
+
const PENDING_STATUSES = new Set([
|
|
9
|
+
"queued",
|
|
10
|
+
"delivered",
|
|
11
|
+
"claimed",
|
|
12
|
+
]);
|
|
13
|
+
const TERMINAL_STATUSES = new Set(["handled", "failed"]);
|
|
14
|
+
function diagnostic(run, reason) {
|
|
15
|
+
return { diagnostic: { reason, run } };
|
|
16
|
+
}
|
|
17
|
+
function statusTimestamp(record, status) {
|
|
18
|
+
if (status === "failed")
|
|
19
|
+
return record.failed_at;
|
|
20
|
+
if (status === "handled")
|
|
21
|
+
return record.handled_at;
|
|
22
|
+
if (status === "claimed")
|
|
23
|
+
return record.claimed_at;
|
|
24
|
+
if (status === "delivered")
|
|
25
|
+
return record.delivered_at;
|
|
26
|
+
return record.queued_at;
|
|
27
|
+
}
|
|
28
|
+
export function classifyRuntimeControl(run, value, nowMs) {
|
|
29
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
30
|
+
return diagnostic(run.run, "invalid_record");
|
|
31
|
+
}
|
|
32
|
+
const record = value;
|
|
33
|
+
const status = record.status;
|
|
34
|
+
if (!PENDING_STATUSES.has(status) && !TERMINAL_STATUSES.has(status)) {
|
|
35
|
+
return diagnostic(run.run, "invalid_status");
|
|
36
|
+
}
|
|
37
|
+
if (typeof record.id !== "string" || !record.id) {
|
|
38
|
+
return diagnostic(run.run, "invalid_control_id");
|
|
39
|
+
}
|
|
40
|
+
if (typeof record.action !== "string" || !record.action) {
|
|
41
|
+
return diagnostic(run.run, "invalid_action");
|
|
42
|
+
}
|
|
43
|
+
if (typeof record.run_instance_id !== "string" || !record.run_instance_id) {
|
|
44
|
+
return diagnostic(run.run, "invalid_generation");
|
|
45
|
+
}
|
|
46
|
+
const timestamp = statusTimestamp(record, status);
|
|
47
|
+
const timestampMs = typeof timestamp === "string" ? Date.parse(timestamp) : Number.NaN;
|
|
48
|
+
if (!Number.isFinite(timestampMs)) {
|
|
49
|
+
return diagnostic(run.run, "invalid_status_timestamp");
|
|
50
|
+
}
|
|
51
|
+
if (TERMINAL_STATUSES.has(status))
|
|
52
|
+
return {};
|
|
53
|
+
const ageMs = Math.max(0, Math.floor(nowMs - timestampMs));
|
|
54
|
+
const staleReason = !run.runInstanceId
|
|
55
|
+
? "run_generation_unavailable"
|
|
56
|
+
: record.run_instance_id !== run.runInstanceId
|
|
57
|
+
? "run_generation_replaced"
|
|
58
|
+
: run.status !== "running"
|
|
59
|
+
? "run_terminal"
|
|
60
|
+
: ageMs >= RUNTIME_CONTROL_STALE_AFTER_MS
|
|
61
|
+
? "age_threshold_reached"
|
|
62
|
+
: undefined;
|
|
63
|
+
const projected = ControlProjection.projectRunControl(record);
|
|
64
|
+
const pending = {
|
|
65
|
+
...projected,
|
|
66
|
+
age_ms: ageMs,
|
|
67
|
+
reason: staleReason ?? "within_age_threshold",
|
|
68
|
+
run: run.run,
|
|
69
|
+
status: status,
|
|
70
|
+
status_at: statusTimestamp(projected, status),
|
|
71
|
+
};
|
|
72
|
+
return {
|
|
73
|
+
pending,
|
|
74
|
+
...(staleReason ? { stale: pending } : {}),
|
|
75
|
+
};
|
|
76
|
+
}
|
|
@@ -264,7 +264,7 @@ export function applyApprovedToolReviewAtSessionBoundary(options = {}) {
|
|
|
264
264
|
...state,
|
|
265
265
|
failedStage: "approval_state",
|
|
266
266
|
lastError: "approved review path is unavailable",
|
|
267
|
-
nextAction: "message
|
|
267
|
+
nextAction: "message target=runtime action=review.retry input={\"scope\":\"tool\"}",
|
|
268
268
|
phase: "processing_failed",
|
|
269
269
|
updatedAt: (options.now ?? (() => new Date()))().toISOString(),
|
|
270
270
|
};
|
|
@@ -287,7 +287,7 @@ export function applyApprovedToolReviewAtSessionBoundary(options = {}) {
|
|
|
287
287
|
...state,
|
|
288
288
|
failedStage: "transaction",
|
|
289
289
|
lastError: `tool review transaction remained ${transaction.phase}`,
|
|
290
|
-
nextAction: "message
|
|
290
|
+
nextAction: "message target=runtime action=review.retry input={\"scope\":\"tool\"}",
|
|
291
291
|
phase: "processing_failed",
|
|
292
292
|
updatedAt: (options.now ?? (() => new Date()))().toISOString(),
|
|
293
293
|
};
|
|
@@ -316,7 +316,7 @@ export function applyApprovedToolReviewAtSessionBoundary(options = {}) {
|
|
|
316
316
|
...pending,
|
|
317
317
|
failedStage: "lineage",
|
|
318
318
|
lastError: error instanceof Error ? error.message : String(error),
|
|
319
|
-
nextAction: "message
|
|
319
|
+
nextAction: "message target=runtime action=review.retry input={\"scope\":\"tool\"}",
|
|
320
320
|
};
|
|
321
321
|
writeJsonAtomic(statePath, failedPending);
|
|
322
322
|
return { outcome: "lineage_pending", state: failedPending };
|
|
@@ -337,7 +337,7 @@ export function applyApprovedToolReviewAtSessionBoundary(options = {}) {
|
|
|
337
337
|
...state,
|
|
338
338
|
failedStage: "transaction",
|
|
339
339
|
lastError: error instanceof Error ? error.message : String(error),
|
|
340
|
-
nextAction: "message
|
|
340
|
+
nextAction: "message target=runtime action=review.retry input={\"scope\":\"tool\"}",
|
|
341
341
|
phase: "processing_failed",
|
|
342
342
|
updatedAt: (options.now ?? (() => new Date()))().toISOString(),
|
|
343
343
|
};
|
|
@@ -401,7 +401,7 @@ export function createToolReviewScheduler(deps) {
|
|
|
401
401
|
...existing,
|
|
402
402
|
failedStage: processed.stage ?? "review_result",
|
|
403
403
|
lastError: processed.error ?? "automatic tool reviewer failed",
|
|
404
|
-
nextAction: "message
|
|
404
|
+
nextAction: "message target=runtime action=review.retry input={\"scope\":\"tool\"}",
|
|
405
405
|
phase: processed.outcome === "processing_failed"
|
|
406
406
|
? "processing_failed"
|
|
407
407
|
: "failed",
|
|
@@ -437,7 +437,7 @@ export function createToolReviewScheduler(deps) {
|
|
|
437
437
|
attempts: existing.attempts + 1,
|
|
438
438
|
failedStage: "review_launch",
|
|
439
439
|
lastError: error instanceof Error ? error.message : String(error),
|
|
440
|
-
nextAction: "message
|
|
440
|
+
nextAction: "message target=runtime action=review.retry input={\"scope\":\"tool\"}",
|
|
441
441
|
phase: "failed",
|
|
442
442
|
updatedAt: now().toISOString(),
|
|
443
443
|
});
|
|
@@ -471,7 +471,7 @@ export function createToolReviewScheduler(deps) {
|
|
|
471
471
|
failedStage: "review_launch",
|
|
472
472
|
inputPath: batch.inputPath,
|
|
473
473
|
lastError: error instanceof Error ? error.message : String(error),
|
|
474
|
-
nextAction: "message
|
|
474
|
+
nextAction: "message target=runtime action=review.retry input={\"scope\":\"tool\"}",
|
|
475
475
|
phase: "failed",
|
|
476
476
|
reviewerInputPath: batch.reviewerInputPath,
|
|
477
477
|
reviewId: batch.reviewId,
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import { join } from "node:path";
|
|
7
7
|
import * as AsyncRuns from "./async-runs.js";
|
|
8
|
+
import * as ControlProjection from "./control-projection.js";
|
|
8
9
|
import * as Inspector from "./inspector.js";
|
|
9
10
|
import * as Limits from "./limits.js";
|
|
10
11
|
import * as Paths from "./paths.js";
|
|
@@ -13,6 +14,8 @@ import * as ReviewDiagnostics from "./review-diagnostics.js";
|
|
|
13
14
|
import * as RunsControls from "./runs-controls.js";
|
|
14
15
|
import * as RunControlDelivery from "./runs-control-delivery.js";
|
|
15
16
|
import * as RunsTrace from "./runs-trace.js";
|
|
17
|
+
import * as RuntimeIdentity from "./runtime-identity.js";
|
|
18
|
+
import * as RuntimeTriage from "./runtime-triage.js";
|
|
16
19
|
import * as Schema from "./schema.js";
|
|
17
20
|
import * as ToolsAccess from "./tools-access.js";
|
|
18
21
|
import * as ToolsResponse from "./tools-response.js";
|
|
@@ -23,7 +26,8 @@ function runtimeStatus() {
|
|
|
23
26
|
return {
|
|
24
27
|
automatic_review: process.env.PI_ACTORS_AUTOMATIC_REVIEW !== "off",
|
|
25
28
|
run_root: Paths.getRunStateRoot(),
|
|
26
|
-
|
|
29
|
+
state_schema: RuntimeIdentity.RUN_STATE_SCHEMA,
|
|
30
|
+
version: RuntimeIdentity.getPackageVersion(),
|
|
27
31
|
};
|
|
28
32
|
}
|
|
29
33
|
function runtimeRuns(ctx, deps, status) {
|
|
@@ -49,14 +53,39 @@ function runtimeTriage(ctx, deps) {
|
|
|
49
53
|
const inventory = runtimeRuns(ctx, deps, undefined);
|
|
50
54
|
const runs = inventory.runs;
|
|
51
55
|
const failed = runs.filter((run) => run.status === "failed");
|
|
52
|
-
const
|
|
56
|
+
const pendingControls = [];
|
|
57
|
+
const staleControls = [];
|
|
58
|
+
const controlDiagnostics = [];
|
|
59
|
+
const nowMs = Date.now();
|
|
60
|
+
for (const run of runs) {
|
|
53
61
|
const stateDir = typeof run.state_dir === "string" ? run.state_dir : undefined;
|
|
54
62
|
if (!stateDir)
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
63
|
+
continue;
|
|
64
|
+
const runId = String(run.run ?? "unknown");
|
|
65
|
+
const journal = RunsControls.readRunControlJournalFromStateDir(stateDir);
|
|
66
|
+
controlDiagnostics.push(...journal.diagnostics.map((diagnostic) => ({
|
|
67
|
+
...(diagnostic.line === undefined ? {} : { line: diagnostic.line }),
|
|
68
|
+
reason: diagnostic.line === undefined
|
|
69
|
+
? "unreadable_control_journal"
|
|
70
|
+
: "invalid_control_json",
|
|
71
|
+
run: runId,
|
|
72
|
+
})));
|
|
73
|
+
for (const control of journal.records) {
|
|
74
|
+
const classified = RuntimeTriage.classifyRuntimeControl({
|
|
75
|
+
run: runId,
|
|
76
|
+
...(typeof run.run_instance_id === "string"
|
|
77
|
+
? { runInstanceId: run.run_instance_id }
|
|
78
|
+
: {}),
|
|
79
|
+
status: String(run.status ?? "unknown"),
|
|
80
|
+
}, control, nowMs);
|
|
81
|
+
if (classified.pending)
|
|
82
|
+
pendingControls.push(classified.pending);
|
|
83
|
+
if (classified.stale)
|
|
84
|
+
staleControls.push(classified.stale);
|
|
85
|
+
if (classified.diagnostic)
|
|
86
|
+
controlDiagnostics.push({ ...classified.diagnostic });
|
|
87
|
+
}
|
|
88
|
+
}
|
|
60
89
|
const attentionEvents = runs.flatMap((run) => {
|
|
61
90
|
const stateDir = typeof run.state_dir === "string" ? run.state_dir : undefined;
|
|
62
91
|
if (!stateDir)
|
|
@@ -72,16 +101,25 @@ function runtimeTriage(ctx, deps) {
|
|
|
72
101
|
ts: event.ts,
|
|
73
102
|
}));
|
|
74
103
|
});
|
|
104
|
+
const boundedStaleControls = staleControls.slice(0, 40);
|
|
105
|
+
const staleKeys = new Set(boundedStaleControls.map((control) => `${control.run}\0${control.run_instance_id}\0${control.id}`));
|
|
106
|
+
const boundedPendingControls = [
|
|
107
|
+
...boundedStaleControls,
|
|
108
|
+
...pendingControls.filter((control) => !staleKeys.has(`${control.run}\0${control.run_instance_id}\0${control.id}`)),
|
|
109
|
+
].slice(0, 40);
|
|
75
110
|
return {
|
|
76
111
|
...inventory,
|
|
77
112
|
failed_runs: failed,
|
|
78
|
-
|
|
113
|
+
pending_control_count: pendingControls.length,
|
|
114
|
+
pending_controls: boundedPendingControls,
|
|
115
|
+
stale_control_count: staleControls.length,
|
|
116
|
+
stale_controls: boundedStaleControls,
|
|
117
|
+
control_diagnostic_count: controlDiagnostics.length,
|
|
118
|
+
control_diagnostics: controlDiagnostics.slice(0, 40),
|
|
79
119
|
attention_events: attentionEvents.slice(-40).reverse(),
|
|
80
120
|
next_actions: [
|
|
81
121
|
...(failed.length ? ["inspect target=runtime view=runs status=failed"] : []),
|
|
82
|
-
...
|
|
83
|
-
? staleControls.slice(0, 3).map((entry) => `inspect target=run:${String(entry.run)} view=control`)
|
|
84
|
-
: []),
|
|
122
|
+
...staleControls.slice(0, 3).map((entry) => `inspect target=run:${entry.run} view=control`),
|
|
85
123
|
...(attentionEvents.length
|
|
86
124
|
? attentionEvents.slice(-3).map((entry) => `inspect target=run:${String(entry.run)} view=trace source=runtime`)
|
|
87
125
|
: []),
|
|
@@ -93,9 +131,9 @@ function compactRuntime(view, details) {
|
|
|
93
131
|
return `\nruntime runs=${details.runs?.length ?? 0}`;
|
|
94
132
|
}
|
|
95
133
|
if (view === "triage") {
|
|
96
|
-
return `\nruntime failed=${details.failed_runs?.length ?? 0} stale_controls=${details.
|
|
134
|
+
return `\nruntime failed=${details.failed_runs?.length ?? 0} pending_controls=${Number(details.pending_control_count ?? 0)} stale_controls=${Number(details.stale_control_count ?? 0)} attention=${details.attention_events?.length ?? 0}`;
|
|
97
135
|
}
|
|
98
|
-
return `\nruntime version=${String(details.version
|
|
136
|
+
return `\nruntime version=${String(details.version)} state_schema=${String(details.state_schema)} automatic_review=${String(details.automatic_review)}`;
|
|
99
137
|
}
|
|
100
138
|
function inspectRuntime(view, input, ctx, deps) {
|
|
101
139
|
if (view === "status")
|
|
@@ -182,7 +220,8 @@ function inspectRun(run, view, input, ctx, deps) {
|
|
|
182
220
|
: undefined,
|
|
183
221
|
recent_controls: RunsControls.readRunControlsFromStateDir(stateDir)
|
|
184
222
|
.slice(-Math.max(1, Number(input.lines || 20)))
|
|
185
|
-
.reverse()
|
|
223
|
+
.reverse()
|
|
224
|
+
.map(ControlProjection.projectRunControl),
|
|
186
225
|
};
|
|
187
226
|
}
|
|
188
227
|
function inspectTool(name, view, deps) {
|
|
@@ -4,7 +4,6 @@
|
|
|
4
4
|
* Owns public Control execution; journaling, delivery, and lifecycle mutation stay in Run domains.
|
|
5
5
|
*/
|
|
6
6
|
export interface ControlToolDeps {
|
|
7
|
-
|
|
8
|
-
handleRuntimeMessage?: (action: string, input: unknown) => Record<string, unknown>;
|
|
7
|
+
handleRuntimeControl?: (action: string, input: unknown) => Record<string, unknown>;
|
|
9
8
|
}
|
|
10
9
|
export declare function createControlToolDefinition<TContext = unknown>(deps?: ControlToolDeps): any;
|
|
@@ -45,11 +45,11 @@ export function createControlToolDefinition(deps = {}) {
|
|
|
45
45
|
description: "Apply one Control action to an existing Run or the pi-actors runtime.",
|
|
46
46
|
parameters: Schema.objectSchema({
|
|
47
47
|
target: Schema.stringSchema("Run target run:<id>, or literal runtime."),
|
|
48
|
-
action: Schema.stringSchema("Lowercase
|
|
48
|
+
action: Schema.stringSchema("Lowercase ASCII Control action, at most 64 characters."),
|
|
49
49
|
input: Schema.unionSchema([
|
|
50
|
-
Schema.stringSchema("Optional Control input string."),
|
|
51
|
-
Schema.looseObjectSchema("Optional structured Control input."),
|
|
52
|
-
Schema.arraySchema("Optional structured Control input array."),
|
|
50
|
+
Schema.stringSchema("Optional Control input string; serialized JSON must fit 380 bytes."),
|
|
51
|
+
Schema.looseObjectSchema("Optional structured Control input; serialized JSON must fit 380 bytes."),
|
|
52
|
+
Schema.arraySchema("Optional structured Control input array; serialized JSON must fit 380 bytes."),
|
|
53
53
|
]),
|
|
54
54
|
verbose: Schema.booleanSchema("Return full JSON instead of compact text."),
|
|
55
55
|
}, ["target", "action"]),
|
|
@@ -60,10 +60,10 @@ export function createControlToolDefinition(deps = {}) {
|
|
|
60
60
|
if (request.action !== "review.retry" && request.action !== "review.reset") {
|
|
61
61
|
throw new Error(`unsupported runtime Control action: ${request.action}; use review.retry or review.reset`);
|
|
62
62
|
}
|
|
63
|
-
if (!deps.
|
|
63
|
+
if (!deps.handleRuntimeControl) {
|
|
64
64
|
throw new Error("runtime Control is unavailable");
|
|
65
65
|
}
|
|
66
|
-
result = deps.
|
|
66
|
+
result = deps.handleRuntimeControl(request.action, request.input);
|
|
67
67
|
}
|
|
68
68
|
else {
|
|
69
69
|
const run = request.target.slice(4);
|
|
@@ -12,7 +12,6 @@ export declare function compactPreview(value: unknown, maxLength?: number): stri
|
|
|
12
12
|
export declare function compactNextActions(actions: string[]): string;
|
|
13
13
|
export declare function runNextActions(run: unknown): string[];
|
|
14
14
|
export declare function compactAsyncRunStatus(value: unknown): string;
|
|
15
|
-
export declare function artifactNextActions(run: unknown, artifacts: Record<string, unknown>): string[];
|
|
16
15
|
export declare function maybeJsonText(value: unknown, verbose: boolean | undefined, compact: string): string;
|
|
17
16
|
export declare function compactRecipeImports(summary: Record<string, unknown>): string;
|
|
18
17
|
export declare function compactRecipeDoctor(summary: Record<string, unknown>): string;
|
|
@@ -121,15 +121,6 @@ export function compactAsyncRunStatus(value) {
|
|
|
121
121
|
tokens.push(`next=${nextActions.map((action) => action.replaceAll(/\s+/g, "_")).join("|")}`);
|
|
122
122
|
return `\n${tokens.join(" ")}`;
|
|
123
123
|
}
|
|
124
|
-
export function artifactNextActions(run, artifacts) {
|
|
125
|
-
const id = String(run ?? "").trim();
|
|
126
|
-
if (!id || Object.keys(artifacts).length === 0)
|
|
127
|
-
return [];
|
|
128
|
-
return [
|
|
129
|
-
`inspect target=run:${id} view=artifacts verbose=true`,
|
|
130
|
-
`inspect target=run:${id} view=messages`,
|
|
131
|
-
];
|
|
132
|
-
}
|
|
133
124
|
export function maybeJsonText(value, verbose, compact) {
|
|
134
125
|
return verbose ? jsonText(value) : compact;
|
|
135
126
|
}
|
package/dist/lib/tools.d.ts
CHANGED
|
@@ -15,7 +15,7 @@ export interface CoreActorToolDefinitionDeps<TContext extends RuntimeToolContext
|
|
|
15
15
|
configPath: string;
|
|
16
16
|
getActiveTools: () => string[];
|
|
17
17
|
getRuntimeTool: (name: string) => unknown;
|
|
18
|
-
|
|
18
|
+
handleRuntimeControl?: (action: string, input: unknown) => Record<string, unknown>;
|
|
19
19
|
registryRuntime: Pick<RegisterToolRuntimeDeps<TContext>, "getToolNameBlocker" | "getTools" | "notify" | "registerRuntimeTool">;
|
|
20
20
|
setActiveTools: (toolNames: string[]) => void;
|
|
21
21
|
}
|
package/dist/lib/tools.js
CHANGED
|
@@ -37,7 +37,7 @@ export function createCoreActorToolDefinitions(deps) {
|
|
|
37
37
|
}),
|
|
38
38
|
ToolsSpawn.createSpawnToolDefinition(),
|
|
39
39
|
ToolsMessage.createControlToolDefinition({
|
|
40
|
-
|
|
40
|
+
handleRuntimeControl: deps.handleRuntimeControl,
|
|
41
41
|
}),
|
|
42
42
|
ToolsInspect.createInspectToolDefinition({
|
|
43
43
|
getTool: (name) => deps.getRuntimeTool(name),
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import { existsSync, statSync } from "node:fs";
|
|
7
7
|
import { join, relative } from "node:path";
|
|
8
|
+
import * as ControlProjection from "./control-projection.js";
|
|
8
9
|
import * as ExecutionSessions from "./execution-sessions.js";
|
|
9
10
|
import * as Limits from "./limits.js";
|
|
10
11
|
import { resolveArtifactManifest, } from "./runs-artifacts.js";
|
|
@@ -39,6 +40,22 @@ function diagnosticItems(diagnostics) {
|
|
|
39
40
|
detail: redact(diagnostic),
|
|
40
41
|
}));
|
|
41
42
|
}
|
|
43
|
+
function controlDiagnosticItems(diagnostics) {
|
|
44
|
+
return diagnostics.slice(-SOURCE_LIMIT).map((diagnostic, index) => ({
|
|
45
|
+
id: `control-diagnostic:${diagnostic.line ?? 0}:${index}`,
|
|
46
|
+
ts: new Date(0).toISOString(),
|
|
47
|
+
source: "runtime",
|
|
48
|
+
kind: "state.read_error",
|
|
49
|
+
summary: "Malformed or unreadable Control evidence",
|
|
50
|
+
level: "warning",
|
|
51
|
+
detail: {
|
|
52
|
+
...(diagnostic.line === undefined ? {} : { line: diagnostic.line }),
|
|
53
|
+
reason: diagnostic.line === undefined
|
|
54
|
+
? "unreadable_control_journal"
|
|
55
|
+
: "invalid_control_json",
|
|
56
|
+
},
|
|
57
|
+
}));
|
|
58
|
+
}
|
|
42
59
|
function traceEventItems(stateDir) {
|
|
43
60
|
const read = readJsonlFileResilient(join(stateDir, "trace.jsonl"));
|
|
44
61
|
return [
|
|
@@ -60,16 +77,19 @@ function traceEventItems(stateDir) {
|
|
|
60
77
|
function controlItems(stateDir) {
|
|
61
78
|
const read = readJsonlFileResilient(join(stateDir, "controls.jsonl"));
|
|
62
79
|
return [
|
|
63
|
-
...read.records.slice(-SOURCE_LIMIT).map((control) =>
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
80
|
+
...read.records.slice(-SOURCE_LIMIT).map((control) => {
|
|
81
|
+
const projected = ControlProjection.projectRunControl(control);
|
|
82
|
+
return {
|
|
83
|
+
id: `control:${projected.id}`,
|
|
84
|
+
ts: latestControlTimestamp(projected),
|
|
85
|
+
source: "control",
|
|
86
|
+
kind: `control.${projected.status}`,
|
|
87
|
+
summary: `${projected.action} ${projected.status}`,
|
|
88
|
+
...(projected.status === "failed" ? { level: "error" } : {}),
|
|
89
|
+
detail: projected,
|
|
90
|
+
};
|
|
91
|
+
}),
|
|
92
|
+
...controlDiagnosticItems(read.diagnostics),
|
|
73
93
|
];
|
|
74
94
|
}
|
|
75
95
|
function agentItems(stateDir) {
|
package/dist/scripts/locker.mjs
CHANGED
|
@@ -9,13 +9,12 @@
|
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
11
|
import { spawnSync } from "node:child_process";
|
|
12
|
-
import { createHash
|
|
12
|
+
import { createHash } from "node:crypto";
|
|
13
13
|
import {
|
|
14
14
|
appendFileSync,
|
|
15
15
|
existsSync,
|
|
16
16
|
mkdirSync,
|
|
17
17
|
readFileSync,
|
|
18
|
-
rmSync,
|
|
19
18
|
} from "node:fs";
|
|
20
19
|
import { createServer } from "node:net";
|
|
21
20
|
import { dirname, join, resolve } from "node:path";
|
|
@@ -35,6 +34,7 @@ async function importRuntimeModule(name) {
|
|
|
35
34
|
|
|
36
35
|
const { acquireFileMutationLock, writeJsonAtomic, writeTextAtomic } =
|
|
37
36
|
await importRuntimeModule("file-state");
|
|
37
|
+
const { appendRunTraceEvent } = await importRuntimeModule("runs-trace");
|
|
38
38
|
|
|
39
39
|
function parseArgs(argv) {
|
|
40
40
|
const args = { mode: "serve", stateDir: "", leaseMs: 600000, lines: 20 };
|
|
@@ -59,7 +59,6 @@ async function runLocker(argv = process.argv.slice(2)) {
|
|
|
59
59
|
const queuePath = join(stateDir, "queue.json");
|
|
60
60
|
const locksPath = join(stateDir, "locks.json");
|
|
61
61
|
const journalPath = join(stateDir, "journal.jsonl");
|
|
62
|
-
const tracePath = join(stateDir, "trace.jsonl");
|
|
63
62
|
const controlsPath = join(stateDir, "controls.jsonl");
|
|
64
63
|
const controlPath = join(stateDir, "control.fifo");
|
|
65
64
|
let runInstanceId;
|
|
@@ -104,18 +103,13 @@ async function runLocker(argv = process.argv.slice(2)) {
|
|
|
104
103
|
}
|
|
105
104
|
|
|
106
105
|
function emitTrace(kind, summary, data = {}, level = "info") {
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
level,
|
|
115
|
-
summary,
|
|
116
|
-
ts: new Date().toISOString(),
|
|
117
|
-
})}\n`,
|
|
118
|
-
);
|
|
106
|
+
appendRunTraceEvent(stateDir, {
|
|
107
|
+
attention: "followup",
|
|
108
|
+
data,
|
|
109
|
+
kind,
|
|
110
|
+
level,
|
|
111
|
+
summary,
|
|
112
|
+
});
|
|
119
113
|
}
|
|
120
114
|
|
|
121
115
|
function now() {
|
|
@@ -454,7 +448,6 @@ async function runLocker(argv = process.argv.slice(2)) {
|
|
|
454
448
|
}
|
|
455
449
|
|
|
456
450
|
async function serveNamedPipe(endpoint) {
|
|
457
|
-
rmSync(endpoint.path, { force: true });
|
|
458
451
|
let resolveStopped;
|
|
459
452
|
const stopped = new Promise((resolve) => {
|
|
460
453
|
resolveStopped = resolve;
|
|
@@ -52,6 +52,7 @@ async function importRuntimeModule(name) {
|
|
|
52
52
|
|
|
53
53
|
const { acquireFileMutationLock, writeTextAtomic } =
|
|
54
54
|
await importRuntimeModule("file-state");
|
|
55
|
+
const { appendRunTraceEvent } = await importRuntimeModule("runs-trace");
|
|
55
56
|
|
|
56
57
|
const AUDIO_EXTENSIONS = new Set([
|
|
57
58
|
".aac",
|
|
@@ -420,18 +421,12 @@ function readText(path) {
|
|
|
420
421
|
}
|
|
421
422
|
|
|
422
423
|
function emitPlayerEvent(ctx, kind, summary, data = {}) {
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
level: "info",
|
|
430
|
-
summary,
|
|
431
|
-
ts: new Date().toISOString(),
|
|
432
|
-
})}\n`,
|
|
433
|
-
"a",
|
|
434
|
-
);
|
|
424
|
+
appendRunTraceEvent(ctx.stateDir, {
|
|
425
|
+
data,
|
|
426
|
+
kind,
|
|
427
|
+
level: "info",
|
|
428
|
+
summary,
|
|
429
|
+
});
|
|
435
430
|
}
|
|
436
431
|
|
|
437
432
|
function emitTrackEvent(ctx, index, count, track, player) {
|
|
@@ -859,7 +854,6 @@ async function playMain(args) {
|
|
|
859
854
|
const ctx = {
|
|
860
855
|
commandFile: join(stateDir, "command.txt"),
|
|
861
856
|
current: undefined,
|
|
862
|
-
eventFile: join(stateDir, "trace.jsonl"),
|
|
863
857
|
controlsFile: join(stateDir, "controls.jsonl"),
|
|
864
858
|
pidFile: join(stateDir, "current.pid"),
|
|
865
859
|
playerControlFile: join(stateDir, "player-control.txt"),
|