@llblab/pi-actors 0.33.0 → 0.34.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (155) hide show
  1. package/AGENTS.md +22 -11
  2. package/BACKLOG.md +3 -34
  3. package/CHANGELOG.md +14 -0
  4. package/dist/index.js +10 -10
  5. package/dist/lib/async-runs.d.ts +23 -75
  6. package/dist/lib/async-runs.js +49 -801
  7. package/dist/lib/command-templates.d.ts +3 -3
  8. package/dist/lib/command-templates.js +3 -3
  9. package/dist/lib/config.d.ts +2 -2
  10. package/dist/lib/config.js +5 -5
  11. package/dist/lib/{output.d.ts → execution-output.d.ts} +3 -3
  12. package/dist/lib/{output.js → execution-output.js} +3 -3
  13. package/dist/lib/execution.js +1 -1
  14. package/dist/lib/{actor-inspector-tui.d.ts → inspector.d.ts} +3 -2
  15. package/dist/lib/{actor-inspector-tui.js → inspector.js} +3 -2
  16. package/dist/lib/mailbox-loop.d.ts +3 -3
  17. package/dist/lib/mailbox-loop.js +3 -3
  18. package/dist/lib/{actor-messages.d.ts → messages.d.ts} +3 -3
  19. package/dist/lib/{actor-messages.js → messages.js} +3 -3
  20. package/dist/lib/{actor-recipe-context.d.ts → recipes-context.d.ts} +3 -2
  21. package/dist/lib/{actor-recipe-context.js → recipes-context.js} +2 -1
  22. package/dist/lib/{recipe-discovery.d.ts → recipes-discovery.d.ts} +8 -8
  23. package/dist/lib/{recipe-discovery.js → recipes-discovery.js} +6 -6
  24. package/dist/lib/registry.d.ts +1 -1
  25. package/dist/lib/registry.js +22 -22
  26. package/dist/lib/{actor-rooms.d.ts → rooms.d.ts} +4 -4
  27. package/dist/lib/{actor-rooms.js → rooms.js} +3 -3
  28. package/dist/lib/runs-artifacts.d.ts +21 -0
  29. package/dist/lib/runs-artifacts.js +68 -0
  30. package/dist/lib/runs-control.d.ts +13 -0
  31. package/dist/lib/runs-control.js +59 -0
  32. package/dist/lib/runs-identity.d.ts +5 -0
  33. package/dist/lib/runs-identity.js +11 -0
  34. package/dist/lib/runs-index.d.ts +18 -0
  35. package/dist/lib/runs-index.js +119 -0
  36. package/dist/lib/runs-mailbox.d.ts +25 -0
  37. package/dist/lib/runs-mailbox.js +146 -0
  38. package/dist/lib/runs-messages.d.ts +15 -0
  39. package/dist/lib/runs-messages.js +179 -0
  40. package/dist/lib/runs-outbox.d.ts +41 -0
  41. package/dist/lib/runs-outbox.js +87 -0
  42. package/dist/lib/runs-process.d.ts +7 -0
  43. package/dist/lib/runs-process.js +36 -0
  44. package/dist/lib/runs-retention.d.ts +8 -0
  45. package/dist/lib/runs-retention.js +56 -0
  46. package/dist/lib/runs-start.d.ts +9 -0
  47. package/dist/lib/runs-start.js +67 -0
  48. package/dist/lib/runs-status.d.ts +8 -0
  49. package/dist/lib/runs-status.js +60 -0
  50. package/dist/lib/runtime.d.ts +2 -2
  51. package/dist/lib/runtime.js +5 -5
  52. package/dist/lib/schema.d.ts +9 -0
  53. package/dist/lib/schema.js +40 -0
  54. package/dist/lib/tools-access.d.ts +19 -0
  55. package/dist/lib/tools-access.js +44 -0
  56. package/dist/lib/tools-inspect.d.ts +11 -0
  57. package/dist/lib/tools-inspect.js +534 -0
  58. package/dist/lib/tools-local.d.ts +14 -0
  59. package/dist/lib/tools-local.js +156 -0
  60. package/dist/lib/tools-mailbox.d.ts +8 -0
  61. package/dist/lib/tools-mailbox.js +48 -0
  62. package/dist/lib/tools-message.d.ts +9 -0
  63. package/dist/lib/tools-message.js +428 -0
  64. package/dist/lib/tools-register.d.ts +18 -0
  65. package/dist/lib/tools-register.js +40 -0
  66. package/dist/lib/tools-response.d.ts +21 -0
  67. package/dist/lib/tools-response.js +219 -0
  68. package/dist/lib/tools-spawn.d.ts +12 -0
  69. package/dist/lib/tools-spawn.js +159 -0
  70. package/dist/lib/tools.d.ts +8 -39
  71. package/dist/lib/tools.js +11 -1490
  72. package/dist/recipes/utility-coordinator-lock-snapshot.json +1 -1
  73. package/dist/scripts/actor-worker.mjs +190 -7
  74. package/dist/scripts/async-runner.mjs +171 -8
  75. package/dist/scripts/build-dist.mjs +1 -1
  76. package/dist/scripts/conformance.mjs +3 -3
  77. package/dist/scripts/coordinator.mjs +733 -11
  78. package/dist/scripts/locker.mjs +345 -16
  79. package/dist/scripts/recipe-utils.mjs +478 -14
  80. package/dist/scripts/validate-recipe.mjs +110 -10
  81. package/dist/skills/actors/SKILL.md +1 -1
  82. package/dist/skills/swarm/SKILL.md +1 -1
  83. package/docs/recipe-library.md +1 -1
  84. package/docs/template-recipes.md +1 -1
  85. package/index.ts +11 -18
  86. package/lib/async-runs.ts +124 -1016
  87. package/lib/command-templates.ts +3 -3
  88. package/lib/config.ts +7 -7
  89. package/lib/{output.ts → execution-output.ts} +3 -3
  90. package/lib/execution.ts +5 -1
  91. package/lib/{actor-inspector-tui.ts → inspector.ts} +4 -3
  92. package/lib/mailbox-loop.ts +3 -3
  93. package/lib/{actor-messages.ts → messages.ts} +3 -3
  94. package/lib/{actor-recipe-context.ts → recipes-context.ts} +3 -2
  95. package/lib/{recipe-discovery.ts → recipes-discovery.ts} +19 -19
  96. package/lib/{recipe-usage.ts → recipes-usage.ts} +2 -2
  97. package/lib/registry.ts +37 -24
  98. package/lib/{actor-rooms.ts → rooms.ts} +4 -4
  99. package/lib/runs-artifacts.ts +96 -0
  100. package/lib/runs-control.ts +85 -0
  101. package/lib/runs-identity.ts +14 -0
  102. package/lib/runs-index.ts +156 -0
  103. package/lib/runs-mailbox.ts +208 -0
  104. package/lib/runs-messages.ts +252 -0
  105. package/lib/runs-outbox.ts +144 -0
  106. package/lib/runs-process.ts +51 -0
  107. package/lib/runs-retention.ts +77 -0
  108. package/lib/runs-start.ts +98 -0
  109. package/lib/runs-status.ts +78 -0
  110. package/lib/runtime.ts +6 -6
  111. package/lib/schema.ts +55 -0
  112. package/lib/tools-access.ts +68 -0
  113. package/lib/tools-inspect.ts +735 -0
  114. package/lib/tools-local.ts +234 -0
  115. package/lib/tools-mailbox.ts +56 -0
  116. package/lib/tools-message.ts +569 -0
  117. package/lib/tools-register.ts +64 -0
  118. package/lib/tools-response.ts +267 -0
  119. package/lib/tools-spawn.ts +240 -0
  120. package/lib/tools.ts +20 -2040
  121. package/package.json +1 -1
  122. package/recipes/utility-coordinator-lock-snapshot.json +1 -1
  123. package/scripts/actor-worker.mjs +190 -7
  124. package/scripts/async-runner.mjs +171 -8
  125. package/scripts/build-dist.mjs +1 -1
  126. package/scripts/conformance.mjs +3 -3
  127. package/scripts/coordinator.mjs +733 -11
  128. package/scripts/locker.mjs +345 -16
  129. package/scripts/recipe-utils.mjs +478 -14
  130. package/scripts/validate-recipe.mjs +110 -10
  131. package/skills/actors/SKILL.md +1 -1
  132. package/skills/swarm/SKILL.md +1 -1
  133. package/dist/lib/actor-worker.d.ts +0 -16
  134. package/dist/lib/actor-worker.js +0 -134
  135. package/dist/lib/async-runner.d.ts +0 -5
  136. package/dist/lib/async-runner.js +0 -134
  137. package/dist/lib/coordinator.d.ts +0 -5
  138. package/dist/lib/coordinator.js +0 -660
  139. package/dist/lib/locker.d.ts +0 -5
  140. package/dist/lib/locker.js +0 -310
  141. package/dist/lib/recipe-utils.d.ts +0 -5
  142. package/dist/lib/recipe-utils.js +0 -385
  143. package/dist/lib/validate-recipe.d.ts +0 -6
  144. package/dist/lib/validate-recipe.js +0 -104
  145. package/lib/actor-worker.ts +0 -174
  146. package/lib/async-runner.ts +0 -173
  147. package/lib/coordinator.ts +0 -742
  148. package/lib/locker.ts +0 -340
  149. package/lib/recipe-utils.ts +0 -486
  150. package/lib/validate-recipe.ts +0 -110
  151. /package/dist/lib/{recipe-references.d.ts → recipes-references.d.ts} +0 -0
  152. /package/dist/lib/{recipe-references.js → recipes-references.js} +0 -0
  153. /package/dist/lib/{recipe-usage.d.ts → recipes-usage.d.ts} +0 -0
  154. /package/dist/lib/{recipe-usage.js → recipes-usage.js} +0 -0
  155. /package/lib/{recipe-references.ts → recipes-references.ts} +0 -0
@@ -1,7 +1,7 @@
1
1
  /**
2
- * Command-template standard helpers
3
- * Zones: shared utils, local process execution, automation standard
4
- * Owns shell-free command-template splitting, placeholder defaults, composition expansion, executable path expansion, and direct execution
2
+ * Command-template execution standard.
3
+ * Zones: shell-free command parsing, placeholder expansion, local process execution, composition semantics
4
+ * Owns portable command-template parsing, expansion, risk checks, retries, timeouts, and direct execution.
5
5
  */
6
6
 
7
7
  import { spawn } from "node:child_process";
package/lib/config.ts CHANGED
@@ -10,7 +10,7 @@ import type { CommandTemplateValue } from "./command-templates.ts";
10
10
  import * as CommandTemplates from "./command-templates.ts";
11
11
  import { writeJsonAtomic } from "./file-state.ts";
12
12
  import { normalizeToolName } from "./identity.ts";
13
- import * as RecipeReferences from "./recipe-references.ts";
13
+ import * as RecipesReferences from "./recipes-references.ts";
14
14
  import * as Schema from "./schema.ts";
15
15
 
16
16
  export interface RegisteredTool {
@@ -19,7 +19,7 @@ export interface RegisteredTool {
19
19
  args: string[];
20
20
  defaults: Record<string, string>;
21
21
  argTypes?: Record<string, Schema.ToolArgType>;
22
- recipe?: RecipeReferences.TemplateRecipeConfig;
22
+ recipe?: RecipesReferences.TemplateRecipeConfig;
23
23
  template?: CommandTemplateValue;
24
24
  storedArgs?: string[];
25
25
  storedDefaults?: Record<string, string>;
@@ -141,7 +141,7 @@ export function normalizeStoredTool(
141
141
  }
142
142
  const recipeName =
143
143
  keyedRecipeName ?? (typeof record.async === "boolean" ? name : undefined);
144
- const recipe: RecipeReferences.TemplateRecipeConfig | undefined = recipeName
144
+ const recipe: RecipesReferences.TemplateRecipeConfig | undefined = recipeName
145
145
  ? {
146
146
  name: recipeName,
147
147
  ...(typeof record.async === "boolean" ? { async: record.async } : {}),
@@ -156,14 +156,14 @@ export function normalizeStoredTool(
156
156
  : {}),
157
157
  }
158
158
  : undefined;
159
- const isRecipe = RecipeReferences.isRecipeTool(template, recipe);
160
- const recipeTemplate = RecipeReferences.getRecipeTemplate(template);
159
+ const isRecipe = RecipesReferences.isRecipeTool(template, recipe);
160
+ const recipeTemplate = RecipesReferences.getRecipeTemplate(template);
161
161
  const argTemplate = recipeTemplate ?? template;
162
162
  const description =
163
163
  typeof record.description === "string" && record.description.trim()
164
164
  ? record.description.trim()
165
165
  : isRecipe
166
- ? `${recipe?.async === true || RecipeReferences.isAsyncRecipeReference(template) ? "Start async" : "Execute"} template recipe: ${recipe?.name ?? formatTemplateForDescription(template)}`
166
+ ? `${recipe?.async === true || RecipesReferences.isAsyncRecipeReference(template) ? "Start async" : "Execute"} template recipe: ${recipe?.name ?? formatTemplateForDescription(template)}`
167
167
  : `Execute command template: ${formatTemplateForDescription(template)}`;
168
168
  const declarations = Schema.normalizeStoredToolArgDeclarations(
169
169
  record.args,
@@ -199,7 +199,7 @@ export function normalizeStoredTool(
199
199
  args:
200
200
  isRecipe && storedArgs !== undefined
201
201
  ? Schema.getExplicitToolArgNames(storedArgs)
202
- : RecipeReferences.isRecipeReference(template) && !recipeTemplate
202
+ : RecipesReferences.isRecipeReference(template) && !recipeTemplate
203
203
  ? Schema.getExplicitToolArgNames(storedArgs)
204
204
  : Schema.getToolArgNames(argTemplateConfig),
205
205
  defaults: declarations.defaults,
@@ -1,7 +1,7 @@
1
1
  /**
2
- * Tool output formatting helpers
3
- * Zones: tool execution, output formatting, temp artifacts
4
- * Owns stdout/stderr failure formatting, tail truncation, and full-output temp-file persistence
2
+ * Registered tool execution output.
3
+ * Zones: command execution results, bounded output formatting, temp artifacts
4
+ * Owns stdout/stderr failure formatting, tail truncation, and full-output temp-file persistence.
5
5
  */
6
6
 
7
7
  import { mkdirSync, mkdtempSync, writeFileSync } from "node:fs";
package/lib/execution.ts CHANGED
@@ -6,7 +6,11 @@
6
6
 
7
7
  import * as CommandTemplates from "./command-templates.ts";
8
8
  import type { RegisteredTool } from "./config.ts";
9
- import { formatFailureOutput, formatOutput, formatToolText } from "./output.ts";
9
+ import {
10
+ formatFailureOutput,
11
+ formatOutput,
12
+ formatToolText,
13
+ } from "./execution-output.ts";
10
14
  import * as Schema from "./schema.ts";
11
15
 
12
16
  export interface ToolExecOptions {
@@ -1,6 +1,7 @@
1
1
  /**
2
- * Actor inspector TUI previews.
3
- * Zones: terminal actor inspection, room/direct message previews, no-dependency UI formatting
2
+ * Inspector preview rendering.
3
+ * Zones: terminal run inspection, room/direct message previews, no-dependency UI formatting
4
+ * Owns read-only previews and TUI-ready summaries; actor routing stays outside.
4
5
  */
5
6
 
6
7
  import * as fs from "node:fs";
@@ -8,7 +9,7 @@ import * as path from "node:path";
8
9
 
9
10
  import { visibleWidth } from "@earendil-works/pi-tui";
10
11
 
11
- import type { ActorMessage } from "./actor-messages.ts";
12
+ import type { ActorMessage } from "./messages.ts";
12
13
  import * as Limits from "./limits.ts";
13
14
  import * as Paths from "./paths.ts";
14
15
  import {
@@ -1,14 +1,14 @@
1
1
  /**
2
- * Minimal mailbox loop helpers.
2
+ * Mailbox worker loop primitives.
3
3
  * Zones: mailbox-consuming actors, run/branch inbox claiming, handler status transitions
4
- * Owns small reusable primitives for recipe authors; scheduling and task policy stay outside.
4
+ * Owns reusable claim/handle/drain behavior across run and branch inboxes; scheduling and task policy stay outside.
5
5
  */
6
6
 
7
7
  import {
8
8
  claimBranchInboxMessage,
9
9
  updateBranchInboxMessageStatus,
10
10
  type BranchInboxRecord,
11
- } from "./actor-rooms.ts";
11
+ } from "./rooms.ts";
12
12
  import {
13
13
  claimRunInboxMessage,
14
14
  updateRunInboxMessageStatus,
@@ -1,7 +1,7 @@
1
1
  /**
2
- * Actor message protocol helpers.
3
- * Zones: 0.10 draft communication protocol, addressed messages, mailbox metadata
4
- * Owns pure validation/normalization for the semantic message envelope; transport routing stays in adapters.
2
+ * Actor message protocol.
3
+ * Zones: addressed envelopes, address parsing, route normalization
4
+ * Owns pure validation/normalization for semantic actor messages; transport routing stays in adapters.
5
5
  */
6
6
 
7
7
  export type ActorAddressKind =
@@ -1,12 +1,13 @@
1
1
  /**
2
- * Actor recipe context prompt helpers.
2
+ * Recipe context prompt assembly.
3
3
  * Zones: async runner prompt context, recipe provenance, LLM child launches
4
+ * Owns compact actor recipe context records appended to child-agent prompts.
4
5
  */
5
6
 
6
7
  import { basename } from "node:path";
7
8
 
8
9
  import type { CommandTemplateActorRecipeContext } from "./command-templates.ts";
9
- import type { TemplateRecipeContextRecord } from "./recipe-references.ts";
10
+ import type { TemplateRecipeContextRecord } from "./recipes-references.ts";
10
11
 
11
12
  export interface MarkedRecipeContextRecord extends TemplateRecipeContextRecord {
12
13
  you_are_here?: true;
@@ -10,8 +10,8 @@ import { join } from "node:path";
10
10
 
11
11
  import * as CommandTemplates from "./command-templates.ts";
12
12
  import type { RegisteredTool } from "./config.ts";
13
- import type { TemplateRecipeConfig } from "./recipe-references.ts";
14
- import * as RecipeReferences from "./recipe-references.ts";
13
+ import type { TemplateRecipeConfig } from "./recipes-references.ts";
14
+ import * as RecipesReferences from "./recipes-references.ts";
15
15
  import * as Schema from "./schema.ts";
16
16
 
17
17
  export interface DiscoveredRecipe {
@@ -43,13 +43,13 @@ export interface RecipeIntegrityManifestEntry {
43
43
  shadowed: boolean;
44
44
  }
45
45
 
46
- export interface RecipeDiscoveryResult {
46
+ export interface RecipesDiscoveryResult {
47
47
  active: Map<string, DiscoveredRecipe>;
48
48
  entries: DiscoveredRecipe[];
49
49
  diagnostics: string[];
50
50
  }
51
51
 
52
- export interface RecipeDiscoverySource {
52
+ export interface RecipesDiscoverySource {
53
53
  root?: string;
54
54
  file?: string;
55
55
  defaultTool?: boolean;
@@ -115,7 +115,7 @@ function getRecipeConfigDiagnostics(
115
115
  config: TemplateRecipeConfig | undefined,
116
116
  ): string[] {
117
117
  if (!config) {
118
- const reason = RecipeReferences.diagnoseRawRecipeConfigFailure(file);
118
+ const reason = RecipesReferences.diagnoseRawRecipeConfigFailure(file);
119
119
  return [`Invalid recipe: ${file}${reason ? `: ${reason}` : ""}`];
120
120
  }
121
121
  const commandTemplateConfig =
@@ -134,9 +134,9 @@ function readDiscoveredRecipe(
134
134
  defaultTool = false,
135
135
  mutableUsage = false,
136
136
  ): DiscoveredRecipe {
137
- const id = RecipeReferences.getRecipeIdFromPath(file);
137
+ const id = RecipesReferences.getRecipeIdFromPath(file);
138
138
  try {
139
- const config = RecipeReferences.readResolvedRecipeConfig(file);
139
+ const config = RecipesReferences.readResolvedRecipeConfig(file);
140
140
  const invalid = !config;
141
141
  const disabled = config?.disabled === true;
142
142
  return {
@@ -174,7 +174,7 @@ function readDiscoveredRecipe(
174
174
  }
175
175
  }
176
176
 
177
- function filesForSource(source: RecipeDiscoverySource): Array<{
177
+ function filesForSource(source: RecipesDiscoverySource): Array<{
178
178
  root: string;
179
179
  file: string;
180
180
  defaultTool: boolean;
@@ -201,7 +201,7 @@ function filesForSource(source: RecipeDiscoverySource): Array<{
201
201
  : [];
202
202
  }
203
203
 
204
- function getRecipeRootDiagnostics(sources: RecipeDiscoverySource[]): string[] {
204
+ function getRecipeRootDiagnostics(sources: RecipesDiscoverySource[]): string[] {
205
205
  const diagnostics: string[] = [];
206
206
  const roots = new Set(
207
207
  sources
@@ -232,8 +232,8 @@ function getRecipeRootDiagnostics(sources: RecipeDiscoverySource[]): string[] {
232
232
  }
233
233
 
234
234
  export function discoverRecipeSources(
235
- sources: RecipeDiscoverySource[],
236
- ): RecipeDiscoveryResult {
235
+ sources: RecipesDiscoverySource[],
236
+ ): RecipesDiscoveryResult {
237
237
  const entries = sources.flatMap((source, priority) =>
238
238
  filesForSource(source).map(({ root, file, defaultTool, mutableUsage }) =>
239
239
  readDiscoveredRecipe(root, file, priority, defaultTool, mutableUsage),
@@ -286,7 +286,7 @@ export function discoverRecipeSources(
286
286
  return { active, entries, diagnostics };
287
287
  }
288
288
 
289
- export function discoverRecipes(roots: string[]): RecipeDiscoveryResult {
289
+ export function discoverRecipes(roots: string[]): RecipesDiscoveryResult {
290
290
  return discoverRecipeSources(roots.map((root) => ({ root })));
291
291
  }
292
292
 
@@ -362,7 +362,7 @@ function cleanupRecommendation(
362
362
  }
363
363
 
364
364
  export function createRecipeIntegrityManifest(
365
- result: RecipeDiscoveryResult,
365
+ result: RecipesDiscoveryResult,
366
366
  ): RecipeIntegrityManifestEntry[] {
367
367
  return result.entries
368
368
  .map((entry) => {
@@ -425,7 +425,7 @@ function diagnosticSuggestedAction(message: string): string {
425
425
  }
426
426
 
427
427
  function diagnosticDetails(
428
- result: RecipeDiscoveryResult,
428
+ result: RecipesDiscoveryResult,
429
429
  ): Array<Record<string, unknown>> {
430
430
  const details: Array<Record<string, unknown>> = [];
431
431
  const seen = new Set<string>();
@@ -529,7 +529,7 @@ function remediationRank(item: Record<string, unknown>): number {
529
529
  }
530
530
 
531
531
  function discoveryRemediations(
532
- result: RecipeDiscoveryResult,
532
+ result: RecipesDiscoveryResult,
533
533
  ): Array<Record<string, unknown>> {
534
534
  return result.entries
535
535
  .map((entry) =>
@@ -560,7 +560,7 @@ function recommendationForEntry(
560
560
  }
561
561
 
562
562
  export function getShadowedLaunchDiagnostic(
563
- result: RecipeDiscoveryResult,
563
+ result: RecipesDiscoveryResult,
564
564
  id: string,
565
565
  ): Record<string, unknown> | undefined {
566
566
  const active = result.active.get(id.trim());
@@ -576,8 +576,8 @@ export function getShadowedLaunchDiagnostic(
576
576
 
577
577
  export function listDraftRecipes(root: string): Array<Record<string, unknown>> {
578
578
  return listRecipeFiles(root).map((path) => {
579
- const id = RecipeReferences.getRecipeIdFromPath(path);
580
- const config = RecipeReferences.readRawRecipeConfig(path);
579
+ const id = RecipesReferences.getRecipeIdFromPath(path);
580
+ const config = RecipesReferences.readRawRecipeConfig(path);
581
581
  return {
582
582
  id,
583
583
  path,
@@ -587,7 +587,7 @@ export function listDraftRecipes(root: string): Array<Record<string, unknown>> {
587
587
  }
588
588
 
589
589
  export function summarizeDiscovery(
590
- result: RecipeDiscoveryResult,
590
+ result: RecipesDiscoveryResult,
591
591
  ): Record<string, unknown> {
592
592
  const recommendations = result.entries
593
593
  .map((entry) =>
@@ -9,7 +9,7 @@ import { existsSync, readFileSync } from "node:fs";
9
9
 
10
10
  import { writeJsonAtomic } from "./file-state.ts";
11
11
 
12
- interface RecipeUsageRecord {
12
+ interface RecipesUsageRecord {
13
13
  calls?: number;
14
14
  direct_calls?: number;
15
15
  fingerprint?: string;
@@ -62,7 +62,7 @@ export function recordRecipeLaunch(
62
62
  try {
63
63
  const raw = JSON.parse(readFileSync(path, "utf8"));
64
64
  if (!isRecord(raw)) return false;
65
- const usage: RecipeUsageRecord = isRecord(raw.usage) ? raw.usage : {};
65
+ const usage: RecipesUsageRecord = isRecord(raw.usage) ? raw.usage : {};
66
66
  const fingerprint = getRecipeFingerprint(raw);
67
67
  const changed =
68
68
  typeof usage.fingerprint === "string" &&
package/lib/registry.ts CHANGED
@@ -7,13 +7,13 @@
7
7
  import { existsSync, mkdirSync, unlinkSync } from "node:fs";
8
8
  import { dirname, join } from "node:path";
9
9
 
10
- import * as Config from "./config.ts";
11
- import * as Identity from "./identity.ts";
12
- import * as Output from "./output.ts";
13
10
  import * as CommandTemplates from "./command-templates.ts";
11
+ import * as Config from "./config.ts";
12
+ import * as ExecutionOutput from "./execution-output.ts";
14
13
  import { writeJsonAtomic } from "./file-state.ts";
14
+ import * as Identity from "./identity.ts";
15
15
  import * as Paths from "./paths.ts";
16
- import * as RecipeReferences from "./recipe-references.ts";
16
+ import * as RecipesReferences from "./recipes-references.ts";
17
17
  import * as Schema from "./schema.ts";
18
18
 
19
19
  export interface RegisterToolInput {
@@ -71,7 +71,7 @@ function listTools<TContext>(
71
71
  return {
72
72
  content: [
73
73
  textContent(
74
- Output.formatToolText(
74
+ ExecutionOutput.formatToolText(
75
75
  names.length > 0
76
76
  ? `Registered tools:\n${names.map((name) => `- ${name}`).join("\n")}`
77
77
  : "No registered tools.",
@@ -122,7 +122,9 @@ function deleteTool<TContext>(
122
122
  if (!tools.has(name)) {
123
123
  return {
124
124
  content: [
125
- textContent(Output.formatToolText(`Tool "${name}" not found.`)),
125
+ textContent(
126
+ ExecutionOutput.formatToolText(`Tool "${name}" not found.`),
127
+ ),
126
128
  ],
127
129
  details: { tool: name },
128
130
  };
@@ -137,7 +139,7 @@ function deleteTool<TContext>(
137
139
  return {
138
140
  content: [
139
141
  textContent(
140
- Output.formatToolText(
142
+ ExecutionOutput.formatToolText(
141
143
  `Deleted tool "${name}". Reload to remove it from the complete registry.`,
142
144
  ),
143
145
  ),
@@ -157,7 +159,7 @@ function getInputTemplate(
157
159
  });
158
160
  if (steps.length === 0)
159
161
  throw new Error(
160
- Output.formatToolText("Tool template sequence is empty."),
162
+ ExecutionOutput.formatToolText("Tool template sequence is empty."),
161
163
  );
162
164
  return value;
163
165
  }
@@ -168,7 +170,9 @@ function getInputTemplate(
168
170
  return value as CommandTemplates.CommandTemplateConfig;
169
171
  }
170
172
  throw new Error(
171
- Output.formatToolText("Tool template must be a string, object, or sequence."),
173
+ ExecutionOutput.formatToolText(
174
+ "Tool template must be a string, object, or sequence.",
175
+ ),
172
176
  );
173
177
  }
174
178
 
@@ -182,26 +186,30 @@ function buildConfig(
182
186
  ? undefined
183
187
  : Schema.parseToolArgDeclarations(input.args);
184
188
  if (explicitArgs?.error)
185
- throw new Error(Output.formatToolText(explicitArgs.error));
189
+ throw new Error(ExecutionOutput.formatToolText(explicitArgs.error));
186
190
  const description = (input.description ?? existing?.description ?? "").trim();
187
191
  if (!description) {
188
192
  throw new Error(
189
- Output.formatToolText("Tool description is required unless deleting."),
193
+ ExecutionOutput.formatToolText(
194
+ "Tool description is required unless deleting.",
195
+ ),
190
196
  );
191
197
  }
192
198
  const template = getInputTemplate(input.template);
193
199
  if (template === null) {
194
200
  throw new Error(
195
- Output.formatToolText("Tool template cannot be null here."),
201
+ ExecutionOutput.formatToolText("Tool template cannot be null here."),
196
202
  );
197
203
  }
198
204
  const finalTemplate =
199
205
  template === undefined || template === "" ? existing?.template : template;
200
206
  if (!finalTemplate) {
201
- throw new Error(Output.formatToolText("Tool template is required."));
207
+ throw new Error(
208
+ ExecutionOutput.formatToolText("Tool template is required."),
209
+ );
202
210
  }
203
211
  const inputRecipe = typeof input.async === "boolean" ? name : undefined;
204
- const recipe: RecipeReferences.TemplateRecipeConfig | undefined = inputRecipe
212
+ const recipe: RecipesReferences.TemplateRecipeConfig | undefined = inputRecipe
205
213
  ? {
206
214
  name: inputRecipe,
207
215
  ...(typeof input.async === "boolean" ? { async: input.async } : {}),
@@ -222,7 +230,7 @@ function buildConfig(
222
230
  : existing?.storedArgs;
223
231
  const storedDefaults =
224
232
  Object.keys(defaults).length > 0 ? defaults : undefined;
225
- const recipeTemplate = RecipeReferences.getRecipeTemplate(finalTemplate);
233
+ const recipeTemplate = RecipesReferences.getRecipeTemplate(finalTemplate);
226
234
  const argTemplate = recipeTemplate ?? finalTemplate;
227
235
  const argTemplateConfig: CommandTemplates.CommandTemplateConfig =
228
236
  typeof argTemplate === "object" && !Array.isArray(argTemplate)
@@ -248,10 +256,10 @@ function buildConfig(
248
256
  template: finalTemplate,
249
257
  ...(recipe ? { recipe } : {}),
250
258
  args:
251
- RecipeReferences.isRecipeTool(finalTemplate, recipe) &&
259
+ RecipesReferences.isRecipeTool(finalTemplate, recipe) &&
252
260
  storedArgs !== undefined
253
261
  ? Schema.getExplicitToolArgNames(storedArgs)
254
- : RecipeReferences.isRecipeReference(finalTemplate) && !recipeTemplate
262
+ : RecipesReferences.isRecipeReference(finalTemplate) && !recipeTemplate
255
263
  ? Schema.getExplicitToolArgNames(storedArgs)
256
264
  : Schema.getToolArgNames(argTemplateConfig),
257
265
  defaults,
@@ -269,9 +277,12 @@ export async function executeRegisterTool<TContext>(
269
277
  const input = params as RegisterToolInput;
270
278
  if (!input.name) return listTools(deps);
271
279
  const name = Identity.normalizeToolName(input.name);
272
- if (!name) throw new Error(Output.formatToolText("Invalid tool name."));
280
+ if (!name)
281
+ throw new Error(ExecutionOutput.formatToolText("Invalid tool name."));
273
282
  if (deps.reservedToolNames.has(name)) {
274
- throw new Error(Output.formatToolText(`Reserved tool name: ${name}`));
283
+ throw new Error(
284
+ ExecutionOutput.formatToolText(`Reserved tool name: ${name}`),
285
+ );
275
286
  }
276
287
  const templateProvided = Object.hasOwn(input, "template");
277
288
  const template = getInputTemplate(input.template);
@@ -280,17 +291,19 @@ export async function executeRegisterTool<TContext>(
280
291
  const tools = deps.getTools();
281
292
  const existing = tools.get(name);
282
293
  const conflict = deps.getExternalToolConflict(name);
283
- if (conflict) throw new Error(Output.formatToolText(conflict));
294
+ if (conflict) throw new Error(ExecutionOutput.formatToolText(conflict));
284
295
  if (existing && !input.update) {
285
296
  throw new Error(
286
- Output.formatToolText(
297
+ ExecutionOutput.formatToolText(
287
298
  `Tool "${name}" already registered. Use update=true to overwrite.`,
288
299
  ),
289
300
  );
290
301
  }
291
302
  if (template === undefined && !existing) {
292
303
  throw new Error(
293
- Output.formatToolText("Tool template is required for new registrations."),
304
+ ExecutionOutput.formatToolText(
305
+ "Tool template is required for new registrations.",
306
+ ),
294
307
  );
295
308
  }
296
309
  const cfg = buildConfig(name, input, existing);
@@ -299,7 +312,7 @@ export async function executeRegisterTool<TContext>(
299
312
  recipePath = persistToolRecipe(deps, cfg);
300
313
  } catch (error) {
301
314
  throw new Error(
302
- Output.formatToolText(
315
+ ExecutionOutput.formatToolText(
303
316
  `Failed to persist tool recipe: ${error instanceof Error ? error.message : String(error)}`,
304
317
  ),
305
318
  );
@@ -320,7 +333,7 @@ export async function executeRegisterTool<TContext>(
320
333
  return {
321
334
  content: [
322
335
  textContent(
323
- Output.formatToolText(
336
+ ExecutionOutput.formatToolText(
324
337
  `${existing ? "Updated" : "Registered"} tool "${name}" (args: ${Schema.formatToolArgs(cfg.args)}).${warningText}`,
325
338
  ),
326
339
  ),
@@ -1,14 +1,14 @@
1
1
  /**
2
- * Actor room persistence helpers.
3
- * Zones: room timelines, room rosters, cross-branch discovery state
4
- * Owns small file-backed room state; routing policy stays in tools/runtime adapters.
2
+ * Actor room persistence.
3
+ * Zones: room timelines, rosters, branch inboxes, cross-branch discovery state
4
+ * Owns file-backed room and branch-mailbox state; routing policy stays in tools/runtime adapters.
5
5
  */
6
6
 
7
7
  import * as fs from "node:fs";
8
8
  import { randomUUID } from "node:crypto";
9
9
  import * as path from "node:path";
10
10
 
11
- import type { ActorMessage } from "./actor-messages.ts";
11
+ import type { ActorMessage } from "./messages.ts";
12
12
  import * as Limits from "./limits.ts";
13
13
  import { notifyRuntimeWake } from "./runtime-notifier.ts";
14
14
  import {
@@ -0,0 +1,96 @@
1
+ /**
2
+ * Async run artifact declarations and manifest resolution.
3
+ * Owns artifact path template expansion and filesystem-backed artifact metadata.
4
+ */
5
+
6
+ import { createHash } from "node:crypto";
7
+ import { readFileSync } from "node:fs";
8
+
9
+ import { substituteCommandTemplateToken } from "./command-templates.ts";
10
+
11
+ export type RunArtifactDeclaration =
12
+ | string
13
+ | { path: string; kind?: string; media_type?: string; required?: boolean };
14
+
15
+ export interface RunArtifactManifestEntry {
16
+ exists: boolean;
17
+ kind?: string;
18
+ media_type?: string;
19
+ path: string;
20
+ required?: boolean;
21
+ sha256?: string;
22
+ size?: number;
23
+ }
24
+
25
+ export function resolveArtifactPaths(
26
+ artifacts: Record<string, RunArtifactDeclaration> | undefined,
27
+ values: Record<string, unknown>,
28
+ ): Record<string, RunArtifactDeclaration> | undefined {
29
+ if (!artifacts) return undefined;
30
+ const resolved: Record<string, RunArtifactDeclaration> = {};
31
+ for (const [key, value] of Object.entries(artifacts)) {
32
+ if (!key.trim()) continue;
33
+ if (typeof value === "string") {
34
+ resolved[key] = substituteCommandTemplateToken(
35
+ value,
36
+ values,
37
+ `recipe artifacts.${key}`,
38
+ );
39
+ } else if (
40
+ value &&
41
+ typeof value === "object" &&
42
+ typeof value.path === "string"
43
+ ) {
44
+ resolved[key] = {
45
+ ...value,
46
+ path: substituteCommandTemplateToken(
47
+ value.path,
48
+ values,
49
+ `recipe artifacts.${key}.path`,
50
+ ),
51
+ };
52
+ }
53
+ }
54
+ return Object.keys(resolved).length > 0 ? resolved : undefined;
55
+ }
56
+
57
+ export function resolveArtifactManifest(
58
+ artifacts: Record<string, RunArtifactDeclaration> | undefined,
59
+ ): Record<string, RunArtifactManifestEntry> | undefined {
60
+ if (!artifacts) return undefined;
61
+ const manifest: Record<string, RunArtifactManifestEntry> = {};
62
+ for (const [name, artifact] of Object.entries(artifacts)) {
63
+ const declaration =
64
+ typeof artifact === "string" ? { path: artifact } : artifact;
65
+ if (!declaration?.path) continue;
66
+ try {
67
+ const content = readFileSync(declaration.path);
68
+ manifest[name] = {
69
+ exists: true,
70
+ ...(declaration.kind ? { kind: declaration.kind } : {}),
71
+ ...(declaration.media_type
72
+ ? { media_type: declaration.media_type }
73
+ : {}),
74
+ path: declaration.path,
75
+ ...(declaration.required !== undefined
76
+ ? { required: declaration.required }
77
+ : {}),
78
+ sha256: createHash("sha256").update(content).digest("hex"),
79
+ size: content.byteLength,
80
+ };
81
+ } catch {
82
+ manifest[name] = {
83
+ exists: false,
84
+ ...(declaration.kind ? { kind: declaration.kind } : {}),
85
+ ...(declaration.media_type
86
+ ? { media_type: declaration.media_type }
87
+ : {}),
88
+ path: declaration.path,
89
+ ...(declaration.required !== undefined
90
+ ? { required: declaration.required }
91
+ : {}),
92
+ };
93
+ }
94
+ }
95
+ return Object.keys(manifest).length > 0 ? manifest : undefined;
96
+ }