@llblab/pi-actors 0.46.1 → 0.47.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 (53) hide show
  1. package/AGENTS.md +7 -5
  2. package/BACKLOG.md +0 -582
  3. package/CHANGELOG.md +11 -0
  4. package/README.md +2 -1
  5. package/dist/lib/prompts.d.ts +6 -5
  6. package/dist/lib/prompts.js +22 -23
  7. package/dist/lib/recipes-discovery.d.ts +4 -0
  8. package/dist/lib/recipes-discovery.js +13 -1
  9. package/dist/lib/recipes-references.js +10 -6
  10. package/dist/lib/registry.d.ts +15 -11
  11. package/dist/lib/registry.js +195 -25
  12. package/dist/lib/runtime.js +37 -2
  13. package/dist/lib/tools-inspect.js +156 -12
  14. package/dist/lib/tools-register.js +2 -1
  15. package/dist/lib/tools-response.js +5 -1
  16. package/dist/scripts/conformance.mjs +1 -0
  17. package/dist/skills/actors/SKILL.md +76 -65
  18. package/dist/skills/actors/references/diagnostics.md +44 -0
  19. package/dist/skills/actors/references/persistent-tools.md +74 -0
  20. package/dist/skills/actors/references/recipes.md +51 -0
  21. package/dist/skills/actors/references/runs.md +39 -0
  22. package/dist/skills/artifacts/SKILL.md +24 -7
  23. package/dist/skills/media/SKILL.md +35 -7
  24. package/dist/skills/project-work/SKILL.md +28 -7
  25. package/dist/skills/recipe-memory/SKILL.md +27 -7
  26. package/dist/skills/swarm/SKILL.md +41 -445
  27. package/dist/skills/swarm/references/development-swarm.md +87 -539
  28. package/dist/skills/swarm/references/review-swarms.md +115 -0
  29. package/docs/README.md +5 -5
  30. package/docs/recipe-library.md +15 -10
  31. package/docs/tool-registry.md +10 -4
  32. package/lib/prompts.ts +24 -24
  33. package/lib/recipes-discovery.ts +22 -1
  34. package/lib/recipes-references.ts +14 -6
  35. package/lib/registry.ts +288 -51
  36. package/lib/runtime.ts +41 -2
  37. package/lib/tools-inspect.ts +202 -10
  38. package/lib/tools-register.ts +4 -3
  39. package/lib/tools-response.ts +5 -1
  40. package/package.json +1 -1
  41. package/scripts/conformance.mjs +1 -0
  42. package/skills/actors/SKILL.md +76 -65
  43. package/skills/actors/references/diagnostics.md +44 -0
  44. package/skills/actors/references/persistent-tools.md +74 -0
  45. package/skills/actors/references/recipes.md +51 -0
  46. package/skills/actors/references/runs.md +39 -0
  47. package/skills/artifacts/SKILL.md +24 -7
  48. package/skills/media/SKILL.md +35 -7
  49. package/skills/project-work/SKILL.md +28 -7
  50. package/skills/recipe-memory/SKILL.md +27 -7
  51. package/skills/swarm/SKILL.md +41 -445
  52. package/skills/swarm/references/development-swarm.md +87 -539
  53. package/skills/swarm/references/review-swarms.md +115 -0
package/lib/registry.ts CHANGED
@@ -27,28 +27,30 @@ export interface RegisterToolInput {
27
27
  description?: string;
28
28
  async?: boolean;
29
29
  template?: CommandTemplates.CommandTemplateValue | null;
30
+ from?: string;
31
+ defaults?: Record<string, unknown>;
30
32
  draft?: string;
31
33
  args?: string;
32
34
  update?: boolean;
33
- values?: Record<string, unknown>;
34
35
  }
35
36
 
36
37
  export interface RegisterToolResultDetails {
37
38
  active_tool?: boolean;
38
39
  activation?: "current_session" | "unverified";
40
+ activation_boundary?: string;
39
41
  args?: string[];
40
42
  async?: boolean;
41
43
  callable_now?: boolean;
42
- config?: string;
43
44
  defaults?: Record<string, string>;
44
- draft?: string;
45
45
  host_registered?: boolean;
46
+ next_actions?: string[];
47
+ optional_args?: string[];
46
48
  persisted?: boolean;
47
49
  promoted?: boolean;
48
50
  registry_active?: boolean;
51
+ required_args?: string[];
49
52
  resolved?: boolean;
50
- recipeName?: string;
51
- template?: CommandTemplates.CommandTemplateValue;
53
+ source?: string;
52
54
  templateWarnings?: string[];
53
55
  tool: string;
54
56
  validated?: boolean;
@@ -59,6 +61,13 @@ export interface RegisterToolResult {
59
61
  details: RegisterToolResultDetails;
60
62
  }
61
63
 
64
+ interface RuntimeActivation {
65
+ active_tool: boolean;
66
+ activation: "current_session" | "unverified";
67
+ callable_now: boolean;
68
+ host_registered: boolean;
69
+ }
70
+
62
71
  export interface RegisterToolRuntimeDeps<TContext> {
63
72
  configPath: string;
64
73
  recipeRoot?: string;
@@ -70,14 +79,7 @@ export interface RegisterToolRuntimeDeps<TContext> {
70
79
  message: string,
71
80
  type: "info" | "warning" | "error",
72
81
  ) => void;
73
- registerRuntimeTool: (cfg: Config.RegisteredTool) =>
74
- | {
75
- active_tool: boolean;
76
- activation: "current_session" | "unverified";
77
- callable_now: boolean;
78
- host_registered: boolean;
79
- }
80
- | void;
82
+ registerRuntimeTool: (cfg: Config.RegisteredTool) => RuntimeActivation | void;
81
83
  reservedToolNames: Set<string>;
82
84
  setActiveTools: (toolNames: string[]) => void;
83
85
  }
@@ -117,6 +119,54 @@ function getToolRecipePath<TContext>(
117
119
  return join(getRecipeRoot(deps), `${name}.json`);
118
120
  }
119
121
 
122
+ function activationBoundary(
123
+ activation: RuntimeActivation | undefined,
124
+ ): string {
125
+ if (activation?.callable_now) return "current_session";
126
+ if (!activation) return "session_activation_unverified";
127
+ if (!activation.host_registered) return "host_registration";
128
+ if (!activation.active_tool) return "active_tool_set";
129
+ return "callability_verification";
130
+ }
131
+
132
+ function recipeDoctorAction(source: string): string {
133
+ return /^[a-z][a-z0-9]*(?:-[a-z0-9]+)*\/[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/u.test(source)
134
+ ? `inspect target=recipes view=doctor identity=${source}`
135
+ : "inspect target=recipes view=doctor";
136
+ }
137
+
138
+ function registrationNextActions(
139
+ tool: string,
140
+ source: string,
141
+ callableNow: boolean,
142
+ ): string[] {
143
+ if (callableNow) {
144
+ return [
145
+ `call tool ${tool}`,
146
+ `inspect target=tool:${tool} view=status`,
147
+ ];
148
+ }
149
+ return [
150
+ `inspect target=tool:${tool} view=status`,
151
+ ...(source === "template" || source === "draft"
152
+ ? []
153
+ : [recipeDoctorAction(source)]),
154
+ ];
155
+ }
156
+
157
+ function activeSkillSummary(
158
+ resolutionContext: RecipesContext.RecipeResolutionContext,
159
+ ): string {
160
+ const names = Object.keys(
161
+ RecipesReferences.getActiveSkillRecipeNamespaces(
162
+ resolutionContext.activeSkills,
163
+ ),
164
+ )
165
+ .sort()
166
+ .slice(0, 20);
167
+ return names.length > 0 ? names.join(", ") : "<none>";
168
+ }
169
+
120
170
  function assertDraftPath<TContext>(
121
171
  deps: RegisterToolRuntimeDeps<TContext>,
122
172
  draft: string,
@@ -199,23 +249,38 @@ function promoteDraftRecipe<TContext>(
199
249
  resolutionContext,
200
250
  ).tool!;
201
251
  tools.set(name, promoted);
202
- deps.registerRuntimeTool(promoted);
252
+ const activation = deps.registerRuntimeTool(promoted) ?? undefined;
253
+ const argSummary = RecipesDiscovery.summarizeRegisteredToolArgs(promoted);
254
+ const callableNow = activation?.callable_now ?? false;
255
+ const nextActions = registrationNextActions(name, "draft", callableNow);
203
256
  deps.notify(ctx, `Promoted draft recipe: ${name}`, "info");
204
257
  return {
205
258
  content: [
206
259
  textContent(
207
260
  ExecutionOutput.formatToolText(
208
- `${existing ? "Updated" : "Registered"} tool "${name}" from draft recipe.`,
261
+ `${existing ? "Updated" : "Registered"} tool "${name}" from draft; callable_now=${callableNow}; next=${nextActions[0]}.`,
209
262
  ),
210
263
  ),
211
264
  ],
212
265
  details: {
266
+ active_tool: activation?.active_tool ?? false,
267
+ activation: activation?.activation ?? "unverified",
268
+ activation_boundary: activationBoundary(activation),
213
269
  args: promoted.args,
214
- config: targetPath,
215
- draft: draftPath,
270
+ callable_now: callableNow,
271
+ defaults: promoted.defaults,
272
+ host_registered: activation?.host_registered ?? false,
273
+ next_actions: nextActions,
274
+ optional_args: argSummary.optional,
275
+ persisted: true,
216
276
  promoted: true,
277
+ registry_active: tools.get(name) === promoted,
278
+ required_args: argSummary.required,
279
+ resolved: true,
280
+ source: "draft",
217
281
  tool: name,
218
- } as RegisterToolResultDetails & Record<string, unknown>,
282
+ validated: true,
283
+ },
219
284
  };
220
285
  }
221
286
 
@@ -261,7 +326,7 @@ function deleteTool<TContext>(
261
326
  ),
262
327
  ),
263
328
  ],
264
- details: { config: recipePath, tool: name },
329
+ details: { tool: name },
265
330
  };
266
331
  }
267
332
 
@@ -296,9 +361,38 @@ function readAuthoritativeStoredTool<TContext>(
296
361
  }
297
362
  }
298
363
 
364
+ function looksLikeRecipeReference(value: string): boolean {
365
+ const trimmed = value.trim();
366
+ return (
367
+ trimmed.endsWith(".json") ||
368
+ trimmed.endsWith(".md") ||
369
+ /^[^/:\\]+\/[^/:\\]+$/.test(trimmed)
370
+ );
371
+ }
372
+
373
+ function assertTemplateIsNotNestedRecipe(value: unknown): void {
374
+ if (!value || typeof value !== "object" || Array.isArray(value)) return;
375
+ const record = value as Record<string, unknown>;
376
+ if (
377
+ typeof record.template === "string" &&
378
+ looksLikeRecipeReference(record.template) &&
379
+ (Object.keys(record).length === 1 ||
380
+ ["artifacts", "async", "control", "description", "imports", "values"].some(
381
+ (key) => Object.hasOwn(record, key),
382
+ ))
383
+ ) {
384
+ throw new Error(
385
+ ExecutionOutput.formatToolText(
386
+ "Nested Recipe-shaped templates are not a register_tool source mode. Next: retry with from=<skill>/<recipe> and defaults={...}.",
387
+ ),
388
+ );
389
+ }
390
+ }
391
+
299
392
  function getInputTemplate(
300
393
  value: CommandTemplates.CommandTemplateValue | null | undefined,
301
394
  ): CommandTemplates.CommandTemplateValue | null | undefined {
395
+ assertTemplateIsNotNestedRecipe(value);
302
396
  if (typeof value === "string") return value.trim();
303
397
  if (value === null || value === undefined) return value;
304
398
  if (Array.isArray(value)) {
@@ -344,6 +438,55 @@ function getRegistrationResolutionContext<TContext>(
344
438
  );
345
439
  }
346
440
 
441
+ function normalizeDefaults(value: unknown): Record<string, unknown> | undefined {
442
+ if (value === undefined) return undefined;
443
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
444
+ throw new Error(
445
+ ExecutionOutput.formatToolText("Tool defaults must be an object."),
446
+ );
447
+ }
448
+ return { ...(value as Record<string, unknown>) };
449
+ }
450
+
451
+ function normalizeFromReference(
452
+ value: string,
453
+ resolutionContext: RecipesContext.RecipeResolutionContext,
454
+ ): string {
455
+ const trimmed = value.trim();
456
+ if (!trimmed) {
457
+ throw new Error(
458
+ ExecutionOutput.formatToolText(
459
+ "Tool from must be a non-empty Recipe reference. Next: retry register_tool with from=<skill>/<recipe> or an explicit .json/.md path.",
460
+ ),
461
+ );
462
+ }
463
+ if (trimmed.startsWith("std:")) {
464
+ throw new Error(
465
+ ExecutionOutput.formatToolText(
466
+ "std: Recipe references were removed; use <skill>/<recipe> or an explicit .json/.md file path.",
467
+ ),
468
+ );
469
+ }
470
+ if (trimmed.startsWith("skill:")) {
471
+ throw new Error(
472
+ ExecutionOutput.formatToolText(
473
+ `skill: Recipe references were removed; use ${trimmed.slice("skill:".length)} without a prefix.`,
474
+ ),
475
+ );
476
+ }
477
+ if (trimmed.endsWith(".json") || trimmed.endsWith(".md")) {
478
+ return resolve(resolutionContext.cwd, trimmed);
479
+ }
480
+ if (!looksLikeRecipeReference(trimmed)) {
481
+ throw new Error(
482
+ ExecutionOutput.formatToolText(
483
+ "Tool from must use <skill>/<recipe> or an explicit .json/.md file path.",
484
+ ),
485
+ );
486
+ }
487
+ return trimmed;
488
+ }
489
+
347
490
  function buildAuthoredRecipe(
348
491
  input: RegisterToolInput,
349
492
  existing: Config.RegisteredTool | undefined,
@@ -355,14 +498,40 @@ function buildAuthoredRecipe(
355
498
  : Schema.parseToolArgDeclarations(input.args);
356
499
  if (explicitArgs?.error)
357
500
  throw new Error(ExecutionOutput.formatToolText(explicitArgs.error));
501
+ const from = typeof input.from === "string" ? input.from.trim() : "";
358
502
  const description = (input.description ?? existing?.description ?? "").trim();
359
- if (!description) {
503
+ if (!description && !from) {
360
504
  throw new Error(
361
505
  ExecutionOutput.formatToolText(
362
- "Tool description is required unless deleting.",
506
+ "Tool description is required for command-template registration.",
363
507
  ),
364
508
  );
365
509
  }
510
+ const inputDefaults = normalizeDefaults(input.defaults);
511
+ if (from) {
512
+ const runtimeOwnedDefault = Object.keys(inputDefaults ?? {}).find((key) =>
513
+ RecipesReferences.isRuntimeOwnedRecipeInput(key),
514
+ );
515
+ if (runtimeOwnedDefault) {
516
+ throw new Error(
517
+ ExecutionOutput.formatToolText(
518
+ `Tool defaults cannot set runtime-owned input: ${runtimeOwnedDefault}. Next: remove that default and retry register_tool; the runtime supplies it.`,
519
+ ),
520
+ );
521
+ }
522
+ const authored: Record<string, unknown> = {
523
+ ...(description ? { description } : {}),
524
+ template: from,
525
+ };
526
+ const retainedDefaults =
527
+ inputDefaults ??
528
+ (existingRecipe?.defaults && typeof existingRecipe.defaults === "object"
529
+ ? (existingRecipe.defaults as Record<string, unknown>)
530
+ : undefined);
531
+ if (retainedDefaults && Object.keys(retainedDefaults).length > 0)
532
+ authored.defaults = retainedDefaults;
533
+ return authored;
534
+ }
366
535
  const template = getInputTemplate(input.template);
367
536
  if (template === null) {
368
537
  throw new Error(
@@ -386,21 +555,71 @@ function buildAuthoredRecipe(
386
555
  if (typeof input.async === "boolean") authored.async = input.async;
387
556
  if (explicitArgs) {
388
557
  authored.args = explicitArgs.declarations;
389
- if (Object.keys(explicitArgs.defaults).length > 0)
390
- authored.defaults = explicitArgs.defaults;
558
+ const defaults = { ...explicitArgs.defaults, ...(inputDefaults ?? {}) };
559
+ if (Object.keys(defaults).length > 0) authored.defaults = defaults;
560
+ else delete authored.defaults;
561
+ } else if (inputDefaults !== undefined) {
562
+ if (Object.keys(inputDefaults).length > 0) authored.defaults = inputDefaults;
391
563
  else delete authored.defaults;
392
564
  }
393
- if (input.values && typeof input.values === "object")
394
- authored.values = input.values;
395
565
  return authored;
396
566
  }
397
567
 
568
+ function assertRegisterToolInputModes(input: RegisterToolInput): void {
569
+ const record = input as RegisterToolInput & Record<string, unknown>;
570
+ if (Object.hasOwn(record, "values")) {
571
+ throw new Error(
572
+ ExecutionOutput.formatToolText(
573
+ "register_tool values was removed. Next: retry register_tool with defaults for caller inputs, or use from=<skill>/<recipe> for maintained composition.",
574
+ ),
575
+ );
576
+ }
577
+ const fromProvided = Object.hasOwn(record, "from");
578
+ if (
579
+ fromProvided &&
580
+ (typeof input.from !== "string" || input.from.trim().length === 0)
581
+ ) {
582
+ throw new Error(
583
+ ExecutionOutput.formatToolText(
584
+ "Tool from must be a non-empty Recipe reference. Next: retry register_tool with from=<skill>/<recipe> or an explicit .json/.md path.",
585
+ ),
586
+ );
587
+ }
588
+ const draftProvided =
589
+ typeof input.draft === "string" && input.draft.trim().length > 0;
590
+ const templateProvided = Object.hasOwn(record, "template");
591
+ const modeCount =
592
+ Number(fromProvided) + Number(draftProvided) + Number(templateProvided);
593
+ if (modeCount > 1) {
594
+ throw new Error(
595
+ ExecutionOutput.formatToolText(
596
+ "Use exactly one register_tool source mode: from, template, or draft. Next: remove the extra source fields and retry register_tool.",
597
+ ),
598
+ );
599
+ }
600
+ if (
601
+ fromProvided &&
602
+ (Object.hasOwn(record, "args") || Object.hasOwn(record, "async"))
603
+ ) {
604
+ throw new Error(
605
+ ExecutionOutput.formatToolText(
606
+ "register_tool from inherits args and async behavior. Next: retry register_tool with from and optional defaults only; omit args and async.",
607
+ ),
608
+ );
609
+ }
610
+ }
611
+
398
612
  function executeRegisterToolUnlocked<TContext>(
399
613
  params: unknown,
400
614
  ctx: TContext,
401
615
  deps: RegisterToolRuntimeDeps<TContext>,
402
616
  ): RegisterToolResult {
403
- const input = params as RegisterToolInput;
617
+ const rawInput =
618
+ params && typeof params === "object"
619
+ ? (params as RegisterToolInput)
620
+ : ({} as RegisterToolInput);
621
+ assertRegisterToolInputModes(rawInput);
622
+ let input = rawInput;
404
623
  if (!input.name) return listTools(deps);
405
624
  const name = Identity.normalizeToolName(input.name);
406
625
  if (!name)
@@ -413,6 +632,27 @@ function executeRegisterToolUnlocked<TContext>(
413
632
  if (typeof input.draft === "string" && input.draft.trim()) {
414
633
  return promoteDraftRecipe(name, input, ctx, deps);
415
634
  }
635
+ const resolutionContext = getRegistrationResolutionContext(ctx, deps);
636
+ const requestedSource =
637
+ typeof rawInput.from === "string" ? rawInput.from.trim() : undefined;
638
+ if (typeof input.from === "string") {
639
+ try {
640
+ input = {
641
+ ...input,
642
+ from: normalizeFromReference(input.from, resolutionContext),
643
+ };
644
+ } catch (error) {
645
+ const reason = error instanceof Error ? error.message.trim() : String(error);
646
+ const diagnosticSource = requestedSource?.startsWith("skill:")
647
+ ? requestedSource.slice("skill:".length)
648
+ : requestedSource ?? "";
649
+ throw new Error(
650
+ ExecutionOutput.formatToolText(
651
+ `Recipe source "${requestedSource}" is invalid: ${reason}. Active Skills: ${activeSkillSummary(resolutionContext)}. Next: ${recipeDoctorAction(diagnosticSource)}`,
652
+ ),
653
+ );
654
+ }
655
+ }
416
656
  const templateProvided = Object.hasOwn(input, "template");
417
657
  const template = getInputTemplate(input.template);
418
658
  if (templateProvided && (template === null || template === ""))
@@ -429,10 +669,10 @@ function executeRegisterToolUnlocked<TContext>(
429
669
  ),
430
670
  );
431
671
  }
432
- if (template === undefined && !existing) {
672
+ if (template === undefined && !input.from && !existing) {
433
673
  throw new Error(
434
674
  ExecutionOutput.formatToolText(
435
- "Tool template is required for new registrations.",
675
+ "New registration requires exactly one source mode: from, template, or draft. Next: retry register_tool with one source field.",
436
676
  ),
437
677
  );
438
678
  }
@@ -442,29 +682,20 @@ function executeRegisterToolUnlocked<TContext>(
442
682
  existing,
443
683
  authoritative.recipe,
444
684
  );
445
- const resolutionContext = getRegistrationResolutionContext(ctx, deps);
446
685
  const candidate = RecipesDiscovery.admitUserRecipe(
447
686
  recipePath,
448
687
  resolutionContext,
449
688
  authoredRecipe,
450
689
  );
451
690
  if (!candidate.validated || !candidate.tool) {
452
- throw new Error(
453
- ExecutionOutput.formatToolText(
454
- `Tool recipe validation failed: ${candidate.diagnostics.join("; ")}`,
455
- ),
456
- );
691
+ const sourceDiagnostic = requestedSource
692
+ ? `Recipe source "${requestedSource}" validation failed: ${candidate.diagnostics.join("; ")}. Active Skills: ${activeSkillSummary(resolutionContext)}. Next: ${recipeDoctorAction(requestedSource)}`
693
+ : `Tool recipe validation failed: ${candidate.diagnostics.join("; ")}`;
694
+ throw new Error(ExecutionOutput.formatToolText(sourceDiagnostic));
457
695
  }
458
696
  const activeBefore = deps.getActiveTools();
459
697
  let persisted = false;
460
- let activation:
461
- | {
462
- active_tool: boolean;
463
- activation: "current_session" | "unverified";
464
- callable_now: boolean;
465
- host_registered: boolean;
466
- }
467
- | undefined;
698
+ let activation: RuntimeActivation | undefined;
468
699
  let cfg: Config.RegisteredTool;
469
700
  try {
470
701
  persistToolRecipe(deps, name, authoredRecipe);
@@ -488,7 +719,7 @@ function executeRegisterToolUnlocked<TContext>(
488
719
  !activation.callable_now)
489
720
  ) {
490
721
  throw new Error(
491
- `Host activation verification failed: ${JSON.stringify(activation)}`,
722
+ `Tool "${name}" activation failed at ${activationBoundary(activation)}; host_registered=${activation.host_registered}, active_tool=${activation.active_tool}, callable_now=${activation.callable_now}. Registration was rolled back. Next: inspect target=runtime view=status; do not use spawn as proof of tool invocation.`,
492
723
  );
493
724
  }
494
725
  } catch (error) {
@@ -520,31 +751,37 @@ function executeRegisterToolUnlocked<TContext>(
520
751
  templateWarnings.length > 0
521
752
  ? `\nWarnings:\n${templateWarnings.map((warning) => `- ${warning}`).join("\n")}`
522
753
  : "";
523
- const outcomeText = activation?.callable_now
524
- ? `${existing ? "Updated" : "Registered"} and activated tool "${name}"`
525
- : `Persisted tool "${name}"; current-session activation is unverified`;
754
+ const callableNow = activation?.callable_now ?? false;
755
+ const source = requestedSource ?? "template";
756
+ const argSummary = RecipesDiscovery.summarizeRegisteredToolArgs(cfg);
757
+ const nextActions = registrationNextActions(name, source, callableNow);
758
+ const outcomeText = callableNow
759
+ ? `${existing ? "Updated" : "Registered"} tool "${name}" from ${source}; callable_now=true`
760
+ : `Persisted tool "${name}" from ${source}; callable_now=false; activation_boundary=${activationBoundary(activation)}`;
526
761
  return {
527
762
  content: [
528
763
  textContent(
529
764
  ExecutionOutput.formatToolText(
530
- `${outcomeText} (args: ${Schema.formatToolArgs(cfg.args)}).${warningText}`,
765
+ `${outcomeText}; required=${Schema.formatToolArgs(argSummary.required)}; optional=${Schema.formatToolArgs(argSummary.optional)}; next=${nextActions[0]}.${warningText}`,
531
766
  ),
532
767
  ),
533
768
  ],
534
769
  details: {
535
770
  active_tool: activation?.active_tool ?? false,
536
771
  activation: activation?.activation ?? "unverified",
772
+ activation_boundary: activationBoundary(activation),
537
773
  args: cfg.args,
538
- callable_now: activation?.callable_now ?? false,
539
- config: recipePath,
774
+ callable_now: callableNow,
540
775
  defaults: cfg.defaults,
541
776
  host_registered: activation?.host_registered ?? false,
777
+ next_actions: nextActions,
778
+ optional_args: argSummary.optional,
542
779
  persisted: true,
543
780
  registry_active: tools.get(name) === cfg,
781
+ required_args: argSummary.required,
544
782
  resolved: true,
783
+ source,
545
784
  ...(cfg.recipe?.async !== undefined ? { async: cfg.recipe.async } : {}),
546
- ...(cfg.recipe?.name ? { recipeName: cfg.recipe.name } : {}),
547
- ...(cfg.template ? { template: cfg.template } : {}),
548
785
  ...(templateWarnings.length > 0 ? { templateWarnings } : {}),
549
786
  tool: name,
550
787
  validated: true,
package/lib/runtime.ts CHANGED
@@ -12,6 +12,7 @@ import type { RegisteredToolExec } from "./execution.ts";
12
12
  import * as Paths from "./paths.ts";
13
13
  import type { RecipeResolutionContext } from "./recipes-context.ts";
14
14
  import * as RecipesDiscovery from "./recipes-discovery.ts";
15
+ import * as RecipesReferences from "./recipes-references.ts";
15
16
  import * as RecipesUsage from "./recipes-usage.ts";
16
17
  import * as ToolsLocal from "./tools-local.ts";
17
18
 
@@ -111,6 +112,25 @@ export function createAutoToolsRuntime(
111
112
  template: cfg.template,
112
113
  });
113
114
  }
115
+ function registeredToolSource(cfg: Config.RegisteredTool): string {
116
+ const raw = cfg.sourcePath
117
+ ? RecipesReferences.readRawRecipeConfig(cfg.sourcePath)
118
+ : undefined;
119
+ const template = raw?.template;
120
+ if (
121
+ typeof template === "string" &&
122
+ /^[a-z][a-z0-9]*(?:-[a-z0-9]+)*\/[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/u.test(template)
123
+ ) {
124
+ return template;
125
+ }
126
+ if (
127
+ typeof template === "string" &&
128
+ (template.endsWith(".json") || template.endsWith(".md"))
129
+ ) {
130
+ return `<explicit-file:${basename(template)}>`;
131
+ }
132
+ return "template";
133
+ }
114
134
  function deactivateMissingRuntimeTools(activeNames: Set<string>): void {
115
135
  const stale = [...runtimeTools].filter((name) => !activeNames.has(name));
116
136
  if (stale.length === 0) return;
@@ -187,6 +207,7 @@ export function createAutoToolsRuntime(
187
207
  lines.push("Other registry diagnostics:");
188
208
  lines.push(...other.map((warning) => `• ${warning}`));
189
209
  }
210
+ lines.push("Next: inspect target=recipes view=doctor");
190
211
  return `${lines.join("\n")}\n`;
191
212
  }
192
213
  function loadTools(
@@ -248,9 +269,27 @@ export function createAutoToolsRuntime(
248
269
  const usage = cfg.sourcePath
249
270
  ? RecipesUsage.readRecipeUsage(cfg.sourcePath)
250
271
  : undefined;
272
+ const activation = activationFor(name);
273
+ const args = RecipesDiscovery.summarizeRegisteredToolArgs(cfg);
251
274
  return {
252
- ...activationFor(name),
253
- launch_kind: usage?.launch_kind,
275
+ ...activation,
276
+ activation_boundary: activation.callable_now
277
+ ? "current_session"
278
+ : !activation.host_registered
279
+ ? "host_registration"
280
+ : "active_tool_set",
281
+ persisted: Boolean(cfg.sourcePath),
282
+ registry_active: true,
283
+ source: registeredToolSource(cfg),
284
+ required_args: args.required,
285
+ optional_args: args.optional,
286
+ next_actions: activation.callable_now
287
+ ? [`call tool ${name}`]
288
+ : [
289
+ `register_tool name=${name} update=true`,
290
+ `inspect target=tool:${name} view=status`,
291
+ ],
292
+ ...(usage?.launch_kind ? { launch_kind: usage.launch_kind } : {}),
254
293
  spawn_calls: Number(usage?.spawn_calls ?? 0),
255
294
  tool_calls: Number(usage?.tool_calls ?? 0),
256
295
  };