@llblab/pi-actors 0.22.3 → 0.22.4

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.
@@ -5,7 +5,14 @@
5
5
  */
6
6
 
7
7
  import { existsSync, readdirSync, readFileSync } from "node:fs";
8
- import { basename, dirname, isAbsolute, join, relative, resolve } from "node:path";
8
+ import {
9
+ basename,
10
+ dirname,
11
+ isAbsolute,
12
+ join,
13
+ relative,
14
+ resolve,
15
+ } from "node:path";
9
16
 
10
17
  import * as AsyncRuns from "./async-runs.ts";
11
18
  import * as Paths from "./paths.ts";
@@ -229,13 +236,25 @@ export function summarizeRuns(
229
236
  const descendantSubagents = processSubagentsByRun.get(run.run) ?? 0;
230
237
  return descendantSubagents > 0 ? { ...run, descendantSubagents } : run;
231
238
  });
232
- const runningRuns = runsWithDescendants.filter((run) => run.status === "running");
239
+ const runningRuns = runsWithDescendants.filter(
240
+ (run) => run.status === "running",
241
+ );
233
242
  const running = runningRuns.length;
234
- const done = runsWithDescendants.filter((run) => run.status === "done").length;
235
- const exited = runsWithDescendants.filter((run) => run.status === "exited").length;
236
- const failed = runsWithDescendants.filter((run) => run.status === "failed").length;
237
- const cancelled = runsWithDescendants.filter((run) => run.status === "cancelled").length;
238
- const killed = runsWithDescendants.filter((run) => run.status === "killed").length;
243
+ const done = runsWithDescendants.filter(
244
+ (run) => run.status === "done",
245
+ ).length;
246
+ const exited = runsWithDescendants.filter(
247
+ (run) => run.status === "exited",
248
+ ).length;
249
+ const failed = runsWithDescendants.filter(
250
+ (run) => run.status === "failed",
251
+ ).length;
252
+ const cancelled = runsWithDescendants.filter(
253
+ (run) => run.status === "cancelled",
254
+ ).length;
255
+ const killed = runsWithDescendants.filter(
256
+ (run) => run.status === "killed",
257
+ ).length;
239
258
  const progressSubagents = runningRuns.reduce(
240
259
  (sum, run) => sum + Math.max(1, Math.floor(run.activeSubagents ?? 0)),
241
260
  0,
@@ -244,7 +263,10 @@ export function summarizeRuns(
244
263
  (sum, count) => sum + count,
245
264
  0,
246
265
  );
247
- const runningSubagents = Math.max(progressSubagents, running + processSubagents);
266
+ const runningSubagents = Math.max(
267
+ progressSubagents,
268
+ running + processSubagents,
269
+ );
248
270
  return {
249
271
  cancelled,
250
272
  done,
@@ -278,11 +300,16 @@ function getProcCommand(pid: string): string {
278
300
  return (readProcFile(`/proc/${pid}/cmdline`) ?? "").replaceAll("\0", " ");
279
301
  }
280
302
 
281
- function getRunningRunPidMap(stateRoot: string, ownerId?: string): Map<string, string> {
303
+ function getRunningRunPidMap(
304
+ stateRoot: string,
305
+ ownerId?: string,
306
+ ): Map<string, string> {
282
307
  const pids = new Map<string, string>();
283
308
  for (const run of summarizeRunsWithoutSubagents(stateRoot, ownerId).runs) {
284
309
  if (run.status !== "running") continue;
285
- const status = AsyncRuns.getRunStatus(run.stateDir ?? join(stateRoot, run.run));
310
+ const status = AsyncRuns.getRunStatus(
311
+ run.stateDir ?? join(stateRoot, run.run),
312
+ );
286
313
  const pid = Number(status.pid || 0);
287
314
  if (pid > 0) pids.set(String(pid), run.run);
288
315
  }
@@ -420,7 +447,10 @@ export function findRunRetirementCandidates(
420
447
  return summary.runs
421
448
  .map((run) => {
422
449
  const activeSubagents = Math.max(0, Math.floor(run.activeSubagents ?? 0));
423
- const descendantSubagents = Math.max(0, Math.floor(run.descendantSubagents ?? 0));
450
+ const descendantSubagents = Math.max(
451
+ 0,
452
+ Math.floor(run.descendantSubagents ?? 0),
453
+ );
424
454
  const childRuns = run.stateDir
425
455
  ? summary.runs.filter(
426
456
  (child) =>
@@ -429,7 +459,9 @@ export function findRunRetirementCandidates(
429
459
  isNestedStateDir(run.stateDir!, child.stateDir),
430
460
  )
431
461
  : [];
432
- const runningChildRuns = childRuns.filter((child) => child.status === "running").length;
462
+ const runningChildRuns = childRuns.filter(
463
+ (child) => child.status === "running",
464
+ ).length;
433
465
  return {
434
466
  activeSubagents,
435
467
  childRuns: childRuns.length,
@@ -441,7 +473,9 @@ export function findRunRetirementCandidates(
441
473
  !run.terminalHandled &&
442
474
  activeSubagents + descendantSubagents + runningChildRuns <= 0,
443
475
  run,
444
- terminalChildRuns: childRuns.filter((child) => TERMINAL.has(child.status)).length,
476
+ terminalChildRuns: childRuns.filter((child) =>
477
+ TERMINAL.has(child.status),
478
+ ).length,
445
479
  };
446
480
  })
447
481
  .filter((item) => item.ready)
@@ -462,7 +496,11 @@ export async function executeRunRetirements(
462
496
  const results: RunRetirementExecution[] = [];
463
497
  for (const candidate of findRunRetirementCandidates(summary)) {
464
498
  if (options.attempted?.has(candidate.stateDir)) {
465
- results.push({ action: "skip", run: candidate.run, stateDir: candidate.stateDir });
499
+ results.push({
500
+ action: "skip",
501
+ run: candidate.run,
502
+ stateDir: candidate.stateDir,
503
+ });
466
504
  continue;
467
505
  }
468
506
  options.attempted?.add(candidate.stateDir);
@@ -472,12 +510,18 @@ export async function executeRunRetirements(
472
510
  `Retiring actor ${candidate.run} after child runs reached terminal state`,
473
511
  "info",
474
512
  );
475
- results.push({ action: "stop", run: candidate.run, stateDir: candidate.stateDir });
513
+ results.push({
514
+ action: "stop",
515
+ run: candidate.run,
516
+ stateDir: candidate.stateDir,
517
+ });
476
518
  continue;
477
519
  } catch (error) {
478
520
  try {
479
521
  const cancelResult = options.cancelRun(candidate);
480
- const cancelled = Boolean((cancelResult as { cancelled?: unknown }).cancelled);
522
+ const cancelled = Boolean(
523
+ (cancelResult as { cancelled?: unknown }).cancelled,
524
+ );
481
525
  options.notify?.(
482
526
  cancelled
483
527
  ? `Retiring actor ${candidate.run} by cancellation after graceful stop failed`
@@ -486,13 +530,23 @@ export async function executeRunRetirements(
486
530
  );
487
531
  results.push({
488
532
  action: cancelled ? "cancel" : "skip",
489
- ...(cancelled ? {} : { error: error instanceof Error ? error.message : String(error) }),
533
+ ...(cancelled
534
+ ? {}
535
+ : {
536
+ error: error instanceof Error ? error.message : String(error),
537
+ }),
490
538
  run: candidate.run,
491
539
  stateDir: candidate.stateDir,
492
540
  });
493
541
  } catch (cancelError) {
494
- const message = cancelError instanceof Error ? cancelError.message : String(cancelError);
495
- options.notify?.(`Actor retirement failed for ${candidate.run}: ${message}`, "error");
542
+ const message =
543
+ cancelError instanceof Error
544
+ ? cancelError.message
545
+ : String(cancelError);
546
+ options.notify?.(
547
+ `Actor retirement failed for ${candidate.run}: ${message}`,
548
+ "error",
549
+ );
496
550
  results.push({
497
551
  action: "failed",
498
552
  error: message,
@@ -505,13 +559,20 @@ export async function executeRunRetirements(
505
559
  return results;
506
560
  }
507
561
 
562
+ function runObservationKey(
563
+ run: Pick<RunObservation, "run" | "stateDir">,
564
+ ): string {
565
+ return run.stateDir ?? run.run;
566
+ }
567
+
508
568
  export function detectRunTransitions(
509
569
  previous: Map<string, RunObservedStatus>,
510
570
  summary: RunSummary,
511
571
  ): RunTransition[] {
512
572
  const transitions: RunTransition[] = [];
513
573
  for (const run of summary.runs) {
514
- const old = previous.get(run.run);
574
+ const key = runObservationKey(run);
575
+ const old = previous.get(key);
515
576
  if (old && old !== run.status && TERMINAL.has(run.status)) {
516
577
  transitions.push({
517
578
  from: old,
@@ -525,7 +586,7 @@ export function detectRunTransitions(
525
586
  ...(run.tool ? { tool: run.tool } : {}),
526
587
  });
527
588
  }
528
- previous.set(run.run, run.status);
589
+ previous.set(key, run.status);
529
590
  }
530
591
  return transitions;
531
592
  }
@@ -599,12 +660,12 @@ export function pruneRunObservationState(
599
660
  summary: RunSummary,
600
661
  terminalRuns: Iterable<string> = [],
601
662
  ): void {
602
- const activeRuns = new Set(summary.runs.map((run) => run.run));
663
+ const activeRuns = new Set(summary.runs.map((run) => runObservationKey(run)));
603
664
  const terminalRunSet = new Set(terminalRuns);
604
665
  const terminalLineKeys = new Set(
605
666
  summary.runs
606
- .filter((run) => terminalRunSet.has(run.run))
607
- .map((run) => run.stateDir ?? run.run),
667
+ .filter((run) => terminalRunSet.has(runObservationKey(run)))
668
+ .map((run) => runObservationKey(run)),
608
669
  );
609
670
  const activeLineKeys = new Set(
610
671
  summary.runs.map((run) => run.stateDir ?? run.run),
@@ -8,9 +8,9 @@ import { createHash } from "node:crypto";
8
8
  import { existsSync, readFileSync, readdirSync, statSync } from "node:fs";
9
9
  import { join } from "node:path";
10
10
 
11
+ import * as CommandTemplates from "./command-templates.ts";
11
12
  import type { RegisteredTool } from "./config.ts";
12
13
  import type { TemplateRecipeConfig } from "./recipe-references.ts";
13
- import * as CommandTemplates from "./command-templates.ts";
14
14
  import * as RecipeReferences from "./recipe-references.ts";
15
15
  import * as Schema from "./schema.ts";
16
16
 
@@ -61,18 +61,28 @@ function assertToolSafeRepeatConfig(
61
61
  argTypes: Record<string, { kind: string }>,
62
62
  defaults: Record<string, unknown>,
63
63
  ): void {
64
- if (typeof config === "string" || config === undefined || config === null) return;
64
+ if (typeof config === "string" || config === undefined || config === null)
65
+ return;
65
66
  if (Array.isArray(config)) {
66
- for (const step of config) assertToolSafeRepeatConfig(step, argTypes, defaults);
67
+ for (const step of config)
68
+ assertToolSafeRepeatConfig(step, argTypes, defaults);
67
69
  return;
68
70
  }
69
71
  if (typeof config !== "object") return;
70
- const node = config as { repeat?: unknown; template?: unknown; recover?: unknown };
72
+ const node = config as {
73
+ repeat?: unknown;
74
+ template?: unknown;
75
+ recover?: unknown;
76
+ };
71
77
  if (typeof node.repeat === "string") {
72
78
  const trimmed = node.repeat.trim();
73
79
  if (!/^\d+$/.test(trimmed)) {
74
80
  const match = trimmed.match(/^\{?([A-Za-z_][A-Za-z0-9_-]*)\.length\}?$/);
75
- if (!match || (argTypes[match[1]]?.kind !== "array" && !Array.isArray(defaults[match[1]])))
81
+ if (
82
+ !match ||
83
+ (argTypes[match[1]]?.kind !== "array" &&
84
+ !Array.isArray(defaults[match[1]]))
85
+ )
76
86
  throw new Error(
77
87
  "Command template repeat must be a positive integer or {array.length} with an array argument/default.",
78
88
  );
@@ -92,23 +102,30 @@ function listRecipeFiles(root: string): string[] {
92
102
  entry.name !== "legacy-tool-registry-migration-report.json",
93
103
  )
94
104
  .map((entry) => join(root, entry.name))
95
- .sort((a, b) => a.replace(/\.md$/, ".json").localeCompare(b.replace(/\.md$/, ".json")) || (a.endsWith(".json") ? -1 : 1));
105
+ .sort(
106
+ (a, b) =>
107
+ a
108
+ .replace(/\.md$/, ".json")
109
+ .localeCompare(b.replace(/\.md$/, ".json")) ||
110
+ (a.endsWith(".json") ? -1 : 1),
111
+ );
96
112
  }
97
113
 
98
114
  function getRecipeConfigDiagnostics(
99
115
  file: string,
100
116
  config: TemplateRecipeConfig | undefined,
101
117
  ): string[] {
102
- if (!config) return [`Invalid recipe: ${file}`];
118
+ if (!config) {
119
+ const reason = RecipeReferences.diagnoseRawRecipeConfigFailure(file);
120
+ return [`Invalid recipe: ${file}${reason ? `: ${reason}` : ""}`];
121
+ }
103
122
  const commandTemplateConfig =
104
123
  typeof config.template === "object" && config.template !== null
105
124
  ? config.template
106
125
  : config;
107
126
  return CommandTemplates.getCommandTemplateWarnings(
108
127
  commandTemplateConfig as CommandTemplates.CommandTemplateConfig,
109
- ).map(
110
- (warning) => `Recipe ${file}: ${warning}`,
111
- );
128
+ ).map((warning) => `Recipe ${file}: ${warning}`);
112
129
  }
113
130
 
114
131
  function readDiscoveredRecipe(
@@ -158,13 +175,23 @@ function readDiscoveredRecipe(
158
175
  }
159
176
  }
160
177
 
161
- function filesForSource(
162
- source: RecipeDiscoverySource,
163
- ): Array<{ root: string; file: string; defaultTool: boolean; mutableUsage: boolean }> {
178
+ function filesForSource(source: RecipeDiscoverySource): Array<{
179
+ root: string;
180
+ file: string;
181
+ defaultTool: boolean;
182
+ mutableUsage: boolean;
183
+ }> {
164
184
  const defaultTool = source.defaultTool === true;
165
185
  const mutableUsage = source.mutableUsage === true;
166
186
  if (source.file)
167
- return [{ root: source.root ?? source.file, file: source.file, defaultTool, mutableUsage }];
187
+ return [
188
+ {
189
+ root: source.root ?? source.file,
190
+ file: source.file,
191
+ defaultTool,
192
+ mutableUsage,
193
+ },
194
+ ];
168
195
  return source.root
169
196
  ? listRecipeFiles(source.root).map((file) => ({
170
197
  root: source.root!,
@@ -226,7 +253,9 @@ export function discoverRecipeSources(
226
253
  bucket.sort(
227
254
  (a, b) =>
228
255
  a.priority - b.priority ||
229
- a.path.replace(/\.md$/, ".json").localeCompare(b.path.replace(/\.md$/, ".json")) ||
256
+ a.path
257
+ .replace(/\.md$/, ".json")
258
+ .localeCompare(b.path.replace(/\.md$/, ".json")) ||
230
259
  (a.path.endsWith(".json") ? -1 : 1),
231
260
  );
232
261
  const winner = bucket[0];
@@ -262,14 +291,18 @@ export function discoverRecipes(roots: string[]): RecipeDiscoveryResult {
262
291
  return discoverRecipeSources(roots.map((root) => ({ root })));
263
292
  }
264
293
 
265
- function recipeUsage(config: TemplateRecipeConfig | undefined): Record<string, unknown> | undefined {
294
+ function recipeUsage(
295
+ config: TemplateRecipeConfig | undefined,
296
+ ): Record<string, unknown> | undefined {
266
297
  const usage = (config as { usage?: unknown } | undefined)?.usage;
267
298
  return usage && typeof usage === "object" && !Array.isArray(usage)
268
299
  ? (usage as Record<string, unknown>)
269
300
  : undefined;
270
301
  }
271
302
 
272
- function cleanupRecommendation(entry: DiscoveredRecipe): Record<string, unknown> | undefined {
303
+ function cleanupRecommendation(
304
+ entry: DiscoveredRecipe,
305
+ ): Record<string, unknown> | undefined {
273
306
  if (entry.invalid) {
274
307
  return {
275
308
  id: entry.id,
@@ -309,7 +342,13 @@ function cleanupRecommendation(entry: DiscoveredRecipe): Record<string, unknown>
309
342
  id: entry.id,
310
343
  path: entry.path,
311
344
  reason: "user recipe is a component, not an active tool",
312
- actions: ["keep component", "move into tool root", "merge", "delete", "archive"],
345
+ actions: [
346
+ "keep component",
347
+ "move into tool root",
348
+ "merge",
349
+ "delete",
350
+ "archive",
351
+ ],
313
352
  };
314
353
  }
315
354
  if (entry.shadows.length > 0) {
@@ -342,9 +381,78 @@ export function createRecipeIntegrityManifest(
342
381
  tool: entry.tool,
343
382
  };
344
383
  })
345
- .sort(
346
- (a, b) => a.id.localeCompare(b.id) || a.path.localeCompare(b.path),
384
+ .sort((a, b) => a.id.localeCompare(b.id) || a.path.localeCompare(b.path));
385
+ }
386
+
387
+ function diagnosticSeverity(message: string): "info" | "warning" | "error" {
388
+ if (
389
+ /invalid|failed to load|not found|cyclic|exceeds|must define|repeat must/i.test(
390
+ message,
391
+ )
392
+ ) {
393
+ return "error";
394
+ }
395
+ if (
396
+ /world-writable|group-writable|invokes bash|eval|destructive|unsafe/i.test(
397
+ message,
398
+ )
399
+ ) {
400
+ return "warning";
401
+ }
402
+ return "info";
403
+ }
404
+
405
+ function diagnosticSuggestedAction(message: string): string {
406
+ if (/must define template/i.test(message))
407
+ return "add a template field or remove the recipe";
408
+ if (/JSON|Expected|parse/i.test(message))
409
+ return "fix recipe syntax or archive the file";
410
+ if (/Markdown recipe/i.test(message))
411
+ return "fix frontmatter and add a fenced template or recipe block";
412
+ if (/cyclic/i.test(message)) return "break the import cycle";
413
+ if (/exceeds.*size/i.test(message))
414
+ return "split large prompt or data into separate files";
415
+ if (/repeat must/i.test(message))
416
+ return "use a positive repeat count or an array-typed repeat source";
417
+ if (/world-writable|group-writable/i.test(message))
418
+ return "tighten recipe root permissions";
419
+ if (/invokes bash/i.test(message))
420
+ return "audit the trusted shell boundary or move details to recipe doctor";
421
+ if (/shadows/i.test(message))
422
+ return "confirm the active override or rename one recipe";
423
+ if (/disabled/i.test(message))
424
+ return "keep disabled intentionally or delete/archive the file";
425
+ return "inspect the recipe and fix or archive it if unexpected";
426
+ }
427
+
428
+ function diagnosticDetails(
429
+ result: RecipeDiscoveryResult,
430
+ ): Array<Record<string, unknown>> {
431
+ const details: Array<Record<string, unknown>> = [];
432
+ const seen = new Set<string>();
433
+ const push = (message: string, entry?: DiscoveredRecipe): void => {
434
+ const key = `${entry?.path ?? "root"}\n${message}`;
435
+ if (seen.has(key)) return;
436
+ seen.add(key);
437
+ details.push({
438
+ ...(entry ? { id: entry.id, path: entry.path } : {}),
439
+ action: diagnosticSuggestedAction(message),
440
+ message,
441
+ severity: diagnosticSeverity(message),
442
+ });
443
+ };
444
+ for (const message of result.diagnostics) push(message);
445
+ for (const entry of result.entries) {
446
+ for (const message of entry.diagnostics) push(message, entry);
447
+ }
448
+ return details.sort((a, b) => {
449
+ const rank = { error: 0, warning: 1, info: 2 } as const;
450
+ return (
451
+ rank[a.severity as keyof typeof rank] -
452
+ rank[b.severity as keyof typeof rank] ||
453
+ String(a.message).localeCompare(String(b.message))
347
454
  );
455
+ });
348
456
  }
349
457
 
350
458
  function recommendationForEntry(
@@ -362,29 +470,49 @@ function recommendationForEntry(
362
470
  return recommendation;
363
471
  }
364
472
 
365
- export function summarizeDiscovery(result: RecipeDiscoveryResult): Record<string, unknown> {
473
+ export function summarizeDiscovery(
474
+ result: RecipeDiscoveryResult,
475
+ ): Record<string, unknown> {
366
476
  const recommendations = result.entries
367
- .map((entry) => recommendationForEntry(entry, result.active.get(entry.id)?.path))
477
+ .map((entry) =>
478
+ recommendationForEntry(entry, result.active.get(entry.id)?.path),
479
+ )
368
480
  .filter((entry): entry is Record<string, unknown> => Boolean(entry))
369
- .sort((a, b) => String(a.id).localeCompare(String(b.id)) || String(a.path).localeCompare(String(b.path)));
481
+ .sort(
482
+ (a, b) =>
483
+ String(a.id).localeCompare(String(b.id)) ||
484
+ String(a.path).localeCompare(String(b.path)),
485
+ );
370
486
  return {
371
- active: [...result.active.values()].map((entry) => ({
372
- id: entry.id,
373
- path: entry.path,
374
- description: entry.config?.description,
375
- tool: entry.tool,
376
- disabled: entry.disabled,
377
- invalid: entry.invalid,
378
- shadows: entry.shadows,
379
- ...(recipeUsage(entry.config) ? { usage: recipeUsage(entry.config) } : {}),
380
- })).sort((a, b) => a.id.localeCompare(b.id)),
487
+ active: [...result.active.values()]
488
+ .map((entry) => ({
489
+ id: entry.id,
490
+ path: entry.path,
491
+ description: entry.config?.description,
492
+ tool: entry.tool,
493
+ disabled: entry.disabled,
494
+ invalid: entry.invalid,
495
+ shadows: entry.shadows,
496
+ ...(recipeUsage(entry.config)
497
+ ? { usage: recipeUsage(entry.config) }
498
+ : {}),
499
+ }))
500
+ .sort((a, b) => a.id.localeCompare(b.id)),
381
501
  shadowed: result.entries
382
502
  .filter((entry) => entry.shadowed)
383
- .map((entry) => ({ id: entry.id, path: entry.path, shadowedBy: result.active.get(entry.id)?.path }))
503
+ .map((entry) => ({
504
+ id: entry.id,
505
+ path: entry.path,
506
+ shadowedBy: result.active.get(entry.id)?.path,
507
+ }))
384
508
  .sort((a, b) => a.id.localeCompare(b.id) || a.path.localeCompare(b.path)),
385
509
  invalid: result.entries
386
510
  .filter((entry) => entry.invalid)
387
- .map((entry) => ({ id: entry.id, path: entry.path, diagnostics: entry.diagnostics }))
511
+ .map((entry) => ({
512
+ id: entry.id,
513
+ path: entry.path,
514
+ diagnostics: entry.diagnostics,
515
+ }))
388
516
  .sort((a, b) => a.id.localeCompare(b.id)),
389
517
  disabled: result.entries
390
518
  .filter((entry) => entry.disabled)
@@ -392,12 +520,16 @@ export function summarizeDiscovery(result: RecipeDiscoveryResult): Record<string
392
520
  .sort((a, b) => a.id.localeCompare(b.id)),
393
521
  recommendations,
394
522
  diagnostics: result.diagnostics,
523
+ diagnostic_details: diagnosticDetails(result),
395
524
  integrity_manifest: createRecipeIntegrityManifest(result),
396
525
  };
397
526
  }
398
527
 
399
- export function toRegisteredTool(entry: DiscoveredRecipe): RegisteredTool | undefined {
400
- if (!entry.tool || entry.invalid || entry.disabled || !entry.config) return undefined;
528
+ export function toRegisteredTool(
529
+ entry: DiscoveredRecipe,
530
+ ): RegisteredTool | undefined {
531
+ if (!entry.tool || entry.invalid || entry.disabled || !entry.config)
532
+ return undefined;
401
533
  const cfg = entry.config;
402
534
  const template = entry.path;
403
535
  const description = cfg.description ?? `Execute template recipe: ${entry.id}`;
@@ -407,7 +539,10 @@ export function toRegisteredTool(entry: DiscoveredRecipe): RegisteredTool | unde
407
539
  ? {
408
540
  ...argTemplate,
409
541
  ...(cfg.args !== undefined ? { args: cfg.args } : {}),
410
- defaults: { ...(argTemplate.defaults ?? {}), ...(cfg.defaults ?? {}) },
542
+ defaults: {
543
+ ...(argTemplate.defaults ?? {}),
544
+ ...(cfg.defaults ?? {}),
545
+ },
411
546
  }
412
547
  : { args: cfg.args, defaults: cfg.defaults ?? {}, template: argTemplate };
413
548
  const explicitArgTypes = Object.fromEntries(
@@ -416,7 +551,11 @@ export function toRegisteredTool(entry: DiscoveredRecipe): RegisteredTool | unde
416
551
  return [parsed.arg, parsed.type];
417
552
  }),
418
553
  );
419
- assertToolSafeRepeatConfig(argTemplateConfig, explicitArgTypes, cfg.defaults ?? {});
554
+ assertToolSafeRepeatConfig(
555
+ argTemplateConfig,
556
+ explicitArgTypes,
557
+ cfg.defaults ?? {},
558
+ );
420
559
  const argTypes = Schema.getTemplateArgTypes(argTemplateConfig);
421
560
  return {
422
561
  name: entry.id,
@@ -425,7 +564,10 @@ export function toRegisteredTool(entry: DiscoveredRecipe): RegisteredTool | unde
425
564
  recipe: cfg,
426
565
  args: Schema.getToolArgNames(argTemplateConfig),
427
566
  defaults: Object.fromEntries(
428
- Object.entries(cfg.defaults ?? {}).map(([key, value]) => [key, String(value)]),
567
+ Object.entries(cfg.defaults ?? {}).map(([key, value]) => [
568
+ key,
569
+ String(value),
570
+ ]),
429
571
  ),
430
572
  ...(Object.keys(argTypes).length > 0 ? { argTypes } : {}),
431
573
  ...(entry.mutableUsage ? { sourcePath: entry.path } : {}),
@@ -433,7 +575,10 @@ export function toRegisteredTool(entry: DiscoveredRecipe): RegisteredTool | unde
433
575
  ...(cfg.defaults
434
576
  ? {
435
577
  storedDefaults: Object.fromEntries(
436
- Object.entries(cfg.defaults).map(([key, value]) => [key, String(value)]),
578
+ Object.entries(cfg.defaults).map(([key, value]) => [
579
+ key,
580
+ String(value),
581
+ ]),
437
582
  ),
438
583
  }
439
584
  : {}),