@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
@@ -43,7 +43,7 @@ function listTools(deps) {
43
43
  content: [
44
44
  textContent(ExecutionOutput.formatToolText(registryListText(rows))),
45
45
  ],
46
- details: { tool: "register_tool" },
46
+ details: { tool: "actors_tool_register" },
47
47
  };
48
48
  }
49
49
  function getRecipeRoot(deps) {
@@ -65,18 +65,18 @@ function activationBoundary(activation) {
65
65
  }
66
66
  function recipeDoctorAction(source) {
67
67
  return /^[a-z][a-z0-9]*(?:-[a-z0-9]+)*\/[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/u.test(source)
68
- ? `inspect target=recipes view=doctor identity=${source}`
69
- : "inspect target=recipes view=doctor";
68
+ ? `actors_inspect target=recipes view=doctor identity=${source}`
69
+ : "actors_inspect target=recipes view=doctor";
70
70
  }
71
71
  function registrationNextActions(tool, source, callableNow) {
72
72
  if (callableNow) {
73
73
  return [
74
74
  `call tool ${tool}`,
75
- `inspect target=tool:${tool} view=status`,
75
+ `actors_inspect target=tool:${tool} view=status`,
76
76
  ];
77
77
  }
78
78
  return [
79
- `inspect target=tool:${tool} view=status`,
79
+ `actors_inspect target=tool:${tool} view=status`,
80
80
  ...(source === "template" || source === "draft"
81
81
  ? []
82
82
  : [recipeDoctorAction(source)]),
@@ -134,7 +134,7 @@ function registrationReceiptText(data) {
134
134
  const omittedNext = omission("next_action", 1, !nextWellFormed);
135
135
  // This producer knows only its existing status hint; invocation advice stays opaque.
136
136
  const next = part(nextWellFormed
137
- ? data.callable ? { instruction: data.next } : { next: { tool: "inspect", target: `tool:${data.tool}`, view: "status" } }
137
+ ? data.callable ? { instruction: data.next } : { next: { tool: "actors_inspect", target: `tool:${data.tool}`, view: "status" } }
138
138
  : omittedNext, omittedNext);
139
139
  const render = () => renderPart({ ...tool.selected, result: data.result, ...source.selected, callable_now: data.callable,
140
140
  ...(data.boundary === undefined ? {} : { activation_boundary: data.boundary }), ...required.selected, ...optional.selected,
@@ -195,7 +195,7 @@ function draftPathFailureText(kind, reference) {
195
195
  const selected = registrySelected(kind === "root" ? "allowed_root" : "path", reference);
196
196
  const render = () => ({ field: "draft", ...selected.selected,
197
197
  result: kind === "root" ? "Draft must be under the allowed root." : "Draft not found.",
198
- ...(kind === "root" ? { instruction: "Use inspect target=recipes view=summary to list drafts." } : {}) });
198
+ ...(kind === "root" ? { instruction: "Use actors_inspect target=recipes view=summary to list drafts." } : {}) });
199
199
  return growRegistryDocument(render, [selected]);
200
200
  }
201
201
  function assertDraftPath(deps, draft) {
@@ -226,8 +226,8 @@ function draftReceiptText(result, tool, callable, next) {
226
226
  const identity = part("tool", tool, { tool });
227
227
  const flag = part("callable_now", callable, { callable_now: callable });
228
228
  // Only this producer's exact canonical read-only status hint is typed, never body advice.
229
- const status = /^inspect target=(tool:[a-z][a-z0-9_]{0,63}) view=status$/u.exec(next);
230
- const hint = part("next_action", next, status ? { next: { tool: "inspect", target: status[1], view: "status" } } : { instruction: next });
229
+ const status = /^actors_inspect target=(tool:[a-z][a-z0-9_]{0,63}) view=status$/u.exec(next);
230
+ const hint = part("next_action", next, status ? { next: { tool: "actors_inspect", target: status[1], view: "status" } } : { instruction: next });
231
231
  const render = () => renderPart({ ...identity.selected, result, source: "draft", ...flag.selected, ...hint.selected });
232
232
  for (const current of [identity, flag, hint]) {
233
233
  if (current.selected === current.full)
@@ -341,6 +341,9 @@ function deleteTool(name, ctx, deps) {
341
341
  details: { tool: name },
342
342
  };
343
343
  }
344
+ const blocker = deps.getToolNameBlocker(name);
345
+ if (blocker)
346
+ throw new Error(ExecutionOutput.formatToolText(blocker));
344
347
  const recipePath = getToolRecipePath(deps, name);
345
348
  if (existsSync(recipePath))
346
349
  unlinkSync(recipePath);
@@ -390,7 +393,7 @@ function assertTemplateIsNotNestedRecipe(value) {
390
393
  (Object.keys(record).length === 1 ||
391
394
  ["artifacts", "async", "control", "description", "imports", "values"].some((key) => Object.hasOwn(record, key)))) {
392
395
  throw new Error(ExecutionOutput.formatToolText(registryMetadataText({ field: "template",
393
- result: "Nested Recipe-shaped templates are not a register_tool source mode.",
396
+ result: "Nested Recipe-shaped templates are not a actors_tool_register source mode.",
394
397
  instruction: "retry with from=<skill>/<recipe> and defaults={...}." })));
395
398
  }
396
399
  }
@@ -437,7 +440,7 @@ function normalizeDefaults(value) {
437
440
  function normalizeFromReference(value, resolutionContext) {
438
441
  const trimmed = value.trim();
439
442
  if (!trimmed) {
440
- throw new Error(ExecutionOutput.formatToolText("Tool from must be a non-empty Recipe reference. Next: retry register_tool with from=<skill>/<recipe> or an explicit .json/.md path."));
443
+ throw new Error(ExecutionOutput.formatToolText("Tool from must be a non-empty Recipe reference. Next: retry actors_tool_register with from=<skill>/<recipe> or an explicit .json/.md path."));
441
444
  }
442
445
  if (trimmed.startsWith("std:")) {
443
446
  throw new Error(ExecutionOutput.formatToolText("std: Recipe references were removed; use <skill>/<recipe> or an explicit .json/.md file path."));
@@ -472,7 +475,7 @@ function buildAuthoredRecipe(input, existing, existingRecipe) {
472
475
  throw new Error(ExecutionOutput.formatToolText(registryMetadataText({ field: "defaults",
473
476
  ...registrySelected("key", runtimeOwnedDefault).selected,
474
477
  result: "Tool defaults cannot set runtime-owned input.",
475
- instruction: "remove that default and retry register_tool; the runtime supplies it." })));
478
+ instruction: "remove that default and retry actors_tool_register; the runtime supplies it." })));
476
479
  }
477
480
  const authored = {
478
481
  ...(description ? { description } : {}),
@@ -522,9 +525,9 @@ function buildAuthoredRecipe(input, existing, existingRecipe) {
522
525
  function assertRegisterToolInputModes(input) {
523
526
  const record = input;
524
527
  if (Object.hasOwn(record, "values")) {
525
- throw new Error(ExecutionOutput.formatToolText(registryMetadataText({ field: "values", result: "register_tool values was removed.",
528
+ throw new Error(ExecutionOutput.formatToolText(registryMetadataText({ field: "values", result: "actors_tool_register values was removed.",
526
529
  replacement_fields: ["defaults", "from"],
527
- instruction: "retry register_tool with defaults for caller inputs, or use from=<skill>/<recipe> for maintained composition." })));
530
+ instruction: "retry actors_tool_register with defaults for caller inputs, or use from=<skill>/<recipe> for maintained composition." })));
528
531
  }
529
532
  const fromProvided = Object.hasOwn(record, "from");
530
533
  if (fromProvided &&
@@ -532,22 +535,22 @@ function assertRegisterToolInputModes(input) {
532
535
  throw new Error(ExecutionOutput.formatToolText(registryMetadataText({ field: "from",
533
536
  result: "Tool from must be a non-empty Recipe reference.",
534
537
  reference_forms: ["<skill>/<recipe>", "explicit .json path", "explicit .md path"],
535
- instruction: "retry register_tool with from=<skill>/<recipe> or an explicit .json/.md path." })));
538
+ instruction: "retry actors_tool_register with from=<skill>/<recipe> or an explicit .json/.md path." })));
536
539
  }
537
540
  const draftProvided = typeof input.draft === "string" && input.draft.trim().length > 0;
538
541
  const templateProvided = Object.hasOwn(record, "template");
539
542
  const modeCount = Number(fromProvided) + Number(draftProvided) + Number(templateProvided);
540
543
  if (modeCount > 1) {
541
- throw new Error(ExecutionOutput.formatToolText(registryMetadataText({ result: "Use exactly one register_tool source mode.",
544
+ throw new Error(ExecutionOutput.formatToolText(registryMetadataText({ result: "Use exactly one actors_tool_register source mode.",
542
545
  source_modes: ["from", "template", "draft"],
543
- instruction: "remove the extra source fields and retry register_tool." })));
546
+ instruction: "remove the extra source fields and retry actors_tool_register." })));
544
547
  }
545
548
  if (fromProvided &&
546
549
  (Object.hasOwn(record, "args") || Object.hasOwn(record, "async"))) {
547
550
  throw new Error(ExecutionOutput.formatToolText(registryMetadataText({ field: "from",
548
- result: "register_tool from inherits args and async behavior.",
551
+ result: "actors_tool_register from inherits args and async behavior.",
549
552
  inherited_fields: ["args", "async"],
550
- instruction: "retry register_tool with from and optional defaults only; omit args and async." })));
553
+ instruction: "retry actors_tool_register with from and optional defaults only; omit args and async." })));
551
554
  }
552
555
  }
553
556
  function reservedRegistrationNameText(value) {
@@ -613,7 +616,7 @@ function executeRegisterToolUnlocked(params, ctx, deps) {
613
616
  if (template === undefined && !input.from && !existing) {
614
617
  throw new Error(ExecutionOutput.formatToolText(registryMetadataText({ result: "New registration requires exactly one source mode.",
615
618
  source_modes: ["from", "template", "draft"],
616
- instruction: "retry register_tool with one source field." })));
619
+ instruction: "retry actors_tool_register with one source field." })));
617
620
  }
618
621
  const recipePath = getToolRecipePath(deps, name);
619
622
  const authoredRecipe = buildAuthoredRecipe(input, existing, authoritative.recipe);
@@ -645,7 +648,7 @@ function executeRegisterToolUnlocked(params, ctx, deps) {
645
648
  (!activation.host_registered ||
646
649
  !activation.active_tool ||
647
650
  !activation.callable_now)) {
648
- throw new Error(`Tool "${name}" activation failed at ${activationBoundary(activation)}; host_registered=${activation.host_registered}, active_tool=${activation.active_tool}, callable_now=${activation.callable_now}. Registration was rolled back. Next: inspect target=runtime view=status; do not use spawn as proof of tool invocation.`);
651
+ throw new Error(`Tool "${name}" activation failed at ${activationBoundary(activation)}; host_registered=${activation.host_registered}, active_tool=${activation.active_tool}, callable_now=${activation.callable_now}. Registration was rolled back. Next: actors_inspect target=runtime view=status; do not use spawn as proof of tool invocation.`);
649
652
  }
650
653
  }
651
654
  catch (error) {
@@ -872,11 +875,11 @@ function retainRecipe(input, ctx, deps) {
872
875
  const facts = { kind: "retained_recipe", result: "retained", persisted: true, resolved: true,
873
876
  validated: true, activation: "not_requested", async: admitted.async };
874
877
  const text = growRegistryDocument(() => ({ ...selectedName.selected, ...facts, ...recipe.selected, ...selectedSource.selected,
875
- ...required.selected, ...optional.selected, next: { tool: "inspect", target: "recipes", view: "summary", verbose: true } }), [recipe, selectedName, selectedSource, required, optional]);
878
+ ...required.selected, ...optional.selected, next: { tool: "actors_inspect", target: "recipes", view: "summary", verbose: true } }), [recipe, selectedName, selectedSource, required, optional]);
876
879
  const result = {
877
880
  content: [textContent(ExecutionOutput.formatToolText(text))],
878
881
  details: { ...facts, tool: name, recipe: path, source, required_args: args.required, optional_args: args.optional,
879
- next_actions: ["inspect target=recipes view=summary verbose=true"] },
882
+ next_actions: ["actors_inspect target=recipes view=summary verbose=true"] },
880
883
  };
881
884
  stage = "release";
882
885
  return result;
@@ -888,7 +891,7 @@ function retainRecipe(input, ctx, deps) {
888
891
  const reason = error instanceof RetainedRecipeRefusal ? { reason: error.reason } : {};
889
892
  throw new Error(ExecutionOutput.formatToolText(registryMetadataText({ operation: "retain", stage, result: "unconfirmed",
890
893
  effects: "unknown; inspect retained inventory before retry", ...reason,
891
- next: { tool: "inspect", target: "recipes", view: "summary", verbose: true } })), { cause: error });
894
+ next: { tool: "actors_inspect", target: "recipes", view: "summary", verbose: true } })), { cause: error });
892
895
  }
893
896
  }
894
897
  export async function executeRegisterTool(params, ctx, deps) {
@@ -90,7 +90,7 @@ export function controlAutomaticReview(action, scope, options = {}) {
90
90
  });
91
91
  if (result.changed === true)
92
92
  schedule(scope, options);
93
- const nextActions = ["inspect target=recipes view=reviews"];
93
+ const nextActions = ["actors_inspect target=recipes view=reviews"];
94
94
  if (action === "review.retry" &&
95
95
  scope === "tool" &&
96
96
  (result.phase === "approved" || result.phase === "lineage_pending"))
@@ -66,7 +66,7 @@ export interface RunCompletionBatch {
66
66
  /** Retained singleton capture; absence preserves older indexed bytes. Never a new-capture selector. */
67
67
  readonly single_result?: true;
68
68
  /** Captured record-list shape; no public format/version choice or source authority. */
69
- 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";
69
+ 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";
70
70
  /** Immutable scope/order/range data, not a caller-selected format or allocation authority. */
71
71
  readonly numbering?: RunCompletionNumbering;
72
72
  /** Formatting version/data only; no recipient ownership, read or publication capability. */
@@ -278,8 +278,11 @@ function parseInventorySource(value, batch) {
278
278
  function isFrontmatterCompletionLayout(layout) {
279
279
  return layout === "frontmatter_v1" || layout === "frontmatter_v2";
280
280
  }
281
+ function isRootedCompletionLayout(layout) {
282
+ return layout === "completion_yaml_files_v1" || layout === "completion_yaml_files_v2";
283
+ }
281
284
  function isYamlCompletionLayout(layout) {
282
- return layout === "completion_yaml_core_v1" || layout === "completion_yaml_files_v1";
285
+ return layout === "completion_yaml_core_v1" || isRootedCompletionLayout(layout);
283
286
  }
284
287
  function isSharedCompletionLayout(layout) {
285
288
  return layout === "headings_shared_results" || layout === "headings_shared_kinds" || isYamlCompletionLayout(layout) || isFrontmatterCompletionLayout(layout);
@@ -688,7 +691,7 @@ export function requestRunCompletionStream(input) {
688
691
  });
689
692
  }
690
693
  export function admitRunCompletionBatch(input) {
691
- return captureCompletionBatch(input, false, "completion_yaml_files_v1");
694
+ return captureCompletionBatch(input, false, "completion_yaml_files_v2");
692
695
  }
693
696
  /** Kernel-only opt-in; ordinary Pi capture stays fenced until session/lifecycle integration is admitted. */
694
697
  export function captureRunCompletionStreamBatch(input) {
@@ -696,7 +699,7 @@ export function captureRunCompletionStreamBatch(input) {
696
699
  }
697
700
  /** Private ordinary candidate composition; stream capture retains its separately gated codec. */
698
701
  export function captureRunCompletionYamlCandidateBatch(input) {
699
- return captureCompletionBatch(input, false, "completion_yaml_files_v1");
702
+ return captureCompletionBatch(input, false, "completion_yaml_files_v2");
700
703
  }
701
704
  function captureCompletionBatch(input, numbered, layout = "frontmatter_v2") {
702
705
  const now = (input.now ?? new Date()).toISOString();
@@ -1697,13 +1700,14 @@ function completionRootedFiles(row, member, locations) {
1697
1700
  }
1698
1701
  function prepareCompletionYamlProjection(safe, candidates, references, ordered) {
1699
1702
  const packet = { batchId: safe.batch_id, records: [] };
1700
- const rooted = safe.result_layout === "completion_yaml_files_v1";
1703
+ const rooted = isRootedCompletionLayout(safe.result_layout);
1704
+ const localSingleton = safe.result_layout === "completion_yaml_files_v2" && !safe.numbering && candidates.length === 1;
1701
1705
  if (!safe.batch_id.isWellFormed())
1702
1706
  return { state: "unsupported" };
1703
1707
  for (const [index, { row, location }] of candidates.entries()) {
1704
1708
  const member = ordered[index];
1705
1709
  const displayed = safe.numbering ? safe.numbering.kind === "stream" ? safe.numbering.first_index + index : "?" : index;
1706
- const metadata = { index: displayed };
1710
+ const metadata = localSingleton ? {} : { index: displayed };
1707
1711
  for (const piece of row.pieces) {
1708
1712
  if (piece.block)
1709
1713
  continue;
@@ -1763,7 +1767,9 @@ function prepareCompletionYamlProjection(safe, candidates, references, ordered)
1763
1767
  }
1764
1768
  packet.records.push({ metadata, parts });
1765
1769
  }
1766
- const service = rooted ? { header_layout: "rooted_files_v1" } : {};
1770
+ const service = rooted ? {
1771
+ header_layout: safe.result_layout === "completion_yaml_files_v2" ? "rooted_files_v2" : "rooted_files_v1",
1772
+ } : {};
1767
1773
  const omitted = safe.members.length - candidates.length;
1768
1774
  if (omitted) {
1769
1775
  service.omitted_results = omitted;
@@ -2302,7 +2308,7 @@ export function readRunCompletionPresentation(content, value) {
2302
2308
  hint.content_sha256 !== createHash("sha256").update(content).digest("hex"))
2303
2309
  return full;
2304
2310
  const segments = [];
2305
- const rooted = core.packet.service?.header_layout === "rooted_files_v1";
2311
+ const rooted = core.packet.service?.header_layout === "rooted_files_v1" || core.packet.service?.header_layout === "rooted_files_v2";
2306
2312
  const facts = Object.fromEntries(Object.entries(core.packet.service ?? {}).filter(([key]) => key !== "header_layout"));
2307
2313
  for (const [index, record] of core.packet.records.entries()) {
2308
2314
  if (index)
@@ -2551,6 +2557,8 @@ export function encodeCompletionYamlCarrier(input) {
2551
2557
  (Object.hasOwn(packet, "service") && (!completionYamlMapping(packet.service) || !Object.keys(packet.service).length)) ||
2552
2558
  (!packet.records.length && !Object.hasOwn(packet, "service")))
2553
2559
  return unavailable;
2560
+ const localSingleton = packet.records.length === 1 && completionYamlMapping(packet.service) &&
2561
+ packet.service.header_layout === "rooted_files_v2" && !Object.hasOwn(packet.service, "numbering");
2554
2562
  const rows = [];
2555
2563
  for (let index = 0; index < Math.max(1, packet.records.length); index++) {
2556
2564
  const row = packet.records[index];
@@ -2568,14 +2576,16 @@ export function encodeCompletionYamlCarrier(input) {
2568
2576
  !completionYamlMapping(row.metadata) || !Array.isArray(row.parts))
2569
2577
  return unavailable;
2570
2578
  const meta = row.metadata;
2571
- if (!(meta.index === "?" || (typeof meta.index === "number" && Number.isSafeInteger(meta.index) && meta.index >= 0)) ||
2579
+ const validIndex = localSingleton ? !Object.hasOwn(meta, "index")
2580
+ : meta.index === "?" || (typeof meta.index === "number" && Number.isSafeInteger(meta.index) && meta.index >= 0);
2581
+ if (!validIndex ||
2572
2582
  typeof meta.run !== "string" || !meta.run.length || typeof meta.state !== "string" || !meta.state.length ||
2573
2583
  (Object.hasOwn(meta, "kind") && meta.kind !== "primary" && meta.kind !== "diagnostic") ||
2574
2584
  Object.hasOwn(meta, completionYamlServiceKey) ||
2575
2585
  // JS enumerates array-index keys before semantic core fields; do not silently reorder the header.
2576
2586
  Object.keys(meta).some(key => /^(0|[1-9]\d*)$/u.test(key) && Number(key) < 4294967295))
2577
2587
  return unavailable;
2578
- for (const key of ["index", "run", "state", ...(Object.hasOwn(meta, "kind") ? ["kind"] : []),
2588
+ for (const key of [...(localSingleton ? [] : ["index"]), "run", "state", ...(Object.hasOwn(meta, "kind") ? ["kind"] : []),
2579
2589
  ...Object.keys(meta).filter(key => !["index", "run", "state", "kind"].includes(key))]) {
2580
2590
  Object.defineProperty(header, key, { value: meta[key], enumerable: true });
2581
2591
  }
@@ -12,8 +12,8 @@ export const TRACE_COMPACTION_VERSION = 1;
12
12
  const PENDING_CONTROL_STATUSES = new Set(["queued", "delivered", "claimed"]);
13
13
  const TERMINAL_CONTROL_STATUSES = new Set(["handled", "failed"]);
14
14
  const CONTROL_ADMISSION_NEXT_ACTIONS = [
15
- "inspect target=run:<id> view=control",
16
- "inspect target=runtime view=triage",
15
+ "actors_inspect target=run:<id> view=control",
16
+ "actors_inspect target=runtime view=triage",
17
17
  ];
18
18
  function isCount(value) {
19
19
  return Number.isSafeInteger(value) && value >= 0;
@@ -27,6 +27,9 @@ export interface ToolRegistryRuntimeDeps {
27
27
  getActiveTools?: () => string[];
28
28
  getAllTools?: () => Array<{
29
29
  name: string;
30
+ sourceInfo?: {
31
+ path: string;
32
+ };
30
33
  }>;
31
34
  registerTool: (definition: ReturnType<typeof ToolsLocal.createRuntimeToolDefinition>) => void;
32
35
  reservedToolNames: Set<string>;
@@ -5,6 +5,7 @@
5
5
  */
6
6
  import { existsSync, watch } from "node:fs";
7
7
  import { basename, dirname } from "node:path";
8
+ import * as Identity from "./identity.js";
8
9
  import * as Paths from "./paths.js";
9
10
  import * as RecipesDiscovery from "./recipes-discovery.js";
10
11
  import * as RecipesReferences from "./recipes-references.js";
@@ -15,6 +16,8 @@ export function createAutoToolsRuntime(deps) {
15
16
  const tools = new Map();
16
17
  const runtimeToolFingerprints = new Map();
17
18
  const runtimeTools = new Set();
19
+ // Host declarations survive deactivation; retain their observed provenance separately.
20
+ const runtimeToolSources = new Map();
18
21
  const recipeRoot = deps.recipeRoot ?? Paths.getRecipeRoot();
19
22
  const status = {
20
23
  active_tool_count: 0,
@@ -28,9 +31,13 @@ export function createAutoToolsRuntime(deps) {
28
31
  ctx.ui.notify(message, type);
29
32
  }
30
33
  function getToolNameBlocker(name) {
31
- return deps.reservedToolNames.has(name)
32
- ? `Reserved tool name: ${name}`
33
- : undefined;
34
+ const normalized = Identity.normalizeToolName(name);
35
+ if ([...deps.reservedToolNames].some((reserved) => Identity.normalizeToolName(reserved) === normalized))
36
+ return `Reserved tool name: ${name}`;
37
+ const foreign = deps.getAllTools?.().find((tool) => Identity.normalizeToolName(tool.name) === normalized &&
38
+ !(tool.name === name && runtimeToolSources.has(name) &&
39
+ tool.sourceInfo?.path === runtimeToolSources.get(name)));
40
+ return foreign ? `Host tool name collision: ${name} conflicts with ${foreign.name}` : undefined;
34
41
  }
35
42
  function getToolFingerprint(cfg) {
36
43
  return JSON.stringify({
@@ -61,13 +68,15 @@ export function createAutoToolsRuntime(deps) {
61
68
  const stale = [...runtimeTools].filter((name) => !activeNames.has(name));
62
69
  if (stale.length === 0)
63
70
  return;
71
+ const hostTools = deps.getAllTools?.();
72
+ const staleOwned = stale.filter((name) => !hostTools || hostTools.some((tool) => tool.name === name && tool.sourceInfo?.path === runtimeToolSources.get(name)));
64
73
  for (const name of stale) {
65
74
  runtimeTools.delete(name);
66
75
  runtimeToolFingerprints.delete(name);
67
76
  }
68
77
  if (!deps.getActiveTools || !deps.setActiveTools)
69
78
  return;
70
- const staleSet = new Set(stale);
79
+ const staleSet = new Set(staleOwned);
71
80
  deps.setActiveTools(deps.getActiveTools().filter((name) => !staleSet.has(name)));
72
81
  }
73
82
  function activationFor(name) {
@@ -81,10 +90,14 @@ export function createAutoToolsRuntime(deps) {
81
90
  };
82
91
  }
83
92
  function registerRuntimeTool(cfg) {
93
+ const blocker = getToolNameBlocker(cfg.name);
94
+ if (blocker)
95
+ throw new Error(blocker);
84
96
  const fingerprint = getToolFingerprint(cfg);
85
97
  if (runtimeToolFingerprints.get(cfg.name) !== fingerprint) {
86
98
  deps.registerTool(ToolsLocal.createRuntimeToolDefinition(cfg, deps.exec));
87
99
  runtimeTools.add(cfg.name);
100
+ runtimeToolSources.set(cfg.name, deps.getAllTools?.().find((tool) => tool.name === cfg.name)?.sourceInfo?.path);
88
101
  runtimeToolFingerprints.set(cfg.name, fingerprint);
89
102
  }
90
103
  if (deps.getActiveTools && deps.setActiveTools) {
@@ -129,7 +142,7 @@ export function createAutoToolsRuntime(deps) {
129
142
  lines.push(...other.map((warning) => `• ${warning}`));
130
143
  groups[groups.length] = ["other_diagnostics", start, lines.length];
131
144
  }
132
- lines.push("Next: inspect target=recipes view=doctor");
145
+ lines.push("Next: actors_inspect target=recipes view=doctor");
133
146
  // Preserve the original layout join/coercion and its faults before projecting owned warning slots.
134
147
  void `${lines.join("\n")}\n`;
135
148
  let text = ToolsResponse.formatMarkdownField("report", "pi-actors recipe registry warning");
@@ -141,7 +154,7 @@ export function createAutoToolsRuntime(deps) {
141
154
  : " - warning:\n - result: unavailable\n - reason: malformed_selected_text");
142
155
  }
143
156
  }
144
- return text + "\n" + ToolsResponse.formatMarkdownField("instruction", "Next: inspect target=recipes view=doctor") + "\n";
157
+ return text + "\n" + ToolsResponse.formatMarkdownField("instruction", "Next: actors_inspect target=recipes view=doctor") + "\n";
145
158
  }
146
159
  function loadTools(ctx, resolutionContext) {
147
160
  const warnings = [];
@@ -170,6 +183,7 @@ export function createAutoToolsRuntime(deps) {
170
183
  const blocker = getToolNameBlocker(cfg.name);
171
184
  if (blocker) {
172
185
  warnings.push(blocker);
186
+ tools.delete(cfg.name);
173
187
  continue;
174
188
  }
175
189
  registerRuntimeTool(cfg);
@@ -195,7 +209,7 @@ export function createAutoToolsRuntime(deps) {
195
209
  }),
196
210
  getToolStatus: (name) => {
197
211
  const cfg = tools.get(name);
198
- if (!cfg)
212
+ if (!cfg || getToolNameBlocker(name))
199
213
  return undefined;
200
214
  const usage = cfg.sourcePath
201
215
  ? RecipesUsage.readRecipeUsage(cfg.sourcePath)
@@ -217,8 +231,8 @@ export function createAutoToolsRuntime(deps) {
217
231
  next_actions: activation.callable_now
218
232
  ? [`call tool ${name}`]
219
233
  : [
220
- `register_tool name=${name} update=true`,
221
- `inspect target=tool:${name} view=status`,
234
+ `actors_tool_register name=${name} update=true`,
235
+ `actors_inspect target=tool:${name} view=status`,
222
236
  ],
223
237
  ...(usage?.launch_kind ? { launch_kind: usage.launch_kind } : {}),
224
238
  spawn_calls: Number(usage?.spawn_calls ?? 0),
@@ -271,7 +285,7 @@ export function createRecipeToolReloadWatcher(runtime, deps = {}) {
271
285
  failureNotified = true;
272
286
  setWatchStatus("failed");
273
287
  try {
274
- ctx.ui.notify("- report: Recipe live reload watcher failed\n- instruction: restart the session or use register_tool again to refresh recipe tools.", "warning");
288
+ ctx.ui.notify("- report: Recipe live reload watcher failed\n- instruction: restart the session or use actors_tool_register again to refresh recipe tools.", "warning");
275
289
  }
276
290
  catch (error) {
277
291
  reportCallbackError(error);
@@ -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 target=runtime action=review.retry input={\"scope\":\"tool\"}",
267
+ nextAction: "actors_control_apply 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 target=runtime action=review.retry input={\"scope\":\"tool\"}",
290
+ nextAction: "actors_control_apply 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 target=runtime action=review.retry input={\"scope\":\"tool\"}",
319
+ nextAction: "actors_control_apply 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 target=runtime action=review.retry input={\"scope\":\"tool\"}",
340
+ nextAction: "actors_control_apply 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 target=runtime action=review.retry input={\"scope\":\"tool\"}",
404
+ nextAction: "actors_control_apply 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 target=runtime action=review.retry input={\"scope\":\"tool\"}",
440
+ nextAction: "actors_control_apply 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 target=runtime action=review.retry input={\"scope\":\"tool\"}",
474
+ nextAction: "actors_control_apply target=runtime action=review.retry input={\"scope\":\"tool\"}",
475
475
  phase: "failed",
476
476
  reviewerInputPath: batch.reviewerInputPath,
477
477
  reviewId: batch.reviewId,
@@ -33,7 +33,7 @@ export function sessionMismatchError(input) {
33
33
  const actor = input.run ? `run:${input.run}` : (input.target ?? "session");
34
34
  return Object.assign(new Error(`${actor} reason=session_mismatch owner_session=${ownerSession} current_session=${currentSession} hint=inspect_runtime_runs`), {
35
35
  current_session: input.currentSession,
36
- hint: "inspect target=runtime view=runs",
36
+ hint: "actors_inspect target=runtime view=runs",
37
37
  owner_session: input.expectedSession,
38
38
  reason: "session_mismatch",
39
39
  run: input.run,
@@ -145,10 +145,10 @@ function runtimeTriage(ctx, deps) {
145
145
  trace_diagnostic_count: traceDiagnostics.length, trace_diagnostics: traceDiagnostics.slice(0, 40),
146
146
  attention_events: attentionEvents.slice(-40).reverse(),
147
147
  next_actions: [
148
- ...(failed.length ? ["inspect target=runtime view=runs status=failed"] : []),
149
- ...new Set([...staleControls, ...backpressuredRuns].slice(0, 3).map((entry) => `inspect target=run:${entry.run} view=control`)),
148
+ ...(failed.length ? ["actors_inspect target=runtime view=runs status=failed"] : []),
149
+ ...new Set([...staleControls, ...backpressuredRuns].slice(0, 3).map((entry) => `actors_inspect target=run:${entry.run} view=control`)),
150
150
  ...(attentionEvents.length
151
- ? attentionEvents.slice(-3).map((entry) => `inspect target=run:${String(entry.run)} view=trace source=runtime`)
151
+ ? attentionEvents.slice(-3).map((entry) => `actors_inspect target=run:${String(entry.run)} view=trace source=runtime`)
152
152
  : []),
153
153
  ],
154
154
  };
@@ -209,7 +209,7 @@ function formatInspectFailure(fields, next) {
209
209
  return { omittable, full, selected };
210
210
  });
211
211
  // Only producer-known read-only inspect literals become a typed next, never a parsed action.
212
- const typedNext = next ? { next: { tool: "inspect", target: next[0], view: next[1] } } : undefined;
212
+ const typedNext = next ? { next: { tool: "actors_inspect", target: next[0], view: next[1] } } : undefined;
213
213
  const nextFallback = { next_action: omitted };
214
214
  const document = () => ({ ...Object.assign({}, ...parts.map((part) => part.selected)),
215
215
  ...(typedNext ?? nextFallback) });
@@ -336,15 +336,15 @@ function inspectRecipeIdentity(identity, resolutionContext, namespaces, inventor
336
336
  ...(rejectedReason ? { rejected_reason: rejectedReason } : {}),
337
337
  next_actions: resolvable
338
338
  ? [
339
- `spawn recipe=${identity}`,
340
- `register_tool name=<tool-name> from=${identity}`,
339
+ `actors_run_spawn recipe=${identity}`,
340
+ `actors_tool_register name=<tool-name> from=${identity}`,
341
341
  ]
342
342
  : roots.length === 0 && skill
343
343
  ? [
344
344
  `activate Skill ${skill}`,
345
- `inspect target=recipes view=doctor identity=${identity}`,
345
+ `actors_inspect target=recipes view=doctor identity=${identity}`,
346
346
  ]
347
- : ["inspect target=recipes view=imports verbose=true"],
347
+ : ["actors_inspect target=recipes view=imports verbose=true"],
348
348
  };
349
349
  }
350
350
  function inspectRecipes(requestedView, input, deps, context) {
@@ -583,7 +583,7 @@ function compactResult(target, view, details) {
583
583
  ? String(details.next_actions[0])
584
584
  : details.callable_now === true
585
585
  ? `call tool ${name}`
586
- : `register_tool name=${name} update=true`;
586
+ : `actors_tool_register name=${name} update=true`;
587
587
  const hint = ToolsResponse.nextActionMetadata(next, Limits.RUN_DELIVERY_MODEL_MAX_BYTES);
588
588
  const required = compactArguments("required_args", details.required_args);
589
589
  const optional = compactArguments("optional_args", details.optional_args);
@@ -601,7 +601,7 @@ function compactResult(target, view, details) {
601
601
  }
602
602
  export function createInspectToolDefinition(deps = {}) {
603
603
  return {
604
- name: "inspect",
604
+ name: "actors_inspect",
605
605
  label: "Inspect",
606
606
  description: "Inspect a Run's Recipe, Trace, or Control evidence, or runtime/recipe/tool diagnostics.",
607
607
  parameters: Schema.objectSchema({
@@ -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
  */