patchwork-os 0.2.0-beta.9.canary.92 → 1.1.0-beta.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 (739) hide show
  1. package/README.bridge.md +15 -16
  2. package/README.md +46 -458
  3. package/dist/activationMetrics.js +2 -3
  4. package/dist/activationMetrics.js.map +1 -1
  5. package/dist/activityLog.d.ts +1 -0
  6. package/dist/activityLog.js +8 -3
  7. package/dist/activityLog.js.map +1 -1
  8. package/dist/adapters/gemini.js +2 -2
  9. package/dist/adapters/gemini.js.map +1 -1
  10. package/dist/adapters/grok.js +6 -1
  11. package/dist/adapters/grok.js.map +1 -1
  12. package/dist/adapters/openai.js +4 -0
  13. package/dist/adapters/openai.js.map +1 -1
  14. package/dist/approvalHttp.d.ts +9 -0
  15. package/dist/approvalHttp.js +210 -171
  16. package/dist/approvalHttp.js.map +1 -1
  17. package/dist/approvalKpi.d.ts +102 -0
  18. package/dist/approvalKpi.js +282 -0
  19. package/dist/approvalKpi.js.map +1 -0
  20. package/dist/approvalQueue.d.ts +1 -1
  21. package/dist/approvalQueue.js.map +1 -1
  22. package/dist/automation.d.ts +45 -4
  23. package/dist/automation.js +123 -15
  24. package/dist/automation.js.map +1 -1
  25. package/dist/bridge.d.ts +10 -0
  26. package/dist/bridge.js +225 -46
  27. package/dist/bridge.js.map +1 -1
  28. package/dist/bridgeLockDiscovery.d.ts +16 -1
  29. package/dist/bridgeLockDiscovery.js +38 -4
  30. package/dist/bridgeLockDiscovery.js.map +1 -1
  31. package/dist/bridgeToolsRules.js +3 -18
  32. package/dist/bridgeToolsRules.js.map +1 -1
  33. package/dist/claudeAuthHttp.d.ts +25 -0
  34. package/dist/claudeAuthHttp.js +219 -0
  35. package/dist/claudeAuthHttp.js.map +1 -0
  36. package/dist/claudeDriver.js +4 -1
  37. package/dist/claudeDriver.js.map +1 -1
  38. package/dist/claudeOrchestrator.d.ts +15 -0
  39. package/dist/claudeOrchestrator.js +44 -0
  40. package/dist/claudeOrchestrator.js.map +1 -1
  41. package/dist/commands/connect.d.ts +47 -0
  42. package/dist/commands/connect.js +419 -0
  43. package/dist/commands/connect.js.map +1 -0
  44. package/dist/commands/install.js +3 -10
  45. package/dist/commands/install.js.map +1 -1
  46. package/dist/commands/launchd.d.ts +7 -0
  47. package/dist/commands/launchd.js +20 -2
  48. package/dist/commands/launchd.js.map +1 -1
  49. package/dist/commands/patchworkInit.d.ts +7 -0
  50. package/dist/commands/patchworkInit.js +26 -0
  51. package/dist/commands/patchworkInit.js.map +1 -1
  52. package/dist/commands/recipe.d.ts +59 -4
  53. package/dist/commands/recipe.js +206 -5
  54. package/dist/commands/recipe.js.map +1 -1
  55. package/dist/commands/recipeInstall.d.ts +9 -0
  56. package/dist/commands/recipeInstall.js +56 -3
  57. package/dist/commands/recipeInstall.js.map +1 -1
  58. package/dist/commands/task.d.ts +25 -0
  59. package/dist/commands/task.js +61 -41
  60. package/dist/commands/task.js.map +1 -1
  61. package/dist/commands/tokenEfficiency.d.ts +4 -0
  62. package/dist/commands/tokenEfficiency.js +23 -26
  63. package/dist/commands/tokenEfficiency.js.map +1 -1
  64. package/dist/commands/tracesExport.d.ts +15 -1
  65. package/dist/commands/tracesExport.js +39 -5
  66. package/dist/commands/tracesExport.js.map +1 -1
  67. package/dist/config.js +16 -0
  68. package/dist/config.js.map +1 -1
  69. package/dist/connectorRoutes.js +613 -62
  70. package/dist/connectorRoutes.js.map +1 -1
  71. package/dist/connectors/asana.js +9 -3
  72. package/dist/connectors/asana.js.map +1 -1
  73. package/dist/connectors/baseConnector.d.ts +16 -0
  74. package/dist/connectors/baseConnector.js +2 -0
  75. package/dist/connectors/baseConnector.js.map +1 -1
  76. package/dist/connectors/caldiy.js +34 -0
  77. package/dist/connectors/caldiy.js.map +1 -1
  78. package/dist/connectors/confluence.js +9 -2
  79. package/dist/connectors/confluence.js.map +1 -1
  80. package/dist/connectors/connectorActivity.d.ts +24 -0
  81. package/dist/connectors/connectorActivity.js +32 -0
  82. package/dist/connectors/connectorActivity.js.map +1 -0
  83. package/dist/connectors/connectorRedirectUri.js +22 -2
  84. package/dist/connectors/connectorRedirectUri.js.map +1 -1
  85. package/dist/connectors/connectorRegistry.d.ts +0 -3
  86. package/dist/connectors/connectorRegistry.js +8 -8
  87. package/dist/connectors/connectorRegistry.js.map +1 -1
  88. package/dist/connectors/datadog.js +8 -1
  89. package/dist/connectors/datadog.js.map +1 -1
  90. package/dist/connectors/discord.js +4 -3
  91. package/dist/connectors/discord.js.map +1 -1
  92. package/dist/connectors/elasticsearch.js +22 -0
  93. package/dist/connectors/elasticsearch.js.map +1 -1
  94. package/dist/connectors/github.d.ts +38 -0
  95. package/dist/connectors/github.js +91 -5
  96. package/dist/connectors/github.js.map +1 -1
  97. package/dist/connectors/gitlab.js +19 -16
  98. package/dist/connectors/gitlab.js.map +1 -1
  99. package/dist/connectors/gmail.d.ts +5 -0
  100. package/dist/connectors/gmail.js +53 -11
  101. package/dist/connectors/gmail.js.map +1 -1
  102. package/dist/connectors/googleCalendar.js +5 -4
  103. package/dist/connectors/googleCalendar.js.map +1 -1
  104. package/dist/connectors/googleDocs.js +7 -6
  105. package/dist/connectors/googleDocs.js.map +1 -1
  106. package/dist/connectors/googleDrive.js +15 -8
  107. package/dist/connectors/googleDrive.js.map +1 -1
  108. package/dist/connectors/grafana.js +39 -3
  109. package/dist/connectors/grafana.js.map +1 -1
  110. package/dist/connectors/jira.d.ts +1 -1
  111. package/dist/connectors/jira.js +19 -4
  112. package/dist/connectors/jira.js.map +1 -1
  113. package/dist/connectors/linear.js +2 -2
  114. package/dist/connectors/linear.js.map +1 -1
  115. package/dist/connectors/mcpClient.d.ts +29 -1
  116. package/dist/connectors/mcpClient.js +114 -44
  117. package/dist/connectors/mcpClient.js.map +1 -1
  118. package/dist/connectors/mcpOAuth.d.ts +1 -0
  119. package/dist/connectors/mcpOAuth.js +49 -4
  120. package/dist/connectors/mcpOAuth.js.map +1 -1
  121. package/dist/connectors/monday.js +5 -4
  122. package/dist/connectors/monday.js.map +1 -1
  123. package/dist/connectors/mongodb.js +32 -5
  124. package/dist/connectors/mongodb.js.map +1 -1
  125. package/dist/connectors/notion.d.ts +1 -1
  126. package/dist/connectors/notion.js +22 -6
  127. package/dist/connectors/notion.js.map +1 -1
  128. package/dist/connectors/oauthError.d.ts +17 -0
  129. package/dist/connectors/oauthError.js +50 -0
  130. package/dist/connectors/oauthError.js.map +1 -0
  131. package/dist/connectors/postgres.d.ts +7 -0
  132. package/dist/connectors/postgres.js +32 -2
  133. package/dist/connectors/postgres.js.map +1 -1
  134. package/dist/connectors/posthog.js +26 -0
  135. package/dist/connectors/posthog.js.map +1 -1
  136. package/dist/connectors/redis.d.ts +1 -0
  137. package/dist/connectors/redis.js +47 -20
  138. package/dist/connectors/redis.js.map +1 -1
  139. package/dist/connectors/salesforce.js +66 -5
  140. package/dist/connectors/salesforce.js.map +1 -1
  141. package/dist/connectors/sentry.js +2 -2
  142. package/dist/connectors/sentry.js.map +1 -1
  143. package/dist/connectors/slack.js +37 -14
  144. package/dist/connectors/slack.js.map +1 -1
  145. package/dist/connectors/snowflake.js +24 -0
  146. package/dist/connectors/snowflake.js.map +1 -1
  147. package/dist/connectors/stripe.js +9 -2
  148. package/dist/connectors/stripe.js.map +1 -1
  149. package/dist/connectors/supabase.js +34 -0
  150. package/dist/connectors/supabase.js.map +1 -1
  151. package/dist/connectors/tokenStorage.d.ts +20 -0
  152. package/dist/connectors/tokenStorage.js +196 -64
  153. package/dist/connectors/tokenStorage.js.map +1 -1
  154. package/dist/connectors/woocommerce.js +34 -0
  155. package/dist/connectors/woocommerce.js.map +1 -1
  156. package/dist/copilot/parseIntent.d.ts +58 -0
  157. package/dist/copilot/parseIntent.js +149 -0
  158. package/dist/copilot/parseIntent.js.map +1 -0
  159. package/dist/decisionReplay.d.ts +8 -6
  160. package/dist/decisionReplay.js +35 -11
  161. package/dist/decisionReplay.js.map +1 -1
  162. package/dist/decisionTraceLog.d.ts +9 -0
  163. package/dist/decisionTraceLog.js +6 -0
  164. package/dist/decisionTraceLog.js.map +1 -1
  165. package/dist/drivers/claude/api.js +49 -7
  166. package/dist/drivers/claude/api.js.map +1 -1
  167. package/dist/drivers/claude/envSanitizer.d.ts +9 -1
  168. package/dist/drivers/claude/envSanitizer.js +41 -3
  169. package/dist/drivers/claude/envSanitizer.js.map +1 -1
  170. package/dist/drivers/claude/streamParser.d.ts +13 -0
  171. package/dist/drivers/claude/streamParser.js.map +1 -1
  172. package/dist/drivers/claude/subprocess.js +131 -16
  173. package/dist/drivers/claude/subprocess.js.map +1 -1
  174. package/dist/drivers/claude/subprocessSettings.d.ts +1 -1
  175. package/dist/drivers/claude/subprocessSettings.js +18 -4
  176. package/dist/drivers/claude/subprocessSettings.js.map +1 -1
  177. package/dist/drivers/gemini/index.d.ts +13 -0
  178. package/dist/drivers/gemini/index.js +211 -67
  179. package/dist/drivers/gemini/index.js.map +1 -1
  180. package/dist/drivers/local/index.d.ts +2 -17
  181. package/dist/drivers/local/index.js +5 -92
  182. package/dist/drivers/local/index.js.map +1 -1
  183. package/dist/drivers/openai/index.js +69 -9
  184. package/dist/drivers/openai/index.js.map +1 -1
  185. package/dist/drivers/outputCap.d.ts +27 -0
  186. package/dist/drivers/outputCap.js +50 -0
  187. package/dist/drivers/outputCap.js.map +1 -0
  188. package/dist/embeddings/cosine.d.ts +23 -0
  189. package/dist/embeddings/cosine.js +59 -0
  190. package/dist/embeddings/cosine.js.map +1 -0
  191. package/dist/embeddings/index.d.ts +19 -0
  192. package/dist/embeddings/index.js +35 -0
  193. package/dist/embeddings/index.js.map +1 -0
  194. package/dist/embeddings/localEmbeddings.d.ts +29 -0
  195. package/dist/embeddings/localEmbeddings.js +103 -0
  196. package/dist/embeddings/localEmbeddings.js.map +1 -0
  197. package/dist/embeddings/localEndpoints.d.ts +24 -0
  198. package/dist/embeddings/localEndpoints.js +37 -0
  199. package/dist/embeddings/localEndpoints.js.map +1 -0
  200. package/dist/embeddings/types.d.ts +32 -0
  201. package/dist/embeddings/types.js +12 -0
  202. package/dist/embeddings/types.js.map +1 -0
  203. package/dist/featureFlags.d.ts +18 -0
  204. package/dist/featureFlags.js +32 -0
  205. package/dist/featureFlags.js.map +1 -1
  206. package/dist/fp/automationInterpreter.js +118 -39
  207. package/dist/fp/automationInterpreter.js.map +1 -1
  208. package/dist/fp/automationProgram.d.ts +10 -0
  209. package/dist/fp/automationProgram.js.map +1 -1
  210. package/dist/fp/automationState.d.ts +6 -0
  211. package/dist/fp/automationState.js +38 -0
  212. package/dist/fp/automationState.js.map +1 -1
  213. package/dist/fp/commandDescription.d.ts +6 -0
  214. package/dist/fp/commandDescription.js +18 -5
  215. package/dist/fp/commandDescription.js.map +1 -1
  216. package/dist/fp/interpreterContext.d.ts +16 -5
  217. package/dist/fp/interpreterContext.js +59 -3
  218. package/dist/fp/interpreterContext.js.map +1 -1
  219. package/dist/fp/policyParser.js +18 -8
  220. package/dist/fp/policyParser.js.map +1 -1
  221. package/dist/haltPushDispatch.d.ts +4 -0
  222. package/dist/haltPushDispatch.js +7 -22
  223. package/dist/haltPushDispatch.js.map +1 -1
  224. package/dist/inboxRoutes.js +2 -2
  225. package/dist/inboxRoutes.js.map +1 -1
  226. package/dist/index.js +672 -135
  227. package/dist/index.js.map +1 -1
  228. package/dist/installGuard.js +6 -0
  229. package/dist/installGuard.js.map +1 -1
  230. package/dist/localEndpointGuard.d.ts +25 -0
  231. package/dist/localEndpointGuard.js +101 -0
  232. package/dist/localEndpointGuard.js.map +1 -0
  233. package/dist/mcpRoutes.js +1 -1
  234. package/dist/mcpRoutes.js.map +1 -1
  235. package/dist/oauth.d.ts +13 -0
  236. package/dist/oauth.js +53 -9
  237. package/dist/oauth.js.map +1 -1
  238. package/dist/orchestrator/childBridgeClient.d.ts +7 -0
  239. package/dist/orchestrator/childBridgeClient.js +42 -1
  240. package/dist/orchestrator/childBridgeClient.js.map +1 -1
  241. package/dist/orchestrator/orchestratorBridge.d.ts +33 -0
  242. package/dist/orchestrator/orchestratorBridge.js +75 -9
  243. package/dist/orchestrator/orchestratorBridge.js.map +1 -1
  244. package/dist/patchworkConfig.d.ts +4 -0
  245. package/dist/patchworkConfig.js +33 -4
  246. package/dist/patchworkConfig.js.map +1 -1
  247. package/dist/probe.js +39 -5
  248. package/dist/probe.js.map +1 -1
  249. package/dist/recipeOrchestration.d.ts +59 -0
  250. package/dist/recipeOrchestration.js +314 -60
  251. package/dist/recipeOrchestration.js.map +1 -1
  252. package/dist/recipeRoutes.d.ts +93 -0
  253. package/dist/recipeRoutes.js +664 -27
  254. package/dist/recipeRoutes.js.map +1 -1
  255. package/dist/recipes/RecipeOrchestrator.d.ts +14 -0
  256. package/dist/recipes/RecipeOrchestrator.js +42 -2
  257. package/dist/recipes/RecipeOrchestrator.js.map +1 -1
  258. package/dist/recipes/agentExecutor.d.ts +51 -2
  259. package/dist/recipes/agentExecutor.js +86 -13
  260. package/dist/recipes/agentExecutor.js.map +1 -1
  261. package/dist/recipes/chainedRunner.d.ts +111 -3
  262. package/dist/recipes/chainedRunner.js +440 -45
  263. package/dist/recipes/chainedRunner.js.map +1 -1
  264. package/dist/recipes/compiler.js +42 -29
  265. package/dist/recipes/compiler.js.map +1 -1
  266. package/dist/recipes/connectorPreflight.js +31 -0
  267. package/dist/recipes/connectorPreflight.js.map +1 -1
  268. package/dist/recipes/dependencyGraph.d.ts +16 -0
  269. package/dist/recipes/dependencyGraph.js +37 -2
  270. package/dist/recipes/dependencyGraph.js.map +1 -1
  271. package/dist/recipes/eventTriggerPrograms.d.ts +36 -0
  272. package/dist/recipes/eventTriggerPrograms.js +158 -0
  273. package/dist/recipes/eventTriggerPrograms.js.map +1 -0
  274. package/dist/recipes/githubInstallSource.d.ts +9 -2
  275. package/dist/recipes/githubInstallSource.js +36 -6
  276. package/dist/recipes/githubInstallSource.js.map +1 -1
  277. package/dist/recipes/haltCategory.d.ts +10 -0
  278. package/dist/recipes/haltCategory.js +11 -1
  279. package/dist/recipes/haltCategory.js.map +1 -1
  280. package/dist/recipes/idempotencyKey.d.ts +30 -3
  281. package/dist/recipes/idempotencyKey.js +89 -5
  282. package/dist/recipes/idempotencyKey.js.map +1 -1
  283. package/dist/recipes/installer.js +12 -35
  284. package/dist/recipes/installer.js.map +1 -1
  285. package/dist/recipes/judgeVerdict.d.ts +11 -1
  286. package/dist/recipes/judgeVerdict.js +47 -7
  287. package/dist/recipes/judgeVerdict.js.map +1 -1
  288. package/dist/recipes/names.d.ts +5 -0
  289. package/dist/recipes/names.js +10 -5
  290. package/dist/recipes/names.js.map +1 -1
  291. package/dist/recipes/parser.js +74 -10
  292. package/dist/recipes/parser.js.map +1 -1
  293. package/dist/recipes/pricing/costRouter.d.ts +43 -0
  294. package/dist/recipes/pricing/costRouter.js +44 -0
  295. package/dist/recipes/pricing/costRouter.js.map +1 -0
  296. package/dist/recipes/pricing/priceTable.d.ts +76 -0
  297. package/dist/recipes/pricing/priceTable.js +144 -0
  298. package/dist/recipes/pricing/priceTable.js.map +1 -0
  299. package/dist/recipes/replayRun.js +5 -2
  300. package/dist/recipes/replayRun.js.map +1 -1
  301. package/dist/recipes/resolveRecipePath.js +15 -1
  302. package/dist/recipes/resolveRecipePath.js.map +1 -1
  303. package/dist/recipes/runBudget.d.ts +93 -32
  304. package/dist/recipes/runBudget.js +213 -49
  305. package/dist/recipes/runBudget.js.map +1 -1
  306. package/dist/recipes/runRegistry.d.ts +32 -0
  307. package/dist/recipes/runRegistry.js +54 -0
  308. package/dist/recipes/runRegistry.js.map +1 -0
  309. package/dist/recipes/scheduler.js +6 -2
  310. package/dist/recipes/scheduler.js.map +1 -1
  311. package/dist/recipes/schema.d.ts +59 -6
  312. package/dist/recipes/schemaGenerator.d.ts +9 -0
  313. package/dist/recipes/schemaGenerator.js +446 -77
  314. package/dist/recipes/schemaGenerator.js.map +1 -1
  315. package/dist/recipes/simulation/aggregateRunRisk.d.ts +46 -0
  316. package/dist/recipes/simulation/aggregateRunRisk.js +120 -0
  317. package/dist/recipes/simulation/aggregateRunRisk.js.map +1 -0
  318. package/dist/recipes/simulation/costProjector.d.ts +32 -0
  319. package/dist/recipes/simulation/costProjector.js +194 -0
  320. package/dist/recipes/simulation/costProjector.js.map +1 -0
  321. package/dist/recipes/simulation/sideEffects.d.ts +32 -0
  322. package/dist/recipes/simulation/sideEffects.js +62 -0
  323. package/dist/recipes/simulation/sideEffects.js.map +1 -0
  324. package/dist/recipes/simulation/simulate.d.ts +36 -0
  325. package/dist/recipes/simulation/simulate.js +264 -0
  326. package/dist/recipes/simulation/simulate.js.map +1 -0
  327. package/dist/recipes/simulation/simulateMockedRun.d.ts +52 -0
  328. package/dist/recipes/simulation/simulateMockedRun.js +84 -0
  329. package/dist/recipes/simulation/simulateMockedRun.js.map +1 -0
  330. package/dist/recipes/simulation/synthesizeMockedOutputs.d.ts +31 -0
  331. package/dist/recipes/simulation/synthesizeMockedOutputs.js +50 -0
  332. package/dist/recipes/simulation/synthesizeMockedOutputs.js.map +1 -0
  333. package/dist/recipes/simulation/types.d.ts +198 -0
  334. package/dist/recipes/simulation/types.js +30 -0
  335. package/dist/recipes/simulation/types.js.map +1 -0
  336. package/dist/recipes/stepObservation.d.ts +12 -0
  337. package/dist/recipes/stepObservation.js +21 -1
  338. package/dist/recipes/stepObservation.js.map +1 -1
  339. package/dist/recipes/toolRegistry.js +61 -24
  340. package/dist/recipes/toolRegistry.js.map +1 -1
  341. package/dist/recipes/tools/airtable.d.ts +15 -0
  342. package/dist/recipes/tools/airtable.js +240 -0
  343. package/dist/recipes/tools/airtable.js.map +1 -0
  344. package/dist/recipes/tools/caldiy.d.ts +13 -0
  345. package/dist/recipes/tools/caldiy.js +215 -0
  346. package/dist/recipes/tools/caldiy.js.map +1 -0
  347. package/dist/recipes/tools/circleci.d.ts +10 -0
  348. package/dist/recipes/tools/circleci.js +204 -0
  349. package/dist/recipes/tools/circleci.js.map +1 -0
  350. package/dist/recipes/tools/cloudflare.d.ts +13 -0
  351. package/dist/recipes/tools/cloudflare.js +211 -0
  352. package/dist/recipes/tools/cloudflare.js.map +1 -0
  353. package/dist/recipes/tools/confluence.js +11 -10
  354. package/dist/recipes/tools/confluence.js.map +1 -1
  355. package/dist/recipes/tools/datadog.js +13 -12
  356. package/dist/recipes/tools/datadog.js.map +1 -1
  357. package/dist/recipes/tools/docs.d.ts +18 -0
  358. package/dist/recipes/tools/docs.js +95 -0
  359. package/dist/recipes/tools/docs.js.map +1 -0
  360. package/dist/recipes/tools/elasticsearch.d.ts +11 -0
  361. package/dist/recipes/tools/elasticsearch.js +157 -0
  362. package/dist/recipes/tools/elasticsearch.js.map +1 -0
  363. package/dist/recipes/tools/fanOut.js +4 -4
  364. package/dist/recipes/tools/fanOut.js.map +1 -1
  365. package/dist/recipes/tools/figma.d.ts +12 -0
  366. package/dist/recipes/tools/figma.js +195 -0
  367. package/dist/recipes/tools/figma.js.map +1 -0
  368. package/dist/recipes/tools/file.js +43 -22
  369. package/dist/recipes/tools/file.js.map +1 -1
  370. package/dist/recipes/tools/github.js +162 -5
  371. package/dist/recipes/tools/github.js.map +1 -1
  372. package/dist/recipes/tools/gmail.js +45 -33
  373. package/dist/recipes/tools/gmail.js.map +1 -1
  374. package/dist/recipes/tools/grafana.d.ts +11 -0
  375. package/dist/recipes/tools/grafana.js +216 -0
  376. package/dist/recipes/tools/grafana.js.map +1 -0
  377. package/dist/recipes/tools/http.d.ts +1 -1
  378. package/dist/recipes/tools/http.js +10 -28
  379. package/dist/recipes/tools/http.js.map +1 -1
  380. package/dist/recipes/tools/hubspot.js +13 -12
  381. package/dist/recipes/tools/hubspot.js.map +1 -1
  382. package/dist/recipes/tools/index.d.ts +28 -0
  383. package/dist/recipes/tools/index.js +28 -0
  384. package/dist/recipes/tools/index.js.map +1 -1
  385. package/dist/recipes/tools/intercom.js +11 -10
  386. package/dist/recipes/tools/intercom.js.map +1 -1
  387. package/dist/recipes/tools/jira.js +13 -0
  388. package/dist/recipes/tools/jira.js.map +1 -1
  389. package/dist/recipes/tools/meetingNotes.js +2 -2
  390. package/dist/recipes/tools/monday.d.ts +22 -0
  391. package/dist/recipes/tools/monday.js +242 -0
  392. package/dist/recipes/tools/monday.js.map +1 -0
  393. package/dist/recipes/tools/mongodb.d.ts +19 -0
  394. package/dist/recipes/tools/mongodb.js +194 -0
  395. package/dist/recipes/tools/mongodb.js.map +1 -0
  396. package/dist/recipes/tools/notion.js +11 -10
  397. package/dist/recipes/tools/notion.js.map +1 -1
  398. package/dist/recipes/tools/obsidian.d.ts +15 -0
  399. package/dist/recipes/tools/obsidian.js +173 -0
  400. package/dist/recipes/tools/obsidian.js.map +1 -0
  401. package/dist/recipes/tools/outcomes.d.ts +15 -0
  402. package/dist/recipes/tools/outcomes.js +111 -0
  403. package/dist/recipes/tools/outcomes.js.map +1 -0
  404. package/dist/recipes/tools/paystack.d.ts +11 -0
  405. package/dist/recipes/tools/paystack.js +212 -0
  406. package/dist/recipes/tools/paystack.js.map +1 -0
  407. package/dist/recipes/tools/pipedrive.d.ts +16 -0
  408. package/dist/recipes/tools/pipedrive.js +234 -0
  409. package/dist/recipes/tools/pipedrive.js.map +1 -0
  410. package/dist/recipes/tools/postgres.d.ts +15 -0
  411. package/dist/recipes/tools/postgres.js +186 -0
  412. package/dist/recipes/tools/postgres.js.map +1 -0
  413. package/dist/recipes/tools/posthog.d.ts +16 -0
  414. package/dist/recipes/tools/posthog.js +219 -0
  415. package/dist/recipes/tools/posthog.js.map +1 -0
  416. package/dist/recipes/tools/redis.d.ts +9 -0
  417. package/dist/recipes/tools/redis.js +141 -0
  418. package/dist/recipes/tools/redis.js.map +1 -0
  419. package/dist/recipes/tools/resend.d.ts +8 -0
  420. package/dist/recipes/tools/resend.js +153 -0
  421. package/dist/recipes/tools/resend.js.map +1 -0
  422. package/dist/recipes/tools/salesforce.d.ts +16 -0
  423. package/dist/recipes/tools/salesforce.js +184 -0
  424. package/dist/recipes/tools/salesforce.js.map +1 -0
  425. package/dist/recipes/tools/sendgrid.d.ts +9 -0
  426. package/dist/recipes/tools/sendgrid.js +175 -0
  427. package/dist/recipes/tools/sendgrid.js.map +1 -0
  428. package/dist/recipes/tools/shopify.d.ts +16 -0
  429. package/dist/recipes/tools/shopify.js +266 -0
  430. package/dist/recipes/tools/shopify.js.map +1 -0
  431. package/dist/recipes/tools/snowflake.d.ts +16 -0
  432. package/dist/recipes/tools/snowflake.js +173 -0
  433. package/dist/recipes/tools/snowflake.js.map +1 -0
  434. package/dist/recipes/tools/stripe.js +13 -12
  435. package/dist/recipes/tools/stripe.js.map +1 -1
  436. package/dist/recipes/tools/supabase.d.ts +9 -0
  437. package/dist/recipes/tools/supabase.js +133 -0
  438. package/dist/recipes/tools/supabase.js.map +1 -0
  439. package/dist/recipes/tools/todoist.d.ts +15 -0
  440. package/dist/recipes/tools/todoist.js +228 -0
  441. package/dist/recipes/tools/todoist.js.map +1 -0
  442. package/dist/recipes/tools/twilio.d.ts +11 -0
  443. package/dist/recipes/tools/twilio.js +180 -0
  444. package/dist/recipes/tools/twilio.js.map +1 -0
  445. package/dist/recipes/tools/vercel.d.ts +9 -0
  446. package/dist/recipes/tools/vercel.js +146 -0
  447. package/dist/recipes/tools/vercel.js.map +1 -0
  448. package/dist/recipes/tools/webflow.d.ts +11 -0
  449. package/dist/recipes/tools/webflow.js +244 -0
  450. package/dist/recipes/tools/webflow.js.map +1 -0
  451. package/dist/recipes/tools/woocommerce.d.ts +18 -0
  452. package/dist/recipes/tools/woocommerce.js +260 -0
  453. package/dist/recipes/tools/woocommerce.js.map +1 -0
  454. package/dist/recipes/tools/wrapConnectorExecute.d.ts +25 -0
  455. package/dist/recipes/tools/wrapConnectorExecute.js +37 -0
  456. package/dist/recipes/tools/wrapConnectorExecute.js.map +1 -0
  457. package/dist/recipes/tools/zendesk.js +11 -10
  458. package/dist/recipes/tools/zendesk.js.map +1 -1
  459. package/dist/recipes/triggerVars.d.ts +15 -0
  460. package/dist/recipes/triggerVars.js +57 -0
  461. package/dist/recipes/triggerVars.js.map +1 -0
  462. package/dist/recipes/validation.d.ts +1 -1
  463. package/dist/recipes/validation.js +536 -12
  464. package/dist/recipes/validation.js.map +1 -1
  465. package/dist/recipes/yamlPositions.d.ts +1 -1
  466. package/dist/recipes/yamlPositions.js +7 -2
  467. package/dist/recipes/yamlPositions.js.map +1 -1
  468. package/dist/recipes/yamlRunner.d.ts +216 -4
  469. package/dist/recipes/yamlRunner.js +1030 -96
  470. package/dist/recipes/yamlRunner.js.map +1 -1
  471. package/dist/recipesHttp.d.ts +5 -0
  472. package/dist/recipesHttp.js +68 -6
  473. package/dist/recipesHttp.js.map +1 -1
  474. package/dist/resources.js +14 -1
  475. package/dist/resources.js.map +1 -1
  476. package/dist/riskSignals.d.ts +42 -0
  477. package/dist/riskSignals.js +197 -0
  478. package/dist/riskSignals.js.map +1 -0
  479. package/dist/riskTier.d.ts +9 -0
  480. package/dist/riskTier.js +48 -0
  481. package/dist/riskTier.js.map +1 -1
  482. package/dist/runLog.d.ts +53 -0
  483. package/dist/runLog.js +75 -16
  484. package/dist/runLog.js.map +1 -1
  485. package/dist/schemas/recipe.v1.json +360 -16
  486. package/dist/server.d.ts +60 -0
  487. package/dist/server.js +192 -31
  488. package/dist/server.js.map +1 -1
  489. package/dist/sessionCheckpoint.js +4 -1
  490. package/dist/sessionCheckpoint.js.map +1 -1
  491. package/dist/sessionDetail.d.ts +43 -0
  492. package/dist/sessionDetail.js +59 -0
  493. package/dist/sessionDetail.js.map +1 -0
  494. package/dist/settingsEnv.d.ts +47 -0
  495. package/dist/settingsEnv.js +88 -0
  496. package/dist/settingsEnv.js.map +1 -0
  497. package/dist/streamableHttp.js +44 -10
  498. package/dist/streamableHttp.js.map +1 -1
  499. package/dist/ta/backtest/abnday.d.ts +53 -0
  500. package/dist/ta/backtest/abnday.js +123 -0
  501. package/dist/ta/backtest/abnday.js.map +1 -0
  502. package/dist/ta/backtest/directional.d.ts +48 -0
  503. package/dist/ta/backtest/directional.js +103 -0
  504. package/dist/ta/backtest/directional.js.map +1 -0
  505. package/dist/ta/backtest/fundx.d.ts +67 -0
  506. package/dist/ta/backtest/fundx.js +264 -0
  507. package/dist/ta/backtest/fundx.js.map +1 -0
  508. package/dist/ta/backtest/fvg.d.ts +62 -0
  509. package/dist/ta/backtest/fvg.js +236 -0
  510. package/dist/ta/backtest/fvg.js.map +1 -0
  511. package/dist/ta/backtest/ichimoku.d.ts +41 -0
  512. package/dist/ta/backtest/ichimoku.js +146 -0
  513. package/dist/ta/backtest/ichimoku.js.map +1 -0
  514. package/dist/ta/backtest/ingest.d.ts +20 -0
  515. package/dist/ta/backtest/ingest.js +104 -0
  516. package/dist/ta/backtest/ingest.js.map +1 -0
  517. package/dist/ta/backtest/msb.d.ts +49 -0
  518. package/dist/ta/backtest/msb.js +162 -0
  519. package/dist/ta/backtest/msb.js.map +1 -0
  520. package/dist/ta/backtest/scoring.d.ts +33 -0
  521. package/dist/ta/backtest/scoring.js +82 -0
  522. package/dist/ta/backtest/scoring.js.map +1 -0
  523. package/dist/ta/backtest/summary.d.ts +20 -0
  524. package/dist/ta/backtest/summary.js +42 -0
  525. package/dist/ta/backtest/summary.js.map +1 -0
  526. package/dist/ta/backtest/tsmom.d.ts +72 -0
  527. package/dist/ta/backtest/tsmom.js +202 -0
  528. package/dist/ta/backtest/tsmom.js.map +1 -0
  529. package/dist/ta/backtest/volsq.d.ts +34 -0
  530. package/dist/ta/backtest/volsq.js +154 -0
  531. package/dist/ta/backtest/volsq.js.map +1 -0
  532. package/dist/ta/backtest/walkForward.d.ts +45 -0
  533. package/dist/ta/backtest/walkForward.js +136 -0
  534. package/dist/ta/backtest/walkForward.js.map +1 -0
  535. package/dist/ta/cycles.d.ts +15 -0
  536. package/dist/ta/cycles.js +31 -0
  537. package/dist/ta/cycles.js.map +1 -0
  538. package/dist/ta/desk/accrualEmitter.d.ts +59 -0
  539. package/dist/ta/desk/accrualEmitter.js +136 -0
  540. package/dist/ta/desk/accrualEmitter.js.map +1 -0
  541. package/dist/ta/desk/cellBacktest.d.ts +179 -0
  542. package/dist/ta/desk/cellBacktest.js +514 -0
  543. package/dist/ta/desk/cellBacktest.js.map +1 -0
  544. package/dist/ta/desk/cells/wpMaRejection.d.ts +49 -0
  545. package/dist/ta/desk/cells/wpMaRejection.js +141 -0
  546. package/dist/ta/desk/cells/wpMaRejection.js.map +1 -0
  547. package/dist/ta/desk/cells/wpVolumeClimax.d.ts +57 -0
  548. package/dist/ta/desk/cells/wpVolumeClimax.js +193 -0
  549. package/dist/ta/desk/cells/wpVolumeClimax.js.map +1 -0
  550. package/dist/ta/desk/collectors.d.ts +18 -0
  551. package/dist/ta/desk/collectors.js +450 -0
  552. package/dist/ta/desk/collectors.js.map +1 -0
  553. package/dist/ta/desk/contract.d.ts +51 -0
  554. package/dist/ta/desk/contract.js +261 -0
  555. package/dist/ta/desk/contract.js.map +1 -0
  556. package/dist/ta/desk/deskLedger.d.ts +36 -0
  557. package/dist/ta/desk/deskLedger.js +239 -0
  558. package/dist/ta/desk/deskLedger.js.map +1 -0
  559. package/dist/ta/desk/liqTape.d.ts +37 -0
  560. package/dist/ta/desk/liqTape.js +99 -0
  561. package/dist/ta/desk/liqTape.js.map +1 -0
  562. package/dist/ta/desk/post.d.ts +20 -0
  563. package/dist/ta/desk/post.js +65 -0
  564. package/dist/ta/desk/post.js.map +1 -0
  565. package/dist/ta/desk/render.d.ts +25 -0
  566. package/dist/ta/desk/render.js +398 -0
  567. package/dist/ta/desk/render.js.map +1 -0
  568. package/dist/ta/desk/surfaces.d.ts +216 -0
  569. package/dist/ta/desk/surfaces.js +472 -0
  570. package/dist/ta/desk/surfaces.js.map +1 -0
  571. package/dist/ta/desk/types.d.ts +174 -0
  572. package/dist/ta/desk/types.js +104 -0
  573. package/dist/ta/desk/types.js.map +1 -0
  574. package/dist/ta/index.d.ts +23 -0
  575. package/dist/ta/index.js +54 -0
  576. package/dist/ta/index.js.map +1 -0
  577. package/dist/ta/ledger.d.ts +44 -0
  578. package/dist/ta/ledger.js +95 -0
  579. package/dist/ta/ledger.js.map +1 -0
  580. package/dist/ta/levels.d.ts +21 -0
  581. package/dist/ta/levels.js +41 -0
  582. package/dist/ta/levels.js.map +1 -0
  583. package/dist/ta/swings.d.ts +30 -0
  584. package/dist/ta/swings.js +83 -0
  585. package/dist/ta/swings.js.map +1 -0
  586. package/dist/ta/types.d.ts +81 -0
  587. package/dist/ta/types.js +20 -0
  588. package/dist/ta/types.js.map +1 -0
  589. package/dist/telemetry.js +20 -9
  590. package/dist/telemetry.js.map +1 -1
  591. package/dist/tokenUsageTracker.d.ts +2 -1
  592. package/dist/tokenUsageTracker.js +38 -20
  593. package/dist/tokenUsageTracker.js.map +1 -1
  594. package/dist/tools/contextBundle.js +13 -9
  595. package/dist/tools/contextBundle.js.map +1 -1
  596. package/dist/tools/ctxGetTaskContext.js +8 -3
  597. package/dist/tools/ctxGetTaskContext.js.map +1 -1
  598. package/dist/tools/ctxQueryTraces.d.ts +18 -1
  599. package/dist/tools/ctxQueryTraces.js +176 -27
  600. package/dist/tools/ctxQueryTraces.js.map +1 -1
  601. package/dist/tools/enrichCommit.js +5 -2
  602. package/dist/tools/enrichCommit.js.map +1 -1
  603. package/dist/tools/getDependencyTree.js +2 -2
  604. package/dist/tools/getDependencyTree.js.map +1 -1
  605. package/dist/tools/getDiagnostics.js +3 -0
  606. package/dist/tools/getDiagnostics.js.map +1 -1
  607. package/dist/tools/getDocumentSymbols.js +5 -1
  608. package/dist/tools/getDocumentSymbols.js.map +1 -1
  609. package/dist/tools/getGitHotspots.js +12 -1
  610. package/dist/tools/getGitHotspots.js.map +1 -1
  611. package/dist/tools/getGitLog.js +5 -2
  612. package/dist/tools/getGitLog.js.map +1 -1
  613. package/dist/tools/getGitStatus.js +6 -1
  614. package/dist/tools/getGitStatus.js.map +1 -1
  615. package/dist/tools/getProjectContext.js +3 -1
  616. package/dist/tools/getProjectContext.js.map +1 -1
  617. package/dist/tools/getToolCapabilities.js +39 -4
  618. package/dist/tools/getToolCapabilities.js.map +1 -1
  619. package/dist/tools/gitWrite.js +12 -3
  620. package/dist/tools/gitWrite.js.map +1 -1
  621. package/dist/tools/github/composite.d.ts +1 -1
  622. package/dist/tools/github/composite.js +7 -7
  623. package/dist/tools/github/composite.js.map +1 -1
  624. package/dist/tools/github/pr.d.ts +6 -6
  625. package/dist/tools/github/pr.js +18 -11
  626. package/dist/tools/github/pr.js.map +1 -1
  627. package/dist/tools/httpClient.js +55 -3
  628. package/dist/tools/httpClient.js.map +1 -1
  629. package/dist/tools/index.d.ts +2 -1
  630. package/dist/tools/index.js +19 -12
  631. package/dist/tools/index.js.map +1 -1
  632. package/dist/tools/lsp.d.ts +6 -0
  633. package/dist/tools/lsp.js +81 -12
  634. package/dist/tools/lsp.js.map +1 -1
  635. package/dist/tools/previewEdit.js +16 -1
  636. package/dist/tools/previewEdit.js.map +1 -1
  637. package/dist/tools/resumeClaudeTask.js +17 -0
  638. package/dist/tools/resumeClaudeTask.js.map +1 -1
  639. package/dist/tools/searchAndReplace.js +9 -5
  640. package/dist/tools/searchAndReplace.js.map +1 -1
  641. package/dist/tools/searchTools.d.ts +26 -0
  642. package/dist/tools/searchTools.js +42 -2
  643. package/dist/tools/searchTools.js.map +1 -1
  644. package/dist/tools/terminal.js +23 -51
  645. package/dist/tools/terminal.js.map +1 -1
  646. package/dist/tools/transaction.d.ts +9 -1
  647. package/dist/tools/transaction.js +59 -4
  648. package/dist/tools/transaction.js.map +1 -1
  649. package/dist/tools/utils.d.ts +16 -0
  650. package/dist/tools/utils.js +77 -7
  651. package/dist/tools/utils.js.map +1 -1
  652. package/dist/transport.d.ts +31 -0
  653. package/dist/transport.js +117 -14
  654. package/dist/transport.js.map +1 -1
  655. package/dist/wireHaltPushDispatch.js +12 -0
  656. package/dist/wireHaltPushDispatch.js.map +1 -1
  657. package/dist/workerGateDecisionLog.d.ts +141 -0
  658. package/dist/workerGateDecisionLog.js +361 -0
  659. package/dist/workerGateDecisionLog.js.map +1 -0
  660. package/dist/workers/actionClass.d.ts +38 -0
  661. package/dist/workers/actionClass.js +155 -0
  662. package/dist/workers/actionClass.js.map +1 -0
  663. package/dist/workers/backtest.d.ts +68 -0
  664. package/dist/workers/backtest.js +113 -0
  665. package/dist/workers/backtest.js.map +1 -0
  666. package/dist/workers/contextRisk.d.ts +35 -0
  667. package/dist/workers/contextRisk.js +20 -0
  668. package/dist/workers/contextRisk.js.map +1 -0
  669. package/dist/workers/contextRiskScorer.d.ts +48 -0
  670. package/dist/workers/contextRiskScorer.js +94 -0
  671. package/dist/workers/contextRiskScorer.js.map +1 -0
  672. package/dist/workers/graduation.d.ts +61 -0
  673. package/dist/workers/graduation.js +112 -0
  674. package/dist/workers/graduation.js.map +1 -0
  675. package/dist/workers/outcomeStore.d.ts +98 -0
  676. package/dist/workers/outcomeStore.js +146 -0
  677. package/dist/workers/outcomeStore.js.map +1 -0
  678. package/dist/workers/outcomesCli.d.ts +34 -0
  679. package/dist/workers/outcomesCli.js +91 -0
  680. package/dist/workers/outcomesCli.js.map +1 -0
  681. package/dist/workers/runWorkerShadow.d.ts +90 -0
  682. package/dist/workers/runWorkerShadow.js +323 -0
  683. package/dist/workers/runWorkerShadow.js.map +1 -0
  684. package/dist/workers/shadowGate.d.ts +30 -0
  685. package/dist/workers/shadowGate.js +58 -0
  686. package/dist/workers/shadowGate.js.map +1 -0
  687. package/dist/workers/shadowObserver.d.ts +165 -0
  688. package/dist/workers/shadowObserver.js +203 -0
  689. package/dist/workers/shadowObserver.js.map +1 -0
  690. package/dist/workers/shadowReport.d.ts +16 -0
  691. package/dist/workers/shadowReport.js +61 -0
  692. package/dist/workers/shadowReport.js.map +1 -0
  693. package/dist/workers/shadowRun.d.ts +38 -0
  694. package/dist/workers/shadowRun.js +44 -0
  695. package/dist/workers/shadowRun.js.map +1 -0
  696. package/dist/workers/trustLevel.d.ts +82 -0
  697. package/dist/workers/trustLevel.js +100 -0
  698. package/dist/workers/trustLevel.js.map +1 -0
  699. package/dist/workers/worker.d.ts +49 -0
  700. package/dist/workers/worker.js +88 -0
  701. package/dist/workers/worker.js.map +1 -0
  702. package/dist/workers/workerGate.d.ts +101 -0
  703. package/dist/workers/workerGate.js +210 -0
  704. package/dist/workers/workerGate.js.map +1 -0
  705. package/dist/workers/workerLevelStore.d.ts +33 -0
  706. package/dist/workers/workerLevelStore.js +87 -0
  707. package/dist/workers/workerLevelStore.js.map +1 -0
  708. package/dist/workers/workerLoader.d.ts +7 -0
  709. package/dist/workers/workerLoader.js +31 -0
  710. package/dist/workers/workerLoader.js.map +1 -0
  711. package/dist/writeFileAtomic.js +40 -13
  712. package/dist/writeFileAtomic.js.map +1 -1
  713. package/package.json +14 -6
  714. package/scripts/mcp-stdio-shim.cjs +247 -87
  715. package/templates/CLAUDE.bridge.md +1 -1
  716. package/templates/recipes/dependency-bump.yaml +33 -0
  717. package/templates/recipes/incident-to-pr.yaml +187 -0
  718. package/templates/recipes/outcome-ingester.yaml +53 -0
  719. package/templates/recipes/release-notes.yaml +30 -0
  720. package/templates/recipes/triage-failing-tests-autofile.yaml +152 -0
  721. package/templates/recipes/triage-failing-tests.yaml +38 -0
  722. package/templates/workers/dependency-upkeep.worker.yaml +26 -0
  723. package/templates/workers/release-notes.worker.yaml +17 -0
  724. package/templates/workers/test-guardian.worker.yaml +24 -0
  725. package/deploy/README.md +0 -172
  726. package/deploy/bootstrap-new-vps.sh +0 -364
  727. package/deploy/bootstrap-vps.sh +0 -192
  728. package/deploy/claude-ide-bridge.service.template +0 -67
  729. package/deploy/claude-ide-bridge@.service +0 -31
  730. package/deploy/deploy-dashboard.sh +0 -198
  731. package/deploy/deploy-landing.sh +0 -136
  732. package/deploy/ecosystem.config.js.example +0 -36
  733. package/deploy/install-vps-service.sh +0 -240
  734. package/deploy/macos/README.md +0 -153
  735. package/deploy/macos/com.patchwork.bridge.plist.template +0 -54
  736. package/deploy/macos/com.patchwork.tunnel.plist.template +0 -76
  737. package/deploy/macos/install-mac-bridge.sh +0 -244
  738. package/deploy/macos/uninstall-mac-bridge.sh +0 -22
  739. package/deploy/nginx-claude-bridge.conf.template +0 -129
@@ -6,14 +6,22 @@
6
6
  * - Conditional step execution (when)
7
7
  * - Dry-run mode
8
8
  */
9
+ import { classifyTool } from "../riskTier.js";
9
10
  import { buildDependencyGraph, executeWithDependencies, } from "./dependencyGraph.js";
10
11
  import { categoriseHaltReason, deriveHaltReasonFromError, } from "./haltCategory.js";
11
12
  import { mockNestedRecipe, resolveNestedVars, validateNestedRecipe, } from "./nestedRecipeStep.js";
12
13
  import { createOutputRegistry } from "./outputRegistry.js";
14
+ import { loadPriceTable } from "./pricing/priceTable.js";
13
15
  import { resolveRecipePath } from "./resolveRecipePath.js";
14
- import { captureForRunlog, detectSilentFail } from "./stepObservation.js";
16
+ import { RunBudget } from "./runBudget.js";
17
+ import { registerRun, unregisterRun } from "./runRegistry.js";
18
+ import { captureForRunlog, detectSilentFail, redactSecretsForPrompt, } from "./stepObservation.js";
15
19
  import { compileTemplate } from "./templateEngine.js";
16
- import { evaluateStepExpect } from "./yamlRunner.js";
20
+ import { computeAgentCallUsage, declaredRecipeEnv, evaluateStepExpect, resolveRouting, } from "./yamlRunner.js";
21
+ /** Normalise the union AgentExecutor return into an AgentResult. */
22
+ function toChainedAgentResult(v) {
23
+ return typeof v === "string" ? { text: v } : v;
24
+ }
17
25
  function nestedRecipeRef(step) {
18
26
  return typeof step.recipe === "string"
19
27
  ? step.recipe
@@ -26,7 +34,7 @@ export function buildTemplateContext(registry, env) {
26
34
  return registry.toTemplateContext(env);
27
35
  }
28
36
  /** Resolve all template strings in a step */
29
- export function resolveStepTemplates(step, context) {
37
+ export function resolveStepTemplates(step, context, secretKeys) {
30
38
  const resolved = {};
31
39
  const errors = [];
32
40
  // W3: keys that are recipe metadata, not tool params
@@ -46,6 +54,12 @@ export function resolveStepTemplates(step, context) {
46
54
  "retry",
47
55
  "retryDelay",
48
56
  "parallel",
57
+ // Runner-meta declared on ChainedStep — must NOT be forwarded as tool
58
+ // params (the `expect` AJV schema object + numeric timeout especially leak
59
+ // internal runner config into tools that log their raw params).
60
+ "expect",
61
+ "timeout_ms",
62
+ "silentFailDetection",
49
63
  ]);
50
64
  // Resolve tool params
51
65
  for (const [key, value] of Object.entries(step)) {
@@ -89,10 +103,20 @@ export function resolveStepTemplates(step, context) {
89
103
  // assert on err.code without needing to inspect step results).
90
104
  resolveRecipePath(resolved.path, { write: toolId !== "file.read" });
91
105
  }
92
- // Resolve agent prompt if present
106
+ // Resolve agent prompt if present.
93
107
  if (step.agent && typeof step.agent.prompt === "string") {
94
108
  const compiled = compileTemplate(step.agent.prompt);
95
- const result = compiled.evaluate(context);
109
+ // Tier-1 #5 (audit 2026-06-22): redact recipe-declared secrets (`type: env`
110
+ // vars) from the LLM-facing prompt. Their raw values still flow to TOOL
111
+ // params above (an http header / DB password legitimately needs the
112
+ // secret), but must never reach the model verbatim. Mirrors the flat runner.
113
+ const promptContext = secretKeys && secretKeys.size > 0
114
+ ? {
115
+ ...context,
116
+ env: redactSecretsForPrompt(context.env, secretKeys),
117
+ }
118
+ : context;
119
+ const result = compiled.evaluate(promptContext);
96
120
  if ("error" in result) {
97
121
  errors.push(result.error);
98
122
  }
@@ -102,34 +126,86 @@ export function resolveStepTemplates(step, context) {
102
126
  }
103
127
  // Evaluate when condition
104
128
  let conditionResult = true;
105
- if (step.when) {
106
- const compiled = compileTemplate(step.when);
107
- const result = compiled.evaluate(context);
108
- if ("error" in result) {
109
- errors.push(result.error);
129
+ if (step.when !== undefined) {
130
+ // M25: YAML may deliver `when: false` as a boolean even though the TS type
131
+ // says string — cast to unknown before comparing to silence the type error.
132
+ if (step.when === false) {
110
133
  conditionResult = false;
111
134
  }
112
- else {
113
- // Simple truthiness check (empty string, "0", "false" are falsy)
114
- const val = result.value.trim().toLowerCase();
115
- conditionResult =
116
- !!val &&
117
- val !== "0" &&
118
- val !== "false" &&
119
- val !== "null" &&
120
- val !== "undefined";
135
+ else if (step.when) {
136
+ const compiled = compileTemplate(step.when);
137
+ const result = compiled.evaluate(context);
138
+ if ("error" in result) {
139
+ errors.push(result.error);
140
+ conditionResult = false;
141
+ }
142
+ else {
143
+ // Falsy if the WHOLE value is a falsy token OR its LAST token is — so a
144
+ // `when` fed an agent's free-text decision (prose ending in "true"/"false")
145
+ // gates on the verdict, not on "non-empty ⇒ truthy". Single-token guards
146
+ // are unchanged (last token === whole value). Kept in lockstep with
147
+ // yamlRunner.ts's guard (runner behavioral parity).
148
+ const val = result.value.trim().toLowerCase();
149
+ const FALSY = new Set(["", "0", "false", "null", "undefined"]);
150
+ const lastToken = (val.split(/\s+/).pop() ?? "").replace(/[^a-z0-9]/g, "");
151
+ conditionResult = !!val && !FALSY.has(val) && !FALSY.has(lastToken);
152
+ }
121
153
  }
122
154
  }
123
155
  return { resolved, conditionResult, errors };
124
156
  }
157
+ /**
158
+ * Race `work` against a per-step timeout. Returns `work` unchanged when no
159
+ * positive timeout is set. On expiry the returned promise rejects with a
160
+ * `step_timeout` error (executeChainedStep's catch turns that into a step
161
+ * failure). The underlying call is not cancellable here — it keeps running but
162
+ * its result is ignored — matching the flat runner's documented behaviour.
163
+ */
164
+ function raceStepTimeout(work, timeoutMs, label, signal) {
165
+ const hasTimeout = typeof timeoutMs === "number" && timeoutMs > 0;
166
+ if (!hasTimeout && !signal)
167
+ return work;
168
+ // Run-cancel: reject promptly when the run is aborted so an in-flight
169
+ // tool/agent step doesn't block the run from draining (the underlying call
170
+ // keeps running but its result is ignored — same contract as the timeout).
171
+ if (signal?.aborted) {
172
+ return Promise.reject(new Error(`step_cancelled: run aborted before step "${label}" completed`));
173
+ }
174
+ const racers = [];
175
+ let timer;
176
+ if (hasTimeout) {
177
+ racers.push(new Promise((_, reject) => {
178
+ timer = setTimeout(() => {
179
+ reject(new Error(`step_timeout: exceeded ${timeoutMs}ms in step "${label}"`));
180
+ }, timeoutMs);
181
+ }));
182
+ }
183
+ let onAbort;
184
+ if (signal) {
185
+ racers.push(new Promise((_, reject) => {
186
+ onAbort = () => reject(new Error(`step_cancelled: run aborted during step "${label}"`));
187
+ signal.addEventListener("abort", onAbort, { once: true });
188
+ }));
189
+ }
190
+ return Promise.race([work, ...racers]).finally(() => {
191
+ if (timer)
192
+ clearTimeout(timer);
193
+ if (signal && onAbort)
194
+ signal.removeEventListener("abort", onAbort);
195
+ });
196
+ }
125
197
  /** Execute a single step */
126
198
  export async function executeChainedStep(ctx, deps) {
127
- const { registry, step, options, depth } = ctx;
199
+ const { registry, step, options, depth, recipe } = ctx;
128
200
  const { dryRun } = options;
129
201
  // Build template context
130
202
  const templateContext = buildTemplateContext(registry, options.env);
203
+ // Tier-1 #5 (audit 2026-06-22): keys declared via a recipe-level `type: env`
204
+ // block are secrets — redacted from the LLM-facing agent prompt (but kept
205
+ // real for tool params). Mirrors the flat runner's `secretKeys`.
206
+ const secretKeys = new Set(Object.keys(declaredRecipeEnv(recipe, options.env)));
131
207
  // Resolve templates
132
- const { resolved, conditionResult, errors } = resolveStepTemplates(step, templateContext);
208
+ const { resolved, conditionResult, errors } = resolveStepTemplates(step, templateContext, secretKeys);
133
209
  if (errors.length > 0) {
134
210
  return {
135
211
  success: false,
@@ -212,6 +288,60 @@ export async function executeChainedStep(ctx, deps) {
212
288
  }
213
289
  // Execute based on step type
214
290
  try {
291
+ // Tier-1 #6 (audit 2026-06-22): budget admission BEFORE dispatch, gating
292
+ // ALL step types — not just agent steps. The admission check used to live
293
+ // inside the `if (step.agent)` branch, so a breached budget kept executing
294
+ // tool / nested-recipe steps unbounded. admit() is a pure read-only breach
295
+ // check (no counter side-effects), so gating every step is safe;
296
+ // subscription drivers report no usage and fail open inside RunBudget, so
297
+ // this is a no-op until a measured agent step breaches the cap. Mirrors the
298
+ // flat runner ("Bug (3)").
299
+ const budget = ctx.budget;
300
+ if (budget) {
301
+ const admission = budget.admit();
302
+ if (!admission.admitted) {
303
+ return {
304
+ success: false,
305
+ error: admission.reason ?? "Run exceeded its budget — budget_exceeded.",
306
+ resolvedParams: resolved,
307
+ };
308
+ }
309
+ }
310
+ // Tier-1 #4 (audit 2026-06-22): approval gate, mirroring the flat runner.
311
+ // Engages only when the bridge injected `requireApprovalFn` (approvalGate
312
+ // != "off"), respects the per-recipe `requireApproval: false` opt-out, and
313
+ // gates only the TOP-LEVEL run (depth === 0). Safe-by-default rationale: a
314
+ // chained recipe always has `trigger.type: "chained"` (that's how
315
+ // dispatchRecipe routes it), so cron/webhook can never fire one directly —
316
+ // they route to the flat runner. A top-level chained run is therefore the
317
+ // user-dispatched (interactive) case the flat runner gates as "manual"; a
318
+ // NESTED chained recipe (depth > 0) runs under a parent whose own gating
319
+ // already governed admission, so re-gating every nested step would block
320
+ // automated parents mid-flight. The injected fn applies the tier threshold
321
+ // itself and returns `true` for steps that don't need sign-off; a `false`
322
+ // result is an explicit human rejection → halt the step (and, via the
323
+ // dependency graph, its dependents). Only agent/tool steps are gated;
324
+ // nested-recipe steps are gated through their own inner steps.
325
+ if (deps.requireApprovalFn &&
326
+ depth === 0 &&
327
+ (step.agent || step.tool) &&
328
+ recipe.requireApproval !== false) {
329
+ const approvalToolId = step.agent ? "agent" : (step.tool ?? "unknown");
330
+ const approved = await deps.requireApprovalFn({
331
+ toolId: approvalToolId,
332
+ tier: classifyTool(approvalToolId),
333
+ summary: step.agent ? "agent step" : `tool ${approvalToolId}`,
334
+ params: step.agent ? undefined : resolved,
335
+ ...(options.signal && { signal: options.signal }), // L1
336
+ });
337
+ if (!approved) {
338
+ return {
339
+ success: false,
340
+ error: "Step rejected by approval gate — approval_rejected.",
341
+ resolvedParams: resolved,
342
+ };
343
+ }
344
+ }
215
345
  const recipeRef = nestedRecipeRef(step);
216
346
  if (recipeRef) {
217
347
  // Nested recipe call
@@ -264,6 +394,8 @@ export async function executeChainedStep(ctx, deps) {
264
394
  maxDepth: options.maxDepth,
265
395
  sourcePath: nestedRecipe.sourcePath,
266
396
  env: { ...options.env, ...resolvedVars }, // Merge resolved vars into env
397
+ budget: ctx.budget, // share the single per-run budget across the tree
398
+ priceTable: ctx.priceTable, // reuse the once-loaded price table
267
399
  };
268
400
  const childResult = await runChainedRecipe(nestedRecipe.recipe, childOptions, deps, childRegistry, depth + 1);
269
401
  return {
@@ -278,16 +410,89 @@ export async function executeChainedStep(ctx, deps) {
278
410
  else if (step.agent) {
279
411
  // Agent step
280
412
  const prompt = resolved.agentPrompt ?? step.agent.prompt;
281
- let result = await deps.executeAgent(prompt, step.agent.model, step.agent.driver);
413
+ // Budget admission already happened at the top of this try block
414
+ // (Tier-1 #6) — it now gates every step type, so the agent-only check
415
+ // that used to live here was removed. `budget` is the try-scoped const.
416
+ // Phase 4: opt-in cost-aware routing. No-op when the step has no
417
+ // `downshift` list or no USD cap is set on the run budget.
418
+ let dispatchDriver = step.agent.driver;
419
+ let dispatchModel = step.agent.model;
420
+ if (budget && step.agent.downshift?.length) {
421
+ const routed = resolveRouting({ driver: step.agent.driver, model: step.agent.model }, step.agent.downshift, prompt, budget);
422
+ dispatchDriver = routed.driver === "api" ? "anthropic" : routed.driver;
423
+ dispatchModel = routed.model;
424
+ }
425
+ const agentReturn = toChainedAgentResult(await raceStepTimeout(deps.executeAgent(prompt, dispatchModel, dispatchDriver, {
426
+ ...(step.agent.mcpAccess !== undefined && {
427
+ mcpAccess: step.agent.mcpAccess,
428
+ }),
429
+ ...(step.agent.sandbox !== undefined && {
430
+ sandbox: step.agent.sandbox,
431
+ }),
432
+ ...(step.agent.tools !== undefined && {
433
+ allowedTools: step.agent.tools,
434
+ }),
435
+ ...(step.agent.disallowedTools !== undefined && {
436
+ disallowedTools: step.agent.disallowedTools,
437
+ }),
438
+ }), step.timeout_ms, step.id, options.signal));
439
+ // Reconcile REAL usage after dispatch (the closure now surfaces it
440
+ // instead of discarding it). Subscription drivers report no usage →
441
+ // RunBudget fails open with a one-time warning.
442
+ if (budget) {
443
+ // Resolve the driver this call should be BILLED against, mirroring the
444
+ // flat runner exactly (#850 parity):
445
+ // 1. Prefer the driver `_executeAgent` actually resolved+stamped
446
+ // (`servedBy.driver`) — the single source of auto-detect truth.
447
+ // 2. Normalize the "api" alias to "anthropic" (the downshift branch
448
+ // already does this) so a metered call with `agent.driver: "api"`
449
+ // and no `servedBy` is charged, not silently dropped.
450
+ // 3. When the step declares NO driver and the call left no
451
+ // `servedBy`, default to "anthropic" — the same API path
452
+ // `_executeAgent`'s auto-detect lands on for an unspecified
453
+ // driver. The flat runner reaches this default via `servedBy`
454
+ // (it always routes through `_executeAgent`); the chained runner
455
+ // may be handed an injected `executeAgent` that returns measured
456
+ // usage without a `servedBy`, so it must apply the same default
457
+ // here or usdMax would be enforced on one runner and not the
458
+ // other for identical recipes. A subscription/CLI call that
459
+ // genuinely incurs no metered cost stamps `servedBy.driver:
460
+ // "subprocess"` via `_executeAgent`, so it never reaches this
461
+ // default and stays correctly fail-open.
462
+ const reconcileDriver = agentReturn.servedBy?.driver ??
463
+ (dispatchDriver === "api" || dispatchDriver === undefined
464
+ ? "anthropic"
465
+ : dispatchDriver);
466
+ budget.reconcile(reconcileDriver, agentReturn.usage, agentReturn.servedBy?.model ?? dispatchModel, {
467
+ inputChars: prompt.length,
468
+ outputChars: agentReturn.text.length,
469
+ });
470
+ }
471
+ // P1: per-step agent token usage (chained = one agent call per step).
472
+ // Attached to every return below so it lands on the persisted step row
473
+ // regardless of success / failure. Absent for unmeasured drivers.
474
+ // Thread the once-loaded price table to avoid per-step disk I/O.
475
+ const usage = computeAgentCallUsage(agentReturn.usage, agentReturn.servedBy, ctx.priceTable);
476
+ let result = agentReturn.text;
282
477
  // Detect failure signals returned as sentinel strings (mirrors flat runner)
283
478
  if (typeof result === "string" &&
284
479
  result.startsWith("[agent step failed:")) {
285
- return { success: false, error: result, resolvedParams: resolved };
480
+ return {
481
+ success: false,
482
+ error: result,
483
+ resolvedParams: resolved,
484
+ ...(usage ? { usage } : {}),
485
+ };
286
486
  }
287
487
  const agentSilentFail = step.silentFailDetection !== false ? detectSilentFail(result) : null;
288
488
  if (agentSilentFail) {
289
489
  const reason = `silent-fail detected (${agentSilentFail.reason}): ${agentSilentFail.matched}`;
290
- return { success: false, error: reason, resolvedParams: resolved };
490
+ return {
491
+ success: false,
492
+ error: reason,
493
+ resolvedParams: resolved,
494
+ ...(usage ? { usage } : {}),
495
+ };
291
496
  }
292
497
  if (step.transform) {
293
498
  try {
@@ -297,21 +502,34 @@ export async function executeChainedStep(ctx, deps) {
297
502
  console.warn(`transform failed for step ${step.id}: ${err}`);
298
503
  }
299
504
  }
505
+ let agentExpectWarnings;
300
506
  if (step.expect) {
301
507
  const failures = await evaluateStepExpect(step.expect, result);
302
- if (failures.length > 0 && (step.expect.on_fail ?? "halt") === "halt") {
303
- return {
304
- success: false,
305
- error: `expect failed: ${failures.join("; ")}`,
306
- resolvedParams: resolved,
307
- };
508
+ if (failures.length > 0) {
509
+ if ((step.expect.on_fail ?? "halt") === "halt") {
510
+ return {
511
+ success: false,
512
+ error: `expect failed: ${failures.join("; ")}`,
513
+ resolvedParams: resolved,
514
+ ...(usage ? { usage } : {}),
515
+ };
516
+ }
517
+ // on_fail: "warn" — keep going but surface the failures (mirrors
518
+ // the flat runner's `last.expectWarnings`); previously dropped.
519
+ agentExpectWarnings = failures;
308
520
  }
309
521
  }
310
- return { success: true, data: result, resolvedParams: resolved };
522
+ return {
523
+ success: true,
524
+ data: result,
525
+ resolvedParams: resolved,
526
+ ...(usage ? { usage } : {}),
527
+ ...(agentExpectWarnings ? { expectWarnings: agentExpectWarnings } : {}),
528
+ };
311
529
  }
312
530
  else if (step.tool) {
313
531
  // Tool step
314
- let result = await deps.executeTool(step.tool, resolved);
532
+ let result = await raceStepTimeout(deps.executeTool(step.tool, resolved), step.timeout_ms, step.id, options.signal);
315
533
  // Detect tool-level errors reported as JSON {ok: false, error: ...} (mirrors flat runner)
316
534
  if (typeof result === "string") {
317
535
  try {
@@ -341,17 +559,28 @@ export async function executeChainedStep(ctx, deps) {
341
559
  console.warn(`transform failed for step ${step.id}: ${err}`);
342
560
  }
343
561
  }
562
+ let toolExpectWarnings;
344
563
  if (step.expect) {
345
564
  const failures = await evaluateStepExpect(step.expect, result);
346
- if (failures.length > 0 && (step.expect.on_fail ?? "halt") === "halt") {
347
- return {
348
- success: false,
349
- error: `expect failed: ${failures.join("; ")}`,
350
- resolvedParams: resolved,
351
- };
565
+ if (failures.length > 0) {
566
+ if ((step.expect.on_fail ?? "halt") === "halt") {
567
+ return {
568
+ success: false,
569
+ error: `expect failed: ${failures.join("; ")}`,
570
+ resolvedParams: resolved,
571
+ };
572
+ }
573
+ // on_fail: "warn" — keep going but surface the failures (mirrors
574
+ // the flat runner's `last.expectWarnings`); previously dropped.
575
+ toolExpectWarnings = failures;
352
576
  }
353
577
  }
354
- return { success: true, data: result, resolvedParams: resolved };
578
+ return {
579
+ success: true,
580
+ data: result,
581
+ resolvedParams: resolved,
582
+ ...(toolExpectWarnings ? { expectWarnings: toolExpectWarnings } : {}),
583
+ };
355
584
  }
356
585
  else {
357
586
  return { success: false, error: "Step has no tool, agent, or recipe" };
@@ -362,15 +591,40 @@ export async function executeChainedStep(ctx, deps) {
362
591
  return { success: false, error: msg };
363
592
  }
364
593
  }
594
+ /** Upper bound on retries — clamps absurd/misconfigured values so a recipe
595
+ * can't spin a step unboundedly. Mirrors the `maximum` on `retry` in the
596
+ * recipe JSON schema. */
597
+ const MAX_RETRIES = 20;
365
598
  async function withRetry(fn, maxRetries, delayMs) {
599
+ // Audit 2026-06-03 (HIGH #8): clamp the retry count. A negative value (typo
600
+ // like `retry: -1`) made `attempt <= maxRetries` immediately false, so the
601
+ // step NEVER ran and silently reported as failed. A non-finite or huge value
602
+ // would spin unboundedly. Floor to an integer in [0, MAX_RETRIES]; non-finite
603
+ // → 0 (run once, no retries).
604
+ const safeRetries = Number.isFinite(maxRetries)
605
+ ? Math.min(MAX_RETRIES, Math.max(0, Math.floor(maxRetries)))
606
+ : 0;
366
607
  let last = { success: false };
367
- for (let attempt = 0; attempt <= maxRetries; attempt++) {
608
+ for (let attempt = 0; attempt <= safeRetries; attempt++) {
368
609
  if (attempt > 0) {
369
610
  await new Promise((r) => setTimeout(r, delayMs));
370
611
  }
371
612
  last = await fn();
372
613
  if (last.success)
373
614
  return last;
615
+ // Audit 2026-06-10 recipe-runners-2: do NOT retry on a step_timeout. The
616
+ // timed-out attempt's tool/agent call keeps running in the background
617
+ // (raceStepTimeout only abandons the wait, it does not cancel the call);
618
+ // re-issuing the step is pointless for write tools (the in-flight
619
+ // idempotency ledger short-circuits the retry to the same promise) and a
620
+ // duplicate-side-effect hazard for any tool the ledger cannot dedup. A
621
+ // true cancel needs an AbortSignal threaded through every tool/connector,
622
+ // which is out of scope; refusing to retry on timeout is the safe contract.
623
+ if (last.error?.startsWith("step_timeout:"))
624
+ return last;
625
+ // Do not retry cancelled steps — the run was aborted intentionally.
626
+ if (last.error?.startsWith("step_cancelled:"))
627
+ return last;
374
628
  }
375
629
  return last;
376
630
  }
@@ -398,6 +652,14 @@ export function expandParallelSteps(steps) {
398
652
  if (Array.isArray(step.parallel) && step.parallel.length > 0) {
399
653
  // Generate a stable group id from position if the group has no id.
400
654
  const groupId = step.id ?? `parallel_${i}`;
655
+ // Track the group container id too — otherwise a later flat step reusing
656
+ // the same id passes the duplicate check and silently corrupts the
657
+ // dependency graph (dependents `awaits: [groupId]` are rewritten to the
658
+ // group's children, never the shadowed flat step).
659
+ if (seenIds.has(groupId)) {
660
+ throw new Error(`expandParallelSteps: duplicate step id "${groupId}" — each step id must be unique`);
661
+ }
662
+ seenIds.add(groupId);
401
663
  const groupAwaits = step.awaits ?? [];
402
664
  const childIds = [];
403
665
  for (let j = 0; j < step.parallel.length; j++) {
@@ -422,6 +684,22 @@ export function expandParallelSteps(steps) {
422
684
  }
423
685
  groupChildren.set(groupId, childIds);
424
686
  }
687
+ else if (typeof step.parallel === "object" &&
688
+ step.parallel !== null &&
689
+ !Array.isArray(step.parallel) &&
690
+ "each" in step.parallel) {
691
+ // parallel: { each, as, steps } — runtime map-reduce over an array that a
692
+ // PRIOR step produces. Real support needs dynamic agent fan-out (per-iter
693
+ // budget/judge/silent-fail) the chained engine doesn't have yet, and the
694
+ // array isn't known at plan-expansion time. This object form used to fall
695
+ // through to the flat-step branch below and execute ZERO iterations —
696
+ // silent data loss with no signal. Fail loud instead. (audit
697
+ // recipe-chained-1)
698
+ throw new Error(`Step "${step.id ?? `parallel_${i}`}" uses parallel:{ each } ` +
699
+ `(runtime map-reduce), which is not yet implemented in chained ` +
700
+ "recipes. Use the `fan_out` tool step for tool-only loops; agent " +
701
+ "fan-out over a runtime-produced array is not yet supported.");
702
+ }
425
703
  else {
426
704
  if (step.id && seenIds.has(step.id)) {
427
705
  throw new Error(`expandParallelSteps: duplicate step id "${step.id}" — each step id must be unique`);
@@ -463,6 +741,20 @@ export async function runChainedRecipe(recipe, options, deps, existingRegistry,
463
741
  }
464
742
  }
465
743
  const registry = existingRegistry ?? createOutputRegistry();
744
+ // Per-run budget: build once at the top level from `recipe.budget`; nested
745
+ // recipe calls inherit the SAME instance via `options.budget` so the whole
746
+ // tree shares one cap (mirrors the flat path's single RunBudget per run).
747
+ // Absent `recipe.budget` → an empty RunBudget whose admit/reconcile are
748
+ // no-ops, so there's no behaviour change for budget-less recipes.
749
+ const budget = options.budget ?? new RunBudget(recipe.budget);
750
+ // True only when THIS call constructed the budget — used to gate
751
+ // refreshPrices() so an injected (unit-test) budget keeps its stable prices
752
+ // and nested calls don't double-refresh the shared parent budget.
753
+ const ownsBudget = options.budget === undefined;
754
+ // Load the price table ONCE per run (inherited by nested recipe calls via
755
+ // options.priceTable) so each agent step's computeAgentCallUsage avoids a
756
+ // synchronous existsSync/readFileSync round-trip — mirrors the flat runner.
757
+ const priceTable = options.priceTable ?? loadPriceTable();
466
758
  // Expand parallel: sugar into flat steps before building the dependency graph.
467
759
  const steps = expandParallelSteps(recipe.steps);
468
760
  // Build dependency graph
@@ -496,7 +788,17 @@ export async function runChainedRecipe(recipe, options, deps, existingRegistry,
496
788
  // Non-fatal — run-log failures must never break recipe execution.
497
789
  }
498
790
  }
499
- if (depGraph.hasCycles) {
791
+ // A phantom `awaits:` target (one matching no real step) is invisible to
792
+ // cycle detection but would silently drop the awaiting step and its
793
+ // dependents from execution while the run still reports success. Reject
794
+ // the run with a descriptive error BEFORE executing any step, mirroring
795
+ // the circular-dependency guard below.
796
+ const fatalGraphError = depGraph.unknownAwaitTargets.length > 0
797
+ ? `Recipe has steps that await unknown step(s): ${depGraph.unknownAwaitTargets.join(", ")}`
798
+ : depGraph.hasCycles
799
+ ? "Recipe has circular dependencies"
800
+ : undefined;
801
+ if (fatalGraphError) {
500
802
  if (depth === 0) {
501
803
  const doneAt = Date.now();
502
804
  const durationMs = doneAt - runStartedAt;
@@ -507,7 +809,7 @@ export async function runChainedRecipe(recipe, options, deps, existingRegistry,
507
809
  doneAt,
508
810
  durationMs,
509
811
  stepResults: [],
510
- errorMessage: "Recipe has circular dependencies",
812
+ errorMessage: fatalGraphError,
511
813
  });
512
814
  }
513
815
  else if (options.runLogDir) {
@@ -522,7 +824,7 @@ export async function runChainedRecipe(recipe, options, deps, existingRegistry,
522
824
  startedAt: runStartedAt,
523
825
  doneAt,
524
826
  durationMs,
525
- errorMessage: "Recipe has circular dependencies",
827
+ errorMessage: fatalGraphError,
526
828
  stepResults: [],
527
829
  });
528
830
  }
@@ -535,7 +837,7 @@ export async function runChainedRecipe(recipe, options, deps, existingRegistry,
535
837
  success: false,
536
838
  stepResults: new Map(),
537
839
  summary: { total: 0, succeeded: 0, failed: 0, skipped: 0 },
538
- errorMessage: "Recipe has circular dependencies",
840
+ errorMessage: fatalGraphError,
539
841
  context: {},
540
842
  };
541
843
  }
@@ -553,6 +855,13 @@ export async function runChainedRecipe(recipe, options, deps, existingRegistry,
553
855
  // Populated at the `registry.set(...)` site during step execution and
554
856
  // attached to the final `RunStepResult` written via `completeRun`.
555
857
  const capturedStepData = new Map();
858
+ // P1 cost/token corpus — per-step agent usage, keyed by stepId. Populated
859
+ // for every depth-0 agent step (independent of the VD-2 runLog-gated
860
+ // capture above) so both the bridge and CLI persistence paths get it.
861
+ const capturedUsage = new Map();
862
+ // `expect` warnings (on_fail: "warn") per depth-0 step, keyed by stepId, so
863
+ // they land on the persisted step row instead of being silently dropped.
864
+ const capturedExpectWarnings = new Map();
556
865
  // VD-1 live-tail: when an `activityLog` is provided AND we have a `runSeq`
557
866
  // (depth 0, runLog supplied), broadcast `recipe_step_start` /
558
867
  // `recipe_step_done` events so the dashboard `/runs/[seq]` page can
@@ -636,21 +945,45 @@ export async function runChainedRecipe(recipe, options, deps, existingRegistry,
636
945
  }
637
946
  : options.onStepComplete;
638
947
  // Execute with dependency tracking
948
+ // Run-level cancellation. The top-level run (depth 0) registers its own
949
+ // AbortController keyed by the RunLog seq so `POST /runs/:seq/cancel` can
950
+ // abort it; nested recipes inherit the parent's signal. Combine both so
951
+ // either source cancels. (run-cancel)
952
+ const runController = depth === 0 && runSeq !== undefined ? registerRun(runSeq) : undefined;
953
+ const cancelSignals = [options.signal, runController?.signal].filter((s) => !!s);
954
+ const effectiveSignal = cancelSignals.length === 0
955
+ ? undefined
956
+ : cancelSignals.length === 1
957
+ ? cancelSignals[0]
958
+ : AbortSignal.any(cancelSignals);
959
+ // Expose the combined signal on the options that flow into executeChainedStep
960
+ // so an in-flight tool/agent step (via raceStepTimeout) also aborts on cancel,
961
+ // not just not-yet-started steps. Safe no-op when undefined. (run-cancel)
962
+ options.signal = effectiveSignal;
639
963
  const execOptions = {
640
964
  maxConcurrency: options.maxConcurrency,
641
965
  onStepStart: wrappedOnStepStart,
642
966
  onStepComplete: wrappedOnStepComplete,
967
+ signal: effectiveSignal,
643
968
  };
644
969
  const stepExecutor = async (stepId) => {
645
970
  const step = stepMap.get(stepId);
646
971
  if (!step)
647
972
  throw new Error(`Step ${stepId} not found`);
973
+ // Honour the refreshPrices() contract for long-running recipes: pick up a
974
+ // mid-run ~/.patchwork/prices.json update for budget enforcement. No-op
975
+ // unless a usdMax cap is set; gated on ownsBudget so an injected
976
+ // (unit-test) budget keeps its stable prices.
977
+ if (ownsBudget)
978
+ budget.refreshPrices();
648
979
  const ctx = {
649
980
  registry,
650
981
  step,
651
982
  options,
652
983
  recipe,
653
984
  depth,
985
+ budget,
986
+ priceTable,
654
987
  };
655
988
  const retryCount = step.retry ?? recipe.on_error?.retry ?? 0;
656
989
  const retryDelay = step.retryDelay ?? recipe.on_error?.retryDelay ?? 1000;
@@ -660,6 +993,15 @@ export async function runChainedRecipe(recipe, options, deps, existingRegistry,
660
993
  durationMs: Date.now() - stepStart,
661
994
  skipped: result.skipped,
662
995
  });
996
+ // P1: stash this step's agent token usage (depth-0 persistence reads it).
997
+ if (depth === 0 && result.usage) {
998
+ capturedUsage.set(stepId, result.usage);
999
+ }
1000
+ // Stash on_fail:"warn" expect failures so the persisted step row + the
1001
+ // dashboard surface them (previously silently dropped in chained runs).
1002
+ if (depth === 0 && result.expectWarnings?.length) {
1003
+ capturedExpectWarnings.set(stepId, result.expectWarnings);
1004
+ }
663
1005
  // Recipe-level on_error.fallback: "log_only" and "deliver_original" both
664
1006
  // treat step failures as non-fatal (fail-open) — same semantics as
665
1007
  // step-level optional: true. "abort" is the default (propagate failure).
@@ -711,7 +1053,17 @@ export async function runChainedRecipe(recipe, options, deps, existingRegistry,
711
1053
  throw new Error(result.error ?? `Step ${stepId} failed`);
712
1054
  }
713
1055
  };
714
- const stepResults = await executeWithDependencies(depGraph, stepExecutor, execOptions);
1056
+ let stepResults;
1057
+ try {
1058
+ stepResults = await executeWithDependencies(depGraph, stepExecutor, execOptions);
1059
+ }
1060
+ finally {
1061
+ // Drop the run from the registry as soon as execution finishes (success,
1062
+ // failure, or cancel) so the seq can't be cancelled post-hoc and the map
1063
+ // doesn't leak. Only the top-level run registered one. (run-cancel)
1064
+ if (runController && runSeq !== undefined)
1065
+ unregisterRun(runSeq);
1066
+ }
715
1067
  // Merge timings into step results
716
1068
  const enrichedResults = new Map();
717
1069
  let failed = 0;
@@ -756,6 +1108,9 @@ export async function runChainedRecipe(recipe, options, deps, existingRegistry,
756
1108
  try {
757
1109
  const doneAt = Date.now();
758
1110
  const stepResultsList = [];
1111
+ // P1: run-level token aggregate, summed from per-step agent usage.
1112
+ const runTok = { inputTokens: 0, outputTokens: 0, measured: false };
1113
+ let runCostUsd;
759
1114
  for (const [id, r] of enrichedResults) {
760
1115
  const step = stepMap.get(id);
761
1116
  const captured = capturedStepData.get(id);
@@ -771,6 +1126,16 @@ export async function runChainedRecipe(recipe, options, deps, existingRegistry,
771
1126
  status,
772
1127
  error: errorMsg,
773
1128
  });
1129
+ const expectWarnings = capturedExpectWarnings.get(id);
1130
+ const usage = capturedUsage.get(id);
1131
+ if (usage) {
1132
+ runTok.measured = true;
1133
+ runTok.inputTokens += usage.inputTokens;
1134
+ runTok.outputTokens += usage.outputTokens;
1135
+ if (typeof usage.costUsd === "number") {
1136
+ runCostUsd = (runCostUsd ?? 0) + usage.costUsd;
1137
+ }
1138
+ }
774
1139
  stepResultsList.push({
775
1140
  id,
776
1141
  tool: step?.tool,
@@ -788,12 +1153,36 @@ export async function runChainedRecipe(recipe, options, deps, existingRegistry,
788
1153
  ...(captured?.startedAt !== undefined && {
789
1154
  startedAt: captured.startedAt,
790
1155
  }),
1156
+ // P1: per-step token usage (absent for tool / unmeasured steps).
1157
+ ...(usage
1158
+ ? {
1159
+ inputTokens: usage.inputTokens,
1160
+ outputTokens: usage.outputTokens,
1161
+ ...(typeof usage.costUsd === "number"
1162
+ ? { costUsd: usage.costUsd }
1163
+ : {}),
1164
+ }
1165
+ : {}),
1166
+ ...(expectWarnings?.length ? { expectWarnings } : {}),
791
1167
  });
792
1168
  }
1169
+ // P1: assemble run-level totals (only when any step reported usage) +
1170
+ // budget totals (only when a budget was configured).
1171
+ const tokenTotals = runTok.measured
1172
+ ? {
1173
+ inputTokens: runTok.inputTokens,
1174
+ outputTokens: runTok.outputTokens,
1175
+ ...(typeof runCostUsd === "number" ? { costUsd: runCostUsd } : {}),
1176
+ }
1177
+ : undefined;
1178
+ const budgetTotals = recipe.budget ? budget.totals() : undefined;
793
1179
  const outputTail = stepResultsList
794
1180
  .map((s) => `[${s.status}] ${s.tool ?? s.id}${s.error ? `: ${s.error}` : ""}`)
795
1181
  .join("\n")
796
1182
  .slice(0, 2000);
1183
+ // Budget warnings/spend — record like the flat path so chained runs
1184
+ // surface spend + fail-open notices on the same dashboard surfaces.
1185
+ const budgetWarnings = budget.finalWarnings();
797
1186
  if (options.runLog && runSeq !== undefined) {
798
1187
  options.runLog.completeRun(runSeq, {
799
1188
  status: result.success ? "done" : "error",
@@ -801,9 +1190,12 @@ export async function runChainedRecipe(recipe, options, deps, existingRegistry,
801
1190
  durationMs: doneAt - runStartedAt,
802
1191
  stepResults: stepResultsList,
803
1192
  outputTail,
1193
+ ...(budgetWarnings.length > 0 && { budgetWarnings }),
804
1194
  ...(result.errorMessage !== undefined && {
805
1195
  errorMessage: result.errorMessage,
806
1196
  }),
1197
+ ...(tokenTotals ? { tokenTotals } : {}),
1198
+ ...(budgetTotals ? { budgetTotals } : {}),
807
1199
  });
808
1200
  if (broadcastActivity && broadcastSeq !== undefined) {
809
1201
  try {
@@ -839,6 +1231,9 @@ export async function runChainedRecipe(recipe, options, deps, existingRegistry,
839
1231
  outputTail,
840
1232
  errorMessage: result.errorMessage,
841
1233
  stepResults: stepResultsList,
1234
+ ...(budgetWarnings.length > 0 && { budgetWarnings }),
1235
+ ...(tokenTotals ? { tokenTotals } : {}),
1236
+ ...(budgetTotals ? { budgetTotals } : {}),
842
1237
  });
843
1238
  }
844
1239
  }