@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
@@ -3,19 +3,34 @@
3
3
  * Zones: registry config, async runs, path resolution
4
4
  * Owns detection, loading, and recipe-layer expansion for template recipe files
5
5
  */
6
- import { existsSync, readFileSync, statSync } from "node:fs";
7
- import { homedir } from "node:os";
8
- import { basename, dirname, extname, isAbsolute, relative, resolve } from "node:path";
6
+ import { existsSync, readFileSync, readdirSync, statSync } from "node:fs";
7
+ import { basename, dirname, extname, isAbsolute, join, relative, resolve } from "node:path";
9
8
  import * as CommandTemplates from "./command-templates.js";
10
9
  import * as Paths from "./paths.js";
11
10
  import * as RecipeControl from "./recipe-control.js";
12
11
  import * as Schema from "./schema.js";
13
12
  const MAX_RECIPE_FILE_BYTES = 1024 * 1024;
14
13
  const MAX_RECIPE_IMPORT_DEPTH = 32;
15
- const RUNTIME_RECIPE_VALUE_NAMES = new Set(["recipe_dir", "skill_dir"]);
16
- const activeSkillRecipeRoots = new Map();
17
- export function setActiveSkillRecipeSources(skills) {
18
- activeSkillRecipeRoots.clear();
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
+ }
32
+ export function createActiveSkillRecipeContext(skills) {
33
+ const rootsByName = new Map();
19
34
  for (const skill of skills) {
20
35
  const name = skill.name.trim();
21
36
  const skillDir = skill.baseDir
@@ -25,62 +40,174 @@ export function setActiveSkillRecipeSources(skills) {
25
40
  : undefined;
26
41
  if (!name || !skillDir)
27
42
  continue;
28
- const roots = activeSkillRecipeRoots.get(name) ?? new Set();
43
+ const roots = rootsByName.get(name) ?? new Set();
29
44
  roots.add(resolve(skillDir, "recipes"));
30
- activeSkillRecipeRoots.set(name, roots);
45
+ rootsByName.set(name, roots);
31
46
  }
32
- }
33
- export function getActiveSkillRecipeNamespaces() {
34
- return Object.fromEntries([...activeSkillRecipeRoots.entries()]
47
+ const namespaces = Object.fromEntries([...rootsByName.entries()]
35
48
  .sort(([left], [right]) => left.localeCompare(right))
36
- .map(([name, roots]) => [name, [...roots].sort()]));
49
+ .map(([name, roots]) => [name, Object.freeze([...roots].sort())]));
50
+ return Object.freeze({ namespaces: Object.freeze(namespaces) });
51
+ }
52
+ export const EMPTY_ACTIVE_SKILL_RECIPE_CONTEXT = createActiveSkillRecipeContext([]);
53
+ export function getActiveSkillRecipeNamespaces(context) {
54
+ return Object.fromEntries(Object.entries(context.namespaces).map(([name, roots]) => [
55
+ name,
56
+ [...roots],
57
+ ]));
58
+ }
59
+ function nestedRecipeFiles(root) {
60
+ if (!existsSync(root))
61
+ return [];
62
+ return readdirSync(root, { withFileTypes: true }).flatMap((entry) => {
63
+ const path = join(root, entry.name);
64
+ if (entry.isDirectory())
65
+ return nestedRecipeFiles(path);
66
+ return entry.isFile() && /\.(?:json|md)$/u.test(entry.name) ? [path] : [];
67
+ });
68
+ }
69
+ export function inventoryActiveSkillRecipeComponents(context) {
70
+ const components = [];
71
+ const rejected = [];
72
+ for (const [skill, roots] of Object.entries(context.namespaces)) {
73
+ if (roots.length > 1) {
74
+ rejected.push({
75
+ reason: `Duplicate active Skill identity ${skill}: ${roots.join(", ")}`,
76
+ skill,
77
+ });
78
+ continue;
79
+ }
80
+ const root = roots[0];
81
+ if (!root || !existsSync(root))
82
+ continue;
83
+ const direct = readdirSync(root, { withFileTypes: true });
84
+ for (const entry of direct.filter((candidate) => candidate.isDirectory())) {
85
+ const nested = nestedRecipeFiles(join(root, entry.name));
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
+ }
94
+ }
95
+ const files = direct
96
+ .filter((entry) => entry.isFile() && /\.(?:json|md)$/u.test(entry.name))
97
+ .map((entry) => join(root, entry.name));
98
+ const byStem = new Map();
99
+ for (const file of files) {
100
+ const stem = getRecipeIdFromPath(file);
101
+ const matches = byStem.get(stem) ?? [];
102
+ matches.push(file);
103
+ byStem.set(stem, matches);
104
+ }
105
+ for (const [stem, matches] of [...byStem].sort(([a], [b]) => a.localeCompare(b))) {
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
+ }
124
+ const file = matches[0];
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
+ }
149
+ }
150
+ }
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;
37
158
  }
38
- function qualifiedRecipeNameForFile(file) {
159
+ const SKILL_RECIPE_REFERENCE = /^([a-z][a-z0-9]*(?:-[a-z0-9]+)*)\/([a-z][a-z0-9]*(?:-[a-z0-9]+)*)$/;
160
+ function qualifiedRecipeNameForFile(file, context) {
39
161
  const path = resolve(file);
40
- const stdRelation = relative(Paths.getPackagedRecipeRoot(), path);
41
- if (stdRelation &&
42
- !stdRelation.startsWith("..") &&
43
- !isAbsolute(stdRelation)) {
44
- return `std:${stdRelation.replace(/\.(?:json|md)$/u, "")}`;
45
- }
46
- for (const [skillName, roots] of activeSkillRecipeRoots) {
47
- if (roots.size !== 1)
162
+ for (const [skillName, roots] of Object.entries(context.namespaces)) {
163
+ if (roots.length !== 1)
48
164
  continue;
49
- const root = [...roots][0];
165
+ const root = roots[0];
50
166
  const relation = relative(root, path);
51
- if (relation && !relation.startsWith("..") && !isAbsolute(relation))
52
- return `skill:${skillName}/${relation.replaceAll("\\", "/").replace(/\.(?:json|md)$/u, "")}`;
167
+ if (relation &&
168
+ dirname(relation) === "." &&
169
+ !relation.startsWith("..") &&
170
+ !isAbsolute(relation)) {
171
+ return `${skillName}/${basename(relation, extname(relation))}`;
172
+ }
53
173
  }
54
174
  return undefined;
55
175
  }
56
- function qualifiedRecipeCandidates(value) {
176
+ function assertRecipeReferencePrefixWasNotRemoved(value) {
57
177
  if (value.startsWith("std:")) {
58
- return recipeNameFiles(value.slice(4)).map((file) => resolve(Paths.getPackagedRecipeRoot(), file));
178
+ throw new Error("std: Recipe references were removed in pi-actors 0.46.\nUse <skill>/<recipe> or an explicit .json/.md file path.");
59
179
  }
60
- if (!value.startsWith("skill:"))
180
+ if (value.startsWith("skill:")) {
181
+ throw new Error("skill: Recipe references were removed in pi-actors 0.46.\nUse <skill>/<recipe> without a prefix.");
182
+ }
183
+ }
184
+ function skillRecipeCandidates(value, context) {
185
+ const match = SKILL_RECIPE_REFERENCE.exec(value);
186
+ if (!match)
61
187
  return undefined;
62
- const qualified = value.slice(6);
63
- const separator = qualified.indexOf("/");
64
- if (separator <= 0 || separator === qualified.length - 1)
65
- throw new Error(`Invalid Skill Recipe reference: ${value}`);
66
- const skillName = qualified.slice(0, separator);
67
- const recipeName = qualified.slice(separator + 1);
68
- const roots = [...(activeSkillRecipeRoots.get(skillName) ?? [])];
188
+ const [, skillName, recipeName] = match;
189
+ const roots = [...(context.namespaces[skillName] ?? [])];
69
190
  if (roots.length === 0)
70
- throw new Error(`Active Skill Recipe namespace not found: ${skillName}`);
191
+ throw new Error(`Active Skill Recipe not found: ${skillName}/${recipeName}`);
71
192
  if (roots.length > 1)
72
- throw new Error(`Ambiguous active Skill Recipe namespace ${skillName}: ${roots.join(", ")}`);
193
+ throw new Error(`Duplicate active Skill identity ${skillName}: ${roots.join(", ")}`);
73
194
  const root = roots[0];
74
- return recipeNameFiles(recipeName).map((file) => {
75
- const candidate = resolve(root, file);
76
- const relation = relative(root, candidate);
77
- if (!relation || relation.startsWith("..") || isAbsolute(relation))
78
- throw new Error(`Skill Recipe reference escapes its recipes root: ${value}`);
79
- return candidate;
80
- });
195
+ const candidates = ["json", "md"].map((extension) => resolve(root, `${recipeName}.${extension}`));
196
+ const existing = candidates.filter((candidate) => existsSync(candidate));
197
+ if (existing.length > 1) {
198
+ throw new Error(`Skill Recipe stem collision: ${skillName}/${recipeName} has both .json and .md files`);
199
+ }
200
+ return candidates;
81
201
  }
82
- function hasWhitespace(value) {
83
- return /\s/.test(value);
202
+ function recipeReferenceCandidates(value, baseDir, context) {
203
+ const trimmed = value.trim();
204
+ if (!trimmed)
205
+ return undefined;
206
+ assertRecipeReferencePrefixWasNotRemoved(trimmed);
207
+ if (trimmed.endsWith(".json") || trimmed.endsWith(".md")) {
208
+ return [resolve(baseDir, trimmed)];
209
+ }
210
+ return skillRecipeCandidates(trimmed, context);
84
211
  }
85
212
  function findOwningSkillDir(recipeFile) {
86
213
  let current = dirname(recipeFile);
@@ -93,11 +220,16 @@ function findOwningSkillDir(recipeFile) {
93
220
  current = parent;
94
221
  }
95
222
  }
223
+ function assertRecipeHasNoDeclaredName(raw) {
224
+ if (!Object.hasOwn(raw, "name"))
225
+ return;
226
+ throw new Error("Recipe.name was removed in pi-actors 0.46.\nFile-backed Recipe identity is derived from its file name.");
227
+ }
96
228
  function assertRuntimeRecipeValuesNotDeclared(raw) {
97
229
  const args = Array.isArray(raw.args)
98
230
  ? Schema.getExplicitToolArgNames(raw.args.map(String))
99
231
  : [];
100
- for (const name of RUNTIME_RECIPE_VALUE_NAMES) {
232
+ for (const name of RESERVED_AUTHORED_RECIPE_VALUE_NAMES) {
101
233
  if (args.includes(name) ||
102
234
  (isRecord(raw.defaults) && Object.hasOwn(raw.defaults, name)) ||
103
235
  (isRecord(raw.values) && Object.hasOwn(raw.values, name))) {
@@ -105,82 +237,28 @@ function assertRuntimeRecipeValuesNotDeclared(raw) {
105
237
  }
106
238
  }
107
239
  }
108
- export function resolveRecipePath(value, recipeRoot = Paths.getRecipeRoot()) {
109
- const trimmed = value.trim();
110
- const repoRoot = resolve(recipeRoot, "..");
111
- const expanded = trimmed
112
- .replaceAll("{repo}", repoRoot)
113
- .replaceAll("{agent}", Paths.getAgentDir());
114
- if (expanded.startsWith("~/"))
115
- return resolve(homedir(), expanded.slice(2));
116
- if (expanded.includes("/"))
117
- return resolve(expanded);
118
- return resolve(recipeRoot, expanded.endsWith(".json") || expanded.endsWith(".md")
119
- ? expanded
120
- : `${expanded}.json`);
121
- }
122
- function isBareRecipeName(value) {
123
- const trimmed = value.trim();
124
- return (Boolean(trimmed) &&
125
- !trimmed.includes("/") &&
126
- !trimmed.startsWith("~") &&
127
- !trimmed.includes("{"));
240
+ export function resolveRecipePath(value, baseDir = Paths.getRecipeRoot()) {
241
+ const candidates = recipeReferenceCandidates(value, baseDir, EMPTY_ACTIVE_SKILL_RECIPE_CONTEXT);
242
+ if (!candidates || candidates.length !== 1) {
243
+ throw new Error(`Recipe file reference must be an explicit .json or .md path: ${value}`);
244
+ }
245
+ return candidates[0];
128
246
  }
129
- function recipeNameFiles(value) {
130
- const trimmed = value.trim();
131
- if (trimmed.endsWith(".json") || trimmed.endsWith(".md"))
132
- return [trimmed];
133
- return [`${trimmed}.json`, `${trimmed}.md`];
134
- }
135
- function recipeCandidatePaths(value, currentRecipeRoot) {
136
- const qualified = qualifiedRecipeCandidates(value.trim());
137
- if (qualified)
138
- return qualified;
139
- if (!isBareRecipeName(value))
140
- return [resolveRecipePath(value, currentRecipeRoot)];
141
- const roots = [
142
- Paths.getRecipeRoot(),
143
- currentRecipeRoot,
144
- Paths.getPackagedRecipeRoot(),
145
- ];
146
- return [
147
- ...new Set(roots.flatMap((root) => recipeNameFiles(value).map((file) => resolve(root, file)))),
148
- ];
149
- }
150
- function resolveRecipeImportPath(value, currentRecipeRoot) {
151
- const candidates = recipeCandidatePaths(value, currentRecipeRoot);
247
+ function resolveRecipeImportPath(value, recipeDir, context) {
248
+ const candidates = recipeReferenceCandidates(value, recipeDir, context);
249
+ if (!candidates) {
250
+ throw new Error(`Recipe import must use <skill>/<recipe> or an explicit .json/.md file path: ${value}`);
251
+ }
152
252
  return candidates.find((candidate) => existsSync(candidate)) ?? candidates[0];
153
253
  }
154
- export function resolveRecipeReferencePath(value, currentRecipeRoot = Paths.getRecipeRoot()) {
254
+ export function resolveRecipeReferencePath(value, baseDir = Paths.getRecipeRoot(), context = EMPTY_ACTIVE_SKILL_RECIPE_CONTEXT) {
155
255
  if (typeof value !== "string")
156
256
  return undefined;
157
- const trimmed = value.trim();
158
- if (!trimmed || hasWhitespace(trimmed))
159
- return undefined;
160
- for (const path of recipeCandidatePaths(trimmed, currentRecipeRoot)) {
161
- if (!existsSync(path))
162
- continue;
163
- try {
164
- const raw = readRawRecipeConfig(path);
165
- if (raw && typeof raw === "object" && Object.hasOwn(raw, "template"))
166
- return path;
167
- return path;
168
- }
169
- catch {
170
- return path;
171
- }
172
- }
173
- return undefined;
257
+ const candidates = recipeReferenceCandidates(value, baseDir, context);
258
+ return candidates?.find((candidate) => existsSync(candidate));
174
259
  }
175
- export function getRecipePath(value, recipeRoot = Paths.getRecipeRoot()) {
176
- if (typeof value !== "string")
177
- return undefined;
178
- const trimmed = value.trim();
179
- if (!trimmed || hasWhitespace(trimmed))
180
- return undefined;
181
- if (trimmed.endsWith(".json") || trimmed.endsWith(".md"))
182
- return resolveRecipePath(trimmed, recipeRoot);
183
- return resolveRecipeReferencePath(trimmed, recipeRoot);
260
+ export function getRecipePath(value, baseDir = Paths.getRecipeRoot(), context = EMPTY_ACTIVE_SKILL_RECIPE_CONTEXT) {
261
+ return resolveRecipeReferencePath(value, baseDir, context);
184
262
  }
185
263
  function isImportNode(value) {
186
264
  if (!isRecord(value) || Object.hasOwn(value, "template"))
@@ -655,7 +733,7 @@ function withActorRecipeContext(value, context) {
655
733
  return { actorRecipeContext: context, template: value };
656
734
  }
657
735
  function loadDelegatedRecipe(value, currentRecipeFile, stack, options) {
658
- const path = resolveRecipeReferencePath(value, dirname(currentRecipeFile));
736
+ const path = resolveRecipeReferencePath(value, dirname(currentRecipeFile), options.skillContext);
659
737
  if (!path)
660
738
  return undefined;
661
739
  const config = readResolvedRecipeConfig(path, [...stack, currentRecipeFile], options);
@@ -712,6 +790,28 @@ function getDirectDelegatedRecipe(value, currentRecipeFile, stack, options = {})
712
790
  }
713
791
  return undefined;
714
792
  }
793
+ function resolveDirectDelegationArgs(delegated, wrapperArgs, wrapperDefaults) {
794
+ if (!Array.isArray(delegated.args))
795
+ return Array.isArray(wrapperArgs) ? wrapperArgs.map(String) : undefined;
796
+ const delegatedSpec = Schema.parseToolArgDeclarationList(delegated.args);
797
+ if (delegatedSpec.error)
798
+ throw new Error(delegatedSpec.error);
799
+ const wrapperDeclarations = Array.isArray(wrapperArgs)
800
+ ? wrapperArgs.map(String)
801
+ : [];
802
+ const wrapperSpec = Schema.parseToolArgDeclarationList(wrapperDeclarations);
803
+ if (wrapperSpec.error)
804
+ throw new Error(wrapperSpec.error);
805
+ const declared = new Set(delegatedSpec.args);
806
+ for (const arg of wrapperSpec.args) {
807
+ if (!declared.has(arg))
808
+ throw new Error(`Unknown delegated Recipe argument: ${arg}`);
809
+ }
810
+ Schema.assertCompatibleToolArgTypes(delegatedSpec.argTypes, wrapperSpec.argTypes);
811
+ assertRecipeDefaultsDeclared(delegated.args, wrapperDefaults, "delegated Recipe default");
812
+ Schema.normalizeRuntimeValues(Object.fromEntries(Object.entries(wrapperDefaults ?? {}).filter(([, value]) => typeof value !== "string" || !/\{[^{}]+\}/.test(value))), delegatedSpec.argTypes);
813
+ return delegated.args;
814
+ }
715
815
  function expandImportNodes(value, imports, options = {}) {
716
816
  if (typeof value === "string")
717
817
  return value;
@@ -764,15 +864,18 @@ export function readResolvedRecipeConfig(file, stack = [], options = {}) {
764
864
  if (stack.length >= MAX_RECIPE_IMPORT_DEPTH) {
765
865
  throw new Error(`Recipe import depth exceeds limit ${MAX_RECIPE_IMPORT_DEPTH}: ${[...stack, path].join(" -> ")}`);
766
866
  }
767
- const raw = readRawRecipeConfig(path);
867
+ const raw = options.authoredEntry?.path === path
868
+ ? options.authoredEntry.recipe
869
+ : readRawRecipeConfig(path);
768
870
  if (!raw || !Object.hasOwn(raw, "template"))
769
871
  return undefined;
872
+ assertRecipeHasNoDeclaredName(raw);
770
873
  RecipeControl.assertRecipeHasNoMailbox(raw);
771
874
  assertRuntimeRecipeValuesNotDeclared(raw);
772
875
  assertRecipeArgumentContract(raw);
773
876
  const imports = {};
774
877
  for (const [alias, binding] of Object.entries(getRecipeImports(raw))) {
775
- const importPath = resolveRecipeImportPath(getImportFrom(binding), dirname(path));
878
+ const importPath = resolveRecipeImportPath(getImportFrom(binding), dirname(path), options.skillContext ?? EMPTY_ACTIVE_SKILL_RECIPE_CONTEXT);
776
879
  const config = readResolvedRecipeConfig(importPath, [...stack, path], options);
777
880
  if (!config)
778
881
  throw new Error(`Recipe import not found: ${alias}`);
@@ -782,7 +885,7 @@ export function readResolvedRecipeConfig(file, stack = [], options = {}) {
782
885
  imports[alias] = {
783
886
  alias,
784
887
  file: importPath,
785
- name: config.name ?? alias,
888
+ name: getRecipeIdFromPath(importPath),
786
889
  config,
787
890
  defaults: { ...(config.defaults ?? {}), ...(bindingDefaults ?? {}) },
788
891
  values: {
@@ -824,7 +927,15 @@ export function readResolvedRecipeConfig(file, stack = [], options = {}) {
824
927
  role: stack.length > 0 ? "import" : "entry",
825
928
  })
826
929
  : expandedTemplate;
827
- const mergedDefaults = mergeDefaults(delegated?.defaults, isRecord(substituted.defaults) ? substituted.defaults : undefined);
930
+ const wrapperDefaults = isRecord(substituted.defaults)
931
+ ? substituted.defaults
932
+ : undefined;
933
+ const mergedDefaults = mergeDefaults(delegated?.defaults, wrapperDefaults);
934
+ const effectiveArgs = delegated
935
+ ? resolveDirectDelegationArgs(delegated, substituted.args, wrapperDefaults)
936
+ : Array.isArray(substituted.args)
937
+ ? substituted.args
938
+ : undefined;
828
939
  const artifactSource = isRecord(substituted.artifacts)
829
940
  ? substituted.artifacts
830
941
  : delegated?.artifacts;
@@ -859,11 +970,7 @@ export function readResolvedRecipeConfig(file, stack = [], options = {}) {
859
970
  ? { imports: getRecipeImports(raw) }
860
971
  : {}),
861
972
  template: templateWithContext,
862
- ...(Array.isArray(substituted.args)
863
- ? { args: substituted.args }
864
- : Array.isArray(delegated?.args)
865
- ? { args: delegated.args }
866
- : {}),
973
+ ...(effectiveArgs ? { args: effectiveArgs } : {}),
867
974
  ...(mergedDefaults ? { defaults: mergedDefaults } : {}),
868
975
  ...(typeof substituted.parallel === "boolean"
869
976
  ? { parallel: substituted.parallel }
@@ -930,7 +1037,7 @@ export function readResolvedRecipeConfig(file, stack = [], options = {}) {
930
1037
  ...(isRecord(substituted.usage) ? { usage: substituted.usage } : {}),
931
1038
  };
932
1039
  }
933
- function collectRecipeContextRecords(file, stack, importPath, alias) {
1040
+ function collectRecipeContextRecords(file, stack, importPath, context, alias) {
934
1041
  const path = resolveRecipePath(file, stack.length > 0 ? dirname(stack.at(-1)) : Paths.getRecipeRoot());
935
1042
  if (stack.includes(path)) {
936
1043
  throw new Error(`Cyclic recipe import: ${[...stack, path].join(" -> ")}`);
@@ -941,26 +1048,32 @@ function collectRecipeContextRecords(file, stack, importPath, alias) {
941
1048
  const raw = readRawRecipeConfig(path);
942
1049
  if (!raw || !Object.hasOwn(raw, "template"))
943
1050
  return [];
944
- const qualifiedName = qualifiedRecipeNameForFile(path);
1051
+ assertRecipeHasNoDeclaredName(raw);
1052
+ const qualifiedName = qualifiedRecipeNameForFile(path, context);
1053
+ const skill = qualifiedName?.split("/", 1)[0];
945
1054
  const record = {
946
1055
  ...(alias ? { alias } : {}),
947
1056
  depth: stack.length,
948
- file: path,
949
1057
  import_path: importPath,
1058
+ logical_reference: qualifiedName ?? basename(path),
950
1059
  name: getRecipeIdFromPath(path),
951
- ...(qualifiedName ? { qualified_name: qualifiedName } : {}),
952
1060
  recipe: raw,
953
1061
  role: stack.length === 0 ? "entry" : "import",
1062
+ ...(skill ? { skill } : {}),
1063
+ source_file: path,
1064
+ source_kind: qualifiedName
1065
+ ? "active_skill_component"
1066
+ : "explicit_file_recipe",
954
1067
  };
955
1068
  const imports = getRecipeImports(raw);
956
1069
  const children = Object.entries(imports).flatMap(([childAlias, binding]) => {
957
- const importFile = resolveRecipeImportPath(getImportFrom(binding), dirname(path));
958
- return collectRecipeContextRecords(importFile, [...stack, path], [...importPath, childAlias], childAlias);
1070
+ const importFile = resolveRecipeImportPath(getImportFrom(binding), dirname(path), context);
1071
+ return collectRecipeContextRecords(importFile, [...stack, path], [...importPath, childAlias], context, childAlias);
959
1072
  });
960
1073
  return [record, ...children];
961
1074
  }
962
- export function buildRecipeContextRecords(file) {
963
- return collectRecipeContextRecords(file, [], []);
1075
+ export function buildRecipeContextRecords(file, context = EMPTY_ACTIVE_SKILL_RECIPE_CONTEXT) {
1076
+ return collectRecipeContextRecords(file, [], [], context);
964
1077
  }
965
1078
  export function getRecipeTemplate(value) {
966
1079
  return readRecipeConfig(value)?.template;
@@ -16,16 +16,24 @@ export interface RegisterToolInput {
16
16
  values?: Record<string, unknown>;
17
17
  }
18
18
  export interface RegisterToolResultDetails {
19
+ active_tool?: boolean;
20
+ activation?: "current_session" | "unverified";
19
21
  args?: string[];
20
22
  async?: boolean;
23
+ callable_now?: boolean;
21
24
  config?: string;
22
25
  defaults?: Record<string, string>;
23
26
  draft?: string;
27
+ host_registered?: boolean;
28
+ persisted?: boolean;
24
29
  promoted?: boolean;
30
+ registry_active?: boolean;
31
+ resolved?: boolean;
25
32
  recipeName?: string;
26
33
  template?: CommandTemplates.CommandTemplateValue;
27
34
  templateWarnings?: string[];
28
35
  tool: string;
36
+ validated?: boolean;
29
37
  }
30
38
  export interface RegisterToolResult {
31
39
  content: Array<{
@@ -41,7 +49,12 @@ export interface RegisterToolRuntimeDeps<TContext> {
41
49
  getTools: () => Map<string, Config.RegisteredTool>;
42
50
  getActiveTools: () => string[];
43
51
  notify: (ctx: TContext, message: string, type: "info" | "warning" | "error") => void;
44
- registerRuntimeTool: (cfg: Config.RegisteredTool) => void;
52
+ registerRuntimeTool: (cfg: Config.RegisteredTool) => {
53
+ active_tool: boolean;
54
+ activation: "current_session" | "unverified";
55
+ callable_now: boolean;
56
+ host_registered: boolean;
57
+ } | void;
45
58
  reservedToolNames: Set<string>;
46
59
  setActiveTools: (toolNames: string[]) => void;
47
60
  }