@llblab/pi-actors 0.33.0 → 0.34.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (155) hide show
  1. package/AGENTS.md +22 -11
  2. package/BACKLOG.md +1 -1
  3. package/CHANGELOG.md +22 -1
  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 +346 -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 +186 -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 +483 -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 +186 -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
@@ -0,0 +1,156 @@
1
+ /**
2
+ * Local tool definition behavior
3
+ * Zones: user recipe tools, generated schemas, async recipe launch
4
+ * Owns wrapping saved local capabilities as executable pi tools
5
+ */
6
+ import * as Rooms from "./rooms.js";
7
+ import * as AsyncRuns from "./async-runs.js";
8
+ import * as Execution from "./execution.js";
9
+ import * as Prompts from "./prompts.js";
10
+ import * as RecipesReferences from "./recipes-references.js";
11
+ import * as RecipesUsage from "./recipes-usage.js";
12
+ import * as Schema from "./schema.js";
13
+ import * as ToolsResponse from "./tools-response.js";
14
+ function getRunOwnerId(ctx) {
15
+ return ctx.sessionManager?.getSessionId?.();
16
+ }
17
+ function typedArgSchema(arg, type) {
18
+ const description = !type || type.kind === "string"
19
+ ? `Argument: ${arg}`
20
+ : type.kind === "path"
21
+ ? `Path argument: ${arg}`
22
+ : `${type.kind[0].toUpperCase()}${type.kind.slice(1)} argument: ${arg}`;
23
+ return Schema.typedArgSchema(description, type);
24
+ }
25
+ function sampleValueForArg(arg, type, defaults) {
26
+ if (Object.hasOwn(defaults, arg))
27
+ return defaults[arg];
28
+ if (!type || type.kind === "string")
29
+ return `<${arg}>`;
30
+ if (type.kind === "path")
31
+ return `./${arg}`;
32
+ if (type.kind === "int")
33
+ return 1;
34
+ if (type.kind === "number")
35
+ return 1.5;
36
+ if (type.kind === "bool")
37
+ return true;
38
+ if (type.kind === "array")
39
+ return [`<${arg}>`];
40
+ return type.values[0] ?? `<${arg}>`;
41
+ }
42
+ function shouldAddRuntimeToolUsageHint(error) {
43
+ const message = error instanceof Error ? error.message : String(error);
44
+ return (/^Argument \S+ must /.test(message) || /^Missing .* value: /.test(message));
45
+ }
46
+ function formatRuntimeToolUsageHint(cfg, required, includeRunId) {
47
+ const optional = cfg.args.filter((arg) => !required.includes(arg));
48
+ const example = {};
49
+ for (const arg of required)
50
+ example[arg] = sampleValueForArg(arg, cfg.argTypes?.[arg], cfg.defaults);
51
+ for (const arg of optional)
52
+ example[arg] = sampleValueForArg(arg, cfg.argTypes?.[arg], cfg.defaults);
53
+ if (includeRunId)
54
+ example.run_id = `${cfg.name}-1`;
55
+ const lines = [
56
+ `Expected call shape for ${cfg.name}:`,
57
+ `${cfg.name}(${JSON.stringify(example, null, 2)})`,
58
+ ];
59
+ if (required.length)
60
+ lines.push(`Required: ${required.join(", ")}`);
61
+ if (optional.length || includeRunId)
62
+ lines.push(`Optional: ${[...optional, ...(includeRunId ? ["run_id"] : [])].join(", ")}`);
63
+ return lines.join("\n");
64
+ }
65
+ function formatRuntimeToolArgumentError(cfg, error, required, includeRunId) {
66
+ const message = error instanceof Error ? error.message : String(error);
67
+ if (!shouldAddRuntimeToolUsageHint(error))
68
+ return error instanceof Error ? error : new Error(message);
69
+ return new Error(`Invalid arguments for tool "${cfg.name}": ${message}\n\n${formatRuntimeToolUsageHint(cfg, required, includeRunId)}`);
70
+ }
71
+ export function createRuntimeToolDefinition(cfg, exec) {
72
+ const paramSchema = {};
73
+ const required = [];
74
+ const isRecipe = RecipesReferences.isRecipeTool(cfg.template, cfg.recipe);
75
+ const isAsyncRecipe = cfg.recipe?.async === true ||
76
+ RecipesReferences.isAsyncRecipeReference(cfg.template);
77
+ const recipeTemplate = cfg.recipe?.template ?? RecipesReferences.getRecipeTemplate(cfg.template);
78
+ const requiredTemplate = recipeTemplate ?? cfg.template;
79
+ const requiredTemplateConfig = typeof requiredTemplate === "object" && !Array.isArray(requiredTemplate)
80
+ ? {
81
+ ...requiredTemplate,
82
+ args: cfg.args,
83
+ defaults: { ...(requiredTemplate.defaults ?? {}), ...cfg.defaults },
84
+ }
85
+ : {
86
+ args: cfg.args,
87
+ defaults: cfg.defaults,
88
+ template: requiredTemplate,
89
+ };
90
+ const requiredArgs = isRecipe && cfg.storedArgs !== undefined
91
+ ? new Set(cfg.args.filter((arg) => !Object.hasOwn(cfg.defaults, arg)))
92
+ : RecipesReferences.isRecipeReference(cfg.template) && !recipeTemplate
93
+ ? new Set(cfg.args.filter((arg) => !Object.hasOwn(cfg.defaults, arg)))
94
+ : Schema.getRequiredToolArgNames(requiredTemplateConfig);
95
+ for (const arg of cfg.args) {
96
+ paramSchema[arg] = typedArgSchema(arg, cfg.argTypes?.[arg]);
97
+ if (requiredArgs.has(arg))
98
+ required.push(arg);
99
+ }
100
+ if (isAsyncRecipe)
101
+ paramSchema.run_id = Schema.stringSchema("Optional run id override for this async template recipe invocation.");
102
+ return {
103
+ name: cfg.name,
104
+ label: cfg.name,
105
+ description: cfg.description,
106
+ parameters: Schema.objectSchema(paramSchema, required),
107
+ promptSnippet: isRecipe
108
+ ? Prompts.formatRecipeToolPromptSnippet(cfg.recipe?.name ?? String(cfg.template), isAsyncRecipe)
109
+ : Prompts.formatRegisteredToolPromptSnippet(cfg.template),
110
+ async execute(_toolCallId, params, signal, _onUpdate, ctx) {
111
+ try {
112
+ if (cfg.sourcePath)
113
+ RecipesUsage.recordRecipeLaunch(cfg.sourcePath, new Date(), "tool");
114
+ if (isAsyncRecipe) {
115
+ const input = params;
116
+ const { run_id, ...values } = input;
117
+ const base = cfg.recipe ? cfg.recipe : { file: String(cfg.template) };
118
+ const runId = typeof run_id === "string" && run_id.trim()
119
+ ? run_id.trim()
120
+ : `${cfg.name}-${Date.now()}`;
121
+ const meta = AsyncRuns.startRun({
122
+ ...base,
123
+ launch_source: "tool",
124
+ ownerId: getRunOwnerId(ctx),
125
+ run_id: runId,
126
+ tool: cfg.name,
127
+ values: Schema.normalizeRuntimeValues({ ...(cfg.recipe?.values ?? {}), ...cfg.defaults, ...values }, cfg.argTypes),
128
+ }, ctx.cwd);
129
+ Rooms.ensureDefaultRoom(meta.state_dir, String(meta.run));
130
+ Rooms.writeCommunicationSnapshot(meta.state_dir, String(meta.run));
131
+ return {
132
+ content: [
133
+ {
134
+ type: "text",
135
+ text: ToolsResponse.compactAsyncRunStatus(meta),
136
+ },
137
+ ],
138
+ details: meta,
139
+ };
140
+ }
141
+ if (isRecipe && recipeTemplate) {
142
+ const paramsWithDefaults = {
143
+ ...(cfg.recipe?.values ?? {}),
144
+ ...cfg.defaults,
145
+ ...params,
146
+ };
147
+ return await Execution.executeRegisteredTool({ ...cfg, template: recipeTemplate }, Schema.normalizeRuntimeValues(paramsWithDefaults, cfg.argTypes), exec, ctx.cwd, signal);
148
+ }
149
+ return await Execution.executeRegisteredTool(cfg, Schema.normalizeRuntimeValues(params, cfg.argTypes), exec, ctx.cwd, signal);
150
+ }
151
+ catch (error) {
152
+ throw formatRuntimeToolArgumentError(cfg, error, required, isAsyncRecipe);
153
+ }
154
+ },
155
+ };
156
+ }
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Public tool mailbox contract helpers
3
+ * Zones: accepted/emitted message type declarations, contract normalization, compact type extraction
4
+ * Owns mailbox metadata normalization for public message and inspect tool paths
5
+ */
6
+ export declare function normalizeMailboxEntry(value: unknown): Record<string, unknown> | undefined;
7
+ export declare function normalizeMailboxContracts(mailbox: Record<string, unknown>): Record<string, unknown[]>;
8
+ export declare function mailboxTypes(entries: unknown[]): string[];
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Public tool mailbox contract helpers
3
+ * Zones: accepted/emitted message type declarations, contract normalization, compact type extraction
4
+ * Owns mailbox metadata normalization for public message and inspect tool paths
5
+ */
6
+ function asRecord(value) {
7
+ return value && typeof value === "object" && !Array.isArray(value)
8
+ ? value
9
+ : {};
10
+ }
11
+ export function normalizeMailboxEntry(value) {
12
+ if (typeof value === "string")
13
+ return { type: value };
14
+ const record = asRecord(value);
15
+ if (typeof record.type !== "string" || !record.type.trim())
16
+ return undefined;
17
+ return {
18
+ type: record.type.trim(),
19
+ ...(record.body_schema !== undefined
20
+ ? { body_schema: record.body_schema }
21
+ : {}),
22
+ ...(record.ack !== undefined ? { ack: record.ack } : {}),
23
+ ...(typeof record.idempotency === "string"
24
+ ? { idempotency: record.idempotency }
25
+ : {}),
26
+ ...(typeof record.level === "string" ? { level: record.level } : {}),
27
+ ...(record.requires_response === true ? { requires_response: true } : {}),
28
+ ...(typeof record.summary === "string" ? { summary: record.summary } : {}),
29
+ };
30
+ }
31
+ export function normalizeMailboxContracts(mailbox) {
32
+ const accepts = Array.isArray(mailbox.accepts)
33
+ ? mailbox.accepts
34
+ .map(normalizeMailboxEntry)
35
+ .filter((entry) => Boolean(entry))
36
+ : [];
37
+ const emits = Array.isArray(mailbox.emits)
38
+ ? mailbox.emits
39
+ .map(normalizeMailboxEntry)
40
+ .filter((entry) => Boolean(entry))
41
+ : [];
42
+ return { accepts, emits };
43
+ }
44
+ export function mailboxTypes(entries) {
45
+ return entries.flatMap((entry) => typeof asRecord(entry).type === "string"
46
+ ? [String(asRecord(entry).type)]
47
+ : []);
48
+ }
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Public message tool behavior
3
+ * Zones: addressed actor messages, run controls, branch/room delivery, tool actor invocation
4
+ * Owns the public message execution path and compact delivery responses
5
+ */
6
+ export interface ActorMessageToolDeps<TContext = unknown> {
7
+ getTool?: (name: string) => any | undefined;
8
+ }
9
+ export declare function createActorMessageToolDefinition<TContext = unknown>(deps?: ActorMessageToolDeps<TContext>): any;
@@ -0,0 +1,346 @@
1
+ /**
2
+ * Public message tool behavior
3
+ * Zones: addressed actor messages, run controls, branch/room delivery, tool actor invocation
4
+ * Owns the public message execution path and compact delivery responses
5
+ */
6
+ import * as AsyncRuns from "./async-runs.js";
7
+ import * as Messages from "./messages.js";
8
+ import * as Rooms from "./rooms.js";
9
+ import * as Schema from "./schema.js";
10
+ import * as ToolsAccess from "./tools-access.js";
11
+ import * as ToolsMailbox from "./tools-mailbox.js";
12
+ import * as ToolsResponse from "./tools-response.js";
13
+ function asRecord(value) {
14
+ return value && typeof value === "object" && !Array.isArray(value)
15
+ ? value
16
+ : {};
17
+ }
18
+ function maybeJsonText(value, verbose, compact) {
19
+ return verbose ? ToolsResponse.jsonText(value) : compact;
20
+ }
21
+ function messageBodyToRunLine(message) {
22
+ if (message.type !== "run.message")
23
+ return JSON.stringify(message);
24
+ if (typeof message.body === "string")
25
+ return message.body;
26
+ if (message.body === undefined)
27
+ return message.type;
28
+ return JSON.stringify(message.body);
29
+ }
30
+ function messageBodyToToolParams(message) {
31
+ if (message.body &&
32
+ typeof message.body === "object" &&
33
+ !Array.isArray(message.body)) {
34
+ return message.body;
35
+ }
36
+ if (message.body === undefined)
37
+ return {};
38
+ return { input: message.body };
39
+ }
40
+ function formatToolActorFailure(tool, message, params, error) {
41
+ const original = error instanceof Error ? error.message : String(error);
42
+ const paramsPreview = ToolsResponse.compactPreview(params, 240) ?? "{}";
43
+ return Object.assign(new Error(`tool actor ${tool} failed for message type ${message.type}: ${original}; params=${paramsPreview}`), {
44
+ message_type: message.type,
45
+ original_error: original,
46
+ params_preview: paramsPreview,
47
+ tool,
48
+ });
49
+ }
50
+ function assertMessageSenderBelongsToRun(message, run, routeLabel) {
51
+ if (!message.from) {
52
+ throw new Error(`message to ${message.to} requires from=<actor address>.`);
53
+ }
54
+ const sender = Messages.parseActorAddress(message.from);
55
+ if ((sender.kind !== "run" && sender.kind !== "branch") ||
56
+ sender.value !== run) {
57
+ throw new Error(`message to ${routeLabel} requires from=run:${run} or branch:${run}/<branch>; got ${message.from}.`);
58
+ }
59
+ }
60
+ async function routeBranchEnvelope(stateDir, runId, recipient, message, _options) {
61
+ const branchMessage = { ...message, to: recipient };
62
+ Rooms.appendBranchInboxMessage(stateDir, runId, recipient, branchMessage);
63
+ try {
64
+ return await AsyncRuns.sendRunMessage(runId, JSON.stringify(branchMessage));
65
+ }
66
+ catch (error) {
67
+ const record = error && typeof error === "object"
68
+ ? error
69
+ : {};
70
+ if (record.queued === true) {
71
+ return {
72
+ control_path: record.control_path,
73
+ control_type: record.control_type,
74
+ delivery_error: record.delivery_error ??
75
+ (error instanceof Error ? error.message : String(error)),
76
+ inbox_id: record.inbox_id,
77
+ queued: true,
78
+ run: runId,
79
+ sent: false,
80
+ state_dir: stateDir,
81
+ };
82
+ }
83
+ throw error;
84
+ }
85
+ }
86
+ function getRoomMulticastRecipients(message, run) {
87
+ const raw = message.metadata?.recipients;
88
+ if (raw === undefined)
89
+ return [];
90
+ if (!Array.isArray(raw)) {
91
+ throw new Error("room multicast metadata.recipients must be an array.");
92
+ }
93
+ return raw.map((recipient) => {
94
+ if (typeof recipient !== "string") {
95
+ throw new Error("room multicast recipients must be actor addresses.");
96
+ }
97
+ const parsed = Messages.parseActorAddress(recipient);
98
+ if (parsed.kind !== "branch" || parsed.value !== run) {
99
+ throw new Error(`room multicast recipient must be branch:${run}/<branch>; got ${recipient}.`);
100
+ }
101
+ return Messages.formatActorAddress(parsed);
102
+ });
103
+ }
104
+ function actorMessageNextActions(message, result) {
105
+ const actions = [];
106
+ const address = Messages.parseActorAddress(message.to);
107
+ if (result.delivery_error || result.sent === false) {
108
+ if (address.kind === "run" && address.value) {
109
+ actions.push(`inspect target=run:${address.value} view=status`);
110
+ actions.push(`inspect target=run:${address.value} view=mailbox`);
111
+ }
112
+ else if (address.kind === "branch" && address.value) {
113
+ actions.push(`inspect target=branch:${address.value}/${address.branch ?? "main"} view=mailbox`);
114
+ actions.push(`inspect target=run:${address.value} view=status`);
115
+ }
116
+ }
117
+ if (result.queued === true) {
118
+ if (address.kind === "branch" && address.value) {
119
+ actions.push(`inspect target=branch:${address.value}/${address.branch ?? "main"} view=mailbox`);
120
+ }
121
+ else if (address.kind === "run" && address.value) {
122
+ actions.push(`inspect target=run:${address.value} view=mailbox`);
123
+ }
124
+ }
125
+ return [...new Set(actions)].slice(0, 3);
126
+ }
127
+ function compactActorMessageResult(message, result) {
128
+ const tokens = [
129
+ `to=${message.to}`,
130
+ `type=${message.type}`,
131
+ `message=${result.sent === true || result.stopped === true ? "sent" : "not_sent"}`,
132
+ ];
133
+ if (result.bytes !== undefined)
134
+ tokens.push(`bytes=${String(result.bytes)}`);
135
+ if (result.queued === true)
136
+ tokens.push("queued=true");
137
+ if (result.control)
138
+ tokens.push(`control=${String(result.control)}`);
139
+ if (result.outbox)
140
+ tokens.push(`messages=${String(result.outbox)}`);
141
+ if (result.message_count !== undefined)
142
+ tokens.push(`messages=${String(result.message_count)}`);
143
+ if (result.roster_count !== undefined)
144
+ tokens.push(`roster=${String(result.roster_count)}`);
145
+ if (result.room)
146
+ tokens.push(`room=${String(result.room)}`);
147
+ if (result.tool)
148
+ tokens.push(`tool=${String(result.tool)}`);
149
+ if (result.stopped === true)
150
+ tokens.push("stopped=true");
151
+ if (result.signal)
152
+ tokens.push(`signal=${String(result.signal)}`);
153
+ if (result.invoked === true)
154
+ tokens.push("invoked=true");
155
+ if (result.delivery_error) {
156
+ tokens.push(`delivery_error=${ToolsResponse.compactPreview(result.delivery_error, 96)}`);
157
+ }
158
+ const nextActions = Array.isArray(result.next_actions)
159
+ ? result.next_actions
160
+ : actorMessageNextActions(message, result);
161
+ if (nextActions.length > 0)
162
+ tokens.push(`next=${nextActions.map((action) => action.replaceAll(/\s+/g, "_")).join("|")}`);
163
+ return `\n${tokens.join(" ")}`;
164
+ }
165
+ export function createActorMessageToolDefinition(deps = {}) {
166
+ return {
167
+ name: "message",
168
+ label: "Message",
169
+ description: "Send one typed addressed message to steer an existing actor instead of restarting it. Core routes are run:<id> and tool:<name>; advanced routes include branch:<run>/<branch>, room:<run> group timelines, coordinator, and session:<id>.",
170
+ parameters: Schema.objectSchema({
171
+ body: Schema.unionSchema([
172
+ Schema.stringSchema("Message body. For run:<id>, this is the run-local command line."),
173
+ Schema.looseObjectSchema("Structured JSON message body."),
174
+ Schema.arraySchema("Structured JSON message body array."),
175
+ ]),
176
+ correlation_id: Schema.stringSchema("Optional correlation id for workflow/task linkage."),
177
+ from: Schema.stringSchema("Optional sender address, such as coordinator or run:<id>."),
178
+ metadata: Schema.looseObjectSchema("Optional structured metadata for routing or domain hints."),
179
+ reply_to: Schema.stringSchema("Optional message id this message replies to."),
180
+ summary: Schema.stringSchema("Optional short human-facing summary."),
181
+ to: Schema.stringSchema("Destination actor address, e.g. run:<id> or tool:<name>; advanced: branch:<run>/<branch>, room:<run>, coordinator, session:<id>."),
182
+ type: Schema.stringSchema("Semantic message type, e.g. control.approve or checkpoint.needs_scope."),
183
+ verbose: Schema.booleanSchema("Return full JSON instead of compact text."),
184
+ }, ["to", "type"]),
185
+ async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
186
+ const input = asRecord(params);
187
+ const message = Messages.normalizeActorMessage(input);
188
+ const address = Messages.parseActorAddress(message.to);
189
+ let result;
190
+ if (address.kind === "run" && address.value) {
191
+ const status = ToolsAccess.assertRunAccessibleToContext(address.value, ctx);
192
+ const normalizedMailbox = ToolsMailbox.normalizeMailboxContracts(asRecord(status.mailbox));
193
+ const acceptedTypes = new Set(ToolsMailbox.mailboxTypes(normalizedMailbox.accepts));
194
+ const advisoryWarnings = acceptedTypes.size > 0 && !acceptedTypes.has(message.type)
195
+ ? [
196
+ `Message type ${message.type} is not declared in mailbox.accepts for run:${address.value}.`,
197
+ ]
198
+ : [];
199
+ if (message.type === "control.kill") {
200
+ result = AsyncRuns.killRun(address.value);
201
+ }
202
+ else if (message.type === "control.archive") {
203
+ result = AsyncRuns.archiveRun(address.value);
204
+ }
205
+ else if (message.type === "control.prune") {
206
+ const body = asRecord(message.body);
207
+ result = AsyncRuns.pruneRun(address.value, {
208
+ preserveArtifacts: body.preserve_artifacts === true ||
209
+ body.preserveArtifacts === true,
210
+ });
211
+ }
212
+ else {
213
+ result = await AsyncRuns.sendRunMessage(address.value, messageBodyToRunLine(message));
214
+ }
215
+ if (advisoryWarnings.length > 0)
216
+ result = { ...result, warnings: advisoryWarnings };
217
+ }
218
+ else if (address.kind === "branch" && address.value) {
219
+ const runId = address.value;
220
+ if (message.from)
221
+ assertMessageSenderBelongsToRun(message, runId, `branch:${runId}/<branch>`);
222
+ const status = ToolsAccess.assertRunAccessibleToContext(runId, ctx);
223
+ const stateDir = String(status.state_dir ?? "");
224
+ if (stateDir && address.branch) {
225
+ const ensureBranchMember = (actorAddress) => {
226
+ Rooms.ensureRoomMember(stateDir, runId, "main", actorAddress, {
227
+ parent: `run:${runId}`,
228
+ role: "branch",
229
+ status: "present",
230
+ }, "Branch joined default room");
231
+ Rooms.writeBranchCommunicationSnapshot(stateDir, runId, actorAddress);
232
+ };
233
+ ensureBranchMember(message.to);
234
+ if (message.from) {
235
+ const sender = Messages.parseActorAddress(message.from);
236
+ if (sender.kind === "branch" && sender.value === runId) {
237
+ ensureBranchMember(message.from);
238
+ }
239
+ }
240
+ Rooms.writeCommunicationSnapshot(stateDir, runId);
241
+ }
242
+ result = await routeBranchEnvelope(stateDir, runId, message.to, message, {
243
+ source: "direct",
244
+ });
245
+ }
246
+ else if (address.kind === "room" && address.value && address.room) {
247
+ const runId = address.value;
248
+ assertMessageSenderBelongsToRun(message, runId, `room:${runId}`);
249
+ const status = ToolsAccess.assertRunAccessibleToContext(runId, ctx);
250
+ const stateDir = String(status.state_dir ?? "");
251
+ if (!stateDir)
252
+ throw new Error(`${message.to} has no run state directory.`);
253
+ const recipients = getRoomMulticastRecipients(message, runId);
254
+ const roomResult = Rooms.appendRoomMessage(stateDir, address.room, message);
255
+ await Promise.all(recipients.map((recipient) => routeBranchEnvelope(stateDir, runId, recipient, message, {
256
+ source: "room-multicast",
257
+ })));
258
+ result = {
259
+ ...roomResult,
260
+ ...(recipients.length > 0
261
+ ? { multicast: recipients, multicast_count: recipients.length }
262
+ : {}),
263
+ };
264
+ }
265
+ else if (address.kind === "tool" && address.value) {
266
+ const tool = deps.getTool?.(address.value);
267
+ if (!tool || typeof tool.execute !== "function") {
268
+ throw new Error(`tool actor not found or not executable: ${address.value}`);
269
+ }
270
+ const toolParams = messageBodyToToolParams(message);
271
+ let toolResult;
272
+ try {
273
+ toolResult = await tool.execute(`message:${message.type}`, toolParams, _signal, _onUpdate, ctx);
274
+ }
275
+ catch (error) {
276
+ throw formatToolActorFailure(address.value, message, toolParams, error);
277
+ }
278
+ result = {
279
+ invoked: true,
280
+ sent: true,
281
+ tool: address.value,
282
+ tool_result: toolResult,
283
+ };
284
+ }
285
+ else if (address.kind === "coordinator" || address.kind === "session") {
286
+ if (!message.from) {
287
+ throw new Error(`message to ${address.kind} requires from=run:<id>.`);
288
+ }
289
+ const sender = Messages.parseActorAddress(message.from);
290
+ if (sender.kind !== "run" || !sender.value) {
291
+ throw new Error(`message to ${address.kind} currently requires from=run:<id>.`);
292
+ }
293
+ const senderStatus = ToolsAccess.assertRunAccessibleToContext(sender.value, ctx);
294
+ if (address.kind === "session") {
295
+ if (!senderStatus.ownerId) {
296
+ throw ToolsAccess.sessionMismatchError({
297
+ currentSession: undefined,
298
+ expectedSession: address.value,
299
+ run: sender.value,
300
+ target: `session:${address.value}`,
301
+ });
302
+ }
303
+ if (senderStatus.ownerId !== address.value) {
304
+ throw ToolsAccess.sessionMismatchError({
305
+ currentSession: String(senderStatus.ownerId),
306
+ expectedSession: address.value,
307
+ run: sender.value,
308
+ target: `session:${address.value}`,
309
+ });
310
+ }
311
+ }
312
+ result = AsyncRuns.appendRunOutboxEvent(sender.value, {
313
+ body: message.body,
314
+ correlation_id: message.correlation_id,
315
+ delivery: message.metadata?.requires_response === true ||
316
+ address.kind === "session"
317
+ ? "followup"
318
+ : undefined,
319
+ event: message.type,
320
+ from: message.from,
321
+ metadata: message.metadata,
322
+ reply_to: message.reply_to,
323
+ summary: message.summary,
324
+ to: message.to,
325
+ type: message.type,
326
+ });
327
+ }
328
+ else {
329
+ throw new Error(`message currently supports run:<id>, branch:<run>/<branch>, room:<run>, tool:<name>, coordinator, and session:<id> destinations; unsupported destination: ${message.to}`);
330
+ }
331
+ const nextActions = actorMessageNextActions(message, result);
332
+ const resultWithNext = nextActions.length
333
+ ? { ...result, next_actions: nextActions }
334
+ : result;
335
+ return {
336
+ content: [
337
+ {
338
+ type: "text",
339
+ text: maybeJsonText({ message, result: resultWithNext }, input.verbose === true, compactActorMessageResult(message, resultWithNext)),
340
+ },
341
+ ],
342
+ details: { message, result: resultWithNext },
343
+ };
344
+ },
345
+ };
346
+ }
@@ -0,0 +1,18 @@
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
5
+ */
6
+ import * as Registry from "./registry.ts";
7
+ import * as Schema from "./schema.ts";
8
+ export type RegisterToolInput = Registry.RegisterToolInput;
9
+ export type RegisterToolRuntimeDeps<TContext> = Registry.RegisterToolRuntimeDeps<TContext>;
10
+ export declare function createRegisterToolDefinition<TContext>(deps: RegisterToolRuntimeDeps<TContext>): {
11
+ name: string;
12
+ label: string;
13
+ description: string;
14
+ promptSnippet: string;
15
+ promptGuidelines: string[];
16
+ parameters: Schema.JsonSchema;
17
+ execute: (_toolCallId: string, params: unknown, _signal: AbortSignal | undefined, _onUpdate: unknown, ctx: TContext) => Promise<Registry.RegisterToolResult>;
18
+ };
@@ -0,0 +1,40 @@
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
5
+ */
6
+ import * as Prompts from "./prompts.js";
7
+ import * as Registry from "./registry.js";
8
+ import * as Schema from "./schema.js";
9
+ const stringSchema = Schema.stringSchema;
10
+ const booleanSchema = Schema.booleanSchema;
11
+ const nullSchema = Schema.nullSchema;
12
+ const arraySchema = Schema.arraySchema;
13
+ const unionSchema = Schema.unionSchema;
14
+ const objectSchema = Schema.objectSchema;
15
+ const looseObjectSchema = Schema.looseObjectSchema;
16
+ export function createRegisterToolDefinition(deps) {
17
+ return {
18
+ name: "register_tool",
19
+ label: "Register Tool",
20
+ description: Prompts.REGISTER_TOOL_DESCRIPTION,
21
+ promptSnippet: Prompts.REGISTER_TOOL_PROMPT_SNIPPET,
22
+ promptGuidelines: Prompts.REGISTER_TOOL_GUIDELINES,
23
+ parameters: objectSchema({
24
+ args: stringSchema(Prompts.REGISTER_TOOL_PARAM_DESCRIPTIONS.args),
25
+ async: booleanSchema(Prompts.REGISTER_TOOL_PARAM_DESCRIPTIONS.async),
26
+ description: stringSchema(Prompts.REGISTER_TOOL_PARAM_DESCRIPTIONS.description),
27
+ name: stringSchema(Prompts.REGISTER_TOOL_PARAM_DESCRIPTIONS.name),
28
+ state_dir: stringSchema(Prompts.REGISTER_TOOL_PARAM_DESCRIPTIONS.state_dir),
29
+ template: unionSchema([
30
+ stringSchema(Prompts.REGISTER_TOOL_PARAM_DESCRIPTIONS.template),
31
+ looseObjectSchema(Prompts.REGISTER_TOOL_PARAM_DESCRIPTIONS.template),
32
+ arraySchema(Prompts.REGISTER_TOOL_PARAM_DESCRIPTIONS.templateArray),
33
+ nullSchema(Prompts.REGISTER_TOOL_PARAM_DESCRIPTIONS.templateNull),
34
+ ]),
35
+ update: booleanSchema(Prompts.REGISTER_TOOL_PARAM_DESCRIPTIONS.update),
36
+ values: looseObjectSchema(Prompts.REGISTER_TOOL_PARAM_DESCRIPTIONS.values),
37
+ }, []),
38
+ execute: async (_toolCallId, params, _signal, _onUpdate, ctx) => Registry.executeRegisterTool(params, ctx, deps),
39
+ };
40
+ }
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Public tool response formatting
3
+ * Zones: compact text summaries, verbose JSON switching, next-action rendering
4
+ * Owns model-facing response helpers shared by public tool execution paths
5
+ */
6
+ export declare function asRecord(value: unknown): Record<string, unknown>;
7
+ export declare function jsonText(value: unknown): string;
8
+ export declare function compactPreview(value: unknown, maxLength?: number): string | undefined;
9
+ export declare function compactNextActions(actions: string[]): string;
10
+ export declare function actorRunNextActions(run: unknown): string[];
11
+ export declare function compactAsyncRunStatus(value: unknown): string;
12
+ export declare function compactInboxMessages(messages: Array<Record<string, unknown>>, emptyLabel: string): string;
13
+ export declare function compactBranchInbox(messages: Array<Record<string, unknown>>): string;
14
+ export declare function compactRunMailbox(run: string, mailbox: Record<string, unknown>, messages: Array<Record<string, unknown>>): string;
15
+ export declare function artifactNextActions(run: unknown, artifacts: Record<string, unknown>): string[];
16
+ export declare function maybeJsonText(value: unknown, verbose: boolean | undefined, compact: string): string;
17
+ export declare function compactRecipeImports(summary: Record<string, unknown>): string;
18
+ export declare function compactRecipeDoctor(summary: Record<string, unknown>): string;
19
+ export declare function recipeRegistryNextActions(summary: Record<string, unknown>, view: string): string[];
20
+ export declare function compactRecipeRegistry(summary: Record<string, unknown>): string;
21
+ export declare const DEFAULT_INSPECT_LINES = 40;