@llblab/pi-actors 0.43.0 → 0.44.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.
Files changed (109) hide show
  1. package/AGENTS.md +16 -10
  2. package/CHANGELOG.md +425 -536
  3. package/README.md +13 -11
  4. package/dist/index.js +1 -1
  5. package/dist/lib/async-runs.d.ts +2 -1
  6. package/dist/lib/async-runs.js +24 -34
  7. package/dist/lib/automatic-review-runtime.d.ts +1 -1
  8. package/dist/lib/automatic-review-runtime.js +5 -5
  9. package/dist/lib/command-templates.d.ts +2 -0
  10. package/dist/lib/command-templates.js +38 -4
  11. package/dist/lib/control-projection.d.ts +20 -0
  12. package/dist/lib/control-projection.js +66 -0
  13. package/dist/lib/control.d.ts +3 -0
  14. package/dist/lib/control.js +27 -14
  15. package/dist/lib/draft-sleep.js +3 -3
  16. package/dist/lib/file-state.d.ts +4 -1
  17. package/dist/lib/file-state.js +118 -44
  18. package/dist/lib/inspector-overlay.d.ts +2 -0
  19. package/dist/lib/inspector-overlay.js +124 -69
  20. package/dist/lib/limits.d.ts +15 -3
  21. package/dist/lib/limits.js +15 -3
  22. package/dist/lib/observability.d.ts +4 -2
  23. package/dist/lib/observability.js +43 -36
  24. package/dist/lib/prompts.d.ts +1 -1
  25. package/dist/lib/prompts.js +1 -1
  26. package/dist/lib/recipe-control.js +6 -2
  27. package/dist/lib/review-control.d.ts +1 -1
  28. package/dist/lib/review-control.js +4 -5
  29. package/dist/lib/run-evidence-policy.d.ts +95 -0
  30. package/dist/lib/run-evidence-policy.js +177 -0
  31. package/dist/lib/run-ui-runtime.js +2 -0
  32. package/dist/lib/runs-control-delivery.d.ts +8 -1
  33. package/dist/lib/runs-control-delivery.js +38 -15
  34. package/dist/lib/runs-controls.d.ts +8 -4
  35. package/dist/lib/runs-controls.js +189 -50
  36. package/dist/lib/runs-retention.js +27 -14
  37. package/dist/lib/runs-trace.d.ts +26 -2
  38. package/dist/lib/runs-trace.js +411 -18
  39. package/dist/lib/runtime-identity.d.ts +7 -0
  40. package/dist/lib/runtime-identity.js +35 -0
  41. package/dist/lib/runtime-triage.d.ts +29 -0
  42. package/dist/lib/runtime-triage.js +60 -0
  43. package/dist/lib/tool-review-scheduler.js +7 -7
  44. package/dist/lib/tools-inspect.js +91 -18
  45. package/dist/lib/tools-message.d.ts +1 -2
  46. package/dist/lib/tools-message.js +6 -6
  47. package/dist/lib/tools-response.d.ts +0 -1
  48. package/dist/lib/tools-response.js +0 -9
  49. package/dist/lib/tools.d.ts +1 -1
  50. package/dist/lib/tools.js +1 -1
  51. package/dist/lib/trace-projection.js +107 -41
  52. package/dist/scripts/conformance.mjs +5 -0
  53. package/dist/scripts/locker.mjs +40 -90
  54. package/dist/scripts/music-player.mjs +48 -142
  55. package/dist/scripts/release-gates.mjs +56 -3
  56. package/dist/scripts/validate-recipe.mjs +5 -4
  57. package/dist/skills/actors/SKILL.md +17 -11
  58. package/dist/skills/swarm/SKILL.md +2 -4
  59. package/docs/README.md +1 -4
  60. package/docs/actor-inspector.md +6 -5
  61. package/docs/async-runs.md +11 -9
  62. package/docs/command-templates.md +6 -116
  63. package/docs/recipe-library.md +4 -6
  64. package/docs/releasing.md +28 -0
  65. package/docs/template-recipes.md +1 -1
  66. package/docs/tool-registry.md +2 -2
  67. package/index.ts +1 -1
  68. package/lib/async-runs.ts +26 -50
  69. package/lib/automatic-review-runtime.ts +7 -7
  70. package/lib/command-templates.ts +44 -4
  71. package/lib/control-projection.ts +105 -0
  72. package/lib/control.ts +33 -18
  73. package/lib/draft-sleep.ts +3 -3
  74. package/lib/file-state.ts +91 -63
  75. package/lib/inspector-overlay.ts +108 -61
  76. package/lib/limits.ts +15 -3
  77. package/lib/observability.ts +55 -57
  78. package/lib/prompts.ts +1 -1
  79. package/lib/recipe-control.ts +9 -2
  80. package/lib/review-control.ts +4 -5
  81. package/lib/run-evidence-policy.ts +242 -0
  82. package/lib/run-ui-runtime.ts +2 -0
  83. package/lib/runs-control-delivery.ts +45 -17
  84. package/lib/runs-controls.ts +180 -102
  85. package/lib/runs-retention.ts +28 -20
  86. package/lib/runs-trace.ts +499 -20
  87. package/lib/runtime-identity.ts +39 -0
  88. package/lib/runtime-triage.ts +106 -0
  89. package/lib/tool-review-scheduler.ts +7 -7
  90. package/lib/tools-inspect.ts +94 -20
  91. package/lib/tools-message.ts +7 -8
  92. package/lib/tools-response.ts +0 -12
  93. package/lib/tools.ts +4 -4
  94. package/lib/trace-projection.ts +156 -71
  95. package/package.json +1 -1
  96. package/scripts/conformance.mjs +5 -0
  97. package/scripts/locker.mjs +40 -90
  98. package/scripts/music-player.mjs +48 -142
  99. package/scripts/release-gates.mjs +56 -3
  100. package/scripts/validate-recipe.mjs +5 -4
  101. package/skills/actors/SKILL.md +17 -11
  102. package/skills/swarm/SKILL.md +2 -4
  103. package/dist/lib/runtime-notifier.d.ts +0 -48
  104. package/dist/lib/runtime-notifier.js +0 -138
  105. package/docs/0.43-baseline.md +0 -44
  106. package/docs/actors-deep-reference.md +0 -108
  107. package/docs/component-recipes.md +0 -45
  108. package/docs/task-first-recipes.md +0 -261
  109. package/lib/runtime-notifier.ts +0 -211
@@ -0,0 +1,106 @@
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
+
7
+ import * as ControlProjection from "./control-projection.ts";
8
+ import {
9
+ classifyRunControlRecord,
10
+ runControlStatusTimestamp,
11
+ type RunControlStatus,
12
+ } from "./run-evidence-policy.ts";
13
+ import type { RunControlRecord } from "./runs-controls.ts";
14
+
15
+ export const RUNTIME_CONTROL_STALE_AFTER_MS = 5 * 60 * 1_000;
16
+
17
+ export interface RuntimeTriageRun {
18
+ run: string;
19
+ runInstanceId?: string;
20
+ status: string;
21
+ }
22
+
23
+ export interface RuntimePendingControl
24
+ extends ControlProjection.ProjectedRunControl {
25
+ age_ms: number;
26
+ reason: "age_threshold_reached" | "run_generation_replaced" | "run_generation_unavailable" | "run_terminal" | "within_age_threshold";
27
+ run: string;
28
+ status: "queued" | "delivered" | "claimed";
29
+ status_at: string;
30
+ }
31
+
32
+ export interface RuntimeControlDiagnostic {
33
+ reason: "invalid_action" | "invalid_control_id" | "invalid_generation" | "invalid_record" | "invalid_status" | "invalid_status_timestamp";
34
+ run: string;
35
+ }
36
+
37
+ export interface RuntimeControlClassification {
38
+ diagnostic?: RuntimeControlDiagnostic;
39
+ pending?: RuntimePendingControl;
40
+ stale?: RuntimePendingControl;
41
+ }
42
+
43
+ function diagnostic(
44
+ run: string,
45
+ reason: RuntimeControlDiagnostic["reason"],
46
+ ): RuntimeControlClassification {
47
+ return { diagnostic: { reason, run } };
48
+ }
49
+
50
+ export function classifyRuntimeControl(
51
+ run: RuntimeTriageRun,
52
+ value: unknown,
53
+ nowMs: number,
54
+ ): RuntimeControlClassification {
55
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
56
+ return diagnostic(run.run, "invalid_record");
57
+ }
58
+ const record = value as Record<string, unknown>;
59
+ const status = record.status as RunControlStatus;
60
+ const recordClass = classifyRunControlRecord(record);
61
+ if (!recordClass) return diagnostic(run.run, "invalid_status");
62
+ if (typeof record.id !== "string" || !record.id) {
63
+ return diagnostic(run.run, "invalid_control_id");
64
+ }
65
+ if (typeof record.action !== "string" || !record.action) {
66
+ return diagnostic(run.run, "invalid_action");
67
+ }
68
+ if (typeof record.run_instance_id !== "string" || !record.run_instance_id) {
69
+ return diagnostic(run.run, "invalid_generation");
70
+ }
71
+ const timestamp = runControlStatusTimestamp(record, status);
72
+ const timestampMs = typeof timestamp === "string" ? Date.parse(timestamp) : Number.NaN;
73
+ if (!Number.isFinite(timestampMs)) {
74
+ return diagnostic(run.run, "invalid_status_timestamp");
75
+ }
76
+ if (recordClass === "terminal") return {};
77
+ const ageMs = Math.max(0, Math.floor(nowMs - timestampMs));
78
+ const staleReason: RuntimePendingControl["reason"] | undefined =
79
+ !run.runInstanceId
80
+ ? "run_generation_unavailable"
81
+ : record.run_instance_id !== run.runInstanceId
82
+ ? "run_generation_replaced"
83
+ : run.status !== "running"
84
+ ? "run_terminal"
85
+ : ageMs >= RUNTIME_CONTROL_STALE_AFTER_MS
86
+ ? "age_threshold_reached"
87
+ : undefined;
88
+ const projected = ControlProjection.projectRunControl(
89
+ record as unknown as RunControlRecord,
90
+ );
91
+ const pending: RuntimePendingControl = {
92
+ ...projected,
93
+ age_ms: ageMs,
94
+ reason: staleReason ?? "within_age_threshold",
95
+ run: run.run,
96
+ status: status as RuntimePendingControl["status"],
97
+ status_at: runControlStatusTimestamp(
98
+ projected as unknown as Record<string, unknown>,
99
+ status,
100
+ ) as string,
101
+ };
102
+ return {
103
+ pending,
104
+ ...(staleReason ? { stale: pending } : {}),
105
+ };
106
+ }
@@ -404,7 +404,7 @@ export function applyApprovedToolReviewAtSessionBoundary(
404
404
  ...state,
405
405
  failedStage: "approval_state",
406
406
  lastError: "approved review path is unavailable",
407
- nextAction: "message to=tool:pi-actors type=review.retry body={\"scope\":\"tool\"}",
407
+ nextAction: "message target=runtime action=review.retry input={\"scope\":\"tool\"}",
408
408
  phase: "processing_failed" as const,
409
409
  updatedAt: (options.now ?? (() => new Date()))().toISOString(),
410
410
  };
@@ -427,7 +427,7 @@ export function applyApprovedToolReviewAtSessionBoundary(
427
427
  ...state,
428
428
  failedStage: "transaction",
429
429
  lastError: `tool review transaction remained ${transaction.phase}`,
430
- nextAction: "message to=tool:pi-actors type=review.retry body={\"scope\":\"tool\"}",
430
+ nextAction: "message target=runtime action=review.retry input={\"scope\":\"tool\"}",
431
431
  phase: "processing_failed" as const,
432
432
  updatedAt: (options.now ?? (() => new Date()))().toISOString(),
433
433
  };
@@ -458,7 +458,7 @@ export function applyApprovedToolReviewAtSessionBoundary(
458
458
  ...pending,
459
459
  failedStage: "lineage",
460
460
  lastError: error instanceof Error ? error.message : String(error),
461
- nextAction: "message to=tool:pi-actors type=review.retry body={\"scope\":\"tool\"}",
461
+ nextAction: "message target=runtime action=review.retry input={\"scope\":\"tool\"}",
462
462
  };
463
463
  writeJsonAtomic(statePath, failedPending);
464
464
  return { outcome: "lineage_pending", state: failedPending };
@@ -478,7 +478,7 @@ export function applyApprovedToolReviewAtSessionBoundary(
478
478
  ...state,
479
479
  failedStage: "transaction",
480
480
  lastError: error instanceof Error ? error.message : String(error),
481
- nextAction: "message to=tool:pi-actors type=review.retry body={\"scope\":\"tool\"}",
481
+ nextAction: "message target=runtime action=review.retry input={\"scope\":\"tool\"}",
482
482
  phase: "processing_failed" as const,
483
483
  updatedAt: (options.now ?? (() => new Date()))().toISOString(),
484
484
  };
@@ -541,7 +541,7 @@ export function createToolReviewScheduler(
541
541
  ...existing,
542
542
  failedStage: processed.stage ?? "review_result",
543
543
  lastError: processed.error ?? "automatic tool reviewer failed",
544
- nextAction: "message to=tool:pi-actors type=review.retry body={\"scope\":\"tool\"}",
544
+ nextAction: "message target=runtime action=review.retry input={\"scope\":\"tool\"}",
545
545
  phase: processed.outcome === "processing_failed"
546
546
  ? "processing_failed"
547
547
  : "failed",
@@ -577,7 +577,7 @@ export function createToolReviewScheduler(
577
577
  attempts: existing.attempts + 1,
578
578
  failedStage: "review_launch",
579
579
  lastError: error instanceof Error ? error.message : String(error),
580
- nextAction: "message to=tool:pi-actors type=review.retry body={\"scope\":\"tool\"}",
580
+ nextAction: "message target=runtime action=review.retry input={\"scope\":\"tool\"}",
581
581
  phase: "failed",
582
582
  updatedAt: now().toISOString(),
583
583
  } satisfies ToolReviewAdmissionState);
@@ -612,7 +612,7 @@ export function createToolReviewScheduler(
612
612
  failedStage: "review_launch",
613
613
  inputPath: batch.inputPath,
614
614
  lastError: error instanceof Error ? error.message : String(error),
615
- nextAction: "message to=tool:pi-actors type=review.retry body={\"scope\":\"tool\"}",
615
+ nextAction: "message target=runtime action=review.retry input={\"scope\":\"tool\"}",
616
616
  phase: "failed",
617
617
  reviewerInputPath: batch.reviewerInputPath,
618
618
  reviewId: batch.reviewId,
@@ -4,17 +4,22 @@
4
4
  * Owns exact inspect target/view dispatch; source projection stays in domain modules.
5
5
  */
6
6
 
7
+ import { statSync } from "node:fs";
7
8
  import { join } from "node:path";
8
9
 
9
10
  import * as AsyncRuns from "./async-runs.ts";
11
+ import * as ControlProjection from "./control-projection.ts";
10
12
  import * as Inspector from "./inspector.ts";
11
13
  import * as Limits from "./limits.ts";
14
+ import { computeRunControlCapacity } from "./run-evidence-policy.ts";
12
15
  import * as Paths from "./paths.ts";
13
16
  import * as RecipesDiscovery from "./recipes-discovery.ts";
14
17
  import * as ReviewDiagnostics from "./review-diagnostics.ts";
15
18
  import * as RunsControls from "./runs-controls.ts";
16
19
  import * as RunControlDelivery from "./runs-control-delivery.ts";
17
20
  import * as RunsTrace from "./runs-trace.ts";
21
+ import * as RuntimeIdentity from "./runtime-identity.ts";
22
+ import * as RuntimeTriage from "./runtime-triage.ts";
18
23
  import * as Schema from "./schema.ts";
19
24
  import * as ToolsAccess from "./tools-access.ts";
20
25
  import * as ToolsResponse from "./tools-response.ts";
@@ -35,7 +40,8 @@ function runtimeStatus(): Record<string, unknown> {
35
40
  return {
36
41
  automatic_review: process.env.PI_ACTORS_AUTOMATIC_REVIEW !== "off",
37
42
  run_root: Paths.getRunStateRoot(),
38
- version: process.env.npm_package_version ?? "unknown",
43
+ state_schema: RuntimeIdentity.RUN_STATE_SCHEMA,
44
+ version: RuntimeIdentity.getPackageVersion(),
39
45
  };
40
46
  }
41
47
 
@@ -69,13 +75,45 @@ function runtimeTriage(
69
75
  const inventory = runtimeRuns(ctx, deps, undefined);
70
76
  const runs = inventory.runs as Array<Record<string, unknown>>;
71
77
  const failed = runs.filter((run) => run.status === "failed");
72
- const staleControls = runs.flatMap((run) => {
78
+ const pendingControls: RuntimeTriage.RuntimePendingControl[] = [];
79
+ const staleControls: RuntimeTriage.RuntimePendingControl[] = [];
80
+ const backpressuredRuns: Array<Record<string, unknown>> = [];
81
+ const controlDiagnostics: Array<Record<string, unknown>> = [];
82
+ const traceDiagnostics: Array<Record<string, unknown>> = [];
83
+ const nowMs = Date.now();
84
+ for (const run of runs) {
73
85
  const stateDir = typeof run.state_dir === "string" ? run.state_dir : undefined;
74
- if (!stateDir) return [];
75
- return RunsControls.readRunControlsFromStateDir(stateDir)
76
- .filter((control) => control.status === "queued" || control.status === "claimed")
77
- .map((control) => ({ run: run.run, control }));
78
- });
86
+ if (!stateDir) continue;
87
+ const runId = String(run.run ?? "unknown");
88
+ const journal = RunsControls.readRunControlJournalFromStateDir(stateDir);
89
+ const capacity = computeRunControlCapacity(journal.records);
90
+ controlDiagnostics.push(...journal.diagnostics.map((diagnostic) => ({
91
+ ...(diagnostic.line === undefined ? {} : { line: diagnostic.line }),
92
+ reason: diagnostic.line === undefined ? "unreadable_control_journal" : "invalid_control_json", run: runId,
93
+ })));
94
+ if (capacity.backpressured) backpressuredRuns.push({
95
+ run: runId, pending: capacity.pending, pending_limit: capacity.limit,
96
+ journal_bytes: statSync(RunsControls.runControlsFile(stateDir)).size,
97
+ });
98
+ const traceSummary = RunsTrace.summarizeRunTraceJournal(RunsTrace.readRunTraceJournal(stateDir));
99
+ if (!traceSummary.history_complete) traceDiagnostics.push({ run: runId, ...traceSummary });
100
+ for (const control of journal.records) {
101
+ const classified = RuntimeTriage.classifyRuntimeControl(
102
+ {
103
+ run: runId,
104
+ ...(typeof run.run_instance_id === "string"
105
+ ? { runInstanceId: run.run_instance_id }
106
+ : {}),
107
+ status: String(run.status ?? "unknown"),
108
+ },
109
+ control,
110
+ nowMs,
111
+ );
112
+ if (classified.pending) pendingControls.push(classified.pending);
113
+ if (classified.stale) staleControls.push(classified.stale);
114
+ if (classified.diagnostic) controlDiagnostics.push({ ...classified.diagnostic });
115
+ }
116
+ }
79
117
  const attentionEvents = runs.flatMap((run) => {
80
118
  const stateDir = typeof run.state_dir === "string" ? run.state_dir : undefined;
81
119
  if (!stateDir) return [];
@@ -90,18 +128,32 @@ function runtimeTriage(
90
128
  ts: event.ts,
91
129
  }));
92
130
  });
131
+ const boundedStaleControls = staleControls.slice(0, 40);
132
+ const staleKeys = new Set(boundedStaleControls.map((control) =>
133
+ `${control.run}\0${control.run_instance_id}\0${control.id}`,
134
+ ));
135
+ const boundedPendingControls = [
136
+ ...boundedStaleControls,
137
+ ...pendingControls.filter((control) =>
138
+ !staleKeys.has(`${control.run}\0${control.run_instance_id}\0${control.id}`),
139
+ ),
140
+ ].slice(0, 40);
93
141
  return {
94
142
  ...inventory,
95
143
  failed_runs: failed,
96
- stale_controls: staleControls.slice(0, 40),
144
+ pending_control_count: pendingControls.length,
145
+ pending_controls: boundedPendingControls,
146
+ stale_control_count: staleControls.length,
147
+ stale_controls: boundedStaleControls,
148
+ backpressured_run_count: backpressuredRuns.length, backpressured_runs: backpressuredRuns.slice(0, 40),
149
+ control_diagnostic_count: controlDiagnostics.length, control_diagnostics: controlDiagnostics.slice(0, 40),
150
+ trace_diagnostic_count: traceDiagnostics.length, trace_diagnostics: traceDiagnostics.slice(0, 40),
97
151
  attention_events: attentionEvents.slice(-40).reverse(),
98
152
  next_actions: [
99
153
  ...(failed.length ? ["inspect target=runtime view=runs status=failed"] : []),
100
- ...(staleControls.length
101
- ? staleControls.slice(0, 3).map((entry) =>
102
- `inspect target=run:${String(entry.run)} view=control`,
103
- )
104
- : []),
154
+ ...new Set([...staleControls, ...backpressuredRuns].slice(0, 3).map((entry) =>
155
+ `inspect target=run:${entry.run} view=control`,
156
+ )),
105
157
  ...(attentionEvents.length
106
158
  ? attentionEvents.slice(-3).map((entry) =>
107
159
  `inspect target=run:${String(entry.run)} view=trace source=runtime`,
@@ -116,9 +168,9 @@ function compactRuntime(view: string, details: Record<string, unknown>): string
116
168
  return `\nruntime runs=${(details.runs as unknown[] | undefined)?.length ?? 0}`;
117
169
  }
118
170
  if (view === "triage") {
119
- return `\nruntime failed=${(details.failed_runs as unknown[] | undefined)?.length ?? 0} stale_controls=${(details.stale_controls as unknown[] | undefined)?.length ?? 0} attention=${(details.attention_events as unknown[] | undefined)?.length ?? 0}`;
171
+ return `\nruntime failed=${(details.failed_runs as unknown[] | undefined)?.length ?? 0} pending_controls=${Number(details.pending_control_count ?? 0)} stale_controls=${Number(details.stale_control_count ?? 0)} backpressured=${Number(details.backpressured_run_count ?? 0)} incomplete_trace=${Number(details.trace_diagnostic_count ?? 0)} attention=${(details.attention_events as unknown[] | undefined)?.length ?? 0}`;
120
172
  }
121
- return `\nruntime version=${String(details.version ?? "unknown")} automatic_review=${String(details.automatic_review)}`;
173
+ return `\nruntime version=${String(details.version)} state_schema=${String(details.state_schema)} automatic_review=${String(details.automatic_review)}`;
122
174
  }
123
175
 
124
176
  function inspectRuntime(
@@ -209,8 +261,8 @@ function inspectRun(
209
261
  throw new Error(`unsupported Trace source: ${source}`);
210
262
  }
211
263
  return {
212
- run,
213
- source,
264
+ run, source,
265
+ summary: RunsTrace.summarizeRunTraceJournal(RunsTrace.readRunTraceJournal(stateDir)),
214
266
  items: TraceProjection.projectRunTrace(stateDir, {
215
267
  artifacts: status.artifacts as
216
268
  | Record<string, AsyncRuns.RunArtifactDeclaration>
@@ -221,18 +273,40 @@ function inspectRun(
221
273
  };
222
274
  }
223
275
  const runInstanceId = String(status.run_instance_id ?? "");
276
+ const journal = RunsControls.readRunControlJournalFromStateDir(stateDir);
277
+ const capacity = computeRunControlCapacity(journal.records);
278
+ const structuralDiagnostics = journal.records.flatMap((value) => {
279
+ const classified = RuntimeTriage.classifyRuntimeControl({ run,
280
+ runInstanceId: runInstanceId || undefined, status: String(status.status ?? "unknown") }, value, Date.now());
281
+ return classified.diagnostic ? [classified.diagnostic.reason] : [];
282
+ });
283
+ let journalBytes = 0;
284
+ try { journalBytes = statSync(RunsControls.runControlsFile(stateDir)).size; } catch {}
285
+ const diagnostics = [...journal.diagnostics.map((item) => ({
286
+ ...(item.line === undefined ? {} : { line: item.line }),
287
+ reason: item.line === undefined ? "unreadable_control_journal" : "invalid_control_json",
288
+ })), ...structuralDiagnostics.map((reason) => ({ reason }))].slice(0, 40);
289
+ const nowMs = Date.now();
290
+ const stalePending = journal.records.reduce<number>((count, control) => count + Number(Boolean(
291
+ RuntimeTriage.classifyRuntimeControl({ run, runInstanceId: runInstanceId || undefined,
292
+ status: String(status.status ?? "unknown") }, control, nowMs).stale)), 0);
224
293
  return {
225
294
  run,
226
295
  run_instance_id: runInstanceId,
227
296
  status: status.status,
297
+ pending: capacity.pending, pending_limit: capacity.limit,
298
+ available: capacity.available, backpressured: capacity.backpressured,
299
+ journal_bytes: journalBytes, journal_limit: Limits.RUN_CONTROL_JOURNAL_MAX_BYTES,
300
+ ...(capacity.oldest_pending_at ? { oldest_pending_at: capacity.oldest_pending_at } : {}),
301
+ stale_pending: stalePending,
302
+ diagnostics,
228
303
  actor_actions: Array.isArray(status.control) ? status.control : [],
229
304
  runtime_actions: status.status === "running" ? ["kill"] : ["archive", "prune"],
230
305
  endpoint: runInstanceId
231
306
  ? RunControlDelivery.readRunControlEndpoint(stateDir, runInstanceId)
232
307
  : undefined,
233
- recent_controls: RunsControls.readRunControlsFromStateDir(stateDir)
234
- .slice(-Math.max(1, Number(input.lines || 20)))
235
- .reverse(),
308
+ recent_controls: journal.records.slice(-Math.max(1, Number(input.lines || 20))).reverse()
309
+ .map((control) => ControlProjection.projectRunControl(control as RunsControls.RunControlRecord)),
236
310
  };
237
311
  }
238
312
 
@@ -46,8 +46,7 @@ function runNextActions(run: string, result: Record<string, unknown>): string[]
46
46
  }
47
47
 
48
48
  export interface ControlToolDeps {
49
- getTool?: (name: string) => unknown;
50
- handleRuntimeMessage?: (
49
+ handleRuntimeControl?: (
51
50
  action: string,
52
51
  input: unknown,
53
52
  ) => Record<string, unknown>;
@@ -64,11 +63,11 @@ export function createControlToolDefinition<TContext = unknown>(
64
63
  parameters: Schema.objectSchema(
65
64
  {
66
65
  target: Schema.stringSchema("Run target run:<id>, or literal runtime."),
67
- action: Schema.stringSchema("Lowercase semantic Control action."),
66
+ action: Schema.stringSchema("Lowercase ASCII Control action, at most 64 characters."),
68
67
  input: Schema.unionSchema([
69
- Schema.stringSchema("Optional Control input string."),
70
- Schema.looseObjectSchema("Optional structured Control input."),
71
- Schema.arraySchema("Optional structured Control input array."),
68
+ Schema.stringSchema("Optional Control input string; serialized JSON must fit 380 bytes."),
69
+ Schema.looseObjectSchema("Optional structured Control input; serialized JSON must fit 380 bytes."),
70
+ Schema.arraySchema("Optional structured Control input array; serialized JSON must fit 380 bytes."),
72
71
  ]),
73
72
  verbose: Schema.booleanSchema("Return full JSON instead of compact text."),
74
73
  },
@@ -89,10 +88,10 @@ export function createControlToolDefinition<TContext = unknown>(
89
88
  `unsupported runtime Control action: ${request.action}; use review.retry or review.reset`,
90
89
  );
91
90
  }
92
- if (!deps.handleRuntimeMessage) {
91
+ if (!deps.handleRuntimeControl) {
93
92
  throw new Error("runtime Control is unavailable");
94
93
  }
95
- result = deps.handleRuntimeMessage(request.action, request.input);
94
+ result = deps.handleRuntimeControl(request.action, request.input);
96
95
  } else {
97
96
  const run = request.target.slice(4);
98
97
  const status = ToolsAccess.assertRunAccessibleToContext(run, ctx);
@@ -133,18 +133,6 @@ export function compactAsyncRunStatus(value: unknown): string {
133
133
  return `\n${tokens.join(" ")}`;
134
134
  }
135
135
 
136
- export function artifactNextActions(
137
- run: unknown,
138
- artifacts: Record<string, unknown>,
139
- ): string[] {
140
- const id = String(run ?? "").trim();
141
- if (!id || Object.keys(artifacts).length === 0) return [];
142
- return [
143
- `inspect target=run:${id} view=artifacts verbose=true`,
144
- `inspect target=run:${id} view=messages`,
145
- ];
146
- }
147
-
148
136
  export function maybeJsonText(
149
137
  value: unknown,
150
138
  verbose: boolean | undefined,
package/lib/tools.ts CHANGED
@@ -25,9 +25,9 @@ export interface CoreActorToolDefinitionDeps<
25
25
  configPath: string;
26
26
  getActiveTools: () => string[];
27
27
  getRuntimeTool: (name: string) => unknown;
28
- handleRuntimeMessage?: (
29
- type: string,
30
- body: unknown,
28
+ handleRuntimeControl?: (
29
+ action: string,
30
+ input: unknown,
31
31
  ) => Record<string, unknown>;
32
32
  registryRuntime: Pick<
33
33
  RegisterToolRuntimeDeps<TContext>,
@@ -74,7 +74,7 @@ export function createCoreActorToolDefinitions<
74
74
  }),
75
75
  ToolsSpawn.createSpawnToolDefinition<TContext>(),
76
76
  ToolsMessage.createControlToolDefinition<TContext>({
77
- handleRuntimeMessage: deps.handleRuntimeMessage,
77
+ handleRuntimeControl: deps.handleRuntimeControl,
78
78
  }),
79
79
  ToolsInspect.createInspectToolDefinition<TContext>({
80
80
  getTool: (name) => deps.getRuntimeTool(name),