@llblab/pi-actors 0.58.0 → 0.58.2

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 (76) hide show
  1. package/AGENTS.md +5 -5
  2. package/BACKLOG.md +1 -1
  3. package/CHANGELOG.md +12 -1
  4. package/README.md +37 -35
  5. package/dist/lib/draft-sleep.js +3 -3
  6. package/dist/lib/extension-runtime.js +7 -2
  7. package/dist/lib/pi.js +1 -1
  8. package/dist/lib/prompts.d.ts +3 -3
  9. package/dist/lib/prompts.js +10 -9
  10. package/dist/lib/recipes-references.js +7 -7
  11. package/dist/lib/registry.js +27 -24
  12. package/dist/lib/review-control.js +1 -1
  13. package/dist/lib/run-delivery.d.ts +1 -1
  14. package/dist/lib/run-delivery.js +19 -9
  15. package/dist/lib/run-evidence-policy.js +2 -2
  16. package/dist/lib/runtime.d.ts +3 -0
  17. package/dist/lib/runtime.js +24 -10
  18. package/dist/lib/tool-review-scheduler.js +7 -7
  19. package/dist/lib/tools-access.js +1 -1
  20. package/dist/lib/tools-inspect.js +10 -10
  21. package/dist/lib/tools-message.d.ts +1 -1
  22. package/dist/lib/tools-message.js +10 -10
  23. package/dist/lib/tools-register.d.ts +3 -3
  24. package/dist/lib/tools-register.js +4 -4
  25. package/dist/lib/tools-response.d.ts +1 -1
  26. package/dist/lib/tools-response.js +27 -28
  27. package/dist/lib/tools-spawn.js +1 -1
  28. package/dist/lib/tools.d.ts +7 -0
  29. package/dist/lib/tools.js +25 -0
  30. package/dist/skills/actors/SKILL.md +29 -21
  31. package/dist/skills/actors/references/diagnostics.md +11 -11
  32. package/dist/skills/actors/references/persistent-tools.md +19 -15
  33. package/dist/skills/actors/references/recipes.md +1 -1
  34. package/dist/skills/actors/references/runs.md +8 -8
  35. package/dist/skills/music-player/SKILL.md +4 -4
  36. package/dist/skills/music-player/scripts/playback.mjs +1 -1
  37. package/dist/skills/recipe-memory/SKILL.md +7 -7
  38. package/docs/README.md +2 -2
  39. package/docs/actor-inspector.md +1 -1
  40. package/docs/agent-experience.md +5 -6
  41. package/docs/async-runs.md +13 -13
  42. package/docs/capability-memory.md +11 -56
  43. package/docs/coordinator-delivery.md +2 -2
  44. package/docs/inspection.md +21 -21
  45. package/docs/output-projection-map.md +1 -1
  46. package/docs/releasing.md +1 -1
  47. package/docs/run-supervision.md +21 -21
  48. package/docs/tool-registry.md +46 -21
  49. package/docs/windows-process-observation.md +1 -1
  50. package/lib/draft-sleep.ts +3 -3
  51. package/lib/extension-runtime.ts +7 -5
  52. package/lib/pi.ts +1 -1
  53. package/lib/prompts.ts +10 -9
  54. package/lib/recipes-references.ts +7 -7
  55. package/lib/registry.ts +26 -24
  56. package/lib/review-control.ts +1 -1
  57. package/lib/run-delivery.ts +22 -11
  58. package/lib/run-evidence-policy.ts +2 -2
  59. package/lib/runtime.ts +26 -11
  60. package/lib/tool-review-scheduler.ts +7 -7
  61. package/lib/tools-access.ts +1 -1
  62. package/lib/tools-inspect.ts +10 -10
  63. package/lib/tools-message.ts +10 -10
  64. package/lib/tools-register.ts +4 -4
  65. package/lib/tools-response.ts +25 -25
  66. package/lib/tools-spawn.ts +1 -1
  67. package/lib/tools.ts +28 -0
  68. package/package.json +1 -1
  69. package/skills/actors/SKILL.md +29 -21
  70. package/skills/actors/references/diagnostics.md +11 -11
  71. package/skills/actors/references/persistent-tools.md +19 -15
  72. package/skills/actors/references/recipes.md +1 -1
  73. package/skills/actors/references/runs.md +8 -8
  74. package/skills/music-player/SKILL.md +4 -4
  75. package/skills/music-player/scripts/playback.mjs +1 -1
  76. package/skills/recipe-memory/SKILL.md +7 -7
@@ -69,7 +69,7 @@ export interface RunCompletionBatch {
69
69
  /** Retained singleton capture; absence preserves older indexed bytes. Never a new-capture selector. */
70
70
  readonly single_result?: true;
71
71
  /** Captured record-list shape; no public format/version choice or source authority. */
72
- readonly result_layout?: "records" | "run_headers" | "headings" | "headings_flat_omissions" | "headings_outcomes" | "headings_shared_results" | "headings_shared_kinds" | "frontmatter_v1" | "frontmatter_v2" | "completion_yaml_core_v1" | "completion_yaml_files_v1";
72
+ readonly result_layout?: "records" | "run_headers" | "headings" | "headings_flat_omissions" | "headings_outcomes" | "headings_shared_results" | "headings_shared_kinds" | "frontmatter_v1" | "frontmatter_v2" | "completion_yaml_core_v1" | "completion_yaml_files_v1" | "completion_yaml_files_v2";
73
73
  /** Immutable scope/order/range data, not a caller-selected format or allocation authority. */
74
74
  readonly numbering?: RunCompletionNumbering;
75
75
  /** Formatting version/data only; no recipient ownership, read or publication capability. */
@@ -467,8 +467,12 @@ function isFrontmatterCompletionLayout(layout: unknown): boolean {
467
467
  return layout === "frontmatter_v1" || layout === "frontmatter_v2";
468
468
  }
469
469
 
470
+ function isRootedCompletionLayout(layout: unknown): boolean {
471
+ return layout === "completion_yaml_files_v1" || layout === "completion_yaml_files_v2";
472
+ }
473
+
470
474
  function isYamlCompletionLayout(layout: unknown): boolean {
471
- return layout === "completion_yaml_core_v1" || layout === "completion_yaml_files_v1";
475
+ return layout === "completion_yaml_core_v1" || isRootedCompletionLayout(layout);
472
476
  }
473
477
 
474
478
  function isSharedCompletionLayout(layout: unknown): boolean {
@@ -895,7 +899,7 @@ export function requestRunCompletionStream(input: { tempDir: string; ownerId: st
895
899
  }
896
900
 
897
901
  export function admitRunCompletionBatch(input: AdmitRunCompletionBatchInput): RunCompletionBatch {
898
- return captureCompletionBatch(input, false, "completion_yaml_files_v1");
902
+ return captureCompletionBatch(input, false, "completion_yaml_files_v2");
899
903
  }
900
904
 
901
905
  /** Kernel-only opt-in; ordinary Pi capture stays fenced until session/lifecycle integration is admitted. */
@@ -905,10 +909,10 @@ export function captureRunCompletionStreamBatch(input: AdmitRunCompletionBatchIn
905
909
 
906
910
  /** Private ordinary candidate composition; stream capture retains its separately gated codec. */
907
911
  export function captureRunCompletionYamlCandidateBatch(input: AdmitRunCompletionBatchInput): RunCompletionBatch {
908
- return captureCompletionBatch(input, false, "completion_yaml_files_v1");
912
+ return captureCompletionBatch(input, false, "completion_yaml_files_v2");
909
913
  }
910
914
 
911
- function captureCompletionBatch(input: AdmitRunCompletionBatchInput, numbered: boolean, layout: "frontmatter_v2" | "completion_yaml_core_v1" | "completion_yaml_files_v1" = "frontmatter_v2"): RunCompletionBatch {
915
+ function captureCompletionBatch(input: AdmitRunCompletionBatchInput, numbered: boolean, layout: "frontmatter_v2" | "completion_yaml_core_v1" | "completion_yaml_files_v1" | "completion_yaml_files_v2" = "frontmatter_v2"): RunCompletionBatch {
912
916
  const now = (input.now ?? new Date()).toISOString();
913
917
  if (Object.hasOwn(input, "presentationFormat")) throw new Error("Completion presentation selection is not supported; new batches use AX.");
914
918
  if (Object.hasOwn(input, "numbering")) throw new Error("Completion numbering is assigned only by journal capture");
@@ -1869,12 +1873,13 @@ type PreparedCompletionYamlProjection =
1869
1873
  function prepareCompletionYamlProjection(safe: RunCompletionBatch, candidates: readonly { row: CompletionRow; location?: CompletionPiece }[],
1870
1874
  references: ReadonlySet<number>, ordered: readonly RunCompletionBatchMember[]): PreparedCompletionYamlProjection {
1871
1875
  const packet: CompletionYamlPacket = { batchId: safe.batch_id, records: [] };
1872
- const rooted = safe.result_layout === "completion_yaml_files_v1";
1876
+ const rooted = isRootedCompletionLayout(safe.result_layout);
1877
+ const localSingleton = safe.result_layout === "completion_yaml_files_v2" && !safe.numbering && candidates.length === 1;
1873
1878
  if (!safe.batch_id.isWellFormed()) return { state: "unsupported" };
1874
1879
  for (const [index, { row, location }] of candidates.entries()) {
1875
1880
  const member = ordered[index]!;
1876
1881
  const displayed = safe.numbering ? safe.numbering.kind === "stream" ? safe.numbering.first_index + index : "?" : index;
1877
- const metadata: Record<string, CompletionYamlData> = { index: displayed };
1882
+ const metadata: Record<string, CompletionYamlData> = localSingleton ? {} : { index: displayed };
1878
1883
  for (const piece of row.pieces) {
1879
1884
  if (piece.block) continue;
1880
1885
  if (!piece.field) return { state: "unsupported" };
@@ -1917,7 +1922,9 @@ function prepareCompletionYamlProjection(safe: RunCompletionBatch, candidates: r
1917
1922
  }
1918
1923
  packet.records.push({ metadata, parts });
1919
1924
  }
1920
- const service: Record<string, CompletionYamlData> = rooted ? { header_layout: "rooted_files_v1" } : {};
1925
+ const service: Record<string, CompletionYamlData> = rooted ? {
1926
+ header_layout: safe.result_layout === "completion_yaml_files_v2" ? "rooted_files_v2" : "rooted_files_v1",
1927
+ } : {};
1921
1928
  const omitted = safe.members.length - candidates.length;
1922
1929
  if (omitted) {
1923
1930
  service.omitted_results = omitted;
@@ -2349,7 +2356,7 @@ export function readRunCompletionPresentation(content: string, value: unknown):
2349
2356
  !completionYamlKeys(hint, ["format", "content_sha256"]) || hint.format !== completionYamlCodec ||
2350
2357
  hint.content_sha256 !== createHash("sha256").update(content).digest("hex")) return full;
2351
2358
  const segments: NonNullable<RunCompletionPresentationView["segments"]>[number][] = [];
2352
- const rooted = core.packet.service?.header_layout === "rooted_files_v1";
2359
+ const rooted = core.packet.service?.header_layout === "rooted_files_v1" || core.packet.service?.header_layout === "rooted_files_v2";
2353
2360
  const facts = Object.fromEntries(Object.entries(core.packet.service ?? {}).filter(([key]) => key !== "header_layout"));
2354
2361
  for (const [index, record] of core.packet.records.entries()) {
2355
2362
  if (index) segments.push({ kind: "separator", text: "\n" });
@@ -2581,6 +2588,8 @@ export function encodeCompletionYamlCarrier(input: unknown): { state: "available
2581
2588
  typeof packet.batchId !== "string" || !packet.batchId.length || !Array.isArray(packet.records) || packet.records.length > 64 ||
2582
2589
  (Object.hasOwn(packet, "service") && (!completionYamlMapping(packet.service) || !Object.keys(packet.service).length)) ||
2583
2590
  (!packet.records.length && !Object.hasOwn(packet, "service"))) return unavailable;
2591
+ const localSingleton = packet.records.length === 1 && completionYamlMapping(packet.service) &&
2592
+ packet.service.header_layout === "rooted_files_v2" && !Object.hasOwn(packet.service, "numbering");
2584
2593
  const rows: string[] = [];
2585
2594
  for (let index = 0; index < Math.max(1, packet.records.length); index++) {
2586
2595
  const row: CompletionYamlData | undefined = packet.records[index];
@@ -2596,13 +2605,15 @@ export function encodeCompletionYamlCarrier(input: unknown): { state: "available
2596
2605
  if (!completionYamlMapping(row) || !completionYamlKeys(row, ["metadata", "parts"]) ||
2597
2606
  !completionYamlMapping(row.metadata) || !Array.isArray(row.parts)) return unavailable;
2598
2607
  const meta = row.metadata;
2599
- if (!(meta.index === "?" || (typeof meta.index === "number" && Number.isSafeInteger(meta.index) && meta.index >= 0)) ||
2608
+ const validIndex = localSingleton ? !Object.hasOwn(meta, "index")
2609
+ : meta.index === "?" || (typeof meta.index === "number" && Number.isSafeInteger(meta.index) && meta.index >= 0);
2610
+ if (!validIndex ||
2600
2611
  typeof meta.run !== "string" || !meta.run.length || typeof meta.state !== "string" || !meta.state.length ||
2601
2612
  (Object.hasOwn(meta, "kind") && meta.kind !== "primary" && meta.kind !== "diagnostic") ||
2602
2613
  Object.hasOwn(meta, completionYamlServiceKey) ||
2603
2614
  // JS enumerates array-index keys before semantic core fields; do not silently reorder the header.
2604
2615
  Object.keys(meta).some(key => /^(0|[1-9]\d*)$/u.test(key) && Number(key) < 4294967295)) return unavailable;
2605
- for (const key of ["index", "run", "state", ...(Object.hasOwn(meta, "kind") ? ["kind"] : []),
2616
+ for (const key of [...(localSingleton ? [] : ["index"]), "run", "state", ...(Object.hasOwn(meta, "kind") ? ["kind"] : []),
2606
2617
  ...Object.keys(meta).filter(key => !["index", "run", "state", "kind"].includes(key))]) {
2607
2618
  Object.defineProperty(header, key, { value: meta[key]!, enumerable: true });
2608
2619
  }
@@ -69,8 +69,8 @@ export type RunControlAdmissionInput = {
69
69
  const PENDING_CONTROL_STATUSES = new Set<RunControlStatus>(["queued", "delivered", "claimed"]);
70
70
  const TERMINAL_CONTROL_STATUSES = new Set<RunControlStatus>(["handled", "failed"]);
71
71
  const CONTROL_ADMISSION_NEXT_ACTIONS = [
72
- "inspect target=run:<id> view=control",
73
- "inspect target=runtime view=triage",
72
+ "actors_inspect target=run:<id> view=control",
73
+ "actors_inspect target=runtime view=triage",
74
74
  ];
75
75
 
76
76
  function isCount(value: number): boolean {
package/lib/runtime.ts CHANGED
@@ -8,6 +8,7 @@ import { existsSync, watch, type FSWatcher } from "node:fs";
8
8
  import { basename, dirname } from "node:path";
9
9
 
10
10
  import * as Config from "./config.ts";
11
+ import * as Identity from "./identity.ts";
11
12
  import type { RegisteredToolExec } from "./execution.ts";
12
13
  import * as Paths from "./paths.ts";
13
14
  import type { RecipeResolutionContext } from "./recipes-context.ts";
@@ -36,7 +37,7 @@ export interface ToolRegistryRuntimeDeps {
36
37
  exec: RegisteredToolExec;
37
38
  recipeRoot?: string;
38
39
  getActiveTools?: () => string[];
39
- getAllTools?: () => Array<{ name: string }>;
40
+ getAllTools?: () => Array<{ name: string; sourceInfo?: { path: string } }>;
40
41
  registerTool: (
41
42
  definition: ReturnType<typeof ToolsLocal.createRuntimeToolDefinition>,
42
43
  ) => void;
@@ -83,6 +84,8 @@ export function createAutoToolsRuntime(
83
84
  const tools = new Map<string, Config.RegisteredTool>();
84
85
  const runtimeToolFingerprints = new Map<string, string>();
85
86
  const runtimeTools = new Set<string>();
87
+ // Host declarations survive deactivation; retain their observed provenance separately.
88
+ const runtimeToolSources = new Map<string, string | undefined>();
86
89
  const recipeRoot = deps.recipeRoot ?? Paths.getRecipeRoot();
87
90
  const status: RecipeRegistryStatus = {
88
91
  active_tool_count: 0,
@@ -99,9 +102,14 @@ export function createAutoToolsRuntime(
99
102
  if (ctx.hasUI) ctx.ui.notify(message, type);
100
103
  }
101
104
  function getToolNameBlocker(name: string): string | undefined {
102
- return deps.reservedToolNames.has(name)
103
- ? `Reserved tool name: ${name}`
104
- : undefined;
105
+ const normalized = Identity.normalizeToolName(name);
106
+ if ([...deps.reservedToolNames].some((reserved) => Identity.normalizeToolName(reserved) === normalized))
107
+ return `Reserved tool name: ${name}`;
108
+ const foreign = deps.getAllTools?.().find((tool) =>
109
+ Identity.normalizeToolName(tool.name) === normalized &&
110
+ !(tool.name === name && runtimeToolSources.has(name) &&
111
+ tool.sourceInfo?.path === runtimeToolSources.get(name)));
112
+ return foreign ? `Host tool name collision: ${name} conflicts with ${foreign.name}` : undefined;
105
113
  }
106
114
  function getToolFingerprint(cfg: Config.RegisteredTool): string {
107
115
  return JSON.stringify({
@@ -135,12 +143,15 @@ export function createAutoToolsRuntime(
135
143
  function deactivateMissingRuntimeTools(activeNames: Set<string>): void {
136
144
  const stale = [...runtimeTools].filter((name) => !activeNames.has(name));
137
145
  if (stale.length === 0) return;
146
+ const hostTools = deps.getAllTools?.();
147
+ const staleOwned = stale.filter((name) => !hostTools || hostTools.some((tool) =>
148
+ tool.name === name && tool.sourceInfo?.path === runtimeToolSources.get(name)));
138
149
  for (const name of stale) {
139
150
  runtimeTools.delete(name);
140
151
  runtimeToolFingerprints.delete(name);
141
152
  }
142
153
  if (!deps.getActiveTools || !deps.setActiveTools) return;
143
- const staleSet = new Set(stale);
154
+ const staleSet = new Set(staleOwned);
144
155
  deps.setActiveTools(
145
156
  deps.getActiveTools().filter((name) => !staleSet.has(name)),
146
157
  );
@@ -157,10 +168,13 @@ export function createAutoToolsRuntime(
157
168
  };
158
169
  }
159
170
  function registerRuntimeTool(cfg: Config.RegisteredTool): RuntimeToolActivation {
171
+ const blocker = getToolNameBlocker(cfg.name);
172
+ if (blocker) throw new Error(blocker);
160
173
  const fingerprint = getToolFingerprint(cfg);
161
174
  if (runtimeToolFingerprints.get(cfg.name) !== fingerprint) {
162
175
  deps.registerTool(ToolsLocal.createRuntimeToolDefinition(cfg, deps.exec));
163
176
  runtimeTools.add(cfg.name);
177
+ runtimeToolSources.set(cfg.name, deps.getAllTools?.().find((tool) => tool.name === cfg.name)?.sourceInfo?.path);
164
178
  runtimeToolFingerprints.set(cfg.name, fingerprint);
165
179
  }
166
180
  if (deps.getActiveTools && deps.setActiveTools) {
@@ -215,7 +229,7 @@ export function createAutoToolsRuntime(
215
229
  lines.push(...other.map((warning) => `• ${warning}`));
216
230
  groups[groups.length] = ["other_diagnostics", start, lines.length];
217
231
  }
218
- lines.push("Next: inspect target=recipes view=doctor");
232
+ lines.push("Next: actors_inspect target=recipes view=doctor");
219
233
  // Preserve the original layout join/coercion and its faults before projecting owned warning slots.
220
234
  void `${lines.join("\n")}\n`;
221
235
  let text = ToolsResponse.formatMarkdownField("report", "pi-actors recipe registry warning");
@@ -227,7 +241,7 @@ export function createAutoToolsRuntime(
227
241
  : " - warning:\n - result: unavailable\n - reason: malformed_selected_text");
228
242
  }
229
243
  }
230
- return text + "\n" + ToolsResponse.formatMarkdownField("instruction", "Next: inspect target=recipes view=doctor") + "\n";
244
+ return text + "\n" + ToolsResponse.formatMarkdownField("instruction", "Next: actors_inspect target=recipes view=doctor") + "\n";
231
245
  }
232
246
  function loadTools(
233
247
  ctx: RuntimeContext,
@@ -259,6 +273,7 @@ export function createAutoToolsRuntime(
259
273
  const blocker = getToolNameBlocker(cfg.name);
260
274
  if (blocker) {
261
275
  warnings.push(blocker);
276
+ tools.delete(cfg.name);
262
277
  continue;
263
278
  }
264
279
  registerRuntimeTool(cfg);
@@ -284,7 +299,7 @@ export function createAutoToolsRuntime(
284
299
  }),
285
300
  getToolStatus: (name) => {
286
301
  const cfg = tools.get(name);
287
- if (!cfg) return undefined;
302
+ if (!cfg || getToolNameBlocker(name)) return undefined;
288
303
  const usage = cfg.sourcePath
289
304
  ? RecipesUsage.readRecipeUsage(cfg.sourcePath)
290
305
  : undefined;
@@ -305,8 +320,8 @@ export function createAutoToolsRuntime(
305
320
  next_actions: activation.callable_now
306
321
  ? [`call tool ${name}`]
307
322
  : [
308
- `register_tool name=${name} update=true`,
309
- `inspect target=tool:${name} view=status`,
323
+ `actors_tool_register name=${name} update=true`,
324
+ `actors_inspect target=tool:${name} view=status`,
310
325
  ],
311
326
  ...(usage?.launch_kind ? { launch_kind: usage.launch_kind } : {}),
312
327
  spawn_calls: Number(usage?.spawn_calls ?? 0),
@@ -372,7 +387,7 @@ export function createRecipeToolReloadWatcher(
372
387
  setWatchStatus("failed");
373
388
  try {
374
389
  ctx.ui.notify(
375
- "- report: Recipe live reload watcher failed\n- instruction: restart the session or use register_tool again to refresh recipe tools.",
390
+ "- report: Recipe live reload watcher failed\n- instruction: restart the session or use actors_tool_register again to refresh recipe tools.",
376
391
  "warning",
377
392
  );
378
393
  } catch (error) {
@@ -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 target=runtime action=review.retry input={\"scope\":\"tool\"}",
407
+ nextAction: "actors_control_apply 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 target=runtime action=review.retry input={\"scope\":\"tool\"}",
430
+ nextAction: "actors_control_apply 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 target=runtime action=review.retry input={\"scope\":\"tool\"}",
461
+ nextAction: "actors_control_apply 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 target=runtime action=review.retry input={\"scope\":\"tool\"}",
481
+ nextAction: "actors_control_apply 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 target=runtime action=review.retry input={\"scope\":\"tool\"}",
544
+ nextAction: "actors_control_apply 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 target=runtime action=review.retry input={\"scope\":\"tool\"}",
580
+ nextAction: "actors_control_apply 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 target=runtime action=review.retry input={\"scope\":\"tool\"}",
615
+ nextAction: "actors_control_apply target=runtime action=review.retry input={\"scope\":\"tool\"}",
616
616
  phase: "failed",
617
617
  reviewerInputPath: batch.reviewerInputPath,
618
618
  reviewId: batch.reviewId,
@@ -55,7 +55,7 @@ export function sessionMismatchError(input: {
55
55
  ),
56
56
  {
57
57
  current_session: input.currentSession,
58
- hint: "inspect target=runtime view=runs",
58
+ hint: "actors_inspect target=runtime view=runs",
59
59
  owner_session: input.expectedSession,
60
60
  reason: "session_mismatch",
61
61
  run: input.run,
@@ -171,13 +171,13 @@ function runtimeTriage(
171
171
  trace_diagnostic_count: traceDiagnostics.length, trace_diagnostics: traceDiagnostics.slice(0, 40),
172
172
  attention_events: attentionEvents.slice(-40).reverse(),
173
173
  next_actions: [
174
- ...(failed.length ? ["inspect target=runtime view=runs status=failed"] : []),
174
+ ...(failed.length ? ["actors_inspect target=runtime view=runs status=failed"] : []),
175
175
  ...new Set([...staleControls, ...backpressuredRuns].slice(0, 3).map((entry) =>
176
- `inspect target=run:${entry.run} view=control`,
176
+ `actors_inspect target=run:${entry.run} view=control`,
177
177
  )),
178
178
  ...(attentionEvents.length
179
179
  ? attentionEvents.slice(-3).map((entry) =>
180
- `inspect target=run:${String(entry.run)} view=trace source=runtime`,
180
+ `actors_inspect target=run:${String(entry.run)} view=trace source=runtime`,
181
181
  )
182
182
  : []),
183
183
  ],
@@ -247,7 +247,7 @@ function formatInspectFailure(
247
247
  return { omittable, full, selected };
248
248
  });
249
249
  // Only producer-known read-only inspect literals become a typed next, never a parsed action.
250
- const typedNext = next ? { next: { tool: "inspect", target: next[0], view: next[1] } } : undefined;
250
+ const typedNext = next ? { next: { tool: "actors_inspect", target: next[0], view: next[1] } } : undefined;
251
251
  const nextFallback = { next_action: omitted };
252
252
  const document = () => ({ ...Object.assign({}, ...parts.map((part) => part.selected)),
253
253
  ...(typedNext ?? nextFallback) });
@@ -412,15 +412,15 @@ function inspectRecipeIdentity(
412
412
  ...(rejectedReason ? { rejected_reason: rejectedReason } : {}),
413
413
  next_actions: resolvable
414
414
  ? [
415
- `spawn recipe=${identity}`,
416
- `register_tool name=<tool-name> from=${identity}`,
415
+ `actors_run_spawn recipe=${identity}`,
416
+ `actors_tool_register name=<tool-name> from=${identity}`,
417
417
  ]
418
418
  : roots.length === 0 && skill
419
419
  ? [
420
420
  `activate Skill ${skill}`,
421
- `inspect target=recipes view=doctor identity=${identity}`,
421
+ `actors_inspect target=recipes view=doctor identity=${identity}`,
422
422
  ]
423
- : ["inspect target=recipes view=imports verbose=true"],
423
+ : ["actors_inspect target=recipes view=imports verbose=true"],
424
424
  };
425
425
  }
426
426
 
@@ -707,7 +707,7 @@ function compactResult(target: string, view: string, details: Record<string, unk
707
707
  ? String(details.next_actions[0])
708
708
  : details.callable_now === true
709
709
  ? `call tool ${name}`
710
- : `register_tool name=${name} update=true`;
710
+ : `actors_tool_register name=${name} update=true`;
711
711
  const hint = ToolsResponse.nextActionMetadata(next, Limits.RUN_DELIVERY_MODEL_MAX_BYTES);
712
712
  const required = compactArguments("required_args", details.required_args);
713
713
  const optional = compactArguments("optional_args", details.optional_args);
@@ -727,7 +727,7 @@ export function createInspectToolDefinition<TContext = unknown>(
727
727
  deps: InspectToolDeps<TContext> = {},
728
728
  ): any {
729
729
  return {
730
- name: "inspect",
730
+ name: "actors_inspect",
731
731
  label: "Inspect",
732
732
  description:
733
733
  "Inspect a Run's Recipe, Trace, or Control evidence, or runtime/recipe/tool diagnostics.",
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Public Control facade under the stable `message` tool name.
2
+ * Public Control facade under the stable `actors_control_apply` tool name.
3
3
  * Zones: Control request validation, Run/runtime dispatch, recipient-scoped receipts
4
4
  * Owns public Control execution; journaling, delivery, and lifecycle mutation stay in Run domains.
5
5
  */
@@ -52,9 +52,9 @@ function controlDocument(render: () => ControlDocument, order: readonly ControlP
52
52
 
53
53
  /** Only producer-known read-only inspect literals become typed next; other advice stays opaque content. */
54
54
  function controlNextPart(action: string): ControlPart {
55
- const inspect = /^inspect target=([^\s]+) view=([^\s]+)$/u.exec(action);
55
+ const inspect = /^actors_inspect target=([^\s]+) view=([^\s]+)$/u.exec(action);
56
56
  if (inspect) {
57
- return ToolsResponse.selectYamlNode("next", { tool: "inspect", target: inspect[1], view: inspect[2] }, CONTROL_BUDGET);
57
+ return ToolsResponse.selectYamlNode("next", { tool: "actors_inspect", target: inspect[1], view: inspect[2] }, CONTROL_BUDGET);
58
58
  }
59
59
  const wellFormed = action.isWellFormed();
60
60
  const omitted: ControlDocument = { next: { instruction: wellFormed ? "[omitted]" : "[unavailable]",
@@ -123,15 +123,15 @@ export function compactControlResult(
123
123
  }
124
124
 
125
125
  function runNextActions(run: string, result: Record<string, unknown>): string[] {
126
- if (result.stopped === true) return [`inspect target=run:${run} view=trace`];
126
+ if (result.stopped === true) return [`actors_inspect target=run:${run} view=trace`];
127
127
  if (result.stopped === false || result.archived === false || result.pruned === false) {
128
128
  if (result.reason === "ownership changed" || result.reason === "run generation changed") {
129
- return ["inspect target=runtime view=runs"];
129
+ return ["actors_inspect target=runtime view=runs"];
130
130
  }
131
- if (result.stopped === false && result.reason !== "not running") return [`inspect target=run:${run} view=trace`];
131
+ if (result.stopped === false && result.reason !== "not running") return [`actors_inspect target=run:${run} view=trace`];
132
132
  }
133
133
  if (result.delivery === "queued" || result.delivery === "delivered") {
134
- return [`inspect target=run:${run} view=control`];
134
+ return [`actors_inspect target=run:${run} view=control`];
135
135
  }
136
136
  return [];
137
137
  }
@@ -148,10 +148,10 @@ export function createControlToolDefinition<TContext = unknown>(
148
148
  deps: ControlToolDeps = {},
149
149
  ): any {
150
150
  return {
151
- name: "message",
151
+ name: "actors_control_apply",
152
152
  label: "Control",
153
153
  description:
154
- "Apply one Control action to an existing Run or the pi-actors runtime.",
154
+ "Apply one Run/runtime Control action. This is not chat or peer messaging.",
155
155
  parameters: Schema.objectSchema(
156
156
  {
157
157
  target: Schema.stringSchema("Run target run:<id>, or literal runtime."),
@@ -260,7 +260,7 @@ export function createControlToolDefinition<TContext = unknown>(
260
260
  // Admission-era correlation is not current reply-scope proof after an awaited dispatch.
261
261
  // Display one whole caller-owned id or an explicit whole-id omission; the warning stays literal.
262
262
  const identity = controlSelected("run", run);
263
- const next = controlNextPart("inspect target=runtime view=runs");
263
+ const next = controlNextPart("actors_inspect target=runtime view=runs");
264
264
  const render = (): ControlDocument => ({ ...identity.selected, action: request.action,
265
265
  result: "request failed", delivery: "unconfirmed", handling: "unconfirmed",
266
266
  warning: "Effects may already exist.\nDo not repeat automatically.", ...next.selected });
@@ -1,7 +1,7 @@
1
1
  /**
2
- * Public register_tool behavior
3
- * Zones: runtime tool registration, tool registry mutation, register_tool schema
4
- * Owns the public register_tool definition that persists local agent capabilities
2
+ * Public actors_tool_register behavior
3
+ * Zones: runtime tool registration, tool registry mutation, actors_tool_register schema
4
+ * Owns the public actors_tool_register definition that persists local agent capabilities
5
5
  */
6
6
 
7
7
  import * as Prompts from "./prompts.ts";
@@ -24,7 +24,7 @@ export function createRegisterToolDefinition<TContext>(
24
24
  deps: RegisterToolRuntimeDeps<TContext>,
25
25
  ) {
26
26
  return {
27
- name: "register_tool",
27
+ name: "actors_tool_register",
28
28
  label: "Register Tool",
29
29
  description: Prompts.REGISTER_TOOL_DESCRIPTION,
30
30
  promptSnippet: Prompts.REGISTER_TOOL_PROMPT_SNIPPET,
@@ -154,8 +154,8 @@ export function formatMarkdownField(key: string, value: string, depth = 0): stri
154
154
  : `${indent}- ${key}: ${value}`;
155
155
  }
156
156
 
157
- export function formatInspectNextAction(target: string, view: string, verbose?: boolean): string {
158
- return ["- next:", formatMarkdownField("tool", "inspect", 1), formatMarkdownField("target", target, 1),
157
+ export function formatInspectNextAction(target: string, view: string, verbose?: boolean, tool = "inspect"): string {
158
+ return ["- next:", formatMarkdownField("tool", tool, 1), formatMarkdownField("target", target, 1),
159
159
  formatMarkdownField("view", view, 1), ...(verbose === undefined ? [] : [formatMarkdownField("verbose", String(verbose), 1)])].join("\n");
160
160
  }
161
161
 
@@ -289,20 +289,20 @@ function launchModelPolicy(value: unknown): Record<string, unknown> {
289
289
  return fields;
290
290
  }
291
291
 
292
- const RUN_FILE_INVESTIGATION_COMMAND = "inspect target=runtime view=runs verbose=true";
292
+ const RUN_FILE_INVESTIGATION_COMMAND = "actors_inspect target=runtime view=runs verbose=true";
293
293
 
294
294
  /** Frozen older completion-format text: new launch layout must not change this shared packet helper. */
295
295
  export function formatRunFileNavigationHandoff(): string {
296
- return `Files (optional; current session):\n\`${RUN_FILE_INVESTIGATION_COMMAND}\``;
296
+ return "Files (optional; current session):\n`inspect target=runtime view=runs verbose=true`";
297
297
  }
298
298
 
299
299
  export function runNextActions(run: unknown): string[] {
300
300
  const id = String(run ?? "").trim();
301
301
  if (!id) return [];
302
302
  return [
303
- `inspect target=run:${id} view=recipe`,
304
- `inspect target=run:${id} view=trace`,
305
- `inspect target=run:${id} view=control`,
303
+ `actors_inspect target=run:${id} view=recipe`,
304
+ `actors_inspect target=run:${id} view=trace`,
305
+ `actors_inspect target=run:${id} view=control`,
306
306
  RUN_FILE_INVESTIGATION_COMMAND,
307
307
  ];
308
308
  }
@@ -329,7 +329,7 @@ export function compactAsyncRunStatus(value: unknown): string {
329
329
  }
330
330
  if (result.killed === true) fields.killed = true;
331
331
  if (status.draft_recipe) fields.draft_recipe = String(status.draft_recipe);
332
- const hint = { file_investigation: "optional; current session", next: { tool: "inspect", target: "runtime", view: "runs", verbose: true } };
332
+ const hint = { file_investigation: "optional; current session", next: { tool: "actors_inspect", target: "runtime", view: "runs", verbose: true } };
333
333
  const hasNavigation = Object.hasOwn(status, "file_navigation");
334
334
  let navigation: unknown;
335
335
  if (hasNavigation) {
@@ -509,25 +509,25 @@ export function recipeRegistryNextActions(
509
509
  : 0;
510
510
  const topAction = asRecord(summary.top_action);
511
511
  if (view !== "doctor" && (invalid > 0 || diagnostics > 0)) {
512
- actions.push("inspect target=recipes view=doctor");
512
+ actions.push("actors_inspect target=recipes view=doctor");
513
513
  }
514
514
  if (view === "doctor" && typeof topAction.action === "string") {
515
515
  actions.push(String(topAction.action));
516
516
  }
517
517
  if (summary.skill_recipe_catalog_partial === true) {
518
- actions.push("inspect target=recipes view=imports verbose=true");
518
+ actions.push("actors_inspect target=recipes view=imports verbose=true");
519
519
  }
520
520
  if (
521
521
  (Array.isArray(summary.retained) && summary.retained.length > 0) ||
522
522
  (Array.isArray(summary.retained_diagnostics) && summary.retained_diagnostics.length > 0)
523
523
  ) {
524
- actions.push("inspect target=recipes view=summary verbose=true");
524
+ actions.push("actors_inspect target=recipes view=summary verbose=true");
525
525
  }
526
526
  if (drafts.length > 0) {
527
- actions.push("inspect target=recipes view=summary verbose=true");
527
+ actions.push("actors_inspect target=recipes view=summary verbose=true");
528
528
  const firstPath =
529
529
  typeof drafts[0]?.path === "string" ? drafts[0].path : undefined;
530
- if (firstPath) actions.push(`spawn file=${firstPath}`);
530
+ if (firstPath) actions.push(`actors_run_spawn file=${firstPath}`);
531
531
  }
532
532
  return [...new Set(actions)].slice(0, 4);
533
533
  }
@@ -538,16 +538,18 @@ export function compactYamlDocument(value: Record<string, unknown>): string {
538
538
  return `\n${text.replace(/\n$/u, "")}`;
539
539
  }
540
540
 
541
+ function recipeInspectionHint(action: string): { view: "doctor" | "imports" | "summary"; verbose?: true } | undefined {
542
+ if (action === "actors_inspect target=recipes view=doctor") return { view: "doctor" };
543
+ if (action === "actors_inspect target=recipes view=imports verbose=true") return { view: "imports", verbose: true };
544
+ if (action === "actors_inspect target=recipes view=summary verbose=true") return { view: "summary", verbose: true };
545
+ return undefined;
546
+ }
547
+
541
548
  /** Only the closed read-only Recipe inspect literals become typed next; other advice stays opaque content. */
542
549
  export function nextActionMetadata(action: string, byteBudget: number): YamlPart {
543
- const recognized = [
544
- ["inspect target=recipes view=doctor", "doctor", false],
545
- ["inspect target=recipes view=imports verbose=true", "imports", true],
546
- ["inspect target=recipes view=summary verbose=true", "summary", true],
547
- ] as const;
548
- const match = recognized.find(([literal]) => literal === action);
549
- if (match) {
550
- const typed = { next: { tool: "inspect", target: "recipes", view: match[1], ...(match[2] ? { verbose: true } : {}) } };
550
+ const hint = recipeInspectionHint(action);
551
+ if (hint) {
552
+ const typed = { next: { tool: "actors_inspect", target: "recipes", ...hint } };
551
553
  return { full: typed, selected: typed };
552
554
  }
553
555
  const instruction = selectYamlField("instruction", action, byteBudget);
@@ -556,10 +558,8 @@ export function nextActionMetadata(action: string, byteBudget: number): YamlPart
556
558
 
557
559
  /** Only these existing read-only inspection hints become action nodes; other advice stays content. */
558
560
  export function formatInspectionHint(action: string): string {
559
- if (action === "inspect target=recipes view=doctor") return formatInspectNextAction("recipes", "doctor");
560
- if (action === "inspect target=recipes view=imports verbose=true") return formatInspectNextAction("recipes", "imports", true);
561
- if (action === "inspect target=recipes view=summary verbose=true") return formatInspectNextAction("recipes", "summary", true);
562
- return formatMarkdownField("instruction", action);
561
+ const hint = recipeInspectionHint(action);
562
+ return hint ? formatInspectNextAction("recipes", hint.view, hint.verbose, "actors_inspect") : formatMarkdownField("instruction", action);
563
563
  }
564
564
 
565
565
  export function compactRecipeRegistry(
@@ -124,7 +124,7 @@ export function createSpawnToolDefinition<
124
124
  TContext extends SpawnToolContext,
125
125
  >(): any {
126
126
  return {
127
- name: "spawn",
127
+ name: "actors_run_spawn",
128
128
  label: "Spawn",
129
129
  description:
130
130
  "Create one controllable Run from a Recipe file or inline command template.",
package/lib/tools.ts CHANGED
@@ -54,12 +54,40 @@ export const RESERVED_TOOL_NAMES = new Set([
54
54
  "find",
55
55
  "grep",
56
56
  "ls",
57
+ "actors_tool_register",
58
+ "actors_run_spawn",
59
+ "actors_control_apply",
60
+ "actors_inspect",
57
61
  "register_tool",
58
62
  "message",
59
63
  "spawn",
60
64
  "inspect",
61
65
  ]);
62
66
 
67
+ // Compatibility dispatch is registered but omitted from model declarations and normal
68
+ // callable listings. Active legacy names still execute saved native tool calls.
69
+ export const LEGACY_TOOL_NAMES = {
70
+ register_tool: "actors_tool_register",
71
+ spawn: "actors_run_spawn",
72
+ message: "actors_control_apply",
73
+ inspect: "actors_inspect",
74
+ } as const;
75
+
76
+ export function createLegacyActorToolDefinitions(
77
+ definitions: ActorToolDefinition[],
78
+ ): ActorToolDefinition[] {
79
+ return Object.entries(LEGACY_TOOL_NAMES).map(([name, canonical]) => {
80
+ const definition = definitions.find((tool) => tool.name === canonical);
81
+ if (!definition) throw new Error(`Missing canonical actor tool: ${canonical}`);
82
+ return {
83
+ ...definition, name, exposure: "deferred",
84
+ description: `Compatibility dispatch for ${canonical}; use the canonical tool for new calls.`,
85
+ promptSnippet: undefined, promptGuidelines: undefined,
86
+ prepareLoadout: () => ({ hiddenDeclarations: Object.keys(LEGACY_TOOL_NAMES) }),
87
+ };
88
+ });
89
+ }
90
+
63
91
  export function createCoreActorToolDefinitions<
64
92
  TContext extends RuntimeToolContext,
65
93
  >(deps: CoreActorToolDefinitionDeps<TContext>): ActorToolDefinition[] {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@llblab/pi-actors",
3
- "version": "0.58.0",
3
+ "version": "0.58.2",
4
4
  "private": false,
5
5
  "description": "Local Actor Kernel for Pi",
6
6
  "keywords": [