@llblab/pi-actors 0.46.0 → 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 (78) hide show
  1. package/AGENTS.md +7 -5
  2. package/CHANGELOG.md +20 -0
  3. package/README.md +2 -1
  4. package/dist/lib/async-runs.d.ts +1 -0
  5. package/dist/lib/async-runs.js +4 -1
  6. package/dist/lib/execution.d.ts +1 -0
  7. package/dist/lib/execution.js +1 -0
  8. package/dist/lib/extension-runtime.js +25 -9
  9. package/dist/lib/inspector.js +1 -0
  10. package/dist/lib/prompts.d.ts +6 -5
  11. package/dist/lib/prompts.js +22 -23
  12. package/dist/lib/recipes-context.d.ts +12 -3
  13. package/dist/lib/recipes-context.js +28 -4
  14. package/dist/lib/recipes-discovery.d.ts +22 -0
  15. package/dist/lib/recipes-discovery.js +108 -23
  16. package/dist/lib/recipes-references.d.ts +18 -0
  17. package/dist/lib/recipes-references.js +129 -38
  18. package/dist/lib/registry.d.ts +23 -6
  19. package/dist/lib/registry.js +294 -101
  20. package/dist/lib/runtime.d.ts +30 -3
  21. package/dist/lib/runtime.js +108 -9
  22. package/dist/lib/tools-inspect.d.ts +3 -0
  23. package/dist/lib/tools-inspect.js +190 -26
  24. package/dist/lib/tools-local.d.ts +2 -2
  25. package/dist/lib/tools-local.js +5 -2
  26. package/dist/lib/tools-register.js +2 -1
  27. package/dist/lib/tools-response.js +7 -1
  28. package/dist/lib/tools-spawn.d.ts +2 -2
  29. package/dist/lib/tools-spawn.js +1 -1
  30. package/dist/lib/tools.d.ts +4 -1
  31. package/dist/lib/tools.js +2 -0
  32. package/dist/scripts/conformance.mjs +1 -0
  33. package/dist/skills/actors/SKILL.md +76 -65
  34. package/dist/skills/actors/references/diagnostics.md +44 -0
  35. package/dist/skills/actors/references/persistent-tools.md +74 -0
  36. package/dist/skills/actors/references/recipes.md +51 -0
  37. package/dist/skills/actors/references/runs.md +39 -0
  38. package/dist/skills/artifacts/SKILL.md +24 -7
  39. package/dist/skills/media/SKILL.md +35 -7
  40. package/dist/skills/project-work/SKILL.md +28 -7
  41. package/dist/skills/recipe-memory/SKILL.md +27 -7
  42. package/dist/skills/swarm/SKILL.md +41 -445
  43. package/dist/skills/swarm/references/development-swarm.md +87 -539
  44. package/dist/skills/swarm/references/review-swarms.md +115 -0
  45. package/docs/README.md +5 -5
  46. package/docs/recipe-library.md +15 -10
  47. package/docs/template-recipes.md +3 -1
  48. package/docs/tool-registry.md +18 -6
  49. package/lib/async-runs.ts +5 -1
  50. package/lib/execution.ts +2 -0
  51. package/lib/extension-runtime.ts +33 -14
  52. package/lib/inspector.ts +1 -0
  53. package/lib/prompts.ts +24 -24
  54. package/lib/recipes-context.ts +49 -4
  55. package/lib/recipes-discovery.ts +186 -25
  56. package/lib/recipes-references.ts +176 -44
  57. package/lib/registry.ts +441 -113
  58. package/lib/runtime.ts +147 -12
  59. package/lib/tools-inspect.ts +254 -28
  60. package/lib/tools-local.ts +9 -3
  61. package/lib/tools-register.ts +4 -3
  62. package/lib/tools-response.ts +7 -1
  63. package/lib/tools-spawn.ts +3 -2
  64. package/lib/tools.ts +4 -1
  65. package/package.json +1 -1
  66. package/scripts/conformance.mjs +1 -0
  67. package/skills/actors/SKILL.md +76 -65
  68. package/skills/actors/references/diagnostics.md +44 -0
  69. package/skills/actors/references/persistent-tools.md +74 -0
  70. package/skills/actors/references/recipes.md +51 -0
  71. package/skills/actors/references/runs.md +39 -0
  72. package/skills/artifacts/SKILL.md +24 -7
  73. package/skills/media/SKILL.md +35 -7
  74. package/skills/project-work/SKILL.md +28 -7
  75. package/skills/recipe-memory/SKILL.md +27 -7
  76. package/skills/swarm/SKILL.md +41 -445
  77. package/skills/swarm/references/development-swarm.md +87 -539
  78. package/skills/swarm/references/review-swarms.md +115 -0
@@ -94,10 +94,12 @@ function getRecipeRiskLabels(config) {
94
94
  "risk.platform_specific",
95
95
  ].filter((label) => labels.has(label));
96
96
  }
97
- function readDiscoveredRecipe(root, file, priority, defaultTool = false, mutableUsage = false) {
97
+ function readDiscoveredRecipe(root, file, priority, defaultTool = false, mutableUsage = false, resolutionContext) {
98
98
  const id = RecipesReferences.getRecipeIdFromPath(file);
99
99
  try {
100
- const config = RecipesReferences.readResolvedRecipeConfig(file);
100
+ const config = RecipesReferences.readResolvedRecipeConfig(file, [], {
101
+ skillContext: resolutionContext?.activeSkills,
102
+ });
101
103
  const invalid = !config;
102
104
  const disabled = config?.disabled === true;
103
105
  return {
@@ -112,6 +114,7 @@ function readDiscoveredRecipe(root, file, priority, defaultTool = false, mutable
112
114
  disabled,
113
115
  tool: defaultTool && !disabled && !invalid,
114
116
  mutableUsage,
117
+ resolutionContext,
115
118
  diagnostics: getRecipeConfigDiagnostics(file, config),
116
119
  riskLabels: getRecipeRiskLabels(config),
117
120
  shadows: [],
@@ -129,8 +132,9 @@ function readDiscoveredRecipe(root, file, priority, defaultTool = false, mutable
129
132
  disabled: false,
130
133
  tool: false,
131
134
  mutableUsage,
135
+ resolutionContext,
132
136
  diagnostics: [
133
- `Failed to load recipe ${file}: ${error instanceof Error ? error.message : String(error)}`,
137
+ `Recipe ${id} rejected: ${error instanceof Error ? error.message : String(error)}`,
134
138
  ],
135
139
  riskLabels: [],
136
140
  shadows: [],
@@ -147,6 +151,7 @@ function filesForSource(source) {
147
151
  file: source.file,
148
152
  defaultTool,
149
153
  mutableUsage,
154
+ resolutionContext: source.resolutionContext,
150
155
  },
151
156
  ];
152
157
  return source.root
@@ -155,6 +160,7 @@ function filesForSource(source) {
155
160
  file,
156
161
  defaultTool,
157
162
  mutableUsage,
163
+ resolutionContext: source.resolutionContext,
158
164
  }))
159
165
  : [];
160
166
  }
@@ -222,7 +228,7 @@ function getRecipeRootDiagnostics(sources) {
222
228
  return diagnostics;
223
229
  }
224
230
  export function discoverRecipeSources(sources) {
225
- const entries = sources.flatMap((source, priority) => filesForSource(source).map(({ root, file, defaultTool, mutableUsage }) => readDiscoveredRecipe(root, file, priority, defaultTool, mutableUsage)));
231
+ const entries = sources.flatMap((source, priority) => filesForSource(source).map(({ root, file, defaultTool, mutableUsage, resolutionContext }) => readDiscoveredRecipe(root, file, priority, defaultTool, mutableUsage, resolutionContext)));
226
232
  const byId = new Map();
227
233
  for (const entry of entries) {
228
234
  const bucket = byId.get(entry.id) ?? [];
@@ -647,12 +653,8 @@ export function summarizeDiscovery(result) {
647
653
  integrity_manifest: createRecipeIntegrityManifest(result),
648
654
  };
649
655
  }
650
- export function toRegisteredTool(entry) {
651
- if (!entry.tool || entry.invalid || entry.disabled || !entry.config)
652
- return undefined;
653
- const cfg = entry.config;
654
- const template = entry.path;
655
- const description = cfg.description ?? `Execute template recipe: ${entry.id}`;
656
+ function projectRegisteredTool(identity, path, cfg, mutableUsage) {
657
+ const description = cfg.description ?? `Execute template recipe: ${identity}`;
656
658
  const argTemplate = cfg.template;
657
659
  const argTemplateConfig = typeof argTemplate === "object" && !Array.isArray(argTemplate)
658
660
  ? {
@@ -669,24 +671,27 @@ export function toRegisteredTool(entry) {
669
671
  return [parsed.arg, parsed.type];
670
672
  }));
671
673
  assertToolSafeRepeatConfig(argTemplateConfig, explicitArgTypes, { ...(cfg.defaults ?? {}), ...(cfg.values ?? {}) });
672
- const argTypes = Schema.getTemplateArgTypes(argTemplateConfig);
673
- Schema.assertCompatibleToolArgTypes(explicitArgTypes, argTypes);
674
+ const inferredArgTypes = Schema.getTemplateArgTypes(argTemplateConfig);
675
+ Schema.assertCompatibleToolArgTypes(explicitArgTypes, inferredArgTypes);
676
+ const effectiveArgTypes = { ...inferredArgTypes, ...explicitArgTypes };
677
+ const args = Schema.getToolArgNames(argTemplateConfig).filter((arg) => !RecipesReferences.isRuntimeOwnedRecipeInput(arg));
678
+ const callerArgSet = new Set(args);
679
+ const argTypes = Object.fromEntries(Object.entries(effectiveArgTypes).filter(([arg]) => callerArgSet.has(arg)));
680
+ const defaults = Object.fromEntries(Object.entries(cfg.defaults ?? {}).filter(([arg]) => callerArgSet.has(arg)));
681
+ const storedArgs = cfg.args?.filter((arg) => callerArgSet.has(Schema.parseToolArgToken(String(arg)).arg));
674
682
  return {
675
- name: entry.id,
683
+ name: identity,
676
684
  description,
677
- template,
685
+ template: path,
678
686
  recipe: cfg,
679
- args: Schema.getToolArgNames(argTemplateConfig),
680
- defaults: Object.fromEntries(Object.entries(cfg.defaults ?? {}).map(([key, value]) => [
681
- key,
682
- String(value),
683
- ])),
687
+ args,
688
+ defaults: Object.fromEntries(Object.entries(defaults).map(([key, value]) => [key, String(value)])),
684
689
  ...(Object.keys(argTypes).length > 0 ? { argTypes } : {}),
685
- ...(entry.mutableUsage ? { sourcePath: entry.path } : {}),
686
- ...(cfg.args ? { storedArgs: cfg.args } : {}),
687
- ...(cfg.defaults
690
+ ...(mutableUsage ? { sourcePath: path } : {}),
691
+ ...(storedArgs ? { storedArgs } : {}),
692
+ ...(Object.keys(defaults).length > 0
688
693
  ? {
689
- storedDefaults: Object.fromEntries(Object.entries(cfg.defaults).map(([key, value]) => [
694
+ storedDefaults: Object.fromEntries(Object.entries(defaults).map(([key, value]) => [
690
695
  key,
691
696
  String(value),
692
697
  ])),
@@ -694,3 +699,83 @@ export function toRegisteredTool(entry) {
694
699
  : {}),
695
700
  };
696
701
  }
702
+ export function summarizeRegisteredToolArgs(tool) {
703
+ const publicArgs = tool.args.filter((arg) => !RecipesReferences.isRuntimeOwnedRecipeInput(arg));
704
+ const inlineDefaults = Schema.getExplicitToolArgDefaults(tool.recipe?.args);
705
+ const required = publicArgs.filter((arg) => !Object.hasOwn(tool.defaults, arg) &&
706
+ !Object.hasOwn(inlineDefaults, arg));
707
+ const requiredSet = new Set(required);
708
+ const optional = publicArgs.filter((arg) => !requiredSet.has(arg));
709
+ if (tool.recipe?.async === true) {
710
+ optional.push("run_id", "transport_context");
711
+ }
712
+ return { optional, required };
713
+ }
714
+ function boundedAdmissionDiagnostic(error) {
715
+ const message = error instanceof Error ? error.message : String(error);
716
+ return message.length <= 4096 ? message : `${message.slice(0, 4095)}…`;
717
+ }
718
+ export function admitUserRecipe(sourcePath, resolutionContext, authoredRecipe, mutableUsage = true) {
719
+ const identity = RecipesReferences.getRecipeIdFromPath(sourcePath);
720
+ try {
721
+ const effectiveRecipe = RecipesReferences.readResolvedRecipeConfig(sourcePath, [], {
722
+ ...(authoredRecipe
723
+ ? { authoredEntry: { path: sourcePath, recipe: authoredRecipe } }
724
+ : {}),
725
+ skillContext: resolutionContext.activeSkills,
726
+ });
727
+ if (!effectiveRecipe) {
728
+ return {
729
+ diagnostics: [
730
+ authoredRecipe
731
+ ? "Recipe must define a valid template."
732
+ : RecipesReferences.diagnoseRawRecipeConfigFailure(sourcePath) ??
733
+ "Recipe must define a valid template.",
734
+ ],
735
+ identity,
736
+ sourcePath,
737
+ validated: false,
738
+ };
739
+ }
740
+ if (effectiveRecipe.disabled === true) {
741
+ return {
742
+ diagnostics: ["Recipe is disabled."],
743
+ effectiveRecipe,
744
+ identity,
745
+ sourcePath,
746
+ validated: false,
747
+ };
748
+ }
749
+ const tool = projectRegisteredTool(identity, sourcePath, effectiveRecipe, mutableUsage);
750
+ return {
751
+ args: tool.args,
752
+ argTypes: tool.argTypes,
753
+ artifacts: effectiveRecipe.artifacts,
754
+ async: effectiveRecipe.async === true,
755
+ control: effectiveRecipe.control,
756
+ defaults: effectiveRecipe.defaults,
757
+ diagnostics: [],
758
+ effectiveRecipe,
759
+ identity,
760
+ sourcePath,
761
+ tool,
762
+ validated: true,
763
+ };
764
+ }
765
+ catch (error) {
766
+ return {
767
+ diagnostics: [boundedAdmissionDiagnostic(error)],
768
+ identity,
769
+ sourcePath,
770
+ validated: false,
771
+ };
772
+ }
773
+ }
774
+ export function toRegisteredTool(entry) {
775
+ if (!entry.tool || entry.invalid || entry.disabled || !entry.config)
776
+ return undefined;
777
+ if (entry.resolutionContext) {
778
+ return admitUserRecipe(entry.path, entry.resolutionContext, undefined, entry.mutableUsage).tool;
779
+ }
780
+ return projectRegisteredTool(entry.id, entry.path, entry.config, entry.mutableUsage);
781
+ }
@@ -56,9 +56,15 @@ export interface TemplateRecipeContextRecord {
56
56
  source_kind: "active_skill_component" | "explicit_file_recipe" | "user_registry_capability";
57
57
  }
58
58
  export interface ReadResolvedRecipeConfigOptions {
59
+ authoredEntry?: {
60
+ path: string;
61
+ recipe: Record<string, unknown>;
62
+ };
59
63
  includeActorRecipeContext?: boolean;
60
64
  skillContext?: ActiveSkillRecipeContext;
61
65
  }
66
+ export declare const RUNTIME_OWNED_RECIPE_INPUT_NAMES: readonly string[];
67
+ export declare function isRuntimeOwnedRecipeInput(name: string): boolean;
62
68
  export interface ActiveSkillRecipeSource {
63
69
  name: string;
64
70
  filePath?: string;
@@ -77,6 +83,18 @@ export interface ActiveSkillRecipeComponent {
77
83
  skill: string;
78
84
  stem: string;
79
85
  }
86
+ export interface SkillRecipeComponentDiagnostic {
87
+ file?: string;
88
+ reason: string;
89
+ skill: string;
90
+ stem?: string;
91
+ }
92
+ export interface SkillRecipeComponentInventory {
93
+ components: ActiveSkillRecipeComponent[];
94
+ partial: boolean;
95
+ rejected: SkillRecipeComponentDiagnostic[];
96
+ }
97
+ export declare function inventoryActiveSkillRecipeComponents(context: ActiveSkillRecipeContext): SkillRecipeComponentInventory;
80
98
  export declare function listActiveSkillRecipeComponents(context: ActiveSkillRecipeContext): ActiveSkillRecipeComponent[];
81
99
  export declare function resolveRecipePath(value: string, baseDir?: string): string;
82
100
  export declare function resolveRecipeReferencePath(value: unknown, baseDir?: string, context?: ActiveSkillRecipeContext): string | undefined;
@@ -11,7 +11,24 @@ import * as RecipeControl from "./recipe-control.js";
11
11
  import * as Schema from "./schema.js";
12
12
  const MAX_RECIPE_FILE_BYTES = 1024 * 1024;
13
13
  const MAX_RECIPE_IMPORT_DEPTH = 32;
14
- const RUNTIME_RECIPE_VALUE_NAMES = new Set(["recipe_dir", "skill_dir"]);
14
+ const RESERVED_AUTHORED_RECIPE_VALUE_NAMES = new Set([
15
+ "recipe_dir",
16
+ "skill_dir",
17
+ ]);
18
+ export const RUNTIME_OWNED_RECIPE_INPUT_NAMES = Object.freeze([
19
+ "owner_id",
20
+ "recipe_dir",
21
+ "run_instance_id",
22
+ "runtime_state_root",
23
+ "session_id",
24
+ "skill_dir",
25
+ "state_dir",
26
+ "trace_file",
27
+ ]);
28
+ const RUNTIME_OWNED_RECIPE_INPUT_SET = new Set(RUNTIME_OWNED_RECIPE_INPUT_NAMES);
29
+ export function isRuntimeOwnedRecipeInput(name) {
30
+ return RUNTIME_OWNED_RECIPE_INPUT_SET.has(name);
31
+ }
15
32
  export function createActiveSkillRecipeContext(skills) {
16
33
  const rootsByName = new Map();
17
34
  for (const skill of skills) {
@@ -49,19 +66,31 @@ function nestedRecipeFiles(root) {
49
66
  return entry.isFile() && /\.(?:json|md)$/u.test(entry.name) ? [path] : [];
50
67
  });
51
68
  }
52
- export function listActiveSkillRecipeComponents(context) {
69
+ export function inventoryActiveSkillRecipeComponents(context) {
53
70
  const components = [];
71
+ const rejected = [];
54
72
  for (const [skill, roots] of Object.entries(context.namespaces)) {
55
- if (roots.length > 1)
56
- throw new Error(`Duplicate active Skill identity ${skill}: ${roots.join(", ")}`);
73
+ if (roots.length > 1) {
74
+ rejected.push({
75
+ reason: `Duplicate active Skill identity ${skill} (${roots.length} active definitions); exact Recipe resolution is ambiguous. Next: inspect target=recipes view=doctor identity=${skill}/<recipe>`,
76
+ skill,
77
+ });
78
+ continue;
79
+ }
57
80
  const root = roots[0];
58
81
  if (!root || !existsSync(root))
59
82
  continue;
60
83
  const direct = readdirSync(root, { withFileTypes: true });
61
84
  for (const entry of direct.filter((candidate) => candidate.isDirectory())) {
62
85
  const nested = nestedRecipeFiles(join(root, entry.name));
63
- if (nested.length > 0)
64
- throw new Error(`Nested Skill Recipe file is not addressable: ${nested[0]}`);
86
+ for (const file of nested) {
87
+ rejected.push({
88
+ file,
89
+ reason: `Nested Skill Recipe file is not addressable: ${file}`,
90
+ skill,
91
+ stem: getRecipeIdFromPath(file),
92
+ });
93
+ }
65
94
  }
66
95
  const files = direct
67
96
  .filter((entry) => entry.isFile() && /\.(?:json|md)$/u.test(entry.name))
@@ -74,28 +103,58 @@ export function listActiveSkillRecipeComponents(context) {
74
103
  byStem.set(stem, matches);
75
104
  }
76
105
  for (const [stem, matches] of [...byStem].sort(([a], [b]) => a.localeCompare(b))) {
77
- if (!/^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/u.test(stem))
78
- throw new Error(`Invalid Skill Recipe filename stem: ${stem}`);
79
- if (matches.length > 1)
80
- throw new Error(`Skill Recipe stem collision: ${skill}/${stem} has both .json and .md files`);
106
+ if (!/^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/u.test(stem)) {
107
+ rejected.push({
108
+ file: matches[0],
109
+ reason: `Invalid Skill Recipe filename stem: ${stem}`,
110
+ skill,
111
+ stem,
112
+ });
113
+ continue;
114
+ }
115
+ if (matches.length > 1) {
116
+ rejected.push({
117
+ file: matches[0],
118
+ reason: `Skill Recipe stem collision: ${skill}/${stem} has both .json and .md files`,
119
+ skill,
120
+ stem,
121
+ });
122
+ continue;
123
+ }
81
124
  const file = matches[0];
82
- const raw = readRawRecipeConfig(file);
83
- if (!raw || !Object.hasOwn(raw, "template"))
84
- throw new Error(`Invalid Skill Recipe component: ${skill}/${stem}`);
85
- assertRecipeHasNoDeclaredName(raw);
86
- components.push({
87
- file,
88
- identity: `${skill}/${stem}`,
89
- imports: Object.fromEntries(Object.entries(getRecipeImports(raw)).map(([alias, value]) => [
90
- alias,
91
- getImportFrom(value),
92
- ])),
93
- skill,
94
- stem,
95
- });
125
+ try {
126
+ const raw = readRawRecipeConfig(file);
127
+ if (!raw || !Object.hasOwn(raw, "template"))
128
+ throw new Error(`Invalid Skill Recipe component: ${skill}/${stem}`);
129
+ assertRecipeHasNoDeclaredName(raw);
130
+ components.push({
131
+ file,
132
+ identity: `${skill}/${stem}`,
133
+ imports: Object.fromEntries(Object.entries(getRecipeImports(raw)).map(([alias, value]) => [
134
+ alias,
135
+ getImportFrom(value),
136
+ ])),
137
+ skill,
138
+ stem,
139
+ });
140
+ }
141
+ catch (error) {
142
+ rejected.push({
143
+ file,
144
+ reason: error instanceof Error ? error.message : String(error),
145
+ skill,
146
+ stem,
147
+ });
148
+ }
96
149
  }
97
150
  }
98
- return components;
151
+ return { components, partial: rejected.length > 0, rejected };
152
+ }
153
+ export function listActiveSkillRecipeComponents(context) {
154
+ const inventory = inventoryActiveSkillRecipeComponents(context);
155
+ if (inventory.rejected.length > 0)
156
+ throw new Error(inventory.rejected[0].reason);
157
+ return inventory.components;
99
158
  }
100
159
  const SKILL_RECIPE_REFERENCE = /^([a-z][a-z0-9]*(?:-[a-z0-9]+)*)\/([a-z][a-z0-9]*(?:-[a-z0-9]+)*)$/;
101
160
  function qualifiedRecipeNameForFile(file, context) {
@@ -116,10 +175,11 @@ function qualifiedRecipeNameForFile(file, context) {
116
175
  }
117
176
  function assertRecipeReferencePrefixWasNotRemoved(value) {
118
177
  if (value.startsWith("std:")) {
119
- throw new Error("std: Recipe references were removed in pi-actors 0.46.\nUse <skill>/<recipe> or an explicit .json/.md file path.");
178
+ throw new Error("std: Recipe references were removed in pi-actors 0.46. Use <skill>/<recipe> or an explicit .json/.md file path. Next: inspect target=recipes view=status to identify the active owning Skill.");
120
179
  }
121
180
  if (value.startsWith("skill:")) {
122
- throw new Error("skill: Recipe references were removed in pi-actors 0.46.\nUse <skill>/<recipe> without a prefix.");
181
+ const identity = value.slice("skill:".length);
182
+ throw new Error(`skill: Recipe references were removed in pi-actors 0.46. Use ${identity} without a prefix. Next: inspect target=recipes view=doctor identity=${identity}`);
123
183
  }
124
184
  }
125
185
  function skillRecipeCandidates(value, context) {
@@ -129,12 +189,15 @@ function skillRecipeCandidates(value, context) {
129
189
  const [, skillName, recipeName] = match;
130
190
  const roots = [...(context.namespaces[skillName] ?? [])];
131
191
  if (roots.length === 0)
132
- throw new Error(`Active Skill Recipe not found: ${skillName}/${recipeName}`);
192
+ throw new Error(`Active Skill Recipe not found: ${skillName}/${recipeName}. Owning Skill "${skillName}" is not active. Next: activate Skill ${skillName}, then inspect target=recipes view=doctor identity=${skillName}/${recipeName}`);
133
193
  if (roots.length > 1)
134
- throw new Error(`Duplicate active Skill identity ${skillName}: ${roots.join(", ")}`);
194
+ throw new Error(`Duplicate active Skill identity ${skillName} (${roots.length} active definitions); ${skillName}/${recipeName} cannot resolve unambiguously. Next: inspect target=recipes view=doctor identity=${skillName}/${recipeName}`);
135
195
  const root = roots[0];
136
196
  const candidates = ["json", "md"].map((extension) => resolve(root, `${recipeName}.${extension}`));
137
197
  const existing = candidates.filter((candidate) => existsSync(candidate));
198
+ if (existing.length === 0) {
199
+ throw new Error(`Skill Recipe component not found: ${skillName}/${recipeName}; owning Skill "${skillName}" is active. Next: inspect target=recipes view=doctor identity=${skillName}/${recipeName}`);
200
+ }
138
201
  if (existing.length > 1) {
139
202
  throw new Error(`Skill Recipe stem collision: ${skillName}/${recipeName} has both .json and .md files`);
140
203
  }
@@ -164,13 +227,13 @@ function findOwningSkillDir(recipeFile) {
164
227
  function assertRecipeHasNoDeclaredName(raw) {
165
228
  if (!Object.hasOwn(raw, "name"))
166
229
  return;
167
- throw new Error("Recipe.name was removed in pi-actors 0.46.\nFile-backed Recipe identity is derived from its file name.");
230
+ throw new Error("Recipe.name was removed in pi-actors 0.46. File-backed Recipe identity is derived from its file name. Next: remove the name field, keep the intended filename stem, then inspect target=recipes view=doctor.");
168
231
  }
169
232
  function assertRuntimeRecipeValuesNotDeclared(raw) {
170
233
  const args = Array.isArray(raw.args)
171
234
  ? Schema.getExplicitToolArgNames(raw.args.map(String))
172
235
  : [];
173
- for (const name of RUNTIME_RECIPE_VALUE_NAMES) {
236
+ for (const name of RESERVED_AUTHORED_RECIPE_VALUE_NAMES) {
174
237
  if (args.includes(name) ||
175
238
  (isRecord(raw.defaults) && Object.hasOwn(raw.defaults, name)) ||
176
239
  (isRecord(raw.values) && Object.hasOwn(raw.values, name))) {
@@ -731,6 +794,28 @@ function getDirectDelegatedRecipe(value, currentRecipeFile, stack, options = {})
731
794
  }
732
795
  return undefined;
733
796
  }
797
+ function resolveDirectDelegationArgs(delegated, wrapperArgs, wrapperDefaults) {
798
+ if (!Array.isArray(delegated.args))
799
+ return Array.isArray(wrapperArgs) ? wrapperArgs.map(String) : undefined;
800
+ const delegatedSpec = Schema.parseToolArgDeclarationList(delegated.args);
801
+ if (delegatedSpec.error)
802
+ throw new Error(delegatedSpec.error);
803
+ const wrapperDeclarations = Array.isArray(wrapperArgs)
804
+ ? wrapperArgs.map(String)
805
+ : [];
806
+ const wrapperSpec = Schema.parseToolArgDeclarationList(wrapperDeclarations);
807
+ if (wrapperSpec.error)
808
+ throw new Error(wrapperSpec.error);
809
+ const declared = new Set(delegatedSpec.args);
810
+ for (const arg of wrapperSpec.args) {
811
+ if (!declared.has(arg))
812
+ throw new Error(`Unknown delegated Recipe argument: ${arg}`);
813
+ }
814
+ Schema.assertCompatibleToolArgTypes(delegatedSpec.argTypes, wrapperSpec.argTypes);
815
+ assertRecipeDefaultsDeclared(delegated.args, wrapperDefaults, "delegated Recipe default");
816
+ Schema.normalizeRuntimeValues(Object.fromEntries(Object.entries(wrapperDefaults ?? {}).filter(([, value]) => typeof value !== "string" || !/\{[^{}]+\}/.test(value))), delegatedSpec.argTypes);
817
+ return delegated.args;
818
+ }
734
819
  function expandImportNodes(value, imports, options = {}) {
735
820
  if (typeof value === "string")
736
821
  return value;
@@ -783,7 +868,9 @@ export function readResolvedRecipeConfig(file, stack = [], options = {}) {
783
868
  if (stack.length >= MAX_RECIPE_IMPORT_DEPTH) {
784
869
  throw new Error(`Recipe import depth exceeds limit ${MAX_RECIPE_IMPORT_DEPTH}: ${[...stack, path].join(" -> ")}`);
785
870
  }
786
- const raw = readRawRecipeConfig(path);
871
+ const raw = options.authoredEntry?.path === path
872
+ ? options.authoredEntry.recipe
873
+ : readRawRecipeConfig(path);
787
874
  if (!raw || !Object.hasOwn(raw, "template"))
788
875
  return undefined;
789
876
  assertRecipeHasNoDeclaredName(raw);
@@ -844,7 +931,15 @@ export function readResolvedRecipeConfig(file, stack = [], options = {}) {
844
931
  role: stack.length > 0 ? "import" : "entry",
845
932
  })
846
933
  : expandedTemplate;
847
- const mergedDefaults = mergeDefaults(delegated?.defaults, isRecord(substituted.defaults) ? substituted.defaults : undefined);
934
+ const wrapperDefaults = isRecord(substituted.defaults)
935
+ ? substituted.defaults
936
+ : undefined;
937
+ const mergedDefaults = mergeDefaults(delegated?.defaults, wrapperDefaults);
938
+ const effectiveArgs = delegated
939
+ ? resolveDirectDelegationArgs(delegated, substituted.args, wrapperDefaults)
940
+ : Array.isArray(substituted.args)
941
+ ? substituted.args
942
+ : undefined;
848
943
  const artifactSource = isRecord(substituted.artifacts)
849
944
  ? substituted.artifacts
850
945
  : delegated?.artifacts;
@@ -879,11 +974,7 @@ export function readResolvedRecipeConfig(file, stack = [], options = {}) {
879
974
  ? { imports: getRecipeImports(raw) }
880
975
  : {}),
881
976
  template: templateWithContext,
882
- ...(Array.isArray(substituted.args)
883
- ? { args: substituted.args }
884
- : Array.isArray(delegated?.args)
885
- ? { args: delegated.args }
886
- : {}),
977
+ ...(effectiveArgs ? { args: effectiveArgs } : {}),
887
978
  ...(mergedDefaults ? { defaults: mergedDefaults } : {}),
888
979
  ...(typeof substituted.parallel === "boolean"
889
980
  ? { parallel: substituted.parallel }
@@ -10,22 +10,32 @@ export interface RegisterToolInput {
10
10
  description?: string;
11
11
  async?: boolean;
12
12
  template?: CommandTemplates.CommandTemplateValue | null;
13
+ from?: string;
14
+ defaults?: Record<string, unknown>;
13
15
  draft?: string;
14
16
  args?: string;
15
17
  update?: boolean;
16
- values?: Record<string, unknown>;
17
18
  }
18
19
  export interface RegisterToolResultDetails {
20
+ active_tool?: boolean;
21
+ activation?: "current_session" | "unverified";
22
+ activation_boundary?: string;
19
23
  args?: string[];
20
24
  async?: boolean;
21
- config?: string;
25
+ callable_now?: boolean;
22
26
  defaults?: Record<string, string>;
23
- draft?: string;
27
+ host_registered?: boolean;
28
+ next_actions?: string[];
29
+ optional_args?: string[];
30
+ persisted?: boolean;
24
31
  promoted?: boolean;
25
- recipeName?: string;
26
- template?: CommandTemplates.CommandTemplateValue;
32
+ registry_active?: boolean;
33
+ required_args?: string[];
34
+ resolved?: boolean;
35
+ source?: string;
27
36
  templateWarnings?: string[];
28
37
  tool: string;
38
+ validated?: boolean;
29
39
  }
30
40
  export interface RegisterToolResult {
31
41
  content: Array<{
@@ -34,6 +44,12 @@ export interface RegisterToolResult {
34
44
  }>;
35
45
  details: RegisterToolResultDetails;
36
46
  }
47
+ interface RuntimeActivation {
48
+ active_tool: boolean;
49
+ activation: "current_session" | "unverified";
50
+ callable_now: boolean;
51
+ host_registered: boolean;
52
+ }
37
53
  export interface RegisterToolRuntimeDeps<TContext> {
38
54
  configPath: string;
39
55
  recipeRoot?: string;
@@ -41,8 +57,9 @@ export interface RegisterToolRuntimeDeps<TContext> {
41
57
  getTools: () => Map<string, Config.RegisteredTool>;
42
58
  getActiveTools: () => string[];
43
59
  notify: (ctx: TContext, message: string, type: "info" | "warning" | "error") => void;
44
- registerRuntimeTool: (cfg: Config.RegisteredTool) => void;
60
+ registerRuntimeTool: (cfg: Config.RegisteredTool) => RuntimeActivation | void;
45
61
  reservedToolNames: Set<string>;
46
62
  setActiveTools: (toolNames: string[]) => void;
47
63
  }
48
64
  export declare function executeRegisterTool<TContext>(params: unknown, ctx: TContext, deps: RegisterToolRuntimeDeps<TContext>): Promise<RegisterToolResult>;
65
+ export {};