@llblab/pi-actors 0.19.11 → 0.20.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 (54) hide show
  1. package/AGENTS.md +1 -1
  2. package/CHANGELOG.md +8 -0
  3. package/dist/lib/actor-inspector-tui.d.ts +55 -0
  4. package/dist/lib/actor-inspector-tui.js +559 -0
  5. package/dist/lib/actor-messages.d.ts +25 -0
  6. package/dist/lib/actor-messages.js +122 -0
  7. package/dist/lib/actor-recipe-context.d.ts +14 -0
  8. package/dist/lib/actor-recipe-context.js +79 -0
  9. package/dist/lib/actor-rooms.d.ts +81 -0
  10. package/dist/lib/actor-rooms.js +468 -0
  11. package/dist/lib/async-runs.d.ts +101 -0
  12. package/dist/lib/async-runs.js +612 -0
  13. package/dist/lib/command-templates.d.ts +70 -0
  14. package/dist/lib/command-templates.js +592 -0
  15. package/dist/lib/config.d.ts +34 -0
  16. package/dist/lib/config.js +226 -0
  17. package/dist/lib/execution.d.ts +63 -0
  18. package/dist/lib/execution.js +450 -0
  19. package/dist/lib/file-state.d.ts +6 -0
  20. package/dist/lib/file-state.js +25 -0
  21. package/dist/lib/identity.d.ts +9 -0
  22. package/dist/lib/identity.js +27 -0
  23. package/dist/lib/observability.d.ts +86 -0
  24. package/dist/lib/observability.js +534 -0
  25. package/dist/lib/output.d.ts +25 -0
  26. package/dist/lib/output.js +89 -0
  27. package/dist/lib/paths.d.ts +11 -0
  28. package/dist/lib/paths.js +28 -0
  29. package/dist/lib/prompts.d.ts +23 -0
  30. package/dist/lib/prompts.js +50 -0
  31. package/dist/lib/recipe-discovery.d.ts +50 -0
  32. package/dist/lib/recipe-discovery.js +317 -0
  33. package/dist/lib/recipe-migration.d.ts +21 -0
  34. package/dist/lib/recipe-migration.js +90 -0
  35. package/dist/lib/recipe-references.d.ts +67 -0
  36. package/dist/lib/recipe-references.js +542 -0
  37. package/dist/lib/recipe-usage.d.ts +6 -0
  38. package/dist/lib/recipe-usage.js +57 -0
  39. package/dist/lib/registry.d.ts +47 -0
  40. package/dist/lib/registry.js +222 -0
  41. package/dist/lib/runtime.d.ts +36 -0
  42. package/dist/lib/runtime.js +126 -0
  43. package/dist/lib/schema.d.ts +48 -0
  44. package/dist/lib/schema.js +355 -0
  45. package/dist/lib/temp.d.ts +10 -0
  46. package/dist/lib/temp.js +90 -0
  47. package/dist/lib/tools.d.ts +39 -0
  48. package/dist/lib/tools.js +982 -0
  49. package/lib/async-runs.ts +20 -4
  50. package/package.json +5 -2
  51. package/scripts/async-runner.mjs +8 -12
  52. package/scripts/validate-recipe.mjs +9 -13
  53. package/skills/actors/SKILL.md +1 -1
  54. package/skills/swarm/SKILL.md +1 -1
@@ -0,0 +1,450 @@
1
+ /**
2
+ * Registered tool execution runtime
3
+ * Zones: tool execution, command templates, output formatting
4
+ * Owns command-template invocation execution and pi tool-result payload formatting
5
+ */
6
+ import * as CommandTemplates from "./command-templates.js";
7
+ import { formatFailureOutput, formatOutput, formatToolText } from "./output.js";
8
+ import * as Schema from "./schema.js";
9
+ const DEFAULT_MAX_PARALLEL_BRANCHES = 64;
10
+ function textContent(text) {
11
+ return { type: "text", text };
12
+ }
13
+ function createTemplateConfig(cfg) {
14
+ if (!cfg.template)
15
+ throw new Error(`Tool "${cfg.name}" has no command template.`);
16
+ if (typeof cfg.template === "object" && !Array.isArray(cfg.template)) {
17
+ return {
18
+ ...cfg.template,
19
+ args: cfg.template.args ?? cfg.args,
20
+ defaults: mergeDefaults(cfg.defaults, cfg.template.defaults),
21
+ };
22
+ }
23
+ return { args: cfg.args, defaults: cfg.defaults, template: cfg.template };
24
+ }
25
+ function quoteCommandDetailPart(value) {
26
+ if (value === "")
27
+ return "''";
28
+ if (/^[A-Za-z0-9_/:=.,@%+\-]+$/.test(value))
29
+ return value;
30
+ return `'${value.replaceAll("'", "'\\''")}'`;
31
+ }
32
+ function formatInvocationDetail(invocation) {
33
+ return [invocation.command, ...invocation.args]
34
+ .map(quoteCommandDetailPart)
35
+ .join(" ");
36
+ }
37
+ function formatCommandDetail(commands) {
38
+ return commands.length === 1 ? commands[0] : commands.join(" && ");
39
+ }
40
+ function mergeDefaults(inherited, own) {
41
+ if (!inherited && !own)
42
+ return undefined;
43
+ return { ...(inherited ?? {}), ...(own ?? {}) };
44
+ }
45
+ function getNodeLabel(config, index) {
46
+ const normalized = CommandTemplates.normalizeCommandTemplateConfig(config);
47
+ if (normalized.label)
48
+ return normalized.label;
49
+ return index === undefined ? "command" : `branch ${index + 1}`;
50
+ }
51
+ function getBranchStatus(result) {
52
+ if (result.code === 0)
53
+ return "done";
54
+ return result.killed ? "timeout" : "failed";
55
+ }
56
+ function createBranchReport(label, command, result) {
57
+ return {
58
+ code: result.code,
59
+ command,
60
+ killed: result.killed,
61
+ label,
62
+ status: getBranchStatus(result),
63
+ ...(result.stderr ? { stderr: result.stderr.slice(0, 1000) } : {}),
64
+ stdoutBytes: Buffer.byteLength(result.stdout),
65
+ };
66
+ }
67
+ function createSoftQuorum(branches) {
68
+ if (branches.length === 0)
69
+ return undefined;
70
+ const done = branches.filter((branch) => branch.status === "done").length;
71
+ const failed = branches.length - done;
72
+ return {
73
+ coverage: done / branches.length,
74
+ degraded: failed > 0,
75
+ done,
76
+ expected: branches.length,
77
+ failed,
78
+ usable: done > 0,
79
+ };
80
+ }
81
+ function getMaxParallelBranches() {
82
+ const raw = Number(process.env.PI_ACTORS_MAX_PARALLEL_BRANCHES ?? "");
83
+ return Number.isInteger(raw) && raw > 0 ? raw : DEFAULT_MAX_PARALLEL_BRANCHES;
84
+ }
85
+ function assertParallelBranchLimit(count) {
86
+ const max = getMaxParallelBranches();
87
+ if (count <= max)
88
+ return;
89
+ throw new Error(`Command template parallel fanout ${count} exceeds limit ${max}; set PI_ACTORS_MAX_PARALLEL_BRANCHES to override intentionally.`);
90
+ }
91
+ function normalizeFailureScope(value) {
92
+ if (value === undefined)
93
+ return "continue";
94
+ if (value === "continue" || value === "branch" || value === "root")
95
+ return value;
96
+ throw new Error("Command template failure must be one of: continue, branch, root.");
97
+ }
98
+ function getFailureScope(config) {
99
+ const normalized = CommandTemplates.normalizeCommandTemplateConfig(config);
100
+ return normalizeFailureScope(normalized.failure);
101
+ }
102
+ function maxFailureScope(...scopes) {
103
+ const rank = { branch: 1, continue: 0, root: 2 };
104
+ return scopes.reduce((current, scope) => rank[scope ?? "continue"] > rank[current] ? scope : current, "continue");
105
+ }
106
+ function normalizeRetry(value, values) {
107
+ const resolved = resolveNumericControlField(value, values, "retry");
108
+ if (resolved === undefined)
109
+ return 1;
110
+ if (!Number.isInteger(resolved) || resolved < 1)
111
+ throw new Error("Command template retry must be a positive integer.");
112
+ return resolved;
113
+ }
114
+ function getRecoverConfig(config) {
115
+ const recovered = Array.isArray(config) ? { template: config } : config;
116
+ const normalized = CommandTemplates.normalizeCommandTemplateConfig(recovered);
117
+ if (normalized.failure !== undefined)
118
+ return recovered;
119
+ return { ...normalized, failure: "root" };
120
+ }
121
+ function addResultFailure(failures, execution) {
122
+ if (execution.result.code === 0)
123
+ return;
124
+ const failure = {
125
+ code: execution.result.code,
126
+ command: execution.commands.at(-1) ?? "<template>",
127
+ killed: execution.result.killed,
128
+ };
129
+ const last = failures.at(-1);
130
+ if (last?.code === failure.code &&
131
+ last.command === failure.command &&
132
+ last.killed === failure.killed)
133
+ return;
134
+ failures.push(failure);
135
+ }
136
+ function mergeExecution(target, source) {
137
+ target.branches.push(...source.branches);
138
+ target.commands.push(...source.commands);
139
+ target.failures.push(...source.failures);
140
+ }
141
+ function sleep(ms, signal) {
142
+ return new Promise((resolve) => {
143
+ let timeoutId;
144
+ const settle = () => {
145
+ if (timeoutId)
146
+ clearTimeout(timeoutId);
147
+ if (signal)
148
+ signal.removeEventListener("abort", settle);
149
+ resolve();
150
+ };
151
+ if (signal?.aborted)
152
+ return settle();
153
+ timeoutId = setTimeout(settle, ms);
154
+ if (signal)
155
+ signal.addEventListener("abort", settle, { once: true });
156
+ });
157
+ }
158
+ function resolveNumericControlField(value, values, label) {
159
+ if (value === undefined)
160
+ return undefined;
161
+ const resolved = typeof value === "string"
162
+ ? CommandTemplates.substituteCommandTemplateToken(value, values, label)
163
+ : value;
164
+ if (resolved === "")
165
+ return undefined;
166
+ const numeric = Number(resolved);
167
+ if (!Number.isFinite(numeric) || numeric < 0)
168
+ throw new Error(`Command template ${label} must be a non-negative number.`);
169
+ return numeric;
170
+ }
171
+ async function applyDelay(delay, values, signal) {
172
+ const resolved = resolveNumericControlField(delay, values, "delay");
173
+ if (resolved === undefined || resolved <= 0)
174
+ return;
175
+ await sleep(resolved, signal);
176
+ }
177
+ function joinParallelStdout(branches, results) {
178
+ return results
179
+ .map((result, index) => {
180
+ const branch = branches[index];
181
+ const header = `--- branch: ${branch.label} status: ${branch.status} ---`;
182
+ if (branch.status === "done")
183
+ return `${header}\n${result.stdout}`;
184
+ const stderr = branch.stderr ? `\nstderr: ${branch.stderr}` : "";
185
+ return `${header}\nexit: ${branch.code}${stderr}`;
186
+ })
187
+ .join("\n");
188
+ }
189
+ async function executeRetriableTemplateConfig(normalized, inherited, params, exec, cwd, signal, stdin, isRoot, actorRecipeContext) {
190
+ const maxAttempts = normalizeRetry(normalized.retry, {
191
+ ...(inherited.defaults ?? {}),
192
+ ...params,
193
+ });
194
+ const attemptConfig = {
195
+ ...normalized,
196
+ delay: undefined,
197
+ recover: undefined,
198
+ retry: undefined,
199
+ };
200
+ const aggregate = {
201
+ branches: [],
202
+ commands: [],
203
+ failures: [],
204
+ result: { code: 1, killed: false, stderr: "", stdout: "" },
205
+ };
206
+ for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
207
+ const executed = await executeTemplateConfig(attemptConfig, inherited, params, exec, cwd, signal, stdin, isRoot, actorRecipeContext);
208
+ mergeExecution(aggregate, executed);
209
+ aggregate.result = executed.result;
210
+ aggregate.criticalFailure = executed.criticalFailure;
211
+ aggregate.failureScope = executed.failureScope;
212
+ if (executed.result.code === 0)
213
+ return aggregate;
214
+ addResultFailure(aggregate.failures, executed);
215
+ if (attempt === maxAttempts)
216
+ return aggregate;
217
+ if (normalized.recover === undefined)
218
+ continue;
219
+ const recovered = await executeTemplateConfig(getRecoverConfig(normalized.recover), inherited, params, exec, cwd, signal, executed.result.stdout, false, actorRecipeContext);
220
+ mergeExecution(aggregate, recovered);
221
+ if (recovered.result.code === 0)
222
+ continue;
223
+ addResultFailure(aggregate.failures, recovered);
224
+ aggregate.result = recovered.result;
225
+ aggregate.criticalFailure = recovered.criticalFailure;
226
+ aggregate.failureScope = maxFailureScope(recovered.failureScope, getFailureScope(normalized));
227
+ return aggregate;
228
+ }
229
+ return aggregate;
230
+ }
231
+ async function executeTemplateConfig(config, inherited, params, exec, cwd, signal, stdin, isRoot, inheritedActorRecipeContext) {
232
+ const normalized = CommandTemplates.normalizeCommandTemplateConfig(config);
233
+ const normalizedDefaults = CommandTemplates.resolveInheritedDefaultReferences(normalized.defaults, inherited.defaults, params);
234
+ const context = {
235
+ ...(inherited.args !== undefined ? { args: inherited.args } : {}),
236
+ ...(inherited.defaults !== undefined
237
+ ? { defaults: inherited.defaults }
238
+ : {}),
239
+ ...(normalized.args !== undefined ? { args: normalized.args } : {}),
240
+ ...(mergeDefaults(inherited.defaults, normalizedDefaults)
241
+ ? { defaults: mergeDefaults(inherited.defaults, normalizedDefaults) }
242
+ : {}),
243
+ };
244
+ const actorRecipeContext = normalized.actorRecipeContext ?? inheritedActorRecipeContext;
245
+ const controlValues = { ...(context.defaults ?? {}), ...params };
246
+ await applyDelay(normalized.delay, controlValues, signal);
247
+ if (!CommandTemplates.shouldRunCommandTemplateNode(normalized.when, controlValues)) {
248
+ return {
249
+ branches: [],
250
+ commands: [],
251
+ failures: [],
252
+ result: { code: 0, killed: false, stderr: "", stdout: stdin ?? "" },
253
+ };
254
+ }
255
+ getFailureScope(normalized);
256
+ if (normalized.repeat !== undefined) {
257
+ const repeat = CommandTemplates.resolveCommandTemplateRepeat(normalized.repeat, { ...(context.defaults ?? {}), ...params });
258
+ if (repeat === undefined)
259
+ throw new Error("Command template repeat could not be resolved.");
260
+ if (normalized.parallel === true)
261
+ assertParallelBranchLimit(repeat);
262
+ const repeatedSteps = Array.from({ length: repeat }, (_unused, index0) => {
263
+ const { repeat: _repeat, ...rest } = normalized;
264
+ return {
265
+ ...rest,
266
+ defaults: {
267
+ ...(context.defaults ?? {}),
268
+ ...(rest.defaults ?? {}),
269
+ ...CommandTemplates.getCommandTemplateRepeatDefaults(index0, repeat),
270
+ },
271
+ };
272
+ });
273
+ return executeTemplateConfig({ parallel: normalized.parallel === true, template: repeatedSteps }, context, params, exec, cwd, signal, stdin, isRoot, actorRecipeContext);
274
+ }
275
+ if (normalized.retry !== undefined &&
276
+ (Array.isArray(normalized.template) || normalized.recover !== undefined)) {
277
+ return executeRetriableTemplateConfig(normalized, context, params, exec, cwd, signal, stdin, isRoot, actorRecipeContext);
278
+ }
279
+ if (normalized.template &&
280
+ typeof normalized.template === "object" &&
281
+ !Array.isArray(normalized.template)) {
282
+ return executeTemplateConfig(normalized.template, context, params, exec, cwd, signal, stdin, false, actorRecipeContext);
283
+ }
284
+ if (!Array.isArray(normalized.template)) {
285
+ const leaf = { ...normalized, ...context };
286
+ const invocation = CommandTemplates.buildCommandTemplateInvocation(leaf, params, cwd, { emptyMessage: "Tool template produced an empty command." });
287
+ const result = await exec(invocation.command, invocation.args, {
288
+ ...(actorRecipeContext ? { actorRecipeContext } : {}),
289
+ cwd,
290
+ signal,
291
+ stdin,
292
+ ...(resolveNumericControlField(normalized.timeout, controlValues, "timeout") !== undefined
293
+ ? {
294
+ timeout: resolveNumericControlField(normalized.timeout, controlValues, "timeout"),
295
+ }
296
+ : {}),
297
+ ...(normalized.retry !== undefined
298
+ ? { retry: normalizeRetry(normalized.retry, controlValues) }
299
+ : {}),
300
+ });
301
+ return {
302
+ branches: [],
303
+ commands: [formatInvocationDetail(invocation)],
304
+ failures: [],
305
+ result,
306
+ };
307
+ }
308
+ const steps = normalized.template;
309
+ if (steps.length === 0)
310
+ throw new Error(formatToolText("Tool template produced no command steps."));
311
+ if (normalized.parallel === true) {
312
+ assertParallelBranchLimit(steps.length);
313
+ const branchResults = await Promise.all(steps.map((step) => executeTemplateConfig(step, context, params, exec, cwd, signal, stdin, false, actorRecipeContext)));
314
+ const commands = branchResults.flatMap((item) => item.commands);
315
+ const failures = branchResults.flatMap((item) => item.failures);
316
+ const branches = branchResults.map((item, index) => createBranchReport(getNodeLabel(steps[index], index), item.commands.at(-1) ?? "<template>", item.result));
317
+ const nodeFailure = getFailureScope(normalized);
318
+ const rootFailure = branchResults.find((item, index) => {
319
+ if (item.result.code === 0)
320
+ return false;
321
+ const branchFailure = maxFailureScope(item.failureScope, item.criticalFailure ? "root" : undefined, getFailureScope(steps[index]), nodeFailure);
322
+ return branchFailure === "root";
323
+ });
324
+ if (rootFailure) {
325
+ return {
326
+ branches: [
327
+ ...branchResults.flatMap((item) => item.branches),
328
+ ...branches,
329
+ ],
330
+ commands,
331
+ criticalFailure: true,
332
+ failureScope: "root",
333
+ failures,
334
+ result: rootFailure.result,
335
+ };
336
+ }
337
+ const firstFailedBranch = branchResults.find((item) => item.result.code !== 0);
338
+ const successful = branchResults.map((item) => {
339
+ if (item.result.code === 0)
340
+ return item.result;
341
+ addResultFailure(failures, item);
342
+ return { ...item.result, code: 0, stdout: "" };
343
+ });
344
+ const result = {
345
+ code: 0,
346
+ killed: successful.some((item) => item.killed),
347
+ stderr: successful
348
+ .map((item) => item.stderr)
349
+ .filter(Boolean)
350
+ .join("\n"),
351
+ stdout: joinParallelStdout(branches, successful),
352
+ };
353
+ if (firstFailedBranch && nodeFailure === "branch") {
354
+ return {
355
+ commands,
356
+ branches: [
357
+ ...branchResults.flatMap((item) => item.branches),
358
+ ...branches,
359
+ ],
360
+ failureScope: "branch",
361
+ failures,
362
+ result: { ...result, code: firstFailedBranch.result.code || 1 },
363
+ };
364
+ }
365
+ return {
366
+ commands,
367
+ branches: [
368
+ ...branchResults.flatMap((item) => item.branches),
369
+ ...branches,
370
+ ],
371
+ failures,
372
+ result,
373
+ };
374
+ }
375
+ const branches = [];
376
+ const commands = [];
377
+ const failures = [];
378
+ let nextStdin = stdin;
379
+ let result;
380
+ for (const step of steps) {
381
+ const executed = await executeTemplateConfig(step, context, params, exec, cwd, signal, nextStdin, false, actorRecipeContext);
382
+ branches.push(...executed.branches);
383
+ commands.push(...executed.commands);
384
+ failures.push(...executed.failures);
385
+ result = executed.result;
386
+ if (result.code !== 0) {
387
+ const failureScope = maxFailureScope(executed.failureScope, executed.criticalFailure ? "root" : undefined, getFailureScope(step), getFailureScope(normalized), isRoot && steps.length === 1 ? "root" : undefined);
388
+ if (failureScope === "root") {
389
+ return {
390
+ branches,
391
+ commands,
392
+ criticalFailure: true,
393
+ failureScope: "root",
394
+ failures,
395
+ result,
396
+ };
397
+ }
398
+ if (failureScope === "branch") {
399
+ addResultFailure(failures, executed);
400
+ return {
401
+ branches,
402
+ commands,
403
+ failureScope: "branch",
404
+ failures,
405
+ result,
406
+ };
407
+ }
408
+ addResultFailure(failures, executed);
409
+ result = { ...result, code: 0, stdout: "" };
410
+ nextStdin = "";
411
+ continue;
412
+ }
413
+ nextStdin = result.stdout;
414
+ }
415
+ return { branches, commands, failures, result: result };
416
+ }
417
+ async function executeTemplateSteps(cfg, params, exec, cwd, signal) {
418
+ return executeTemplateConfig(createTemplateConfig(cfg), {}, params, exec, cwd, signal, undefined, true, undefined);
419
+ }
420
+ export async function executeRegisteredTool(cfg, params, exec, cwd, signal) {
421
+ const executed = await executeTemplateSteps(cfg, Schema.normalizeRuntimeValues(params, cfg.argTypes), exec, cwd, signal);
422
+ const command = formatCommandDetail(executed.commands);
423
+ const result = executed.result;
424
+ if (result.code !== 0) {
425
+ const formatted = formatFailureOutput(cfg.name, result.code, result.killed, result.stdout, result.stderr);
426
+ throw new Error(formatted.text);
427
+ }
428
+ const formatted = formatOutput(cfg.name, "stdout", result.stdout);
429
+ const templateWarnings = CommandTemplates.getCommandTemplateWarnings(createTemplateConfig(cfg));
430
+ return {
431
+ content: [textContent(formatted.text)],
432
+ details: {
433
+ code: result.code,
434
+ command,
435
+ fullOutputPath: formatted.fullOutputPath,
436
+ killed: result.killed,
437
+ ...(executed.branches.length > 0 ? { branches: executed.branches } : {}),
438
+ ...(executed.failures.length > 0
439
+ ? { nonCriticalFailures: executed.failures }
440
+ : {}),
441
+ ...(createSoftQuorum(executed.branches)
442
+ ? { softQuorum: createSoftQuorum(executed.branches) }
443
+ : {}),
444
+ template: cfg.template,
445
+ ...(templateWarnings.length > 0 ? { templateWarnings } : {}),
446
+ tool: cfg.name,
447
+ truncated: formatted.truncated,
448
+ },
449
+ };
450
+ }
@@ -0,0 +1,6 @@
1
+ /**
2
+ * File state persistence helpers
3
+ * Zones: file persistence, atomic writes, runtime state support
4
+ * Owns generic durable JSON file writes shared by registry config and async run state.
5
+ */
6
+ export declare function writeJsonAtomic(path: string, value: unknown): void;
@@ -0,0 +1,25 @@
1
+ /**
2
+ * File state persistence helpers
3
+ * Zones: file persistence, atomic writes, runtime state support
4
+ * Owns generic durable JSON file writes shared by registry config and async run state.
5
+ */
6
+ import { randomUUID } from "node:crypto";
7
+ import { mkdirSync, renameSync, unlinkSync, writeFileSync } from "node:fs";
8
+ import { dirname } from "node:path";
9
+ export function writeJsonAtomic(path, value) {
10
+ mkdirSync(dirname(path), { recursive: true });
11
+ const tempPath = `${path}.${process.pid}.${Date.now()}.${randomUUID()}.tmp`;
12
+ try {
13
+ writeFileSync(tempPath, `${JSON.stringify(value, null, 2)}\n`, "utf8");
14
+ renameSync(tempPath, path);
15
+ }
16
+ catch (error) {
17
+ try {
18
+ unlinkSync(tempPath);
19
+ }
20
+ catch {
21
+ /* best effort */
22
+ }
23
+ throw error;
24
+ }
25
+ }
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Identifier normalization helpers
3
+ * Zones: registry identity, shared validation
4
+ * Owns stable tool, argument, and file-label identifier normalization
5
+ */
6
+ export declare function normalizeIdentifier(value: string, prefix: string): string;
7
+ export declare function normalizeToolName(value: string): string;
8
+ export declare function normalizeArgName(value: string): string;
9
+ export declare function sanitizeFilePart(value: string): string;
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Identifier normalization helpers
3
+ * Zones: registry identity, shared validation
4
+ * Owns stable tool, argument, and file-label identifier normalization
5
+ */
6
+ export function normalizeIdentifier(value, prefix) {
7
+ let name = value
8
+ .trim()
9
+ .toLowerCase()
10
+ .replace(/[^a-z0-9_]/g, "_")
11
+ .replace(/_+/g, "_")
12
+ .replace(/^_+|_+$/g, "");
13
+ if (!name)
14
+ return "";
15
+ if (/^[0-9]/.test(name))
16
+ name = `${prefix}_${name}`;
17
+ return name.slice(0, 64).replace(/_+$/g, "");
18
+ }
19
+ export function normalizeToolName(value) {
20
+ return normalizeIdentifier(value, "tool");
21
+ }
22
+ export function normalizeArgName(value) {
23
+ return normalizeIdentifier(value, "arg");
24
+ }
25
+ export function sanitizeFilePart(value) {
26
+ return normalizeIdentifier(value, "tool") || "tool";
27
+ }
@@ -0,0 +1,86 @@
1
+ /**
2
+ * Async run observability helpers
3
+ * Zones: async runtime, ambient UI, diagnostics
4
+ * Owns ambient summaries, terminal events, and run outbox delivery for detached command-template runs
5
+ */
6
+ import * as AsyncRuns from "./async-runs.ts";
7
+ export type RunObservedStatus = "running" | "done" | "failed" | "exited" | "cancelled" | "killed";
8
+ export type RunOutboxDelivery = "log" | "notify" | "followup";
9
+ export type RunOutboxLevel = "info" | "warning" | "error";
10
+ export interface RunObservation {
11
+ activeSubagents?: number;
12
+ completed?: number;
13
+ descendantSubagents?: number;
14
+ failures?: number;
15
+ ownerId?: string;
16
+ artifacts?: Record<string, string>;
17
+ launchSource?: AsyncRuns.AsyncRunLaunchSource;
18
+ recipeFile?: string;
19
+ terminalHandled?: boolean;
20
+ retireWhen?: string;
21
+ run: string;
22
+ tool?: string;
23
+ stateDir?: string;
24
+ status: RunObservedStatus;
25
+ updatedAt?: string;
26
+ }
27
+ export interface RunSummary {
28
+ cancelled: number;
29
+ done: number;
30
+ exited: number;
31
+ failed: number;
32
+ killed: number;
33
+ running: number;
34
+ runningSubagents: number;
35
+ runs: RunObservation[];
36
+ total: number;
37
+ }
38
+ export interface RunRetirementCandidate {
39
+ activeSubagents: number;
40
+ descendantSubagents: number;
41
+ run: string;
42
+ stateDir: string;
43
+ }
44
+ export interface RunTransition {
45
+ from: RunObservedStatus;
46
+ run: string;
47
+ stateDir?: string;
48
+ artifacts?: Record<string, string>;
49
+ launchSource?: AsyncRuns.AsyncRunLaunchSource;
50
+ recipeFile?: string;
51
+ terminalHandled?: boolean;
52
+ to: RunObservedStatus;
53
+ tool?: string;
54
+ }
55
+ export interface RunOutboxEvent {
56
+ body?: unknown;
57
+ data?: unknown;
58
+ delivery: RunOutboxDelivery;
59
+ event: string;
60
+ id: string;
61
+ level: RunOutboxLevel;
62
+ metadata?: Record<string, unknown>;
63
+ run: string;
64
+ stateDir: string;
65
+ summary: string;
66
+ ts: string;
67
+ }
68
+ export type RunTransitionNotificationType = "info" | "warning" | "error";
69
+ export declare function summarizeRuns(stateRoot?: string, ownerId?: string): RunSummary;
70
+ export declare function countRunningSubagentsByRun(stateRoot?: string, ownerId?: string): Map<string, number>;
71
+ export declare function countRunningSubagents(stateRoot?: string, ownerId?: string): number;
72
+ export declare function renderSubagentStatus(count: number, frame?: number): string | undefined;
73
+ export declare function renderRunStatus(summary: RunSummary, frame?: number): string | undefined;
74
+ export declare function findRunRetirementCandidates(summary: RunSummary): RunRetirementCandidate[];
75
+ export declare function detectRunTransitions(previous: Map<string, RunObservedStatus>, summary: RunSummary): RunTransition[];
76
+ export declare function pruneRunObservationState(previousStatuses: Map<string, RunObservedStatus>, previousLineCounts: Map<string, number>, summary: RunSummary, terminalRuns?: Iterable<string>): void;
77
+ export declare function detectRunOutboxEvents(previousLineCounts: Map<string, number>, summary: RunSummary): RunOutboxEvent[];
78
+ export declare function getRunOutboxNotificationType(event: RunOutboxEvent): RunTransitionNotificationType;
79
+ export declare function shouldNotifyRunOutboxEvent(event: RunOutboxEvent): boolean;
80
+ export declare function shouldSendRunOutboxFollowUp(event: RunOutboxEvent): boolean;
81
+ export declare function formatRunOutboxMessage(event: RunOutboxEvent): string;
82
+ export declare function getRunTransitionNotificationType(transition: RunTransition): RunTransitionNotificationType;
83
+ export declare function shouldNotifyRunTransition(transition: RunTransition): boolean;
84
+ export declare function shouldSendRunTransitionFollowUp(transition: RunTransition): boolean;
85
+ export declare function shouldSuggestRecipePersistence(transition: RunTransition): boolean;
86
+ export declare function formatRunTransitionMessage(transition: RunTransition): string;