@llblab/pi-actors 0.45.1 → 0.46.1

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 (228) hide show
  1. package/AGENTS.md +9 -7
  2. package/BACKLOG.md +582 -0
  3. package/CHANGELOG.md +17 -1
  4. package/README.md +27 -13
  5. package/dist/fixtures/protocol/recipe-summary.json +1 -1
  6. package/dist/index.js +1 -1
  7. package/dist/lib/async-runs.d.ts +5 -1
  8. package/dist/lib/async-runs.js +26 -24
  9. package/dist/lib/automatic-review-runtime.d.ts +2 -0
  10. package/dist/lib/automatic-review-runtime.js +12 -5
  11. package/dist/lib/draft-sleep.js +0 -1
  12. package/dist/lib/execution.d.ts +1 -0
  13. package/dist/lib/execution.js +1 -0
  14. package/dist/lib/extension-runtime.d.ts +1 -1
  15. package/dist/lib/extension-runtime.js +26 -4
  16. package/dist/lib/inspector.js +46 -7
  17. package/dist/lib/paths.d.ts +0 -1
  18. package/dist/lib/paths.js +0 -7
  19. package/dist/lib/prompts.d.ts +1 -1
  20. package/dist/lib/prompts.js +4 -4
  21. package/dist/lib/recipes-context.d.ts +12 -3
  22. package/dist/lib/recipes-context.js +30 -6
  23. package/dist/lib/recipes-discovery.d.ts +18 -0
  24. package/dist/lib/recipes-discovery.js +95 -22
  25. package/dist/lib/recipes-references.d.ts +42 -9
  26. package/dist/lib/recipes-references.js +248 -135
  27. package/dist/lib/registry.d.ts +14 -1
  28. package/dist/lib/registry.js +110 -87
  29. package/dist/lib/runtime.d.ts +30 -4
  30. package/dist/lib/runtime.js +74 -12
  31. package/dist/lib/tools-inspect.d.ts +3 -1
  32. package/dist/lib/tools-inspect.js +56 -9
  33. package/dist/lib/tools-local.d.ts +2 -0
  34. package/dist/lib/tools-local.js +5 -2
  35. package/dist/lib/tools-response.js +2 -0
  36. package/dist/lib/tools-spawn.d.ts +2 -0
  37. package/dist/lib/tools-spawn.js +24 -52
  38. package/dist/lib/tools.d.ts +4 -1
  39. package/dist/lib/tools.js +2 -0
  40. package/dist/scripts/build-dist.mjs +13 -4
  41. package/dist/skills/actors/SKILL.md +8 -8
  42. package/dist/skills/actors/recipes/recipe-validate.json +11 -0
  43. package/{recipes/utility-coordinator-lock-snapshot.json → dist/skills/actors/recipes/resource-locker-snapshot.json} +1 -3
  44. package/dist/{recipes → skills/actors/recipes}/resource-locker.json +1 -3
  45. package/dist/{recipes/utility-run-ops-snapshot.json → skills/actors/recipes/run-ops-snapshot.json} +1 -3
  46. package/dist/skills/actors/recipes/run-summary.json +9 -0
  47. package/dist/{scripts/locker.mjs → skills/actors/scripts/resource-locker.mjs} +2 -2
  48. package/dist/skills/actors/scripts/run-utils.mjs +164 -0
  49. package/{scripts → dist/skills/actors/scripts}/validate-recipe.mjs +134 -17
  50. package/dist/skills/artifacts/SKILL.md +16 -0
  51. package/{recipes/pipeline-artifact-bundle.json → dist/skills/artifacts/recipes/bundle.json} +4 -4
  52. package/dist/{recipes/utility-artifact-write.json → skills/artifacts/recipes/file-write.json} +1 -3
  53. package/dist/{recipes/utility-artifact-manifest.json → skills/artifacts/recipes/manifest.json} +1 -3
  54. package/{recipes/pipeline-artifact-report.json → dist/skills/artifacts/recipes/report.json} +2 -2
  55. package/dist/{recipes/pipeline-artifact-write.json → skills/artifacts/recipes/write.json} +3 -3
  56. package/dist/skills/artifacts/scripts/artifact-utils.mjs +63 -0
  57. package/dist/skills/media/SKILL.md +16 -0
  58. package/dist/{recipes/pipeline-media-library.json → skills/media/recipes/library.json} +2 -2
  59. package/{recipes/music-player.json → dist/skills/media/recipes/player.json} +1 -3
  60. package/dist/{recipes/utility-playlist-build.json → skills/media/recipes/playlist-build.json} +1 -3
  61. package/dist/skills/media/scripts/media-utils.mjs +47 -0
  62. package/dist/{scripts → skills/media/scripts}/music-player.mjs +9 -3
  63. package/dist/skills/project-work/SKILL.md +16 -0
  64. package/dist/skills/project-work/recipes/changelog-section.json +11 -0
  65. package/{recipes/pipeline-docs-maintenance.json → dist/skills/project-work/recipes/docs-maintenance.json} +4 -4
  66. package/dist/skills/project-work/recipes/package-summary.json +9 -0
  67. package/{recipes/pipeline-release-readiness.json → dist/skills/project-work/recipes/release-readiness.json} +6 -6
  68. package/dist/{recipes/pipeline-release-summary.json → skills/project-work/recipes/release-summary.json} +5 -5
  69. package/{recipes/pipeline-repo-health.json → dist/skills/project-work/recipes/repo-health.json} +6 -6
  70. package/{recipes/pipeline-async-run-ops.json → dist/skills/project-work/recipes/run-ops.json} +3 -3
  71. package/dist/skills/project-work/recipes/skill-summary.json +11 -0
  72. package/dist/skills/project-work/scripts/project-utils.mjs +89 -0
  73. package/dist/skills/recipe-memory/SKILL.md +16 -0
  74. package/dist/skills/swarm/SKILL.md +4 -4
  75. package/{recipes/pipeline-architect-coordinator.json → dist/skills/swarm/recipes/architect.json} +5 -5
  76. package/{recipes/pipeline-checkpoint-continuation.json → dist/skills/swarm/recipes/checkpoint-continuation.json} +3 -3
  77. package/dist/{recipes/pipeline-development-tasking.json → skills/swarm/recipes/development-tasking.json} +4 -4
  78. package/dist/{recipes/lens-swarm.json → skills/swarm/recipes/lens-review.json} +1 -1
  79. package/dist/{recipes/pipeline-quorum-review.json → skills/swarm/recipes/quorum-review.json} +4 -4
  80. package/{recipes/pipeline-research-synthesis.json → dist/skills/swarm/recipes/research-synthesis.json} +6 -6
  81. package/{recipes/pipeline-review-readiness.json → dist/skills/swarm/recipes/review-readiness.json} +1 -1
  82. package/{recipes/subagents-prompts.json → dist/skills/swarm/recipes/subagent-prompts.json} +1 -1
  83. package/{recipes → dist/skills/swarm/recipes}/subagent-quorum.json +1 -1
  84. package/dist/{recipes → skills/swarm/recipes}/subagent-review-coordinator.json +6 -6
  85. package/docs/actor-inspector.md +4 -3
  86. package/docs/async-runs.md +2 -2
  87. package/docs/recipe-library.md +23 -23
  88. package/docs/releasing.md +2 -2
  89. package/docs/template-recipes.md +32 -14
  90. package/docs/tool-registry.md +10 -4
  91. package/fixtures/protocol/recipe-summary.json +1 -1
  92. package/index.ts +2 -1
  93. package/lib/async-runs.ts +52 -25
  94. package/lib/automatic-review-runtime.ts +16 -3
  95. package/lib/draft-sleep.ts +0 -1
  96. package/lib/execution.ts +2 -0
  97. package/lib/extension-runtime.ts +42 -4
  98. package/lib/inspector.ts +57 -11
  99. package/lib/paths.ts +0 -7
  100. package/lib/prompts.ts +4 -4
  101. package/lib/recipes-context.ts +51 -6
  102. package/lib/recipes-discovery.ts +164 -24
  103. package/lib/recipes-references.ts +359 -146
  104. package/lib/registry.ts +185 -94
  105. package/lib/runtime.ts +109 -17
  106. package/lib/tools-inspect.ts +77 -10
  107. package/lib/tools-local.ts +9 -1
  108. package/lib/tools-response.ts +2 -0
  109. package/lib/tools-spawn.ts +38 -74
  110. package/lib/tools.ts +4 -1
  111. package/package.json +2 -4
  112. package/scripts/build-dist.mjs +13 -4
  113. package/skills/actors/SKILL.md +8 -8
  114. package/skills/actors/recipes/recipe-validate.json +11 -0
  115. package/{dist/recipes/utility-coordinator-lock-snapshot.json → skills/actors/recipes/resource-locker-snapshot.json} +1 -3
  116. package/{recipes → skills/actors/recipes}/resource-locker.json +1 -3
  117. package/{recipes/utility-run-ops-snapshot.json → skills/actors/recipes/run-ops-snapshot.json} +1 -3
  118. package/skills/actors/recipes/run-summary.json +9 -0
  119. package/{scripts/locker.mjs → skills/actors/scripts/resource-locker.mjs} +2 -2
  120. package/skills/actors/scripts/run-utils.mjs +164 -0
  121. package/{dist → skills/actors}/scripts/validate-recipe.mjs +134 -17
  122. package/skills/artifacts/SKILL.md +16 -0
  123. package/{dist/recipes/pipeline-artifact-bundle.json → skills/artifacts/recipes/bundle.json} +4 -4
  124. package/{recipes/utility-artifact-write.json → skills/artifacts/recipes/file-write.json} +1 -3
  125. package/{recipes/utility-artifact-manifest.json → skills/artifacts/recipes/manifest.json} +1 -3
  126. package/{dist/recipes/pipeline-artifact-report.json → skills/artifacts/recipes/report.json} +2 -2
  127. package/{recipes/pipeline-artifact-write.json → skills/artifacts/recipes/write.json} +3 -3
  128. package/skills/artifacts/scripts/artifact-utils.mjs +63 -0
  129. package/skills/media/SKILL.md +16 -0
  130. package/{recipes/pipeline-media-library.json → skills/media/recipes/library.json} +2 -2
  131. package/{dist/recipes/music-player.json → skills/media/recipes/player.json} +1 -3
  132. package/{recipes/utility-playlist-build.json → skills/media/recipes/playlist-build.json} +1 -3
  133. package/skills/media/scripts/media-utils.mjs +47 -0
  134. package/{scripts → skills/media/scripts}/music-player.mjs +9 -3
  135. package/skills/project-work/SKILL.md +16 -0
  136. package/skills/project-work/recipes/changelog-section.json +11 -0
  137. package/{dist/recipes/pipeline-docs-maintenance.json → skills/project-work/recipes/docs-maintenance.json} +4 -4
  138. package/skills/project-work/recipes/package-summary.json +9 -0
  139. package/{dist/recipes/pipeline-release-readiness.json → skills/project-work/recipes/release-readiness.json} +6 -6
  140. package/{recipes/pipeline-release-summary.json → skills/project-work/recipes/release-summary.json} +5 -5
  141. package/{dist/recipes/pipeline-repo-health.json → skills/project-work/recipes/repo-health.json} +6 -6
  142. package/{dist/recipes/pipeline-async-run-ops.json → skills/project-work/recipes/run-ops.json} +3 -3
  143. package/skills/project-work/recipes/skill-summary.json +11 -0
  144. package/skills/project-work/scripts/project-utils.mjs +89 -0
  145. package/skills/recipe-memory/SKILL.md +16 -0
  146. package/skills/swarm/SKILL.md +4 -4
  147. package/{dist/recipes/pipeline-architect-coordinator.json → skills/swarm/recipes/architect.json} +5 -5
  148. package/{dist/recipes/pipeline-checkpoint-continuation.json → skills/swarm/recipes/checkpoint-continuation.json} +3 -3
  149. package/{recipes/pipeline-development-tasking.json → skills/swarm/recipes/development-tasking.json} +4 -4
  150. package/{recipes/lens-swarm.json → skills/swarm/recipes/lens-review.json} +1 -1
  151. package/{recipes/pipeline-quorum-review.json → skills/swarm/recipes/quorum-review.json} +4 -4
  152. package/{dist/recipes/pipeline-research-synthesis.json → skills/swarm/recipes/research-synthesis.json} +6 -6
  153. package/{dist/recipes/pipeline-review-readiness.json → skills/swarm/recipes/review-readiness.json} +1 -1
  154. package/{dist/recipes/subagents-prompts.json → skills/swarm/recipes/subagent-prompts.json} +1 -1
  155. package/{dist → skills/swarm}/recipes/subagent-quorum.json +1 -1
  156. package/{recipes → skills/swarm/recipes}/subagent-review-coordinator.json +6 -6
  157. package/dist/recipes/utility-changelog-section.json +0 -13
  158. package/dist/recipes/utility-package-summary.json +0 -11
  159. package/dist/recipes/utility-run-summary.json +0 -11
  160. package/dist/recipes/utility-skill-summary.json +0 -13
  161. package/dist/recipes/utility-validate-recipe.json +0 -13
  162. package/dist/scripts/executable-block-style.d.mts +0 -1
  163. package/dist/scripts/executable-block-style.mjs +0 -73
  164. package/dist/scripts/recipe-utils.mjs +0 -404
  165. package/dist/scripts/release-gates.mjs +0 -281
  166. package/recipes/utility-changelog-section.json +0 -13
  167. package/recipes/utility-package-summary.json +0 -11
  168. package/recipes/utility-run-summary.json +0 -11
  169. package/recipes/utility-skill-summary.json +0 -13
  170. package/recipes/utility-validate-recipe.json +0 -13
  171. package/scripts/executable-block-style.d.mts +0 -1
  172. package/scripts/executable-block-style.mjs +0 -73
  173. package/scripts/recipe-utils.mjs +0 -404
  174. package/scripts/release-gates.mjs +0 -281
  175. /package/dist/{recipes/utility-validation-wrapper.json → skills/actors/recipes/command-validate.json} +0 -0
  176. /package/dist/{recipes/utility-jsonl-tail.json → skills/actors/recipes/jsonl-tail.json} +0 -0
  177. /package/dist/{recipes/utility-run-state-files.json → skills/actors/recipes/run-state-files.json} +0 -0
  178. /package/dist/{recipes/utility-playlist-scan.json → skills/media/recipes/playlist-scan.json} +0 -0
  179. /package/dist/{recipes/utility-changelog-head.json → skills/project-work/recipes/changelog-head.json} +0 -0
  180. /package/dist/{recipes/utility-git-log.json → skills/project-work/recipes/git-log.json} +0 -0
  181. /package/dist/{recipes/utility-git-status.json → skills/project-work/recipes/git-status.json} +0 -0
  182. /package/dist/{recipes/utility-markdown-index.json → skills/project-work/recipes/markdown-index.json} +0 -0
  183. /package/dist/{recipes → skills/recipe-memory/recipes}/draft-review.json +0 -0
  184. /package/dist/{recipes → skills/recipe-memory/recipes}/tool-review.json +0 -0
  185. /package/dist/{recipes → skills/swarm/recipes}/subagent-artifact.json +0 -0
  186. /package/dist/{recipes → skills/swarm/recipes}/subagent-checkpoint.json +0 -0
  187. /package/dist/{recipes → skills/swarm/recipes}/subagent-conflict-report.json +0 -0
  188. /package/dist/{recipes → skills/swarm/recipes}/subagent-contradiction-map.json +0 -0
  189. /package/dist/{recipes → skills/swarm/recipes}/subagent-critic.json +0 -0
  190. /package/dist/{recipes → skills/swarm/recipes}/subagent-evidence-map.json +0 -0
  191. /package/dist/{recipes → skills/swarm/recipes}/subagent-followup.json +0 -0
  192. /package/dist/{recipes → skills/swarm/recipes}/subagent-judge.json +0 -0
  193. /package/dist/{recipes → skills/swarm/recipes}/subagent-merge.json +0 -0
  194. /package/dist/{recipes → skills/swarm/recipes}/subagent-normalize.json +0 -0
  195. /package/dist/{recipes → skills/swarm/recipes}/subagent-plan.json +0 -0
  196. /package/dist/{recipes → skills/swarm/recipes}/subagent-preflight.json +0 -0
  197. /package/dist/{recipes → skills/swarm/recipes}/subagent-prompt.json +0 -0
  198. /package/dist/{recipes → skills/swarm/recipes}/subagent-review.json +0 -0
  199. /package/dist/{recipes → skills/swarm/recipes}/subagent-task-card.json +0 -0
  200. /package/dist/{recipes → skills/swarm/recipes}/subagent-tools.json +0 -0
  201. /package/dist/{recipes → skills/swarm/recipes}/subagent-verify.json +0 -0
  202. /package/{recipes/utility-validation-wrapper.json → skills/actors/recipes/command-validate.json} +0 -0
  203. /package/{recipes/utility-jsonl-tail.json → skills/actors/recipes/jsonl-tail.json} +0 -0
  204. /package/{recipes/utility-run-state-files.json → skills/actors/recipes/run-state-files.json} +0 -0
  205. /package/{recipes/utility-playlist-scan.json → skills/media/recipes/playlist-scan.json} +0 -0
  206. /package/{recipes/utility-changelog-head.json → skills/project-work/recipes/changelog-head.json} +0 -0
  207. /package/{recipes/utility-git-log.json → skills/project-work/recipes/git-log.json} +0 -0
  208. /package/{recipes/utility-git-status.json → skills/project-work/recipes/git-status.json} +0 -0
  209. /package/{recipes/utility-markdown-index.json → skills/project-work/recipes/markdown-index.json} +0 -0
  210. /package/{recipes → skills/recipe-memory/recipes}/draft-review.json +0 -0
  211. /package/{recipes → skills/recipe-memory/recipes}/tool-review.json +0 -0
  212. /package/{recipes → skills/swarm/recipes}/subagent-artifact.json +0 -0
  213. /package/{recipes → skills/swarm/recipes}/subagent-checkpoint.json +0 -0
  214. /package/{recipes → skills/swarm/recipes}/subagent-conflict-report.json +0 -0
  215. /package/{recipes → skills/swarm/recipes}/subagent-contradiction-map.json +0 -0
  216. /package/{recipes → skills/swarm/recipes}/subagent-critic.json +0 -0
  217. /package/{recipes → skills/swarm/recipes}/subagent-evidence-map.json +0 -0
  218. /package/{recipes → skills/swarm/recipes}/subagent-followup.json +0 -0
  219. /package/{recipes → skills/swarm/recipes}/subagent-judge.json +0 -0
  220. /package/{recipes → skills/swarm/recipes}/subagent-merge.json +0 -0
  221. /package/{recipes → skills/swarm/recipes}/subagent-normalize.json +0 -0
  222. /package/{recipes → skills/swarm/recipes}/subagent-plan.json +0 -0
  223. /package/{recipes → skills/swarm/recipes}/subagent-preflight.json +0 -0
  224. /package/{recipes → skills/swarm/recipes}/subagent-prompt.json +0 -0
  225. /package/{recipes → skills/swarm/recipes}/subagent-review.json +0 -0
  226. /package/{recipes → skills/swarm/recipes}/subagent-task-card.json +0 -0
  227. /package/{recipes → skills/swarm/recipes}/subagent-tools.json +0 -0
  228. /package/{recipes → skills/swarm/recipes}/subagent-verify.json +0 -0
@@ -8,9 +8,11 @@ import { dirname, join, relative, resolve } from "node:path";
8
8
  import * as CommandTemplates from "./command-templates.js";
9
9
  import * as Config from "./config.js";
10
10
  import * as ExecutionOutput from "./execution-output.js";
11
- import { withFileMutationLock, writeJsonAtomic } from "./file-state.js";
11
+ import { withFileMutationLock, writeJsonAtomic, writeTextAtomic, } from "./file-state.js";
12
12
  import * as Identity from "./identity.js";
13
13
  import * as Paths from "./paths.js";
14
+ import * as RecipesContext from "./recipes-context.js";
15
+ import * as RecipesDiscovery from "./recipes-discovery.js";
14
16
  import * as RecipesReferences from "./recipes-references.js";
15
17
  import * as Schema from "./schema.js";
16
18
  function textContent(text) {
@@ -58,21 +60,24 @@ function promoteDraftRecipe(name, input, ctx, deps) {
58
60
  if ((existing || existsSync(targetPath)) && !input.update) {
59
61
  throw new Error(ExecutionOutput.formatToolText(`Tool "${name}" already registered. Use update=true to overwrite.`));
60
62
  }
61
- const config = RecipesReferences.readResolvedRecipeConfig(draftPath);
62
- if (!config) {
63
+ const raw = RecipesReferences.readRawRecipeConfig(draftPath);
64
+ if (!raw) {
63
65
  const reason = RecipesReferences.diagnoseRawRecipeConfigFailure(draftPath);
64
66
  throw new Error(ExecutionOutput.formatToolText(`Draft recipe is invalid${reason ? `: ${reason}` : "."}`));
65
67
  }
66
- const promoted = buildConfig(name, {
68
+ const authoredRecipe = buildAuthoredRecipe({
67
69
  ...input,
68
- description: input.description ?? config.description,
69
- template: draftPath,
70
- }, existing);
71
- const raw = RecipesReferences.readRawRecipeConfig(draftPath);
70
+ description: input.description ??
71
+ (typeof raw.description === "string" ? raw.description : undefined),
72
+ }, existing, raw);
73
+ const resolutionContext = getRegistrationResolutionContext(ctx, deps);
74
+ const candidate = RecipesDiscovery.admitUserRecipe(targetPath, resolutionContext, authoredRecipe);
75
+ if (!candidate.validated || !candidate.tool) {
76
+ throw new Error(ExecutionOutput.formatToolText(`Draft recipe is invalid: ${candidate.diagnostics.join("; ")}`));
77
+ }
72
78
  mkdirSync(dirname(targetPath), { recursive: true });
73
- writeJsonAtomic(targetPath, raw);
74
- promoted.template = targetPath;
75
- promoted.sourcePath = targetPath;
79
+ writeJsonAtomic(targetPath, authoredRecipe);
80
+ const promoted = RecipesDiscovery.admitUserRecipe(targetPath, resolutionContext).tool;
76
81
  tools.set(name, promoted);
77
82
  deps.registerRuntimeTool(promoted);
78
83
  deps.notify(ctx, `Promoted draft recipe: ${name}`, "info");
@@ -89,17 +94,10 @@ function promoteDraftRecipe(name, input, ctx, deps) {
89
94
  },
90
95
  };
91
96
  }
92
- function persistToolRecipe(deps, cfg) {
93
- const path = getToolRecipePath(deps, cfg.name);
97
+ function persistToolRecipe(deps, name, recipe) {
98
+ const path = getToolRecipePath(deps, name);
94
99
  mkdirSync(dirname(path), { recursive: true });
95
- writeJsonAtomic(path, {
96
- description: cfg.description,
97
- ...(cfg.recipe?.async !== undefined ? { async: cfg.recipe.async } : {}),
98
- ...(cfg.storedArgs ? { args: cfg.storedArgs } : {}),
99
- ...(cfg.storedDefaults ? { defaults: cfg.storedDefaults } : {}),
100
- ...(cfg.recipe?.values ? { values: cfg.recipe.values } : {}),
101
- template: cfg.template,
102
- });
100
+ writeJsonAtomic(path, recipe);
103
101
  return path;
104
102
  }
105
103
  function deleteTool(name, ctx, deps) {
@@ -130,11 +128,15 @@ function readAuthoritativeStoredTool(deps, name) {
130
128
  if (!existsSync(path))
131
129
  return { exists: false };
132
130
  try {
133
- const normalized = Config.normalizeStoredTool(name, JSON.parse(readFileSync(path, "utf8")), deps.reservedToolNames);
131
+ const bytes = readFileSync(path, "utf8");
132
+ const recipe = JSON.parse(bytes);
133
+ const normalized = Config.normalizeStoredTool(name, recipe, deps.reservedToolNames);
134
134
  if (!normalized.cfg)
135
- return { exists: true };
135
+ return { bytes, exists: true, recipe };
136
136
  return {
137
+ bytes,
137
138
  exists: true,
139
+ recipe,
138
140
  tool: { ...normalized.cfg, sourcePath: path },
139
141
  };
140
142
  }
@@ -161,7 +163,17 @@ function getInputTemplate(value) {
161
163
  }
162
164
  throw new Error(ExecutionOutput.formatToolText("Tool template must be a string, object, or sequence."));
163
165
  }
164
- function buildConfig(name, input, existing) {
166
+ function getRegistrationResolutionContext(ctx, deps) {
167
+ if (ctx &&
168
+ typeof ctx === "object" &&
169
+ "recipeResolutionContext" in ctx) {
170
+ const resolutionContext = ctx.recipeResolutionContext;
171
+ if (resolutionContext)
172
+ return resolutionContext;
173
+ }
174
+ return RecipesContext.createEmptyRecipeResolutionContext("offline-register-tool", getRecipeRoot(deps));
175
+ }
176
+ function buildAuthoredRecipe(input, existing, existingRecipe) {
165
177
  const explicitArgs = input.args === undefined
166
178
  ? undefined
167
179
  : Schema.parseToolArgDeclarations(input.args);
@@ -175,63 +187,29 @@ function buildConfig(name, input, existing) {
175
187
  if (template === null) {
176
188
  throw new Error(ExecutionOutput.formatToolText("Tool template cannot be null here."));
177
189
  }
178
- const finalTemplate = template === undefined || template === "" ? existing?.template : template;
190
+ const finalTemplate = template === undefined || template === ""
191
+ ? existingRecipe?.template ?? existing?.template
192
+ : template;
179
193
  if (!finalTemplate) {
180
194
  throw new Error(ExecutionOutput.formatToolText("Tool template is required."));
181
195
  }
182
- const inputRecipe = typeof input.async === "boolean" ? name : undefined;
183
- const recipe = inputRecipe
184
- ? {
185
- name: inputRecipe,
186
- ...(typeof input.async === "boolean" ? { async: input.async } : {}),
187
- template: finalTemplate,
188
- ...(input.values && typeof input.values === "object"
189
- ? { values: input.values }
190
- : {}),
191
- }
192
- : template === undefined
193
- ? existing?.recipe
194
- : undefined;
195
- const defaults = explicitArgs?.defaults ?? existing?.storedDefaults ?? {};
196
- const storedArgs = explicitArgs
197
- ? explicitArgs.declarations
198
- : existing?.storedArgs;
199
- const storedDefaults = Object.keys(defaults).length > 0 ? defaults : undefined;
200
- const recipeTemplate = RecipesReferences.getRecipeTemplate(finalTemplate);
201
- const argTemplate = recipeTemplate ?? finalTemplate;
202
- const argTemplateConfig = typeof argTemplate === "object" && !Array.isArray(argTemplate)
203
- ? {
204
- ...argTemplate,
205
- ...(storedArgs !== undefined ? { args: storedArgs } : {}),
206
- defaults: { ...(argTemplate.defaults ?? {}), ...defaults },
207
- }
208
- : {
209
- args: storedArgs,
210
- defaults,
211
- template: argTemplate,
212
- };
213
- const inferredArgTypes = Schema.getTemplateArgTypes(argTemplateConfig);
214
- const argTypes = {
215
- ...inferredArgTypes,
216
- ...(existing?.argTypes ?? {}),
217
- ...(explicitArgs?.argTypes ?? {}),
218
- };
219
- return {
220
- name,
196
+ const authored = {
197
+ ...(existingRecipe ?? {}),
221
198
  description,
222
199
  template: finalTemplate,
223
- ...(recipe ? { recipe } : {}),
224
- args: RecipesReferences.isRecipeTool(finalTemplate, recipe) &&
225
- storedArgs !== undefined
226
- ? Schema.getExplicitToolArgNames(storedArgs)
227
- : RecipesReferences.isRecipeReference(finalTemplate) && !recipeTemplate
228
- ? Schema.getExplicitToolArgNames(storedArgs)
229
- : Schema.getToolArgNames(argTemplateConfig),
230
- defaults,
231
- ...(Object.keys(argTypes).length > 0 ? { argTypes } : {}),
232
- ...(storedArgs !== undefined ? { storedArgs } : {}),
233
- ...(storedDefaults !== undefined ? { storedDefaults } : {}),
234
200
  };
201
+ if (typeof input.async === "boolean")
202
+ authored.async = input.async;
203
+ if (explicitArgs) {
204
+ authored.args = explicitArgs.declarations;
205
+ if (Object.keys(explicitArgs.defaults).length > 0)
206
+ authored.defaults = explicitArgs.defaults;
207
+ else
208
+ delete authored.defaults;
209
+ }
210
+ if (input.values && typeof input.values === "object")
211
+ authored.values = input.values;
212
+ return authored;
235
213
  }
236
214
  function executeRegisterToolUnlocked(params, ctx, deps) {
237
215
  const input = params;
@@ -262,37 +240,82 @@ function executeRegisterToolUnlocked(params, ctx, deps) {
262
240
  if (template === undefined && !existing) {
263
241
  throw new Error(ExecutionOutput.formatToolText("Tool template is required for new registrations."));
264
242
  }
265
- const cfg = buildConfig(name, input, existing);
266
- let recipePath;
243
+ const recipePath = getToolRecipePath(deps, name);
244
+ const authoredRecipe = buildAuthoredRecipe(input, existing, authoritative.recipe);
245
+ const resolutionContext = getRegistrationResolutionContext(ctx, deps);
246
+ const candidate = RecipesDiscovery.admitUserRecipe(recipePath, resolutionContext, authoredRecipe);
247
+ if (!candidate.validated || !candidate.tool) {
248
+ throw new Error(ExecutionOutput.formatToolText(`Tool recipe validation failed: ${candidate.diagnostics.join("; ")}`));
249
+ }
250
+ const activeBefore = deps.getActiveTools();
251
+ let persisted = false;
252
+ let activation;
253
+ let cfg;
267
254
  try {
268
- recipePath = persistToolRecipe(deps, cfg);
255
+ persistToolRecipe(deps, name, authoredRecipe);
256
+ persisted = true;
257
+ const admitted = RecipesDiscovery.admitUserRecipe(recipePath, resolutionContext);
258
+ if (!admitted.validated || !admitted.tool) {
259
+ throw new Error(`Persisted tool recipe admission failed: ${admitted.diagnostics.join("; ")}`);
260
+ }
261
+ cfg = admitted.tool;
262
+ tools.set(name, cfg);
263
+ activation = deps.registerRuntimeTool(cfg) ?? undefined;
264
+ if (activation &&
265
+ (!activation.host_registered ||
266
+ !activation.active_tool ||
267
+ !activation.callable_now)) {
268
+ throw new Error(`Host activation verification failed: ${JSON.stringify(activation)}`);
269
+ }
269
270
  }
270
271
  catch (error) {
271
- throw new Error(ExecutionOutput.formatToolText(`Failed to persist tool recipe: ${error instanceof Error ? error.message : String(error)}`));
272
+ if (persisted) {
273
+ if (authoritative.bytes !== undefined)
274
+ writeTextAtomic(recipePath, authoritative.bytes);
275
+ else if (existsSync(recipePath))
276
+ unlinkSync(recipePath);
277
+ }
278
+ if (existing) {
279
+ tools.set(name, existing);
280
+ deps.registerRuntimeTool(existing);
281
+ }
282
+ else {
283
+ tools.delete(name);
284
+ }
285
+ deps.setActiveTools(activeBefore);
286
+ throw new Error(ExecutionOutput.formatToolText(`Tool registration transaction failed: ${error instanceof Error ? error.message : String(error)}`));
272
287
  }
273
- cfg.sourcePath = recipePath;
274
- tools.set(name, cfg);
275
- deps.registerRuntimeTool(cfg);
276
- deps.notify(ctx, `Tool persisted: ${name}`, "info");
277
- const templateWarnings = CommandTemplates.getCommandTemplateWarnings(typeof cfg.template === "object" && !Array.isArray(cfg.template)
278
- ? cfg.template
279
- : { template: cfg.template });
288
+ deps.notify(ctx, `Tool activated: ${name}`, "info");
289
+ const templateWarnings = CommandTemplates.getCommandTemplateWarnings(typeof cfg.recipe?.template === "object" && !Array.isArray(cfg.recipe.template)
290
+ ? cfg.recipe.template
291
+ : { template: cfg.recipe?.template ?? cfg.template });
280
292
  const warningText = templateWarnings.length > 0
281
293
  ? `\nWarnings:\n${templateWarnings.map((warning) => `- ${warning}`).join("\n")}`
282
294
  : "";
295
+ const outcomeText = activation?.callable_now
296
+ ? `${existing ? "Updated" : "Registered"} and activated tool "${name}"`
297
+ : `Persisted tool "${name}"; current-session activation is unverified`;
283
298
  return {
284
299
  content: [
285
- textContent(ExecutionOutput.formatToolText(`${existing ? "Updated" : "Registered"} tool "${name}" (args: ${Schema.formatToolArgs(cfg.args)}).${warningText}`)),
300
+ textContent(ExecutionOutput.formatToolText(`${outcomeText} (args: ${Schema.formatToolArgs(cfg.args)}).${warningText}`)),
286
301
  ],
287
302
  details: {
303
+ active_tool: activation?.active_tool ?? false,
304
+ activation: activation?.activation ?? "unverified",
288
305
  args: cfg.args,
306
+ callable_now: activation?.callable_now ?? false,
289
307
  config: recipePath,
290
308
  defaults: cfg.defaults,
309
+ host_registered: activation?.host_registered ?? false,
310
+ persisted: true,
311
+ registry_active: tools.get(name) === cfg,
312
+ resolved: true,
291
313
  ...(cfg.recipe?.async !== undefined ? { async: cfg.recipe.async } : {}),
292
314
  ...(cfg.recipe?.name ? { recipeName: cfg.recipe.name } : {}),
293
315
  ...(cfg.template ? { template: cfg.template } : {}),
294
316
  ...(templateWarnings.length > 0 ? { templateWarnings } : {}),
295
317
  tool: name,
318
+ validated: true,
296
319
  },
297
320
  };
298
321
  }
@@ -6,6 +6,7 @@
6
6
  import { watch } from "node:fs";
7
7
  import * as Config from "./config.ts";
8
8
  import type { RegisteredToolExec } from "./execution.ts";
9
+ import type { RecipeResolutionContext } from "./recipes-context.ts";
9
10
  import * as ToolsLocal from "./tools-local.ts";
10
11
  export interface RuntimeContext {
11
12
  hasUI: boolean;
@@ -13,22 +14,46 @@ export interface RuntimeContext {
13
14
  notify(message: string, type?: "info" | "warning" | "error"): void;
14
15
  };
15
16
  }
17
+ export interface RuntimeToolActivation {
18
+ active_tool: boolean;
19
+ activation: "current_session" | "unverified";
20
+ callable_now: boolean;
21
+ host_registered: boolean;
22
+ }
16
23
  export interface ToolRegistryRuntimeDeps {
17
24
  configPath: string;
18
25
  exec: RegisteredToolExec;
19
- packagedRecipeRoot?: string;
20
26
  recipeRoot?: string;
21
27
  getActiveTools?: () => string[];
28
+ getAllTools?: () => Array<{
29
+ name: string;
30
+ }>;
22
31
  registerTool: (definition: ReturnType<typeof ToolsLocal.createRuntimeToolDefinition>) => void;
23
32
  reservedToolNames: Set<string>;
24
33
  setActiveTools?: (toolNames: string[]) => void;
25
34
  }
35
+ export interface RecipeRegistryStatus {
36
+ active_tool_count: number;
37
+ last_scan_counts: {
38
+ active: number;
39
+ rejected: number;
40
+ scanned: number;
41
+ };
42
+ last_scan_at?: string;
43
+ registry_generation: number;
44
+ resolution_generation?: string;
45
+ watch_status: "closed" | "failed" | "watching_parent" | "watching_root";
46
+ watched_root: string;
47
+ }
26
48
  export interface ToolRegistryRuntime {
49
+ getStatus(): RecipeRegistryStatus;
50
+ getToolStatus(name: string): Record<string, unknown> | undefined;
27
51
  getToolNameBlocker(name: string): string | undefined;
28
52
  getTools(): Map<string, Config.RegisteredTool>;
29
- loadTools(ctx: RuntimeContext): void;
53
+ loadTools(ctx: RuntimeContext, resolutionContext?: RecipeResolutionContext): void;
30
54
  notify(ctx: RuntimeContext, message: string, type: "info" | "warning" | "error"): void;
31
- registerRuntimeTool(cfg: Config.RegisteredTool): void;
55
+ registerRuntimeTool(cfg: Config.RegisteredTool): RuntimeToolActivation;
56
+ setWatchStatus(status: RecipeRegistryStatus["watch_status"]): void;
32
57
  }
33
58
  export interface RecipeToolReloadWatcher {
34
59
  close(): void;
@@ -37,7 +62,8 @@ export interface RecipeToolReloadWatcher {
37
62
  export declare function createAutoToolsRuntime(deps: ToolRegistryRuntimeDeps): ToolRegistryRuntime;
38
63
  export interface RecipeToolReloadWatcherDeps {
39
64
  exists?: (path: string) => boolean;
65
+ getResolutionContext?: () => RecipeResolutionContext | undefined;
40
66
  recipeRoot?: string;
41
67
  watchPath?: typeof watch;
42
68
  }
43
- export declare function createRecipeToolReloadWatcher(runtime: Pick<ToolRegistryRuntime, "loadTools">, deps?: RecipeToolReloadWatcherDeps): RecipeToolReloadWatcher;
69
+ export declare function createRecipeToolReloadWatcher(runtime: Pick<ToolRegistryRuntime, "loadTools"> & Partial<Pick<ToolRegistryRuntime, "setWatchStatus">>, deps?: RecipeToolReloadWatcherDeps): RecipeToolReloadWatcher;
@@ -7,11 +7,20 @@ import { existsSync, watch } from "node:fs";
7
7
  import { basename, dirname } from "node:path";
8
8
  import * as Paths from "./paths.js";
9
9
  import * as RecipesDiscovery from "./recipes-discovery.js";
10
+ import * as RecipesUsage from "./recipes-usage.js";
10
11
  import * as ToolsLocal from "./tools-local.js";
11
12
  export function createAutoToolsRuntime(deps) {
12
13
  const tools = new Map();
13
14
  const runtimeToolFingerprints = new Map();
14
15
  const runtimeTools = new Set();
16
+ const recipeRoot = deps.recipeRoot ?? Paths.getRecipeRoot();
17
+ const status = {
18
+ active_tool_count: 0,
19
+ last_scan_counts: { active: 0, rejected: 0, scanned: 0 },
20
+ registry_generation: 0,
21
+ watch_status: "closed",
22
+ watched_root: recipeRoot,
23
+ };
15
24
  function notify(ctx, message, type) {
16
25
  if (ctx.hasUI)
17
26
  ctx.ui.notify(message, type);
@@ -44,13 +53,29 @@ export function createAutoToolsRuntime(deps) {
44
53
  const staleSet = new Set(stale);
45
54
  deps.setActiveTools(deps.getActiveTools().filter((name) => !staleSet.has(name)));
46
55
  }
56
+ function activationFor(name) {
57
+ const hostRegistered = deps.getAllTools?.().some((tool) => tool.name === name) ?? false;
58
+ const activeTool = deps.getActiveTools?.().includes(name) ?? false;
59
+ return {
60
+ active_tool: activeTool,
61
+ activation: hostRegistered && activeTool ? "current_session" : "unverified",
62
+ callable_now: hostRegistered && activeTool,
63
+ host_registered: hostRegistered,
64
+ };
65
+ }
47
66
  function registerRuntimeTool(cfg) {
48
67
  const fingerprint = getToolFingerprint(cfg);
49
- if (runtimeToolFingerprints.get(cfg.name) === fingerprint)
50
- return;
51
- deps.registerTool(ToolsLocal.createRuntimeToolDefinition(cfg, deps.exec));
52
- runtimeTools.add(cfg.name);
53
- runtimeToolFingerprints.set(cfg.name, fingerprint);
68
+ if (runtimeToolFingerprints.get(cfg.name) !== fingerprint) {
69
+ deps.registerTool(ToolsLocal.createRuntimeToolDefinition(cfg, deps.exec));
70
+ runtimeTools.add(cfg.name);
71
+ runtimeToolFingerprints.set(cfg.name, fingerprint);
72
+ }
73
+ if (deps.getActiveTools && deps.setActiveTools) {
74
+ deps.setActiveTools([
75
+ ...new Set([...deps.getActiveTools(), cfg.name]),
76
+ ]);
77
+ }
78
+ return activationFor(cfg.name);
54
79
  }
55
80
  function isStartupActionableRegistryWarning(warning) {
56
81
  if (warning.includes(" shadows "))
@@ -69,7 +94,7 @@ export function createAutoToolsRuntime(deps) {
69
94
  const other = warnings.filter((warning) => !shadowed.includes(warning) && !skipped.includes(warning));
70
95
  const lines = ["pi-actors recipe registry warning"];
71
96
  if (shadowed.length > 0) {
72
- lines.push("User recipes override packaged recipes:");
97
+ lines.push("User recipes shadow lower-priority user sources:");
73
98
  lines.push(...shadowed.map((warning) => `• ${warning}`));
74
99
  }
75
100
  if (skipped.length > 0) {
@@ -82,13 +107,15 @@ export function createAutoToolsRuntime(deps) {
82
107
  }
83
108
  return `${lines.join("\n")}\n`;
84
109
  }
85
- function loadTools(ctx) {
110
+ function loadTools(ctx, resolutionContext) {
86
111
  const warnings = [];
87
- const recipeRoot = deps.recipeRoot ?? Paths.getRecipeRoot();
88
- const packagedRecipeRoot = deps.packagedRecipeRoot ?? Paths.getPackagedRecipeRoot();
89
112
  const discovered = RecipesDiscovery.discoverRecipeSources([
90
- { root: recipeRoot, defaultTool: true, mutableUsage: true },
91
- { root: packagedRecipeRoot },
113
+ {
114
+ root: recipeRoot,
115
+ defaultTool: true,
116
+ mutableUsage: true,
117
+ resolutionContext,
118
+ },
92
119
  ]);
93
120
  warnings.push(...discovered.diagnostics);
94
121
  tools.clear();
@@ -111,17 +138,47 @@ export function createAutoToolsRuntime(deps) {
111
138
  }
112
139
  registerRuntimeTool(cfg);
113
140
  }
141
+ status.active_tool_count = tools.size;
142
+ status.last_scan_at = new Date().toISOString();
143
+ status.last_scan_counts = {
144
+ active: tools.size,
145
+ rejected: discovered.entries.filter((entry) => entry.invalid).length,
146
+ scanned: discovered.entries.length,
147
+ };
148
+ status.registry_generation += 1;
149
+ status.resolution_generation = resolutionContext?.generation;
114
150
  const startupWarnings = warnings.filter(isStartupActionableRegistryWarning);
115
151
  if (startupWarnings.length > 0) {
116
152
  notify(ctx, formatRecipeToolWarnings(startupWarnings), "warning");
117
153
  }
118
154
  }
119
155
  return {
156
+ getStatus: () => ({
157
+ ...status,
158
+ last_scan_counts: { ...status.last_scan_counts },
159
+ }),
160
+ getToolStatus: (name) => {
161
+ const cfg = tools.get(name);
162
+ if (!cfg)
163
+ return undefined;
164
+ const usage = cfg.sourcePath
165
+ ? RecipesUsage.readRecipeUsage(cfg.sourcePath)
166
+ : undefined;
167
+ return {
168
+ ...activationFor(name),
169
+ launch_kind: usage?.launch_kind,
170
+ spawn_calls: Number(usage?.spawn_calls ?? 0),
171
+ tool_calls: Number(usage?.tool_calls ?? 0),
172
+ };
173
+ },
120
174
  getToolNameBlocker,
121
175
  getTools: () => tools,
122
176
  loadTools,
123
177
  notify,
124
178
  registerRuntimeTool,
179
+ setWatchStatus: (watchStatus) => {
180
+ status.watch_status = watchStatus;
181
+ },
125
182
  };
126
183
  }
127
184
  export function createRecipeToolReloadWatcher(runtime, deps = {}) {
@@ -131,6 +188,7 @@ export function createRecipeToolReloadWatcher(runtime, deps = {}) {
131
188
  let rootWatcher;
132
189
  let parentWatcher;
133
190
  let failureNotified = false;
191
+ const setWatchStatus = (watchStatus) => runtime.setWatchStatus?.(watchStatus);
134
192
  const close = () => {
135
193
  const closingRoot = rootWatcher;
136
194
  rootWatcher = undefined;
@@ -141,11 +199,13 @@ export function createRecipeToolReloadWatcher(runtime, deps = {}) {
141
199
  if (reloadTimeout)
142
200
  clearTimeout(reloadTimeout);
143
201
  reloadTimeout = undefined;
202
+ setWatchStatus("closed");
144
203
  };
145
204
  const notifyFailure = (ctx) => {
146
205
  if (failureNotified)
147
206
  return;
148
207
  failureNotified = true;
208
+ setWatchStatus("failed");
149
209
  ctx.ui.notify("Recipe live reload watcher failed; restart the session or use register_tool again to refresh recipe tools.", "warning");
150
210
  };
151
211
  const scheduleReload = (ctx) => {
@@ -153,7 +213,7 @@ export function createRecipeToolReloadWatcher(runtime, deps = {}) {
153
213
  if (reloadTimeout)
154
214
  clearTimeout(reloadTimeout);
155
215
  reloadTimeout = setTimeout(() => {
156
- runtime.loadTools(ctx);
216
+ runtime.loadTools(ctx, deps.getResolutionContext?.());
157
217
  ctx.ui.notify("Recipe tools refreshed from ~/.pi/agent/recipes", "info");
158
218
  }, 150);
159
219
  reloadTimeout.unref?.();
@@ -183,6 +243,7 @@ export function createRecipeToolReloadWatcher(runtime, deps = {}) {
183
243
  scheduleReload(ctx);
184
244
  });
185
245
  parentWatcher = watcher;
246
+ setWatchStatus("watching_parent");
186
247
  watcher.on("error", () => {
187
248
  if (parentWatcher !== watcher)
188
249
  return;
@@ -216,6 +277,7 @@ export function createRecipeToolReloadWatcher(runtime, deps = {}) {
216
277
  scheduleReload(ctx);
217
278
  });
218
279
  rootWatcher = watcher;
280
+ setWatchStatus("watching_root");
219
281
  watcher.on("error", () => {
220
282
  if (rootWatcher !== watcher)
221
283
  return;
@@ -3,11 +3,13 @@
3
3
  * Zones: Run Recipe/Trace/Control views and runtime/recipe/tool diagnostics
4
4
  * Owns exact inspect target/view dispatch; source projection stays in domain modules.
5
5
  */
6
+ import * as Runtime from "./runtime.ts";
6
7
  export interface InspectToolDeps<TContext = unknown> {
7
8
  getRunStatus?: (runOrDir: string) => Record<string, any>;
8
9
  getTool?: (name: string) => any | undefined;
10
+ getToolStatus?: (name: string) => Record<string, unknown> | undefined;
9
11
  listRuns?: () => Array<Record<string, any>>;
10
- packagedRecipeRoot?: string;
11
12
  recipeRoot?: string;
13
+ registryStatus?: () => Runtime.RecipeRegistryStatus;
12
14
  }
13
15
  export declare function createInspectToolDefinition<TContext = unknown>(deps?: InspectToolDeps<TContext>): any;
@@ -4,7 +4,7 @@
4
4
  * Owns exact inspect target/view dispatch; source projection stays in domain modules.
5
5
  */
6
6
  import { statSync } from "node:fs";
7
- import { join } from "node:path";
7
+ import { isAbsolute, join, relative } from "node:path";
8
8
  import * as AsyncRuns from "./async-runs.js";
9
9
  import * as ControlProjection from "./control-projection.js";
10
10
  import * as Inspector from "./inspector.js";
@@ -157,7 +157,16 @@ function inspectRuntime(view, input, ctx, deps) {
157
157
  return runtimeTriage(ctx, deps);
158
158
  throw new Error("inspect runtime supports view=status, view=runs, or view=triage.");
159
159
  }
160
- function inspectRecipes(view, deps) {
160
+ function portableSkillRecipePath(file, skill, namespaces) {
161
+ const root = namespaces[skill]?.[0];
162
+ if (!root)
163
+ return `<active-skill:${skill}>`;
164
+ const relation = relative(root, file);
165
+ return !relation.startsWith("..") && !isAbsolute(relation)
166
+ ? `<active-skill:${skill}>/${relation.replaceAll("\\", "/")}`
167
+ : `<active-skill:${skill}>`;
168
+ }
169
+ function inspectRecipes(view, deps, context) {
161
170
  if (view !== "status" &&
162
171
  view !== "summary" &&
163
172
  view !== "doctor" &&
@@ -171,17 +180,54 @@ function inspectRecipes(view, deps) {
171
180
  }
172
181
  const discovered = RecipesDiscovery.discoverRecipeSources([
173
182
  { root: recipeRoot, defaultTool: true, mutableUsage: true },
174
- { root: deps.packagedRecipeRoot ?? Paths.getPackagedRecipeRoot() },
175
183
  ]);
176
- const skillRecipeNamespaces = RecipesReferences.getActiveSkillRecipeNamespaces();
184
+ const resolutionContext = context && typeof context === "object" && "recipeResolutionContext" in context
185
+ ? context.recipeResolutionContext
186
+ : undefined;
187
+ const activeSkillContext = resolutionContext?.activeSkills ??
188
+ RecipesReferences.EMPTY_ACTIVE_SKILL_RECIPE_CONTEXT;
189
+ const skillRecipeNamespaces = RecipesReferences.getActiveSkillRecipeNamespaces(activeSkillContext);
190
+ const skillInventory = RecipesReferences.inventoryActiveSkillRecipeComponents(activeSkillContext);
191
+ const skillRecipeComponents = skillInventory.components.map((component) => ({
192
+ identity: component.identity,
193
+ source_kind: "active_skill_component",
194
+ skill: component.skill,
195
+ stem: component.stem,
196
+ imports: component.imports,
197
+ }));
198
+ const skillRecipeComponentDiagnostics = skillInventory.rejected.map((diagnostic) => ({
199
+ error: diagnostic.reason,
200
+ ...(diagnostic.file
201
+ ? {
202
+ file: portableSkillRecipePath(diagnostic.file, diagnostic.skill, skillRecipeNamespaces),
203
+ }
204
+ : {}),
205
+ skill: diagnostic.skill,
206
+ ...(diagnostic.stem ? { stem: diagnostic.stem } : {}),
207
+ }));
208
+ const discoverySummary = RecipesDiscovery.summarizeDiscovery(discovered);
177
209
  const summary = {
178
- ...RecipesDiscovery.summarizeDiscovery(discovered),
179
- skill_recipe_namespaces: skillRecipeNamespaces,
210
+ ...discoverySummary,
211
+ ...(deps.registryStatus
212
+ ? {
213
+ ...deps.registryStatus(),
214
+ watched_root: "~/.pi/agent/recipes",
215
+ }
216
+ : {}),
217
+ active: Array.isArray(discoverySummary.active)
218
+ ? discoverySummary.active.map((entry) => ({
219
+ ...entry,
220
+ source_kind: "user_registry_capability",
221
+ }))
222
+ : discoverySummary.active,
223
+ skill_recipe_components: skillRecipeComponents,
224
+ skill_recipe_component_diagnostics: skillRecipeComponentDiagnostics,
225
+ skill_recipe_catalog_partial: skillInventory.partial,
226
+ active_skill_recipe_identities: Object.keys(skillRecipeNamespaces).sort(),
180
227
  skill_recipe_namespace_diagnostics: Object.entries(skillRecipeNamespaces)
181
228
  .filter(([, roots]) => roots.length > 1)
182
- .map(([name, roots]) => ({
229
+ .map(([name]) => ({
183
230
  name,
184
- roots,
185
231
  error: `Ambiguous active Skill Recipe namespace ${name}`,
186
232
  })),
187
233
  drafts: RecipesDiscovery.listDraftRecipes(join(recipeRoot, "drafts")),
@@ -280,6 +326,7 @@ function inspectTool(name, view, deps) {
280
326
  throw new Error(`registered tool not found: ${name}`);
281
327
  return {
282
328
  name,
329
+ ...(view === "status" ? deps.getToolStatus?.(name) : {}),
283
330
  description: tool.description,
284
331
  parameters: tool.parameters,
285
332
  promptSnippet: tool.promptSnippet,
@@ -316,7 +363,7 @@ export function createInspectToolDefinition(deps = {}) {
316
363
  details = inspectRuntime(view, input, ctx, deps);
317
364
  }
318
365
  else if (target === "recipes") {
319
- details = inspectRecipes(view, deps);
366
+ details = inspectRecipes(view, deps, ctx);
320
367
  }
321
368
  else {
322
369
  const run = parseRunTarget(target);
@@ -6,7 +6,9 @@
6
6
  import * as ModelContext from "./model-context.ts";
7
7
  import type { RegisteredTool } from "./config.ts";
8
8
  import * as Execution from "./execution.ts";
9
+ import type * as RecipeResolution from "./recipes-context.ts";
9
10
  export interface RuntimeToolContext extends ModelContext.CurrentModelContext {
11
+ recipeResolutionContext?: RecipeResolution.RecipeResolutionContext;
10
12
  cwd: string;
11
13
  sessionManager?: {
12
14
  getSessionId?: () => string;