@introspection-ai/recipes 0.13.0 → 0.14.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 (222) hide show
  1. package/README.md +46 -57
  2. package/dist/{agent-tool.d.ts → agents.d.ts} +4 -1
  3. package/dist/agents.d.ts.map +1 -0
  4. package/dist/{agent-tool.js → agents.js} +1 -1
  5. package/dist/agents.js.map +1 -0
  6. package/dist/api/extensions.d.ts +3 -0
  7. package/dist/api/extensions.d.ts.map +1 -0
  8. package/dist/api/extensions.js +2 -0
  9. package/dist/api/extensions.js.map +1 -0
  10. package/dist/api/mcp.d.ts +4 -0
  11. package/dist/api/mcp.d.ts.map +1 -0
  12. package/dist/api/mcp.js +2 -0
  13. package/dist/api/mcp.js.map +1 -0
  14. package/dist/api/session.d.ts +3 -0
  15. package/dist/api/session.d.ts.map +1 -0
  16. package/dist/api/session.js +2 -0
  17. package/dist/api/session.js.map +1 -0
  18. package/dist/child-agent.d.ts +5 -4
  19. package/dist/child-agent.d.ts.map +1 -1
  20. package/dist/child-agent.js +26 -117
  21. package/dist/child-agent.js.map +1 -1
  22. package/dist/child-session.d.ts +23 -0
  23. package/dist/child-session.d.ts.map +1 -0
  24. package/dist/child-session.js +48 -0
  25. package/dist/child-session.js.map +1 -0
  26. package/dist/extensions.d.ts +29 -0
  27. package/dist/extensions.d.ts.map +1 -0
  28. package/dist/extensions.js +119 -0
  29. package/dist/extensions.js.map +1 -0
  30. package/dist/index.d.ts +4 -13
  31. package/dist/index.d.ts.map +1 -1
  32. package/dist/index.js +2 -13
  33. package/dist/index.js.map +1 -1
  34. package/dist/inspect.d.ts +38 -5
  35. package/dist/inspect.d.ts.map +1 -1
  36. package/dist/inspect.js +92 -41
  37. package/dist/inspect.js.map +1 -1
  38. package/dist/interactions.d.ts +18 -2
  39. package/dist/interactions.d.ts.map +1 -1
  40. package/dist/interactions.js +41 -10
  41. package/dist/interactions.js.map +1 -1
  42. package/dist/mcp-catalog.d.ts +1 -0
  43. package/dist/mcp-catalog.d.ts.map +1 -1
  44. package/dist/mcp-catalog.js +14 -6
  45. package/dist/mcp-catalog.js.map +1 -1
  46. package/dist/mcp-chunks/auth-command-ZLGYIEUY.js +273 -0
  47. package/dist/mcp-chunks/call-arguments-LU3W6D3K.js +13 -0
  48. package/dist/mcp-chunks/call-command-LKTKSYTH.js +612 -0
  49. package/dist/mcp-chunks/chunk-2U4BSDC4.js +3951 -0
  50. package/dist/mcp-chunks/chunk-2XL7MG7R.js +272 -0
  51. package/dist/mcp-chunks/chunk-3BJA27PY.js +2321 -0
  52. package/dist/mcp-chunks/chunk-4SQKWYFF.js +15 -0
  53. package/dist/mcp-chunks/chunk-5QOSCSQB.js +290 -0
  54. package/dist/mcp-chunks/chunk-6W5QFASN.js +77 -0
  55. package/dist/mcp-chunks/chunk-6XCZGU3G.js +1004 -0
  56. package/dist/mcp-chunks/chunk-7FHEBT5G.js +149 -0
  57. package/dist/mcp-chunks/chunk-7KA2VJVZ.js +114 -0
  58. package/dist/mcp-chunks/chunk-7UDVSOOF.js +138 -0
  59. package/dist/mcp-chunks/chunk-BNV3TPE4.js +66 -0
  60. package/dist/mcp-chunks/chunk-BTLAWHTA.js +129 -0
  61. package/dist/mcp-chunks/chunk-BV56DXPW.js +13 -0
  62. package/dist/mcp-chunks/chunk-BWHIA4PC.js +90 -0
  63. package/dist/mcp-chunks/chunk-BZIQOWE6.js +267 -0
  64. package/dist/mcp-chunks/chunk-D3MRAZ6H.js +16140 -0
  65. package/dist/mcp-chunks/chunk-DHBE7UBH.js +750 -0
  66. package/dist/mcp-chunks/chunk-E22DYOW5.js +251 -0
  67. package/dist/mcp-chunks/chunk-ECIVB7P4.js +139 -0
  68. package/dist/mcp-chunks/chunk-HKRJ6O36.js +1076 -0
  69. package/dist/mcp-chunks/chunk-IABRYROW.js +44 -0
  70. package/dist/mcp-chunks/chunk-J6LYIZXN.js +45 -0
  71. package/dist/mcp-chunks/chunk-L7NTQVYN.js +161 -0
  72. package/dist/mcp-chunks/chunk-LEZSACDE.js +141 -0
  73. package/dist/mcp-chunks/chunk-LKIBAYKU.js +208 -0
  74. package/dist/mcp-chunks/chunk-LNH5LAJD.js +152 -0
  75. package/dist/mcp-chunks/chunk-LQQEYD76.js +124 -0
  76. package/dist/mcp-chunks/chunk-LRKZP7DJ.js +47 -0
  77. package/dist/mcp-chunks/chunk-LS7PMWSR.js +109 -0
  78. package/dist/mcp-chunks/chunk-M3TPODXI.js +95 -0
  79. package/dist/mcp-chunks/chunk-MZ2NARG7.js +400 -0
  80. package/dist/mcp-chunks/chunk-N46NEFAF.js +45 -0
  81. package/dist/mcp-chunks/chunk-NNOEGEUZ.js +17273 -0
  82. package/dist/mcp-chunks/chunk-NRAJX5E4.js +69 -0
  83. package/dist/mcp-chunks/chunk-P3PSC54P.js +869 -0
  84. package/dist/mcp-chunks/chunk-PIIYJR44.js +6341 -0
  85. package/dist/mcp-chunks/chunk-PWYFKGKG.js +385 -0
  86. package/dist/mcp-chunks/chunk-SJCCBNZ4.js +45 -0
  87. package/dist/mcp-chunks/chunk-TATVT5IT.js +194 -0
  88. package/dist/mcp-chunks/chunk-TEUKFFQL.js +29 -0
  89. package/dist/mcp-chunks/chunk-UKYNQGNE.js +150 -0
  90. package/dist/mcp-chunks/chunk-UNKB4SBV.js +37 -0
  91. package/dist/mcp-chunks/chunk-WD6BOD24.js +138 -0
  92. package/dist/mcp-chunks/chunk-WQJZMCJN.js +73 -0
  93. package/dist/mcp-chunks/chunk-XANPGTLI.js +364 -0
  94. package/dist/mcp-chunks/chunk-XQK2JSW4.js +35 -0
  95. package/dist/mcp-chunks/chunk-YHQUTU6L.js +571 -0
  96. package/dist/mcp-chunks/chunk-YVZKMV5H.js +57 -0
  97. package/dist/mcp-chunks/cli-UV4QJZZM.js +929 -0
  98. package/dist/mcp-chunks/client-S7RQUG5U.js +13 -0
  99. package/dist/mcp-chunks/config-command-NLAGKID5.js +1101 -0
  100. package/dist/mcp-chunks/daemon-command-U242Z6VY.js +26 -0
  101. package/dist/mcp-chunks/dist-LK3SHAMX.js +6478 -0
  102. package/dist/mcp-chunks/emit-ts-command-VBXVXO6V.js +386 -0
  103. package/dist/mcp-chunks/generate-cli-runner-6CAZZA3P.js +1426 -0
  104. package/dist/mcp-chunks/inspect-cli-command-3ZORTOR7.js +111 -0
  105. package/dist/mcp-chunks/launch-YKRXHXJI.js +10 -0
  106. package/dist/mcp-chunks/lifecycle-XVKRHPH3.js +16 -0
  107. package/dist/mcp-chunks/list-command-2MEGGNFQ.js +4190 -0
  108. package/dist/mcp-chunks/output-utils-NKESLAJ6.js +12 -0
  109. package/dist/mcp-chunks/prompt-B1Yc1NPt-VGOTWJMN.js +918 -0
  110. package/dist/mcp-chunks/record-command-YXHTTTT5.js +17 -0
  111. package/dist/mcp-chunks/replay-command-25HJ6AQU.js +85 -0
  112. package/dist/mcp-chunks/resource-command-OO4NYF5C.js +97 -0
  113. package/dist/mcp-chunks/result-utils-MOHHLNM7.js +15 -0
  114. package/dist/mcp-chunks/runtime-5WJ3G25Y.js +33 -0
  115. package/dist/mcp-chunks/runtime-wrapper-RPWDIDBF.js +10 -0
  116. package/dist/mcp-chunks/serve-command-DHXJZMMN.js +3302 -0
  117. package/dist/mcp-chunks/timeouts-W2XWCCXR.js +18 -0
  118. package/dist/mcp-chunks/vault-command-PIYIUEZL.js +154 -0
  119. package/dist/mcp-daemon-client.d.ts +6 -1
  120. package/dist/mcp-daemon-client.d.ts.map +1 -1
  121. package/dist/mcp-daemon-client.js +119 -2
  122. package/dist/mcp-daemon-client.js.map +1 -1
  123. package/dist/mcp-daemon-protocol.d.ts +18 -1
  124. package/dist/mcp-daemon-protocol.d.ts.map +1 -1
  125. package/dist/mcp-daemon-protocol.js +31 -0
  126. package/dist/mcp-daemon-protocol.js.map +1 -1
  127. package/dist/mcp-daemon.js +169 -76602
  128. package/dist/mcp-daemon.js.map +1 -1
  129. package/dist/mcp-policy.d.ts +11 -0
  130. package/dist/mcp-policy.d.ts.map +1 -0
  131. package/dist/mcp-policy.js +27 -0
  132. package/dist/mcp-policy.js.map +1 -0
  133. package/dist/mcp-run-worker.js +38 -76540
  134. package/dist/mcp-tools.d.ts +39 -0
  135. package/dist/mcp-tools.d.ts.map +1 -0
  136. package/dist/mcp-tools.js +493 -0
  137. package/dist/mcp-tools.js.map +1 -0
  138. package/dist/mcp.d.ts +21 -9
  139. package/dist/mcp.d.ts.map +1 -1
  140. package/dist/mcp.js +101 -31
  141. package/dist/mcp.js.map +1 -1
  142. package/dist/model-binding.d.ts +39 -0
  143. package/dist/model-binding.d.ts.map +1 -0
  144. package/dist/model-binding.js +103 -0
  145. package/dist/model-binding.js.map +1 -0
  146. package/dist/pi-extension.d.ts +2 -22
  147. package/dist/pi-extension.d.ts.map +1 -1
  148. package/dist/pi-extension.js +296 -71
  149. package/dist/pi-extension.js.map +1 -1
  150. package/dist/recipe/resolve.d.ts +43 -20
  151. package/dist/recipe/resolve.d.ts.map +1 -1
  152. package/dist/recipe/resolve.js +215 -70
  153. package/dist/recipe/resolve.js.map +1 -1
  154. package/dist/recipe-agent.d.ts +24 -27
  155. package/dist/recipe-agent.d.ts.map +1 -1
  156. package/dist/recipe-agent.js +278 -230
  157. package/dist/recipe-agent.js.map +1 -1
  158. package/dist/recipe-check.d.ts +18 -0
  159. package/dist/recipe-check.d.ts.map +1 -0
  160. package/dist/recipe-check.js +261 -0
  161. package/dist/recipe-check.js.map +1 -0
  162. package/dist/recipe-extensions.d.ts.map +1 -1
  163. package/dist/recipe-extensions.js +0 -3
  164. package/dist/recipe-extensions.js.map +1 -1
  165. package/dist/recipe-model.d.ts +3 -1
  166. package/dist/recipe-model.d.ts.map +1 -1
  167. package/dist/recipe-model.js +15 -12
  168. package/dist/recipe-model.js.map +1 -1
  169. package/dist/recipe-package.d.ts +6 -28
  170. package/dist/recipe-package.d.ts.map +1 -1
  171. package/dist/recipe-package.js +248 -191
  172. package/dist/recipe-package.js.map +1 -1
  173. package/dist/recipe-skills.d.ts.map +1 -1
  174. package/dist/recipe-skills.js +42 -29
  175. package/dist/recipe-skills.js.map +1 -1
  176. package/dist/run-controller.d.ts +10 -9
  177. package/dist/run-controller.d.ts.map +1 -1
  178. package/dist/run-controller.js +28 -21
  179. package/dist/run-controller.js.map +1 -1
  180. package/dist/session.d.ts +40 -53
  181. package/dist/session.d.ts.map +1 -1
  182. package/dist/session.js +312 -173
  183. package/dist/session.js.map +1 -1
  184. package/dist/test-utils.d.ts +7 -7
  185. package/dist/test-utils.d.ts.map +1 -1
  186. package/dist/test-utils.js +32 -15
  187. package/dist/test-utils.js.map +1 -1
  188. package/docs/agent-composition.md +41 -35
  189. package/docs/host-api.md +201 -0
  190. package/docs/index.md +9 -14
  191. package/docs/interactions.md +12 -11
  192. package/docs/mcp-auth.md +6 -6
  193. package/docs/mcp-configuration.md +73 -25
  194. package/docs/pi-extension.md +39 -11
  195. package/docs/recipe-flow.md +9 -9
  196. package/docs/recipe-format.md +106 -41
  197. package/docs/recipe-judges.md +24 -36
  198. package/package.json +23 -17
  199. package/vendor/mcp-client/darwin-arm64/mcp-client +0 -0
  200. package/vendor/mcp-client/darwin-x64/mcp-client +0 -0
  201. package/vendor/mcp-client/linux-arm64/mcp-client +0 -0
  202. package/vendor/recipe-check/darwin-arm64/recipe-check +0 -0
  203. package/vendor/recipe-check/darwin-x64/recipe-check +0 -0
  204. package/vendor/recipe-check/linux-arm64/recipe-check +0 -0
  205. package/vendor/recipe-check/linux-x64/recipe-check +0 -0
  206. package/vendor/recipe-check/linux-x64-musl/recipe-check +0 -0
  207. package/vendor/recipe-check/win32-x64/recipe-check.exe +0 -0
  208. package/dist/agent-tool.d.ts.map +0 -1
  209. package/dist/agent-tool.js.map +0 -1
  210. package/dist/run.d.ts +0 -28
  211. package/dist/run.d.ts.map +0 -1
  212. package/dist/run.js +0 -84
  213. package/dist/run.js.map +0 -1
  214. package/dist/testing.d.ts +0 -25
  215. package/dist/testing.d.ts.map +0 -1
  216. package/dist/testing.js +0 -96
  217. package/dist/testing.js.map +0 -1
  218. package/docs/deployment-configuration.md +0 -65
  219. package/docs/migration.md +0 -70
  220. package/docs/python-bindings-release.md +0 -47
  221. package/docs/recipe-evals.md +0 -70
  222. package/docs/runtime-library.md +0 -171
package/dist/session.js CHANGED
@@ -1,137 +1,123 @@
1
1
  import { randomUUID } from "node:crypto";
2
- import { InMemoryCredentialStore } from "@earendil-works/pi-ai";
3
- import { getEnvApiKey, getModel } from "@earendil-works/pi-ai/compat";
4
- import { createAgentSessionFromServices, createAgentSessionServices, ModelRuntime, SessionManager, SettingsManager, } from "@earendil-works/pi-coding-agent";
2
+ import { rm } from "node:fs/promises";
3
+ import { getModel } from "@earendil-works/pi-ai/compat";
4
+ import { createBashToolDefinition, createAgentSessionFromServices, createAgentSessionServices, ModelRuntime, SessionManager, SettingsManager, } from "@earendil-works/pi-coding-agent";
5
5
  import { instrumentSession, } from "@introspection-sdk/introspection-pi";
6
- import { createAgentTool } from "./agent-tool.js";
6
+ import { createAgentTool } from "./agents.js";
7
7
  import { clearMcpCatalogPreload, preloadMcpCatalogs, } from "./mcp-catalog.js";
8
- import { configureMcpLocalConfigPath, isolateMcpEnvironment, materializeMcpSession, materializeSessionMcpCli, restoreMcpEnvironment, resolveAgentMcpSelections, snapshotMcpEnvironment, stopMcpDaemon, } from "./mcp.js";
9
- import { expectedProviderEnvVars } from "./provider-env.js";
8
+ import { createIsolatedMcpEnvironment, configureMcpLocalConfigPath, isolateMcpEnvironment, materializeMcpSession, materializeSessionMcpCli, readMcpSessionConfig, restoreMcpEnvironment, resolveAgentMcpSelections, snapshotMcpEnvironment, stopMcpDaemon, } from "./mcp.js";
9
+ import { createMcpToolSet } from "./mcp-tools.js";
10
+ import { assertRecipeModelTransport, RecipeCredentialError, RecipeModelError, RecipeModelTransportError, resolveRecipeCredentials, } from "./model-binding.js";
10
11
  import { loadRecipeExtensionFactory } from "./recipe-extensions.js";
11
- import { applyRecipeAgentModelConfigToModel, applyRecipeAgentModelConfigToSession, } from "./recipe-model.js";
12
- import { resolveRecipe } from "./recipe/resolve.js";
12
+ import { bindRecipeExtensionFactory, createRecipeExtensionRegistrationRegistry, recipeExtensionToolAllowlist, } from "./extensions.js";
13
+ import { applyRecipeAgentModelConfigToModel, applyRecipeAgentModelConfigToSession, cloneModelForRecipe, } from "./recipe-model.js";
13
14
  export { expectedProviderEnvVars } from "./provider-env.js";
14
- /** The session model's provider has no resolvable credential. */
15
- export class RecipeCredentialError extends Error {
16
- provider;
17
- expectedEnvVars;
18
- name = "RecipeCredentialError";
19
- constructor(provider,
20
- /** Env vars that would satisfy the provider, most conventional first. */
21
- expectedEnvVars) {
22
- super(`No credential for model provider "${provider}": set ${expectedEnvVars.join(" or ")}, or pass a CredentialStore via credentials`);
23
- this.provider = provider;
24
- this.expectedEnvVars = expectedEnvVars;
15
+ export { RecipeCredentialError, RecipeModelError, RecipeModelTransportError, };
16
+ function schemaChildPosition(position, key) {
17
+ if (position === "schema-map")
18
+ return "schema";
19
+ if (position === "literal")
20
+ return "literal";
21
+ if (key === "properties" ||
22
+ key === "patternProperties" ||
23
+ key === "$defs" ||
24
+ key === "definitions" ||
25
+ key === "dependentSchemas") {
26
+ return "schema-map";
25
27
  }
26
- }
27
- /** The recipe's model spec does not resolve to a known model. */
28
- export class RecipeModelError extends Error {
29
- modelSpec;
30
- name = "RecipeModelError";
31
- constructor(modelSpec) {
32
- super(`Recipe model is not available: ${modelSpec}`);
33
- this.modelSpec = modelSpec;
28
+ if (key === "default" ||
29
+ key === "const" ||
30
+ key === "examples" ||
31
+ key === "enum") {
32
+ return "literal";
34
33
  }
34
+ return "schema";
35
35
  }
36
- function parseModelSpec(spec) {
37
- const slash = spec.indexOf("/");
38
- if (slash < 0) {
39
- throw new Error(`Invalid recipe model "${spec}" - expected "<provider>/<model_id>"`);
36
+ function stripAdditionalProperties(value, position = "schema") {
37
+ if (value === null || typeof value !== "object")
38
+ return value;
39
+ const clone = Array.isArray(value)
40
+ ? []
41
+ : Object.create(Object.getPrototypeOf(value));
42
+ for (const key of Reflect.ownKeys(value)) {
43
+ if (position === "schema" && key === "additionalProperties")
44
+ continue;
45
+ const descriptor = Object.getOwnPropertyDescriptor(value, key);
46
+ if (!descriptor)
47
+ continue;
48
+ if ("value" in descriptor) {
49
+ descriptor.value = stripAdditionalProperties(descriptor.value, schemaChildPosition(position, key));
50
+ }
51
+ Object.defineProperty(clone, key, descriptor);
40
52
  }
41
- const provider = spec.slice(0, slash);
42
- const modelId = spec.slice(slash + 1);
43
- const lookupProvider = provider === "gemini" ? "google" : provider;
44
- return { provider, modelId, lookupProvider };
53
+ return clone;
45
54
  }
46
55
  /**
47
- * Ambient-credential providers resolve from their own SDK chain (AWS
48
- * profiles, ADC); `getEnvApiKey` reports them as "<authenticated>".
49
- */
50
- const AMBIENT_CREDENTIAL_SENTINEL = "<authenticated>";
51
- /**
52
- * Resolve credentials fail-closed against the session's own `env`, never the
53
- * ambient process env: explicit store → per-provider env keys → error.
56
+ * Gemini rejects JSON Schema's `additionalProperties` keyword in tool
57
+ * declarations. Normalize the final Recipe toolset after host tools and the
58
+ * shared agent tool have been registered so every host gets the same behavior.
54
59
  */
55
- async function resolveCredentialStore(lookupProvider, opts) {
56
- if (opts.credentials) {
57
- const stored = await opts.credentials.read(lookupProvider);
58
- if (!stored) {
59
- throw new RecipeCredentialError(lookupProvider, expectedProviderEnvVars(lookupProvider));
60
- }
61
- return opts.credentials;
62
- }
63
- const env = opts.env ?? process.env;
64
- const store = new InMemoryCredentialStore();
65
- const apiKey = getEnvApiKey(lookupProvider, env) ??
66
- env[`${lookupProvider.toUpperCase().replace(/-/g, "_")}_API_KEY`];
67
- if (!apiKey) {
68
- throw new RecipeCredentialError(lookupProvider, expectedProviderEnvVars(lookupProvider));
69
- }
70
- if (apiKey !== AMBIENT_CREDENTIAL_SENTINEL) {
71
- await store.modify(lookupProvider, async () => ({
72
- type: "api_key",
73
- key: apiKey,
74
- }));
60
+ function normalizeSessionToolsForModel(session, model) {
61
+ const isGemini = [model.id, model.name].some((value) => value?.toLowerCase().includes("gemini"));
62
+ if (!isGemini)
63
+ return;
64
+ const mutableSession = session;
65
+ for (const tool of mutableSession.agent?.state?.tools ?? []) {
66
+ tool.parameters = stripAdditionalProperties(tool.parameters);
75
67
  }
76
- // Ambient-credential providers (Bedrock, Vertex ADC) keep an empty store;
77
- // the pi-ai provider resolves its own credential chain at stream time.
78
- return store;
79
68
  }
80
69
  function scopedMcpSelections(recipe) {
81
- return [recipe.agent, ...recipe.subagents.values()].flatMap((agent) => resolveAgentMcpSelections(agent.mcp));
70
+ return resolveAgentMcpSelections(recipe.mcp);
82
71
  }
83
72
  const leasedMcpEnvironments = new WeakSet();
84
73
  export class RecipeMcpEnvironmentInUseError extends Error {
85
74
  name = "RecipeMcpEnvironmentInUseError";
86
75
  constructor() {
87
- super("This environment already belongs to a live materialized Recipe MCP session; use a separate env object per concurrent session or let the host materialize MCP once and pass mcpMode: \"inherit\"");
88
- }
89
- }
90
- /**
91
- * Everything `createRecipeSession` fail-closes on, without creating a
92
- * session or starting MCP: recipe resolution, model lookup, credentials.
93
- * Hosts with a boot phase (the serve layer) run this once to fail fast.
94
- */
95
- export async function preflightRecipeSession(opts) {
96
- const recipe = resolveRecipe({
97
- recipeDir: opts.recipeDir,
98
- ...(opts.agentName ? { agentName: opts.agentName } : {}),
99
- });
100
- const modelSpec = opts.model ?? recipe.modelSpec;
101
- const { lookupProvider, modelId } = parseModelSpec(modelSpec);
102
- const credentialProvider = opts.modelOverride?.provider ?? lookupProvider;
103
- const credentials = await resolveCredentialStore(credentialProvider, opts);
104
- const modelRuntime = await ModelRuntime.create({
105
- credentials,
106
- modelsPath: null,
107
- });
108
- const model = opts.modelOverride ??
109
- modelRuntime.getModel(lookupProvider, modelId) ??
110
- getModel(lookupProvider, modelId);
111
- if (!model) {
112
- throw new RecipeModelError(modelSpec);
76
+ super("This environment already belongs to a live materialized Recipe MCP session; use a separate env object per concurrent session or let the host materialize MCP once and pass mcpProvisioning: \"host\"");
113
77
  }
114
- return recipe;
115
78
  }
116
79
  /**
117
80
  * Materialize the session MCP runtime for a recipe scope into `env` at
118
- * `cwd`. Exposed for hosts that materialize once per process (the serve
119
- * layer) and create their sessions with `mcpMode: "inherit"`.
81
+ * `cwd`. Exposed for hosts that materialize once per process and create their
82
+ * sessions with `mcpProvisioning: "host"`.
120
83
  */
121
84
  export async function materializeRecipeSessionMcp(recipe, cwd, env, opts = {}) {
122
- return configureSessionMcp(recipe, cwd, env, {
123
- recipeDir: recipe.recipeDir,
124
- ...opts,
125
- }, false);
126
- }
127
- async function configureSessionMcp(recipe, cwd, env, opts, leaseEnvironment) {
128
85
  const selections = scopedMcpSelections(recipe);
129
- if (opts.mcpMode === "inherit")
130
- return { materialized: false };
131
86
  if (selections.length === 0)
132
87
  return { materialized: false };
133
- const snapshot = leaseEnvironment ? snapshotMcpEnvironment(env) : undefined;
134
- if (leaseEnvironment) {
88
+ if (opts.mcpBindingsPath) {
89
+ env.PI_RECIPES_MCP_LOCAL_CONFIG = opts.mcpBindingsPath;
90
+ }
91
+ else if (!opts.mcpBindings) {
92
+ configureMcpLocalConfigPath({ cwd, recipeDir: recipe.recipeDir, env });
93
+ }
94
+ if (recipe.mcp?.mode === "cli") {
95
+ await materializeSessionMcpCli({ cwd, env });
96
+ }
97
+ await materializeMcpSession({
98
+ cwd,
99
+ manifest: recipe.manifest,
100
+ agentMcp: selections,
101
+ env,
102
+ ...(opts.mcpBindings ? { localConfig: opts.mcpBindings } : {}),
103
+ });
104
+ return { materialized: true };
105
+ }
106
+ async function configureSessionMcp(recipe, cwd, env, opts, activation) {
107
+ const selections = scopedMcpSelections(recipe);
108
+ if (selections.length === 0) {
109
+ return { available: false, materialized: false };
110
+ }
111
+ const mode = recipe.mcp?.mode ?? "cli";
112
+ const hostProvisioned = opts.mcpProvisioning === "host";
113
+ const mcpCwd = opts.mcpRuntimeDir ?? cwd;
114
+ const snapshot = !hostProvisioned && mode === "cli"
115
+ ? snapshotMcpEnvironment(env)
116
+ : undefined;
117
+ let runtimeEnv = env;
118
+ let privateDirectory;
119
+ let privateMcporterConfigPath;
120
+ if (snapshot) {
135
121
  if (leasedMcpEnvironments.has(env)) {
136
122
  throw new RecipeMcpEnvironmentInUseError();
137
123
  }
@@ -139,46 +125,87 @@ async function configureSessionMcp(recipe, cwd, env, opts, leaseEnvironment) {
139
125
  isolateMcpEnvironment(env);
140
126
  }
141
127
  const release = async () => {
142
- if (!snapshot)
128
+ if (hostProvisioned)
143
129
  return;
144
- try {
145
- clearMcpCatalogPreload(env);
146
- await stopMcpDaemon(env);
130
+ clearMcpCatalogPreload(runtimeEnv);
131
+ await stopMcpDaemon(runtimeEnv);
132
+ if (privateDirectory) {
133
+ await rm(privateDirectory, { recursive: true, force: true });
147
134
  }
148
- finally {
135
+ if (snapshot) {
149
136
  restoreMcpEnvironment(env, snapshot);
150
137
  leasedMcpEnvironments.delete(env);
151
138
  }
152
139
  };
153
140
  try {
154
- if (opts.mcpBindingsPath) {
155
- env.PI_RECIPES_MCP_LOCAL_CONFIG = opts.mcpBindingsPath;
141
+ if (!hostProvisioned && mode === "tools") {
142
+ const isolated = await createIsolatedMcpEnvironment(env);
143
+ runtimeEnv = isolated.env;
144
+ privateDirectory = isolated.directory;
145
+ privateMcporterConfigPath = isolated.mcporterConfigPath;
156
146
  }
157
- else if (!opts.mcpBindings) {
158
- configureMcpLocalConfigPath({ cwd, recipeDir: recipe.recipeDir, env });
147
+ let session;
148
+ if (hostProvisioned) {
149
+ session = readMcpSessionConfig(cwd, runtimeEnv);
159
150
  }
160
- const [cliResult, sessionResult] = await Promise.allSettled([
161
- materializeSessionMcpCli({ cwd, env }),
162
- materializeMcpSession({
163
- cwd,
151
+ else {
152
+ if (opts.mcpBindingsPath) {
153
+ runtimeEnv.PI_RECIPES_MCP_LOCAL_CONFIG = opts.mcpBindingsPath;
154
+ }
155
+ else if (!opts.mcpBindings) {
156
+ configureMcpLocalConfigPath({
157
+ cwd,
158
+ recipeDir: recipe.recipeDir,
159
+ env: runtimeEnv,
160
+ });
161
+ }
162
+ if (mode === "cli") {
163
+ await materializeSessionMcpCli({ cwd: mcpCwd, env: runtimeEnv });
164
+ }
165
+ session = await materializeMcpSession({
166
+ cwd: mcpCwd,
164
167
  manifest: recipe.manifest,
165
168
  agentMcp: selections,
166
- env,
169
+ env: runtimeEnv,
167
170
  ...(opts.mcpBindings ? { localConfig: opts.mcpBindings } : {}),
168
- }),
169
- ]);
170
- if (cliResult.status === "rejected")
171
- throw cliResult.reason;
172
- if (sessionResult.status === "rejected")
173
- throw sessionResult.reason;
174
- const session = sessionResult.value;
171
+ ...(privateMcporterConfigPath
172
+ ? { mcporterConfigPath: privateMcporterConfigPath }
173
+ : {}),
174
+ });
175
+ }
176
+ if (mode === "tools") {
177
+ const catalogs = session.servers.length > 0
178
+ ? await preloadMcpCatalogs({
179
+ env: runtimeEnv,
180
+ allowPartial: true,
181
+ })
182
+ : [];
183
+ const materialized = createMcpToolSet({
184
+ session,
185
+ catalogs,
186
+ mcp: recipe.mcp,
187
+ env: runtimeEnv,
188
+ activation,
189
+ });
190
+ return {
191
+ available: true,
192
+ materialized: !hostProvisioned,
193
+ tools: materialized.tools,
194
+ initialActiveToolNames: materialized.initialActiveToolNames,
195
+ ...(materialized.searchToolName
196
+ ? { searchToolName: materialized.searchToolName }
197
+ : {}),
198
+ release,
199
+ };
200
+ }
175
201
  if (session.servers.length > 0) {
176
202
  // Warm tool catalogs in the background; sessions work without the warmup.
177
- void preloadMcpCatalogs({ env }).catch(() => { });
203
+ void preloadMcpCatalogs({ env: runtimeEnv }).catch(() => { });
178
204
  }
179
205
  return {
180
- materialized: true,
181
- ...(snapshot ? { release } : {}),
206
+ available: true,
207
+ materialized: !hostProvisioned,
208
+ release,
182
209
  };
183
210
  }
184
211
  catch (error) {
@@ -186,42 +213,49 @@ async function configureSessionMcp(recipe, cwd, env, opts, leaseEnvironment) {
186
213
  throw error;
187
214
  }
188
215
  }
189
- export async function createRecipeSession(opts) {
190
- const recipe = resolveRecipe({
191
- recipeDir: opts.recipeDir,
192
- ...(opts.agentName ? { agentName: opts.agentName } : {}),
193
- });
216
+ async function createSessionForAgent(recipe, opts) {
194
217
  const cwd = opts.cwd ?? process.cwd();
195
218
  const env = opts.env ?? process.env;
219
+ const recipeExtensionContext = Object.freeze({
220
+ recipe: Object.freeze({ name: recipe.manifest.name }),
221
+ agent: Object.freeze({ name: recipe.name }),
222
+ session: Object.freeze({
223
+ role: opts.sessionRole,
224
+ }),
225
+ });
196
226
  const otel = opts.otel
197
227
  ? {
198
228
  ...opts.otel,
199
229
  meta: {
200
230
  conversationId: opts.otel.meta?.conversationId ?? randomUUID(),
201
231
  agentId: opts.otel.meta?.agentId ??
202
- `${recipe.manifest.name}/${recipe.agentName}`,
203
- agentName: opts.otel.meta?.agentName ?? recipe.agentName,
232
+ `${recipe.manifest.name}/${recipe.name}`,
233
+ agentName: opts.otel.meta?.agentName ?? recipe.name,
204
234
  },
205
235
  }
206
236
  : undefined;
207
237
  // Model + credentials, fail-closed before any MCP runtime starts.
208
- const modelSpec = opts.model ?? recipe.modelSpec;
209
- const { lookupProvider, modelId } = parseModelSpec(modelSpec);
238
+ const modelSpec = recipe.modelSpec;
239
+ const { lookupProvider, modelId } = assertRecipeModelTransport(modelSpec, opts.modelOverride);
210
240
  const credentialProvider = opts.modelOverride?.provider ?? lookupProvider;
211
- const credentials = await resolveCredentialStore(credentialProvider, opts);
241
+ const credentials = opts.credentialsResolved && opts.credentials
242
+ ? opts.credentials
243
+ : await resolveRecipeCredentials({
244
+ provider: credentialProvider,
245
+ env,
246
+ ...(opts.modelOverride ? { model: opts.modelOverride } : {}),
247
+ ...(opts.credentials ? { credentials: opts.credentials } : {}),
248
+ });
212
249
  const modelRuntime = await ModelRuntime.create({ credentials, modelsPath: null });
213
- const model = opts.modelOverride ??
214
- modelRuntime.getModel(lookupProvider, modelId) ??
215
- getModel(lookupProvider, modelId);
216
- if (!model) {
217
- throw new RecipeModelError(modelSpec);
218
- }
219
- applyRecipeAgentModelConfigToModel(model, recipe.modelConfig);
220
- const mcp = await configureSessionMcp(recipe, cwd, env, opts, true);
250
+ let model;
221
251
  let session;
222
- let runs;
252
+ let agentRuns;
223
253
  let unsubscribe;
224
254
  let detachInstrumentation;
255
+ const mcp = await configureSessionMcp(recipe, cwd, env, opts, {
256
+ getActiveTools: () => session?.getActiveToolNames() ?? [],
257
+ setActiveTools: (names) => session?.setActiveToolsByName(names),
258
+ });
225
259
  const cleanup = async () => {
226
260
  try {
227
261
  unsubscribe?.();
@@ -230,13 +264,9 @@ export async function createRecipeSession(opts) {
230
264
  // Continue releasing the remaining session-owned resources.
231
265
  }
232
266
  unsubscribe = undefined;
233
- if (runs) {
267
+ if (agentRuns) {
234
268
  try {
235
- for (const run of runs.list()) {
236
- if (run.status !== "closed") {
237
- await runs.close(run.agent_run_id).catch(() => { });
238
- }
239
- }
269
+ await agentRuns.shutdown();
240
270
  }
241
271
  catch {
242
272
  // Child teardown is best-effort; the session still disposes.
@@ -270,83 +300,182 @@ export async function createRecipeSession(opts) {
270
300
  // Recipe extensions load through the shared jiti loader; host extensions
271
301
  // append after them, matching the Pi extension host's ordering.
272
302
  const inlineExtensions = [];
303
+ const recipeRegistrations = createRecipeExtensionRegistrationRegistry();
304
+ for (const toolName of [
305
+ "read",
306
+ "bash",
307
+ "edit",
308
+ "write",
309
+ "grep",
310
+ "find",
311
+ "ls",
312
+ ...(opts.customTools ?? []).map((tool) => tool.name),
313
+ ...(recipe.subagents.size > 0 && opts.runController !== null
314
+ ? ["agent"]
315
+ : []),
316
+ ]) {
317
+ recipeRegistrations.claim("tool", toolName, "<host>");
318
+ }
273
319
  for (const extensionPath of recipe.extensionPaths) {
274
- inlineExtensions.push(await loadRecipeExtensionFactory(recipe.recipeDir, extensionPath));
320
+ inlineExtensions.push(bindRecipeExtensionFactory(await loadRecipeExtensionFactory(recipe.recipeDir, extensionPath), recipeExtensionContext, recipeRegistrations, extensionPath, recipeExtensionToolAllowlist(recipe.tools, recipe.subagents.size > 0 && opts.runController !== null, [
321
+ ...(mcp.tools?.map((tool) => tool.name) ?? []),
322
+ ...(mcp.searchToolName ? [mcp.searchToolName] : []),
323
+ ])));
275
324
  }
276
325
  inlineExtensions.push(...(opts.extensionFactories ?? []));
326
+ const settingsManager = opts.settingsManager ?? SettingsManager.create(cwd, recipe.recipeDir);
277
327
  const services = await createAgentSessionServices({
278
328
  cwd,
279
329
  agentDir: recipe.recipeDir,
280
330
  modelRuntime,
281
- settingsManager: opts.settingsManager ?? SettingsManager.create(cwd, recipe.recipeDir),
331
+ settingsManager,
282
332
  resourceLoaderOptions: {
283
333
  eventBus: opts.eventBus,
334
+ noExtensions: true,
284
335
  noSkills: true,
336
+ noPromptTemplates: true,
337
+ noContextFiles: true,
285
338
  additionalSkillPaths: [
286
- ...(opts.skillPaths ?? recipe.skillPaths),
339
+ ...(opts.materializedSkillPaths ?? recipe.skillPaths),
287
340
  ...(opts.additionalSkillPaths ?? []),
288
341
  ],
289
- additionalPromptTemplatePaths: recipe.promptPaths,
342
+ additionalPromptTemplatePaths: [...recipe.promptPaths],
290
343
  extensionFactories: inlineExtensions,
291
344
  systemPromptOverride: (base) => {
292
345
  const resolved = recipe.systemPromptOverride(base);
293
- return opts.systemPrompt ? opts.systemPrompt(resolved ?? "") : resolved;
346
+ return opts.transformSystemPrompt
347
+ ? opts.transformSystemPrompt(resolved ?? "")
348
+ : resolved;
294
349
  },
295
350
  },
296
351
  });
297
352
  opts.onDiagnostics?.(services.diagnostics);
353
+ const extensionLoadErrors = services.resourceLoader.getExtensions().errors;
354
+ const fatalDiagnostics = services.diagnostics.filter((diagnostic) => diagnostic.type === "error");
355
+ if (fatalDiagnostics.length > 0 || extensionLoadErrors.length > 0) {
356
+ throw new Error(`Recipe extension startup failed:\n${[
357
+ ...extensionLoadErrors.map(({ path, error }) => `- ${path}: ${error}`),
358
+ ...fatalDiagnostics.map((diagnostic) => `- ${diagnostic.message}`),
359
+ ]
360
+ .join("\n")}`);
361
+ }
362
+ const selectedModel = opts.modelOverride ??
363
+ services.modelRuntime.getModel(lookupProvider, modelId) ??
364
+ modelRuntime.getModel(lookupProvider, modelId) ??
365
+ getModel(lookupProvider, modelId);
366
+ if (!selectedModel) {
367
+ throw new RecipeModelError(modelSpec);
368
+ }
369
+ model = cloneModelForRecipe(selectedModel);
370
+ applyRecipeAgentModelConfigToModel(model, recipe.modelConfig);
298
371
  // Subagents: the shared `agent` tool against an injected or in-process
299
372
  // controller. `runController: null` disables delegation outright.
300
373
  const wantsSubagents = recipe.subagents.size > 0 && opts.runController !== null;
301
374
  if (opts.runController) {
302
- runs = opts.runController;
375
+ agentRuns = opts.runController;
303
376
  }
304
377
  else {
305
378
  const { createInProcessRunController, inertRunController } = await import("./run-controller.js");
306
- runs = wantsSubagents
379
+ agentRuns = wantsSubagents
307
380
  ? createInProcessRunController({
308
- recipeDir: recipe.recipeDir,
381
+ recipe: opts.recipe,
309
382
  cwd,
310
383
  env,
311
384
  ...(opts.credentials ? { credentials: opts.credentials } : {}),
312
- concurrency: opts.subagentLimits?.concurrency,
385
+ concurrency: opts.inProcessRunController?.concurrency,
313
386
  ...(otel ? { otel } : {}),
314
387
  })
315
388
  : inertRunController();
316
389
  }
317
- const tools = wantsSubagents
318
- ? recipe.tools
319
- : recipe.tools.filter((tool) => tool !== "agent");
390
+ const tools = [
391
+ ...recipe.tools,
392
+ ...(wantsSubagents ? ["agent"] : []),
393
+ ];
394
+ const mcpToolNames = mcp.tools?.map((tool) => tool.name) ?? [];
395
+ const recipeExtensionToolNames = new Set(services.resourceLoader
396
+ .getExtensions()
397
+ .extensions.flatMap((extension) => [...extension.tools.keys()]));
398
+ const occupiedToolNames = new Set([
399
+ ...tools,
400
+ ...(opts.customTools ?? []).map((tool) => tool.name),
401
+ ...(wantsSubagents ? ["agent"] : []),
402
+ ]);
403
+ const collisions = mcpToolNames.filter((name) => occupiedToolNames.has(name) || recipeExtensionToolNames.has(name));
404
+ if (collisions.length > 0) {
405
+ throw new Error(`Recipe MCP tool name collision: ${collisions.join(", ")}`);
406
+ }
407
+ const selectedToolNames = [...tools, ...mcpToolNames];
408
+ const environmentBash = (recipe.mcp?.mode ?? "cli") === "cli" &&
409
+ mcp.available &&
410
+ env !== process.env &&
411
+ tools.includes("bash") &&
412
+ !(opts.customTools ?? []).some((tool) => tool.name === "bash")
413
+ ? createBashToolDefinition(cwd, {
414
+ commandPrefix: settingsManager.getShellCommandPrefix(),
415
+ shellPath: settingsManager.getShellPath(),
416
+ spawnHook: (context) => ({
417
+ ...context,
418
+ env: { ...context.env, ...env },
419
+ }),
420
+ })
421
+ : undefined;
320
422
  const customTools = [
423
+ ...(environmentBash ? [environmentBash] : []),
321
424
  ...(opts.customTools ?? []),
322
425
  ...(wantsSubagents
323
- ? [createAgentTool(runs, recipe.subagents, opts.agentToolOptions)]
426
+ ? [
427
+ createAgentTool(agentRuns, recipe.subagents, opts.agentToolOptions),
428
+ ]
324
429
  : []),
430
+ ...(mcp.tools ?? []),
325
431
  ];
326
432
  const created = await createAgentSessionFromServices({
327
433
  services,
328
434
  sessionManager: opts.sessionManager ?? SessionManager.inMemory(cwd),
329
435
  model,
330
- ...(opts.thinkingLevel ?? recipe.thinkingLevel
331
- ? { thinkingLevel: (opts.thinkingLevel ?? recipe.thinkingLevel) }
436
+ ...(recipe.thinkingLevel
437
+ ? { thinkingLevel: recipe.thinkingLevel }
332
438
  : {}),
333
- tools,
439
+ tools: selectedToolNames,
334
440
  customTools,
335
441
  });
336
442
  session = created.session;
443
+ normalizeSessionToolsForModel(session, model);
337
444
  applyRecipeAgentModelConfigToSession(session, recipe.modelConfig);
338
445
  await session.bindExtensions({});
446
+ const registered = new Set(session.getAllTools().map((tool) => tool.name));
447
+ const missingDeclaredTools = selectedToolNames.filter((name) => !registered.has(name));
448
+ if (missingDeclaredTools.length > 0) {
449
+ throw new Error(`Recipe agent "${recipe.name}" declares unavailable tool(s): ${missingDeclaredTools.join(", ")}`);
450
+ }
451
+ if (mcp.tools) {
452
+ const missingRegistered = mcpToolNames.filter((name) => !registered.has(name));
453
+ if (missingRegistered.length > 0) {
454
+ throw new Error(`Pi did not register Recipe MCP tool(s): ${missingRegistered.join(", ")}`);
455
+ }
456
+ const activeTools = [
457
+ ...tools,
458
+ ...(mcp.initialActiveToolNames ?? []),
459
+ ...(mcp.searchToolName ? [mcp.searchToolName] : []),
460
+ ];
461
+ session.setActiveToolsByName(activeTools);
462
+ const applied = new Set(session.getActiveToolNames());
463
+ const missing = activeTools.filter((name) => !applied.has(name));
464
+ if (missing.length > 0) {
465
+ throw new Error(`Pi did not activate Recipe tool(s): ${missing.join(", ")}`);
466
+ }
467
+ }
339
468
  unsubscribe = opts.onEvent ? session.subscribe(opts.onEvent) : undefined;
340
469
  detachInstrumentation = otel
341
470
  ? instrumentSession(session, otel).detach
342
471
  : undefined;
343
472
  const liveSession = session;
344
- const liveRuns = runs;
473
+ const liveRuns = agentRuns;
345
474
  let disposed = false;
346
475
  return {
347
476
  session: liveSession,
348
- recipe,
349
- runs: liveRuns,
477
+ agent: recipe,
478
+ agentRuns: liveRuns,
350
479
  async dispose() {
351
480
  if (disposed)
352
481
  return;
@@ -360,4 +489,14 @@ export async function createRecipeSession(opts) {
360
489
  throw error;
361
490
  }
362
491
  }
492
+ export async function createAgentSession(opts) {
493
+ return createSessionForAgent(opts.recipe.selectAgent(opts.agentName), {
494
+ ...opts,
495
+ sessionRole: "root",
496
+ });
497
+ }
498
+ /** @internal Construct a child with Recipes-owned lifecycle state. */
499
+ export async function createAgentSessionInternal(opts) {
500
+ return createSessionForAgent(opts.recipe.selectAgent(opts.agentName), opts);
501
+ }
363
502
  //# sourceMappingURL=session.js.map