@llblab/pi-actors 0.46.0 → 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 (50) hide show
  1. package/BACKLOG.md +582 -0
  2. package/CHANGELOG.md +9 -0
  3. package/README.md +1 -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 +1 -1
  11. package/dist/lib/prompts.js +2 -2
  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 +18 -0
  15. package/dist/lib/recipes-discovery.js +95 -22
  16. package/dist/lib/recipes-references.d.ts +18 -0
  17. package/dist/lib/recipes-references.js +120 -33
  18. package/dist/lib/registry.d.ts +14 -1
  19. package/dist/lib/registry.js +110 -87
  20. package/dist/lib/runtime.d.ts +30 -3
  21. package/dist/lib/runtime.js +73 -9
  22. package/dist/lib/tools-inspect.d.ts +3 -0
  23. package/dist/lib/tools-inspect.js +40 -20
  24. package/dist/lib/tools-local.d.ts +2 -2
  25. package/dist/lib/tools-local.js +5 -2
  26. package/dist/lib/tools-response.js +2 -0
  27. package/dist/lib/tools-spawn.d.ts +2 -2
  28. package/dist/lib/tools-spawn.js +1 -1
  29. package/dist/lib/tools.d.ts +4 -1
  30. package/dist/lib/tools.js +2 -0
  31. package/dist/skills/actors/SKILL.md +3 -3
  32. package/docs/template-recipes.md +3 -1
  33. package/docs/tool-registry.md +9 -3
  34. package/lib/async-runs.ts +5 -1
  35. package/lib/execution.ts +2 -0
  36. package/lib/extension-runtime.ts +33 -14
  37. package/lib/inspector.ts +1 -0
  38. package/lib/prompts.ts +2 -2
  39. package/lib/recipes-context.ts +49 -4
  40. package/lib/recipes-discovery.ts +164 -24
  41. package/lib/recipes-references.ts +163 -39
  42. package/lib/registry.ts +185 -94
  43. package/lib/runtime.ts +108 -12
  44. package/lib/tools-inspect.ts +57 -23
  45. package/lib/tools-local.ts +9 -3
  46. package/lib/tools-response.ts +2 -0
  47. package/lib/tools-spawn.ts +3 -2
  48. package/lib/tools.ts +4 -1
  49. package/package.json +1 -1
  50. package/skills/actors/SKILL.md +3 -3
@@ -8,6 +8,7 @@ import * as CommandTemplates from "./command-templates.js";
8
8
  import * as Paths from "./paths.js";
9
9
  import * as Pi from "./pi.js";
10
10
  import * as Prompts from "./prompts.js";
11
+ import * as RecipeResolution from "./recipes-context.js";
11
12
  import * as RecipesReferences from "./recipes-references.js";
12
13
  import * as RunUiRuntime from "./run-ui-runtime.js";
13
14
  import * as Runtime from "./runtime.js";
@@ -16,10 +17,16 @@ import * as Tools from "./tools.js";
16
17
  import * as ToolsResponse from "./tools-response.js";
17
18
  export function createActorExtensionRuntime(pi) {
18
19
  let activeRunContext;
19
- const skillContextsBySession = new Map();
20
+ const recipeResolutionContextsBySession = new Map();
20
21
  const getRunOwnerId = Pi.getSessionId;
21
- const getSkillContext = (ctx) => skillContextsBySession.get(getRunOwnerId(ctx)) ??
22
- RecipesReferences.EMPTY_ACTIVE_SKILL_RECIPE_CONTEXT;
22
+ const getRecipeResolutionContext = (ctx) => {
23
+ const sessionId = getRunOwnerId(ctx);
24
+ const resolutionContext = recipeResolutionContextsBySession.get(sessionId);
25
+ if (!resolutionContext) {
26
+ throw new Error(`Recipe resolution context is unavailable for session ${sessionId}.`);
27
+ }
28
+ return resolutionContext;
29
+ };
23
30
  const automaticReview = AutomaticReviewRuntime.createAutomaticReviewRuntime({
24
31
  getActiveContext: () => activeRunContext,
25
32
  getRunOwnerId,
@@ -44,7 +51,7 @@ export function createActorExtensionRuntime(pi) {
44
51
  if (ctx && typeof ctx === "object") {
45
52
  nextArgs[4] = {
46
53
  ...ctx,
47
- activeSkillRecipeContext: getSkillContext(ctx),
54
+ recipeResolutionContext: getRecipeResolutionContext(ctx),
48
55
  getThinkingLevel: () => pi.getThinkingLevel(),
49
56
  };
50
57
  }
@@ -61,6 +68,7 @@ export function createActorExtensionRuntime(pi) {
61
68
  configPath: Paths.EXTENSION_RUNTIME_PATHS.configPath,
62
69
  exec: CommandTemplates.execCommandTemplate,
63
70
  getActiveTools: () => pi.getActiveTools(),
71
+ getAllTools: () => pi.getAllTools(),
64
72
  registerTool: (definition) => {
65
73
  const wrapped = withCurrentThinkingContext(definition);
66
74
  actorToolDefinitions.set(wrapped.name, wrapped);
@@ -69,10 +77,17 @@ export function createActorExtensionRuntime(pi) {
69
77
  reservedToolNames: Tools.RESERVED_TOOL_NAMES,
70
78
  setActiveTools: (toolNames) => pi.setActiveTools(toolNames),
71
79
  });
72
- const recipeReload = Runtime.createRecipeToolReloadWatcher(runtime);
80
+ const recipeReload = Runtime.createRecipeToolReloadWatcher(runtime, {
81
+ getResolutionContext: () => activeRunContext
82
+ ? getRecipeResolutionContext(activeRunContext)
83
+ : undefined,
84
+ });
73
85
  return {
74
86
  beforeAgentStart(systemPrompt, skills, ctx) {
75
- skillContextsBySession.set(getRunOwnerId(ctx), RecipesReferences.createActiveSkillRecipeContext(skills));
87
+ const sessionId = getRunOwnerId(ctx);
88
+ const resolutionContext = RecipeResolution.createRecipeResolutionContext(sessionId, ctx.cwd, RecipesReferences.createActiveSkillRecipeContext(skills));
89
+ recipeResolutionContextsBySession.set(sessionId, resolutionContext);
90
+ runtime.loadTools(ctx, resolutionContext);
76
91
  return {
77
92
  systemPrompt: `${systemPrompt}\n\n${Prompts.ONBOARDING_SYSTEM_PROMPT}`,
78
93
  };
@@ -87,14 +102,15 @@ export function createActorExtensionRuntime(pi) {
87
102
  automaticReview.schedule();
88
103
  },
89
104
  onSessionShutdown(reason, ctx) {
90
- skillContextsBySession.delete(getRunOwnerId(ctx));
105
+ recipeResolutionContextsBySession.delete(getRunOwnerId(ctx));
91
106
  activeRunContext = undefined;
92
107
  automaticReview.close();
93
108
  recipeReload.close();
94
109
  runUiRuntime.shutdown(reason, ctx);
95
110
  },
96
111
  async onSessionStart(ctx) {
97
- skillContextsBySession.set(getRunOwnerId(ctx), RecipesReferences.EMPTY_ACTIVE_SKILL_RECIPE_CONTEXT);
112
+ const sessionId = getRunOwnerId(ctx);
113
+ recipeResolutionContextsBySession.set(sessionId, RecipeResolution.createEmptyRecipeResolutionContext(sessionId, ctx.cwd));
98
114
  ctx.ui.setWidget("zz-pi-actors-comms", undefined);
99
115
  activeRunContext = ctx;
100
116
  runUiRuntime.close();
@@ -104,7 +120,6 @@ export function createActorExtensionRuntime(pi) {
104
120
  if (activeRunContext !== ctx)
105
121
  return;
106
122
  automaticReview.start(ctx);
107
- runtime.loadTools(ctx);
108
123
  runUiRuntime.start(ctx);
109
124
  recipeReload.watch(ctx);
110
125
  },
@@ -113,6 +128,7 @@ export function createActorExtensionRuntime(pi) {
113
128
  configPath: Paths.EXTENSION_RUNTIME_PATHS.configPath,
114
129
  getActiveTools: () => pi.getActiveTools(),
115
130
  getRuntimeTool: (name) => Tools.resolveActiveRuntimeTool(name, runtime.getTools(), (activeName) => actorToolDefinitions.get(activeName)),
131
+ getRuntimeToolStatus: runtime.getToolStatus,
116
132
  handleRuntimeControl: automaticReview.handleControl,
117
133
  registryRuntime: runtime,
118
134
  setActiveTools: (toolNames) => pi.setActiveTools(toolNames),
@@ -115,6 +115,7 @@ export function readActorInspectorRecipe(stateDir) {
115
115
  logical_reference: primaryLogicalReference,
116
116
  ...(typeof primary?.skill === "string" ? { skill: primary.skill } : {}),
117
117
  source_kind: primarySourceKind,
118
+ launch_kind: meta.launch_kind ?? meta.launch_source,
118
119
  launch_source: meta.launch_source,
119
120
  }),
120
121
  launch: redactedRecord({
@@ -6,7 +6,7 @@
6
6
  export declare const REGISTER_TOOL_DESCRIPTION: string;
7
7
  export declare const REGISTER_TOOL_PROMPT_SNIPPET = "Register persistent command templates as agent-callable tools";
8
8
  export declare const REGISTER_TOOL_GUIDELINES: string[];
9
- export declare const ONBOARDING_SYSTEM_PROMPT = "pi-actors quick model:\n- Local-first actor memory: persist trusted local capabilities instead of rebuilding shell recipes.\n- Layers: task -> command template -> recipe/tool -> spawn -> run:<id>; tool:<name> wraps registered capabilities.\n- Command templates stay sync and shell-free: string leaves split into executable + argv, infer .js/.mjs through node\u2192bun\u2192deno run and .sh through bash, and treat operators such as && as literal arguments; use template arrays for sequencing or an explicit trusted shell/script when shell semantics are required. Flags include args/defaults, parallel, concurrency, min_successful, when, timeout, delay, retry, failure, recover, repeat, accept_output, output.\n- Placeholders support typed/default args plus {value??fallback} and {flag?yes:no}.\n- ~/.pi/agent/recipes/*.json is actor muscle memory: every recipe there is auto-registered as an agent tool across sessions; register_tool writes there.\n- Recipes own template directly and may declare metadata/defaults/imports/control/artifacts; files >1 MiB or import depth >32 fail closed.\n- Recipe imports are local variables; imported recipes are definitions, not nested async runs; parent async:true creates one run.\n- Actor-mode trigger: if work may outlive this turn, need steering/follow-up/artifacts, run as a service, fan out, or be resumed/inspected later, use spawn -> message -> inspect instead of ad hoc shell backgrounding.\n- Use spawn/message/inspect for actor-level start/send/observe; short foreground checks can stay ordinary tools/templates; avoid internal transport vocabulary in public guidance.\n- Run state lives under ~/.pi/agent/tmp/pi-actors/runs. Inspect intentionally and avoid busy-polling. Terminal and coordinator-bound notifications queue as Pi follow-ups so concurrently completed actors can reach the coordinator after current work instead of steering between tool calls. Terminal follow-up content stays minimal: run, status, one base path, and relative artifact names only; semantic output stays in non-LLM details and run state. When a deferred actor result gates the next step, wait for its terminal follow-up; do not schedule continuation loops, repeatedly inspect, or mutate its reviewed scope while it runs. Inspect early only for an operator request, a meaningful actor event, or diagnosis of an overdue/stuck run.\n- Maintain ~/.pi/agent/recipes like MEMORY.md for capabilities: keep useful tools, curate stale ones, and fix/remove/disable invalid recipes flagged by registry warnings; active-Skill and explicit file Recipes remain components outside user tool discovery; offer to save successful recurring patterns only after confirmation.\n- Prefer maintained active-Skill Recipes with spawn recipe=<skill>/<recipe> before ad hoc scripts/wrappers; explicit file Recipes use exact .json/.md paths; review swarms inherit current model/thinking, preflight before fanout, and expose quorum/concurrency/TTL knobs unless explicit args are passed.\n- For any non-trivial actor use or pi-actors change, read the bundled actors skill first. Before launching multiple actors/subagents for parallel implementation, independent artifact generation, delegated audit, or review, also read the bundled swarm skill; the coordinator owns decomposition, disjoint scopes, launch correctness, integration, and final validation. For deeper guidance, inspect installed extension sources/docs/recipes because README/docs are not automatically in context.";
9
+ export declare const ONBOARDING_SYSTEM_PROMPT = "pi-actors quick model:\n- Local-first actor memory: persist trusted local capabilities instead of rebuilding shell recipes.\n- Layers: task -> command template -> recipe/tool -> spawn -> run:<id>; tool:<name> wraps registered capabilities.\n- Command templates stay sync and shell-free: string leaves split into executable + argv, infer .js/.mjs through node\u2192bun\u2192deno run and .sh through bash, and treat operators such as && as literal arguments; use template arrays for sequencing or an explicit trusted shell/script when shell semantics are required. Flags include args/defaults, parallel, concurrency, min_successful, when, timeout, delay, retry, failure, recover, repeat, accept_output, output.\n- Placeholders support typed/default args plus {value??fallback} and {flag?yes:no}.\n- ~/.pi/agent/recipes/*.json is actor muscle memory: valid admitted recipes are reconciled as tools across sessions; register_tool writes there and reports current activation truth.\n- Recipes own template directly and may declare metadata/defaults/imports/control/artifacts; files >1 MiB or import depth >32 fail closed.\n- Recipe imports are local variables; imported recipes are definitions, not nested async runs; parent async:true creates one run.\n- Actor-mode trigger: if work may outlive this turn, need steering/follow-up/artifacts, run as a service, fan out, or be resumed/inspected later, use spawn -> message -> inspect instead of ad hoc shell backgrounding.\n- Use spawn/message/inspect for actor-level start/send/observe; short foreground checks can stay ordinary tools/templates; avoid internal transport vocabulary in public guidance.\n- Run state lives under ~/.pi/agent/tmp/pi-actors/runs. Inspect intentionally and avoid busy-polling. Terminal and coordinator-bound notifications queue as Pi follow-ups so concurrently completed actors can reach the coordinator after current work instead of steering between tool calls. Terminal follow-up content stays minimal: run, status, one base path, and relative artifact names only; semantic output stays in non-LLM details and run state. When a deferred actor result gates the next step, wait for its terminal follow-up; do not schedule continuation loops, repeatedly inspect, or mutate its reviewed scope while it runs. Inspect early only for an operator request, a meaningful actor event, or diagnosis of an overdue/stuck run.\n- Maintain ~/.pi/agent/recipes like MEMORY.md for capabilities: keep useful tools, curate stale ones, and fix/remove/disable invalid recipes flagged by registry warnings; active-Skill and explicit file Recipes remain components outside user tool discovery; offer to save successful recurring patterns only after confirmation.\n- Prefer maintained active-Skill Recipes with spawn recipe=<skill>/<recipe> before ad hoc scripts/wrappers; explicit file Recipes use exact .json/.md paths; review swarms inherit current model/thinking, preflight before fanout, and expose quorum/concurrency/TTL knobs unless explicit args are passed.\n- For any non-trivial actor use or pi-actors change, read the bundled actors skill first. Before launching multiple actors/subagents for parallel implementation, independent artifact generation, delegated audit, or review, also read the bundled swarm skill; the coordinator owns decomposition, disjoint scopes, launch correctness, integration, and final validation. For deeper guidance, inspect installed extension sources/docs/recipes because README/docs are not automatically in context.";
10
10
  export declare const REGISTER_TOOL_PARAM_DESCRIPTIONS: {
11
11
  readonly name: "Tool name in snake_case (e.g., 'transcribe')";
12
12
  readonly description: "Describe what the tool does for the LLM. Required unless deleting; omitted updates keep the old description.";
@@ -9,7 +9,7 @@ export const REGISTER_TOOL_DESCRIPTION = "Register a persistent custom tool from
9
9
  export const REGISTER_TOOL_PROMPT_SNIPPET = "Register persistent command templates as agent-callable tools";
10
10
  export const REGISTER_TOOL_GUIDELINES = [
11
11
  "Use register_tool to wrap trusted local commands, scripts, programs, libraries, or template recipes as persistent pi tools.",
12
- "After register_tool succeeds, the new tool is immediately callable and remains available after reload.",
12
+ "After register_tool succeeds, trust its callable_now and activation result; persisted definitions remain available for admission after reload.",
13
13
  'Set template=null or template="" in register_tool to delete a persisted tool.',
14
14
  "Set update=true in register_tool to overwrite an existing tool registration.",
15
15
  ];
@@ -18,7 +18,7 @@ export const ONBOARDING_SYSTEM_PROMPT = `pi-actors quick model:
18
18
  - Layers: task -> command template -> recipe/tool -> spawn -> run:<id>; tool:<name> wraps registered capabilities.
19
19
  - Command templates stay sync and shell-free: string leaves split into executable + argv, infer .js/.mjs through node→bun→deno run and .sh through bash, and treat operators such as && as literal arguments; use template arrays for sequencing or an explicit trusted shell/script when shell semantics are required. Flags include args/defaults, parallel, concurrency, min_successful, when, timeout, delay, retry, failure, recover, repeat, accept_output, output.
20
20
  - Placeholders support typed/default args plus {value??fallback} and {flag?yes:no}.
21
- - ~/.pi/agent/recipes/*.json is actor muscle memory: every recipe there is auto-registered as an agent tool across sessions; register_tool writes there.
21
+ - ~/.pi/agent/recipes/*.json is actor muscle memory: valid admitted recipes are reconciled as tools across sessions; register_tool writes there and reports current activation truth.
22
22
  - Recipes own template directly and may declare metadata/defaults/imports/control/artifacts; files >1 MiB or import depth >32 fail closed.
23
23
  - Recipe imports are local variables; imported recipes are definitions, not nested async runs; parent async:true creates one run.
24
24
  - Actor-mode trigger: if work may outlive this turn, need steering/follow-up/artifacts, run as a service, fan out, or be resumed/inspected later, use spawn -> message -> inspect instead of ad hoc shell backgrounding.
@@ -1,10 +1,19 @@
1
1
  /**
2
- * Recipe context prompt assembly.
3
- * Zones: async runner prompt context, recipe provenance, LLM child launches
4
- * Owns compact actor recipe context records appended to child-agent prompts.
2
+ * Recipe context contracts and prompt assembly.
3
+ * Zones: live session resolution, async runner prompt context, recipe provenance, LLM child launches
4
+ * Owns the immutable live Recipe environment and compact actor context appended to child-agent prompts.
5
5
  */
6
6
  import type { CommandTemplateActorRecipeContext } from "./command-templates.ts";
7
+ import * as RecipesReferences from "./recipes-references.ts";
7
8
  import type { TemplateRecipeContextRecord } from "./recipes-references.ts";
9
+ export interface RecipeResolutionContext {
10
+ readonly activeSkills: RecipesReferences.ActiveSkillRecipeContext;
11
+ readonly cwd: string;
12
+ readonly generation: string;
13
+ readonly sessionId: string;
14
+ }
15
+ export declare function createRecipeResolutionContext(sessionId: string, cwd: string, activeSkills: RecipesReferences.ActiveSkillRecipeContext): RecipeResolutionContext;
16
+ export declare function createEmptyRecipeResolutionContext(sessionId: string, cwd: string): RecipeResolutionContext;
8
17
  export interface MarkedRecipeContextRecord extends TemplateRecipeContextRecord {
9
18
  you_are_here?: true;
10
19
  you_are_here_path?: string;
@@ -1,10 +1,34 @@
1
1
  /**
2
- * Recipe context prompt assembly.
3
- * Zones: async runner prompt context, recipe provenance, LLM child launches
4
- * Owns compact actor recipe context records appended to child-agent prompts.
2
+ * Recipe context contracts and prompt assembly.
3
+ * Zones: live session resolution, async runner prompt context, recipe provenance, LLM child launches
4
+ * Owns the immutable live Recipe environment and compact actor context appended to child-agent prompts.
5
5
  */
6
+ import { createHash } from "node:crypto";
6
7
  import { writeFileSync } from "node:fs";
7
- import { basename } from "node:path";
8
+ import { basename, resolve } from "node:path";
9
+ import * as RecipesReferences from "./recipes-references.js";
10
+ export function createRecipeResolutionContext(sessionId, cwd, activeSkills) {
11
+ const normalizedSessionId = sessionId.trim();
12
+ if (!normalizedSessionId)
13
+ throw new Error("Recipe resolution session id is required.");
14
+ const normalizedCwd = resolve(cwd);
15
+ const generation = createHash("sha256")
16
+ .update(JSON.stringify({
17
+ activeSkills: RecipesReferences.getActiveSkillRecipeNamespaces(activeSkills),
18
+ cwd: normalizedCwd,
19
+ sessionId: normalizedSessionId,
20
+ }))
21
+ .digest("hex");
22
+ return Object.freeze({
23
+ activeSkills,
24
+ cwd: normalizedCwd,
25
+ generation,
26
+ sessionId: normalizedSessionId,
27
+ });
28
+ }
29
+ export function createEmptyRecipeResolutionContext(sessionId, cwd) {
30
+ return createRecipeResolutionContext(sessionId, cwd, RecipesReferences.EMPTY_ACTIVE_SKILL_RECIPE_CONTEXT);
31
+ }
8
32
  function commandName(command) {
9
33
  return basename(command).toLowerCase();
10
34
  }
@@ -5,6 +5,7 @@
5
5
  */
6
6
  import * as CommandTemplates from "./command-templates.ts";
7
7
  import type { RegisteredTool } from "./config.ts";
8
+ import type { RecipeResolutionContext } from "./recipes-context.ts";
8
9
  import type { TemplateRecipeConfig } from "./recipes-references.ts";
9
10
  export interface DiscoveredRecipe {
10
11
  id: string;
@@ -18,6 +19,7 @@ export interface DiscoveredRecipe {
18
19
  disabled: boolean;
19
20
  tool: boolean;
20
21
  mutableUsage: boolean;
22
+ resolutionContext?: RecipeResolutionContext;
21
23
  diagnostics: string[];
22
24
  riskLabels: CommandTemplates.CommandTemplateRiskLabel[];
23
25
  shadows: string[];
@@ -44,6 +46,21 @@ export interface RecipesDiscoverySource {
44
46
  file?: string;
45
47
  defaultTool?: boolean;
46
48
  mutableUsage?: boolean;
49
+ resolutionContext?: RecipeResolutionContext;
50
+ }
51
+ export interface UserRecipeAdmission {
52
+ args?: string[];
53
+ argTypes?: RegisteredTool["argTypes"];
54
+ artifacts?: Record<string, string>;
55
+ async?: boolean;
56
+ control?: string[];
57
+ defaults?: Record<string, unknown>;
58
+ diagnostics: string[];
59
+ effectiveRecipe?: TemplateRecipeConfig;
60
+ identity: string;
61
+ sourcePath: string;
62
+ tool?: RegisteredTool;
63
+ validated: boolean;
47
64
  }
48
65
  export declare function hasBroadWindowsWriteAcl(icaclsOutput: string): boolean;
49
66
  export declare function discoverRecipeSources(sources: RecipesDiscoverySource[]): RecipesDiscoveryResult;
@@ -52,4 +69,5 @@ export declare function createRecipeIntegrityManifest(result: RecipesDiscoveryRe
52
69
  export declare function getShadowedLaunchDiagnostic(result: RecipesDiscoveryResult, id: string): Record<string, unknown> | undefined;
53
70
  export declare function listDraftRecipes(root: string): Array<Record<string, unknown>>;
54
71
  export declare function summarizeDiscovery(result: RecipesDiscoveryResult): Record<string, unknown>;
72
+ export declare function admitUserRecipe(sourcePath: string, resolutionContext: RecipeResolutionContext, authoredRecipe?: Record<string, unknown>, mutableUsage?: boolean): UserRecipeAdmission;
55
73
  export declare function toRegisteredTool(entry: DiscoveredRecipe): RegisteredTool | undefined;
@@ -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,6 +132,7 @@ 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
137
  `Failed to load recipe ${file}: ${error instanceof Error ? error.message : String(error)}`,
134
138
  ],
@@ -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,71 @@ export function toRegisteredTool(entry) {
694
699
  : {}),
695
700
  };
696
701
  }
702
+ function boundedAdmissionDiagnostic(error) {
703
+ const message = error instanceof Error ? error.message : String(error);
704
+ return message.length <= 4096 ? message : `${message.slice(0, 4095)}…`;
705
+ }
706
+ export function admitUserRecipe(sourcePath, resolutionContext, authoredRecipe, mutableUsage = true) {
707
+ const identity = RecipesReferences.getRecipeIdFromPath(sourcePath);
708
+ try {
709
+ const effectiveRecipe = RecipesReferences.readResolvedRecipeConfig(sourcePath, [], {
710
+ ...(authoredRecipe
711
+ ? { authoredEntry: { path: sourcePath, recipe: authoredRecipe } }
712
+ : {}),
713
+ skillContext: resolutionContext.activeSkills,
714
+ });
715
+ if (!effectiveRecipe) {
716
+ return {
717
+ diagnostics: [
718
+ authoredRecipe
719
+ ? "Recipe must define a valid template."
720
+ : RecipesReferences.diagnoseRawRecipeConfigFailure(sourcePath) ??
721
+ "Recipe must define a valid template.",
722
+ ],
723
+ identity,
724
+ sourcePath,
725
+ validated: false,
726
+ };
727
+ }
728
+ if (effectiveRecipe.disabled === true) {
729
+ return {
730
+ diagnostics: ["Recipe is disabled."],
731
+ effectiveRecipe,
732
+ identity,
733
+ sourcePath,
734
+ validated: false,
735
+ };
736
+ }
737
+ const tool = projectRegisteredTool(identity, sourcePath, effectiveRecipe, mutableUsage);
738
+ return {
739
+ args: tool.args,
740
+ argTypes: tool.argTypes,
741
+ artifacts: effectiveRecipe.artifacts,
742
+ async: effectiveRecipe.async === true,
743
+ control: effectiveRecipe.control,
744
+ defaults: effectiveRecipe.defaults,
745
+ diagnostics: [],
746
+ effectiveRecipe,
747
+ identity,
748
+ sourcePath,
749
+ tool,
750
+ validated: true,
751
+ };
752
+ }
753
+ catch (error) {
754
+ return {
755
+ diagnostics: [boundedAdmissionDiagnostic(error)],
756
+ identity,
757
+ sourcePath,
758
+ validated: false,
759
+ };
760
+ }
761
+ }
762
+ export function toRegisteredTool(entry) {
763
+ if (!entry.tool || entry.invalid || entry.disabled || !entry.config)
764
+ return undefined;
765
+ if (entry.resolutionContext) {
766
+ return admitUserRecipe(entry.path, entry.resolutionContext, undefined, entry.mutableUsage).tool;
767
+ }
768
+ return projectRegisteredTool(entry.id, entry.path, entry.config, entry.mutableUsage);
769
+ }
@@ -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;