patchwork-os 0.2.0-alpha.8 → 0.2.0-beta.0

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 (581) hide show
  1. package/README.bridge.md +6 -0
  2. package/README.md +315 -35
  3. package/deploy/bootstrap-new-vps.sh +12 -12
  4. package/deploy/bootstrap-vps.sh +187 -0
  5. package/deploy/deploy-dashboard.sh +174 -0
  6. package/deploy/deploy-landing.sh +136 -0
  7. package/dist/activationMetrics.d.ts +67 -0
  8. package/dist/activationMetrics.js +255 -0
  9. package/dist/activationMetrics.js.map +1 -0
  10. package/dist/activityLog.d.ts +49 -0
  11. package/dist/activityLog.js +78 -0
  12. package/dist/activityLog.js.map +1 -1
  13. package/dist/approvalHttp.d.ts +49 -2
  14. package/dist/approvalHttp.js +217 -21
  15. package/dist/approvalHttp.js.map +1 -1
  16. package/dist/approvalInsights.d.ts +49 -0
  17. package/dist/approvalInsights.js +97 -0
  18. package/dist/approvalInsights.js.map +1 -0
  19. package/dist/approvalQueue.d.ts +27 -1
  20. package/dist/approvalQueue.js +123 -3
  21. package/dist/approvalQueue.js.map +1 -1
  22. package/dist/approvalSignals.d.ts +124 -0
  23. package/dist/approvalSignals.js +512 -0
  24. package/dist/approvalSignals.js.map +1 -0
  25. package/dist/automation.d.ts +57 -0
  26. package/dist/automation.js +156 -59
  27. package/dist/automation.js.map +1 -1
  28. package/dist/automationSuggestions.d.ts +79 -0
  29. package/dist/automationSuggestions.js +150 -0
  30. package/dist/automationSuggestions.js.map +1 -0
  31. package/dist/bridge.d.ts +3 -0
  32. package/dist/bridge.js +174 -143
  33. package/dist/bridge.js.map +1 -1
  34. package/dist/bridgeToken.js +57 -19
  35. package/dist/bridgeToken.js.map +1 -1
  36. package/dist/ccPermissions.d.ts +15 -0
  37. package/dist/ccPermissions.js +21 -4
  38. package/dist/ccPermissions.js.map +1 -1
  39. package/dist/claudeDriver.js +74 -16
  40. package/dist/claudeDriver.js.map +1 -1
  41. package/dist/claudeOrchestrator.d.ts +1 -1
  42. package/dist/claudeOrchestrator.js +14 -8
  43. package/dist/claudeOrchestrator.js.map +1 -1
  44. package/dist/commands/dashboard.js +1 -1
  45. package/dist/commands/dashboard.js.map +1 -1
  46. package/dist/commands/launchd.d.ts +2 -0
  47. package/dist/commands/launchd.js +94 -0
  48. package/dist/commands/launchd.js.map +1 -0
  49. package/dist/commands/patchworkInit.d.ts +8 -0
  50. package/dist/commands/patchworkInit.js +77 -11
  51. package/dist/commands/patchworkInit.js.map +1 -1
  52. package/dist/commands/recipe.d.ts +289 -0
  53. package/dist/commands/recipe.js +1359 -0
  54. package/dist/commands/recipe.js.map +1 -0
  55. package/dist/commands/recipeInstall.d.ts +150 -0
  56. package/dist/commands/recipeInstall.js +647 -0
  57. package/dist/commands/recipeInstall.js.map +1 -0
  58. package/dist/commands/tracesExport.d.ts +83 -0
  59. package/dist/commands/tracesExport.js +269 -0
  60. package/dist/commands/tracesExport.js.map +1 -0
  61. package/dist/commands/tracesImport.d.ts +56 -0
  62. package/dist/commands/tracesImport.js +161 -0
  63. package/dist/commands/tracesImport.js.map +1 -0
  64. package/dist/config.d.ts +22 -1
  65. package/dist/config.js +108 -9
  66. package/dist/config.js.map +1 -1
  67. package/dist/connectorRoutes.d.ts +43 -0
  68. package/dist/connectorRoutes.js +1609 -0
  69. package/dist/connectorRoutes.js.map +1 -0
  70. package/dist/connectors/asana.d.ts +198 -0
  71. package/dist/connectors/asana.js +679 -0
  72. package/dist/connectors/asana.js.map +1 -0
  73. package/dist/connectors/baseConnector.d.ts +153 -0
  74. package/dist/connectors/baseConnector.js +336 -0
  75. package/dist/connectors/baseConnector.js.map +1 -0
  76. package/dist/connectors/confluence.d.ts +111 -0
  77. package/dist/connectors/confluence.js +406 -0
  78. package/dist/connectors/confluence.js.map +1 -0
  79. package/dist/connectors/datadog.d.ts +116 -0
  80. package/dist/connectors/datadog.js +385 -0
  81. package/dist/connectors/datadog.js.map +1 -0
  82. package/dist/connectors/discord.d.ts +150 -0
  83. package/dist/connectors/discord.js +543 -0
  84. package/dist/connectors/discord.js.map +1 -0
  85. package/dist/connectors/fixtureLibrary.d.ts +21 -0
  86. package/dist/connectors/fixtureLibrary.js +70 -0
  87. package/dist/connectors/fixtureLibrary.js.map +1 -0
  88. package/dist/connectors/fixtureRecorder.d.ts +1 -0
  89. package/dist/connectors/fixtureRecorder.js +35 -0
  90. package/dist/connectors/fixtureRecorder.js.map +1 -0
  91. package/dist/connectors/github.js +17 -18
  92. package/dist/connectors/github.js.map +1 -1
  93. package/dist/connectors/gitlab.d.ts +180 -0
  94. package/dist/connectors/gitlab.js +582 -0
  95. package/dist/connectors/gitlab.js.map +1 -0
  96. package/dist/connectors/gmail.d.ts +4 -1
  97. package/dist/connectors/gmail.js +157 -27
  98. package/dist/connectors/gmail.js.map +1 -1
  99. package/dist/connectors/googleCalendar.d.ts +4 -1
  100. package/dist/connectors/googleCalendar.js +88 -25
  101. package/dist/connectors/googleCalendar.js.map +1 -1
  102. package/dist/connectors/googleDrive.d.ts +34 -0
  103. package/dist/connectors/googleDrive.js +321 -0
  104. package/dist/connectors/googleDrive.js.map +1 -0
  105. package/dist/connectors/htmlEscape.d.ts +5 -0
  106. package/dist/connectors/htmlEscape.js +13 -0
  107. package/dist/connectors/htmlEscape.js.map +1 -0
  108. package/dist/connectors/hubspot.d.ts +112 -0
  109. package/dist/connectors/hubspot.js +408 -0
  110. package/dist/connectors/hubspot.js.map +1 -0
  111. package/dist/connectors/intercom.d.ts +102 -0
  112. package/dist/connectors/intercom.js +402 -0
  113. package/dist/connectors/intercom.js.map +1 -0
  114. package/dist/connectors/jira.d.ts +98 -0
  115. package/dist/connectors/jira.js +379 -0
  116. package/dist/connectors/jira.js.map +1 -0
  117. package/dist/connectors/linear.js +30 -19
  118. package/dist/connectors/linear.js.map +1 -1
  119. package/dist/connectors/mcpOAuth.d.ts +3 -0
  120. package/dist/connectors/mcpOAuth.js +64 -10
  121. package/dist/connectors/mcpOAuth.js.map +1 -1
  122. package/dist/connectors/mockConnector.d.ts +28 -0
  123. package/dist/connectors/mockConnector.js +81 -0
  124. package/dist/connectors/mockConnector.js.map +1 -0
  125. package/dist/connectors/notion.d.ts +143 -0
  126. package/dist/connectors/notion.js +424 -0
  127. package/dist/connectors/notion.js.map +1 -0
  128. package/dist/connectors/oauthStateStore.d.ts +31 -0
  129. package/dist/connectors/oauthStateStore.js +52 -0
  130. package/dist/connectors/oauthStateStore.js.map +1 -0
  131. package/dist/connectors/pagerduty.d.ts +160 -0
  132. package/dist/connectors/pagerduty.js +464 -0
  133. package/dist/connectors/pagerduty.js.map +1 -0
  134. package/dist/connectors/sentry.js +5 -13
  135. package/dist/connectors/sentry.js.map +1 -1
  136. package/dist/connectors/slack.d.ts +16 -1
  137. package/dist/connectors/slack.js +155 -32
  138. package/dist/connectors/slack.js.map +1 -1
  139. package/dist/connectors/stripe.d.ts +116 -0
  140. package/dist/connectors/stripe.js +379 -0
  141. package/dist/connectors/stripe.js.map +1 -0
  142. package/dist/connectors/tokenStorage.d.ts +35 -0
  143. package/dist/connectors/tokenStorage.js +484 -0
  144. package/dist/connectors/tokenStorage.js.map +1 -0
  145. package/dist/connectors/zendesk.d.ts +104 -0
  146. package/dist/connectors/zendesk.js +442 -0
  147. package/dist/connectors/zendesk.js.map +1 -0
  148. package/dist/cors.d.ts +10 -0
  149. package/dist/cors.js +29 -0
  150. package/dist/cors.js.map +1 -0
  151. package/dist/decisionReplay.d.ts +72 -0
  152. package/dist/decisionReplay.js +92 -0
  153. package/dist/decisionReplay.js.map +1 -0
  154. package/dist/decisionTraceLog.d.ts +6 -0
  155. package/dist/decisionTraceLog.js +54 -2
  156. package/dist/decisionTraceLog.js.map +1 -1
  157. package/dist/drivers/gemini/index.d.ts +5 -1
  158. package/dist/drivers/gemini/index.js +39 -5
  159. package/dist/drivers/gemini/index.js.map +1 -1
  160. package/dist/drivers/index.d.ts +5 -0
  161. package/dist/drivers/index.js +1 -1
  162. package/dist/drivers/index.js.map +1 -1
  163. package/dist/featureFlags.d.ts +79 -0
  164. package/dist/featureFlags.js +208 -0
  165. package/dist/featureFlags.js.map +1 -0
  166. package/dist/fp/automationInterpreter.js +26 -21
  167. package/dist/fp/automationInterpreter.js.map +1 -1
  168. package/dist/fp/automationProgram.d.ts +1 -1
  169. package/dist/fp/automationProgram.js.map +1 -1
  170. package/dist/fp/automationState.js +4 -1
  171. package/dist/fp/automationState.js.map +1 -1
  172. package/dist/fp/policyParser.js +21 -1
  173. package/dist/fp/policyParser.js.map +1 -1
  174. package/dist/inboxRoutes.d.ts +22 -0
  175. package/dist/inboxRoutes.js +114 -0
  176. package/dist/inboxRoutes.js.map +1 -0
  177. package/dist/index.js +1400 -201
  178. package/dist/index.js.map +1 -1
  179. package/dist/installGuard.d.ts +25 -0
  180. package/dist/installGuard.js +48 -0
  181. package/dist/installGuard.js.map +1 -0
  182. package/dist/mcpRoutes.d.ts +37 -0
  183. package/dist/mcpRoutes.js +76 -0
  184. package/dist/mcpRoutes.js.map +1 -0
  185. package/dist/oauth.d.ts +7 -1
  186. package/dist/oauth.js +201 -39
  187. package/dist/oauth.js.map +1 -1
  188. package/dist/oauthRoutes.d.ts +32 -0
  189. package/dist/oauthRoutes.js +124 -0
  190. package/dist/oauthRoutes.js.map +1 -0
  191. package/dist/orchestrator/orchestratorBridge.js +2 -2
  192. package/dist/orchestrator/orchestratorBridge.js.map +1 -1
  193. package/dist/patchworkConfig.d.ts +16 -0
  194. package/dist/patchworkConfig.js +1 -1
  195. package/dist/patchworkConfig.js.map +1 -1
  196. package/dist/pluginLoader.d.ts +28 -0
  197. package/dist/pluginLoader.js +77 -11
  198. package/dist/pluginLoader.js.map +1 -1
  199. package/dist/pluginWatcher.js +8 -3
  200. package/dist/pluginWatcher.js.map +1 -1
  201. package/dist/preToolUseHook.d.ts +12 -0
  202. package/dist/preToolUseHook.js +23 -0
  203. package/dist/preToolUseHook.js.map +1 -1
  204. package/dist/recipeOrchestration.d.ts +121 -0
  205. package/dist/recipeOrchestration.js +955 -0
  206. package/dist/recipeOrchestration.js.map +1 -0
  207. package/dist/recipeRoutes.d.ts +180 -0
  208. package/dist/recipeRoutes.js +1345 -0
  209. package/dist/recipeRoutes.js.map +1 -0
  210. package/dist/recipes/RecipeOrchestrator.d.ts +40 -0
  211. package/dist/recipes/RecipeOrchestrator.js +51 -0
  212. package/dist/recipes/RecipeOrchestrator.js.map +1 -0
  213. package/dist/recipes/agentExecutor.d.ts +29 -0
  214. package/dist/recipes/agentExecutor.js +49 -0
  215. package/dist/recipes/agentExecutor.js.map +1 -0
  216. package/dist/recipes/chainedRunner.d.ts +191 -0
  217. package/dist/recipes/chainedRunner.js +759 -0
  218. package/dist/recipes/chainedRunner.js.map +1 -0
  219. package/dist/recipes/compiler.js +3 -3
  220. package/dist/recipes/compiler.js.map +1 -1
  221. package/dist/recipes/dependencyGraph.d.ts +39 -0
  222. package/dist/recipes/dependencyGraph.js +199 -0
  223. package/dist/recipes/dependencyGraph.js.map +1 -0
  224. package/dist/recipes/disabledMarkers.d.ts +48 -0
  225. package/dist/recipes/disabledMarkers.js +52 -0
  226. package/dist/recipes/disabledMarkers.js.map +1 -0
  227. package/dist/recipes/installer.js +3 -3
  228. package/dist/recipes/installer.js.map +1 -1
  229. package/dist/recipes/legacyRecipeCompat.d.ts +10 -0
  230. package/dist/recipes/legacyRecipeCompat.js +131 -0
  231. package/dist/recipes/legacyRecipeCompat.js.map +1 -0
  232. package/dist/recipes/manifest.d.ts +47 -0
  233. package/dist/recipes/manifest.js +156 -0
  234. package/dist/recipes/manifest.js.map +1 -0
  235. package/dist/recipes/migrationWarnings.d.ts +12 -0
  236. package/dist/recipes/migrationWarnings.js +44 -0
  237. package/dist/recipes/migrationWarnings.js.map +1 -0
  238. package/dist/recipes/migrations/index.d.ts +24 -0
  239. package/dist/recipes/migrations/index.js +55 -0
  240. package/dist/recipes/migrations/index.js.map +1 -0
  241. package/dist/recipes/migrations/types.d.ts +28 -0
  242. package/dist/recipes/migrations/types.js +2 -0
  243. package/dist/recipes/migrations/types.js.map +1 -0
  244. package/dist/recipes/migrations/v1.d.ts +11 -0
  245. package/dist/recipes/migrations/v1.js +18 -0
  246. package/dist/recipes/migrations/v1.js.map +1 -0
  247. package/dist/recipes/names.d.ts +40 -0
  248. package/dist/recipes/names.js +66 -0
  249. package/dist/recipes/names.js.map +1 -0
  250. package/dist/recipes/nestedRecipeStep.d.ts +58 -0
  251. package/dist/recipes/nestedRecipeStep.js +95 -0
  252. package/dist/recipes/nestedRecipeStep.js.map +1 -0
  253. package/dist/recipes/outputRegistry.d.ts +28 -0
  254. package/dist/recipes/outputRegistry.js +52 -0
  255. package/dist/recipes/outputRegistry.js.map +1 -0
  256. package/dist/recipes/parser.js +4 -1
  257. package/dist/recipes/parser.js.map +1 -1
  258. package/dist/recipes/replayRun.d.ts +62 -0
  259. package/dist/recipes/replayRun.js +97 -0
  260. package/dist/recipes/replayRun.js.map +1 -0
  261. package/dist/recipes/resolveRecipePath.d.ts +69 -0
  262. package/dist/recipes/resolveRecipePath.js +202 -0
  263. package/dist/recipes/resolveRecipePath.js.map +1 -0
  264. package/dist/recipes/scheduler.d.ts +23 -7
  265. package/dist/recipes/scheduler.js +225 -45
  266. package/dist/recipes/scheduler.js.map +1 -1
  267. package/dist/recipes/schema.d.ts +17 -2
  268. package/dist/recipes/schemaGenerator.d.ts +28 -0
  269. package/dist/recipes/schemaGenerator.js +565 -0
  270. package/dist/recipes/schemaGenerator.js.map +1 -0
  271. package/dist/recipes/stepObservation.d.ts +44 -0
  272. package/dist/recipes/stepObservation.js +232 -0
  273. package/dist/recipes/stepObservation.js.map +1 -0
  274. package/dist/recipes/templateEngine.d.ts +62 -0
  275. package/dist/recipes/templateEngine.js +201 -0
  276. package/dist/recipes/templateEngine.js.map +1 -0
  277. package/dist/recipes/toolRegistry.d.ts +186 -0
  278. package/dist/recipes/toolRegistry.js +309 -0
  279. package/dist/recipes/toolRegistry.js.map +1 -0
  280. package/dist/recipes/tools/asana.d.ts +16 -0
  281. package/dist/recipes/tools/asana.js +524 -0
  282. package/dist/recipes/tools/asana.js.map +1 -0
  283. package/dist/recipes/tools/calendar.d.ts +6 -0
  284. package/dist/recipes/tools/calendar.js +61 -0
  285. package/dist/recipes/tools/calendar.js.map +1 -0
  286. package/dist/recipes/tools/confluence.d.ts +6 -0
  287. package/dist/recipes/tools/confluence.js +254 -0
  288. package/dist/recipes/tools/confluence.js.map +1 -0
  289. package/dist/recipes/tools/datadog.d.ts +6 -0
  290. package/dist/recipes/tools/datadog.js +239 -0
  291. package/dist/recipes/tools/datadog.js.map +1 -0
  292. package/dist/recipes/tools/diagnostics.d.ts +6 -0
  293. package/dist/recipes/tools/diagnostics.js +36 -0
  294. package/dist/recipes/tools/diagnostics.js.map +1 -0
  295. package/dist/recipes/tools/discord.d.ts +18 -0
  296. package/dist/recipes/tools/discord.js +254 -0
  297. package/dist/recipes/tools/discord.js.map +1 -0
  298. package/dist/recipes/tools/file.d.ts +12 -0
  299. package/dist/recipes/tools/file.js +174 -0
  300. package/dist/recipes/tools/file.js.map +1 -0
  301. package/dist/recipes/tools/git.d.ts +6 -0
  302. package/dist/recipes/tools/git.js +63 -0
  303. package/dist/recipes/tools/git.js.map +1 -0
  304. package/dist/recipes/tools/github.d.ts +6 -0
  305. package/dist/recipes/tools/github.js +116 -0
  306. package/dist/recipes/tools/github.js.map +1 -0
  307. package/dist/recipes/tools/gitlab.d.ts +11 -0
  308. package/dist/recipes/tools/gitlab.js +285 -0
  309. package/dist/recipes/tools/gitlab.js.map +1 -0
  310. package/dist/recipes/tools/gmail.d.ts +6 -0
  311. package/dist/recipes/tools/gmail.js +434 -0
  312. package/dist/recipes/tools/gmail.js.map +1 -0
  313. package/dist/recipes/tools/googleDrive.d.ts +1 -0
  314. package/dist/recipes/tools/googleDrive.js +55 -0
  315. package/dist/recipes/tools/googleDrive.js.map +1 -0
  316. package/dist/recipes/tools/hubspot.d.ts +6 -0
  317. package/dist/recipes/tools/hubspot.js +232 -0
  318. package/dist/recipes/tools/hubspot.js.map +1 -0
  319. package/dist/recipes/tools/index.d.ts +30 -0
  320. package/dist/recipes/tools/index.js +33 -0
  321. package/dist/recipes/tools/index.js.map +1 -0
  322. package/dist/recipes/tools/intercom.d.ts +6 -0
  323. package/dist/recipes/tools/intercom.js +226 -0
  324. package/dist/recipes/tools/intercom.js.map +1 -0
  325. package/dist/recipes/tools/jira.d.ts +14 -0
  326. package/dist/recipes/tools/jira.js +369 -0
  327. package/dist/recipes/tools/jira.js.map +1 -0
  328. package/dist/recipes/tools/linear.d.ts +7 -0
  329. package/dist/recipes/tools/linear.js +307 -0
  330. package/dist/recipes/tools/linear.js.map +1 -0
  331. package/dist/recipes/tools/meetingNotes.d.ts +21 -0
  332. package/dist/recipes/tools/meetingNotes.js +701 -0
  333. package/dist/recipes/tools/meetingNotes.js.map +1 -0
  334. package/dist/recipes/tools/notion.d.ts +6 -0
  335. package/dist/recipes/tools/notion.js +278 -0
  336. package/dist/recipes/tools/notion.js.map +1 -0
  337. package/dist/recipes/tools/pagerduty.d.ts +15 -0
  338. package/dist/recipes/tools/pagerduty.js +451 -0
  339. package/dist/recipes/tools/pagerduty.js.map +1 -0
  340. package/dist/recipes/tools/sentry.d.ts +12 -0
  341. package/dist/recipes/tools/sentry.js +73 -0
  342. package/dist/recipes/tools/sentry.js.map +1 -0
  343. package/dist/recipes/tools/slack.d.ts +6 -0
  344. package/dist/recipes/tools/slack.js +82 -0
  345. package/dist/recipes/tools/slack.js.map +1 -0
  346. package/dist/recipes/tools/stripe.d.ts +6 -0
  347. package/dist/recipes/tools/stripe.js +265 -0
  348. package/dist/recipes/tools/stripe.js.map +1 -0
  349. package/dist/recipes/tools/zendesk.d.ts +6 -0
  350. package/dist/recipes/tools/zendesk.js +245 -0
  351. package/dist/recipes/tools/zendesk.js.map +1 -0
  352. package/dist/recipes/validation.d.ts +13 -0
  353. package/dist/recipes/validation.js +617 -0
  354. package/dist/recipes/validation.js.map +1 -0
  355. package/dist/recipes/yamlRunner.d.ts +116 -1
  356. package/dist/recipes/yamlRunner.js +1000 -401
  357. package/dist/recipes/yamlRunner.js.map +1 -1
  358. package/dist/recipesHttp.d.ts +137 -6
  359. package/dist/recipesHttp.js +941 -29
  360. package/dist/recipesHttp.js.map +1 -1
  361. package/dist/riskTier.js +7 -1
  362. package/dist/riskTier.js.map +1 -1
  363. package/dist/runLog.d.ts +100 -1
  364. package/dist/runLog.js +258 -5
  365. package/dist/runLog.js.map +1 -1
  366. package/dist/schemas/dry-run-plan.v1.json +139 -0
  367. package/dist/schemas/recipe.v1.json +684 -0
  368. package/dist/server.d.ts +121 -8
  369. package/dist/server.js +538 -735
  370. package/dist/server.js.map +1 -1
  371. package/dist/ssrfGuard.d.ts +54 -0
  372. package/dist/ssrfGuard.js +122 -0
  373. package/dist/ssrfGuard.js.map +1 -0
  374. package/dist/streamableHttp.d.ts +39 -1
  375. package/dist/streamableHttp.js +128 -17
  376. package/dist/streamableHttp.js.map +1 -1
  377. package/dist/tokenUsageTracker.d.ts +33 -0
  378. package/dist/tokenUsageTracker.js +146 -0
  379. package/dist/tokenUsageTracker.js.map +1 -0
  380. package/dist/tools/activityLog.d.ts +2 -0
  381. package/dist/tools/addLinearComment.d.ts +1 -0
  382. package/dist/tools/addLinearComment.js +4 -2
  383. package/dist/tools/addLinearComment.js.map +1 -1
  384. package/dist/tools/batchLsp.d.ts +3 -0
  385. package/dist/tools/bridgeDoctor.d.ts +1 -0
  386. package/dist/tools/bridgeDoctor.js +2 -2
  387. package/dist/tools/bridgeDoctor.js.map +1 -1
  388. package/dist/tools/bridgeStatus.d.ts +1 -0
  389. package/dist/tools/cancelClaudeTask.d.ts +2 -0
  390. package/dist/tools/cancelClaudeTask.js +1 -0
  391. package/dist/tools/cancelClaudeTask.js.map +1 -1
  392. package/dist/tools/checkDocumentDirty.d.ts +1 -0
  393. package/dist/tools/clipboard.d.ts +2 -0
  394. package/dist/tools/closeTabs.d.ts +2 -0
  395. package/dist/tools/codeLens.d.ts +1 -0
  396. package/dist/tools/contextBundle.d.ts +1 -0
  397. package/dist/tools/createIssueFromAIComment.d.ts +1 -0
  398. package/dist/tools/createLinearIssue.d.ts +1 -0
  399. package/dist/tools/ctxGetTaskContext.d.ts +1 -0
  400. package/dist/tools/ctxQueryTraces.d.ts +1 -0
  401. package/dist/tools/ctxSaveTrace.d.ts +1 -0
  402. package/dist/tools/debug.d.ts +4 -0
  403. package/dist/tools/decorations.d.ts +2 -0
  404. package/dist/tools/documentLinks.d.ts +1 -0
  405. package/dist/tools/editText.d.ts +1 -0
  406. package/dist/tools/enrichCommit.d.ts +1 -0
  407. package/dist/tools/enrichStackTrace.d.ts +1 -0
  408. package/dist/tools/explainDiagnostic.d.ts +1 -0
  409. package/dist/tools/explainSymbol.d.ts +1 -0
  410. package/dist/tools/fetchCalendarEvents.d.ts +1 -0
  411. package/dist/tools/fetchGithubIssue.d.ts +1 -0
  412. package/dist/tools/fetchGithubPR.d.ts +1 -0
  413. package/dist/tools/fetchLinearIssue.d.ts +1 -0
  414. package/dist/tools/fetchSentryIssue.d.ts +1 -0
  415. package/dist/tools/fetchSlackProfile.d.ts +1 -0
  416. package/dist/tools/fetchSlackProfile.js +4 -1
  417. package/dist/tools/fetchSlackProfile.js.map +1 -1
  418. package/dist/tools/fileOperations.d.ts +3 -0
  419. package/dist/tools/fileWatcher.d.ts +2 -0
  420. package/dist/tools/findFiles.d.ts +1 -0
  421. package/dist/tools/findRelatedTests.d.ts +1 -0
  422. package/dist/tools/fixAllLintErrors.d.ts +1 -0
  423. package/dist/tools/foldingRanges.d.ts +1 -0
  424. package/dist/tools/formatDocument.d.ts +1 -0
  425. package/dist/tools/generateTests.d.ts +1 -0
  426. package/dist/tools/getAIComments.d.ts +1 -0
  427. package/dist/tools/getAnalyticsReport.d.ts +1 -0
  428. package/dist/tools/getArchitectureContext.d.ts +1 -0
  429. package/dist/tools/getBufferContent.d.ts +1 -0
  430. package/dist/tools/getChangeImpact.d.ts +1 -0
  431. package/dist/tools/getClaudeTaskStatus.d.ts +2 -0
  432. package/dist/tools/getClaudeTaskStatus.js +1 -0
  433. package/dist/tools/getClaudeTaskStatus.js.map +1 -1
  434. package/dist/tools/getCodeCoverage.d.ts +1 -0
  435. package/dist/tools/getCommitsForIssue.d.ts +1 -0
  436. package/dist/tools/getConnectorStatus.d.ts +1 -0
  437. package/dist/tools/getCurrentSelection.d.ts +2 -0
  438. package/dist/tools/getDebugState.d.ts +1 -0
  439. package/dist/tools/getDependencyTree.d.ts +1 -0
  440. package/dist/tools/getDiagnostics.d.ts +1 -0
  441. package/dist/tools/getDiffFromHandoff.d.ts +1 -0
  442. package/dist/tools/getDocumentSymbols.d.ts +25 -0
  443. package/dist/tools/getDocumentSymbols.js +74 -8
  444. package/dist/tools/getDocumentSymbols.js.map +1 -1
  445. package/dist/tools/getFileTree.d.ts +1 -0
  446. package/dist/tools/getGitDiff.d.ts +1 -0
  447. package/dist/tools/getGitHotspots.d.ts +1 -0
  448. package/dist/tools/getGitLog.d.ts +1 -0
  449. package/dist/tools/getGitStatus.d.ts +1 -0
  450. package/dist/tools/getImportTree.d.ts +1 -0
  451. package/dist/tools/getImportedSignatures.d.ts +1 -0
  452. package/dist/tools/getOpenEditors.d.ts +1 -0
  453. package/dist/tools/getPRTemplate.d.ts +1 -0
  454. package/dist/tools/getProjectContext.d.ts +1 -0
  455. package/dist/tools/getProjectInfo.d.ts +1 -0
  456. package/dist/tools/getSecurityAdvisories.d.ts +1 -0
  457. package/dist/tools/getSecurityAdvisories.js +10 -1
  458. package/dist/tools/getSecurityAdvisories.js.map +1 -1
  459. package/dist/tools/getSessionUsage.d.ts +4 -0
  460. package/dist/tools/getSessionUsage.js +3 -0
  461. package/dist/tools/getSessionUsage.js.map +1 -1
  462. package/dist/tools/getSymbolHistory.d.ts +1 -0
  463. package/dist/tools/getToolCapabilities.d.ts +1 -0
  464. package/dist/tools/getTypeSignature.d.ts +1 -0
  465. package/dist/tools/getWorkspaceFolders.d.ts +1 -0
  466. package/dist/tools/getWorkspaceSettings.d.ts +1 -0
  467. package/dist/tools/gitHistory.d.ts +2 -0
  468. package/dist/tools/gitWrite.d.ts +11 -0
  469. package/dist/tools/github/actions.d.ts +2 -0
  470. package/dist/tools/github/actions.js +4 -2
  471. package/dist/tools/github/actions.js.map +1 -1
  472. package/dist/tools/github/composite.d.ts +342 -0
  473. package/dist/tools/github/composite.js +343 -0
  474. package/dist/tools/github/composite.js.map +1 -0
  475. package/dist/tools/github/index.d.ts +1 -0
  476. package/dist/tools/github/index.js +1 -0
  477. package/dist/tools/github/index.js.map +1 -1
  478. package/dist/tools/github/issues.d.ts +4 -0
  479. package/dist/tools/github/issues.js +8 -4
  480. package/dist/tools/github/issues.js.map +1 -1
  481. package/dist/tools/github/pr.d.ts +7 -0
  482. package/dist/tools/github/pr.js +50 -12
  483. package/dist/tools/github/pr.js.map +1 -1
  484. package/dist/tools/handoffNote.d.ts +4 -0
  485. package/dist/tools/handoffNote.js +2 -0
  486. package/dist/tools/handoffNote.js.map +1 -1
  487. package/dist/tools/hoverAtCursor.d.ts +1 -0
  488. package/dist/tools/httpClient.d.ts +2 -0
  489. package/dist/tools/index.d.ts +8 -0
  490. package/dist/tools/index.js +47 -8
  491. package/dist/tools/index.js.map +1 -1
  492. package/dist/tools/inlayHints.d.ts +1 -0
  493. package/dist/tools/launchQuickTask.d.ts +2 -0
  494. package/dist/tools/launchQuickTask.js +1 -0
  495. package/dist/tools/launchQuickTask.js.map +1 -1
  496. package/dist/tools/listClaudeTasks.d.ts +2 -0
  497. package/dist/tools/listClaudeTasks.js +1 -0
  498. package/dist/tools/listClaudeTasks.js.map +1 -1
  499. package/dist/tools/listTerminals.d.ts +1 -0
  500. package/dist/tools/lsp.d.ts +14 -0
  501. package/dist/tools/navigateToSymbolByName.d.ts +1 -0
  502. package/dist/tools/openDiff.d.ts +1 -0
  503. package/dist/tools/openFile.d.ts +1 -0
  504. package/dist/tools/openInBrowser.d.ts +1 -0
  505. package/dist/tools/organizeImports.d.ts +1 -0
  506. package/dist/tools/performanceReport.d.ts +1 -0
  507. package/dist/tools/planPersistence.d.ts +5 -0
  508. package/dist/tools/previewEdit.d.ts +1 -0
  509. package/dist/tools/refactorAnalyze.d.ts +1 -0
  510. package/dist/tools/refactorPreview.d.ts +2 -0
  511. package/dist/tools/refactorPreview.js +1 -0
  512. package/dist/tools/refactorPreview.js.map +1 -1
  513. package/dist/tools/replaceBlock.d.ts +1 -0
  514. package/dist/tools/resumeClaudeTask.d.ts +2 -0
  515. package/dist/tools/resumeClaudeTask.js +1 -0
  516. package/dist/tools/resumeClaudeTask.js.map +1 -1
  517. package/dist/tools/runClaudeTask.d.ts +2 -0
  518. package/dist/tools/runClaudeTask.js +1 -0
  519. package/dist/tools/runClaudeTask.js.map +1 -1
  520. package/dist/tools/runCommand.d.ts +1 -0
  521. package/dist/tools/runTests.d.ts +1 -0
  522. package/dist/tools/saveDocument.d.ts +1 -0
  523. package/dist/tools/screenshotAndAnnotate.d.ts +1 -0
  524. package/dist/tools/searchAndReplace.d.ts +1 -0
  525. package/dist/tools/searchTools.d.ts +1 -0
  526. package/dist/tools/searchTools.js +1 -1
  527. package/dist/tools/searchTools.js.map +1 -1
  528. package/dist/tools/searchWorkspace.d.ts +1 -0
  529. package/dist/tools/selectionRanges.d.ts +1 -0
  530. package/dist/tools/semanticTokens.d.ts +1 -0
  531. package/dist/tools/setActiveWorkspaceFolder.d.ts +1 -0
  532. package/dist/tools/signatureHelp.d.ts +1 -0
  533. package/dist/tools/slackListChannels.d.ts +1 -0
  534. package/dist/tools/slackListChannels.js.map +1 -1
  535. package/dist/tools/slackPostMessage.d.ts +1 -0
  536. package/dist/tools/slackPostMessage.js +11 -6
  537. package/dist/tools/slackPostMessage.js.map +1 -1
  538. package/dist/tools/terminal.d.ts +6 -0
  539. package/dist/tools/testTraceToSource.d.ts +1 -0
  540. package/dist/tools/testTraceToSource.js +2 -2
  541. package/dist/tools/testTraceToSource.js.map +1 -1
  542. package/dist/tools/transaction.d.ts +23 -0
  543. package/dist/tools/transaction.js +29 -0
  544. package/dist/tools/transaction.js.map +1 -1
  545. package/dist/tools/typeHierarchy.d.ts +1 -0
  546. package/dist/tools/updateLinearIssue.d.ts +1 -0
  547. package/dist/tools/updateLinearIssue.js +20 -6
  548. package/dist/tools/updateLinearIssue.js.map +1 -1
  549. package/dist/tools/utils.d.ts +2 -0
  550. package/dist/tools/utils.js.map +1 -1
  551. package/dist/tools/vscodeCommands.d.ts +2 -0
  552. package/dist/tools/vscodeTasks.d.ts +2 -0
  553. package/dist/tools/workspaceSettings.d.ts +1 -0
  554. package/dist/traceEncryption.d.ts +46 -0
  555. package/dist/traceEncryption.js +124 -0
  556. package/dist/traceEncryption.js.map +1 -0
  557. package/dist/transport.d.ts +46 -1
  558. package/dist/transport.js +173 -19
  559. package/dist/transport.js.map +1 -1
  560. package/package.json +30 -8
  561. package/scripts/mcp-stdio-shim.cjs +19 -3
  562. package/scripts/start-all.sh +30 -1
  563. package/templates/automation-policies/recipe-authoring.json +25 -0
  564. package/templates/automation-policy.example.json +6 -0
  565. package/templates/co.patchwork-os.bridge.plist +34 -0
  566. package/templates/policies/README.md +72 -0
  567. package/templates/policies/conservative.json +14 -0
  568. package/templates/policies/developer.json +14 -0
  569. package/templates/policies/headless-ci.json +24 -0
  570. package/templates/policies/personal-assistant.json +15 -0
  571. package/templates/policies/regulated-industry.json +18 -0
  572. package/templates/recipes/lint-on-save.yaml +1 -2
  573. package/templates/recipes/morning-brief-slack.yaml +57 -0
  574. package/templates/recipes/morning-brief.yaml +2 -2
  575. package/templates/recipes/project-health-check.yaml +50 -0
  576. package/templates/recipes/webhook/README.md +70 -0
  577. package/templates/recipes/webhook/capture-thought.yaml +26 -0
  578. package/templates/recipes/webhook/customer-escalation.yaml +49 -0
  579. package/templates/recipes/webhook/incident-intake.yaml +46 -0
  580. package/templates/recipes/webhook/meeting-prep.yaml +48 -0
  581. package/templates/recipes/webhook/morning-brief.yaml +57 -0
@@ -0,0 +1,150 @@
1
+ /**
2
+ * automationSuggestions — pattern-mine the activity log + run history to
3
+ * surface "you've been doing X by hand; want to make a recipe?" hints.
4
+ *
5
+ * From docs/strategic/2026-05-02/memory-ecosystem-report.md §6. Three of
6
+ * the four query-catalog patterns ship here; the fourth ("repeated
7
+ * manual workflow" via PrefixSpan-lite sequence mining) is deferred to
8
+ * a follow-up — the agent flagged it as the only entry in the catalog
9
+ * that requires a new ~50-line primitive (`mineSequences`), and shipping
10
+ * it without dashboard rendering would be premature.
11
+ *
12
+ * 1. **Co-occurring pairs** — tools that often run within W minutes of
13
+ * each other but don't already appear together in any successful
14
+ * recipe → "create a recipe?" suggestion.
15
+ * 2. **Installed but unused** — tools registered with the bridge but
16
+ * never called → "review your installed tool list" suggestion.
17
+ * 3. **Recipe trust graduation** — recipes with ≥ 10 runs all status
18
+ * "done" → "consider auto-approving" suggestion (does NOT auto-
19
+ * change policy; this is purely a hint).
20
+ *
21
+ * Pure function over (ActivityLog, RecipeRunLog, ToolRegistry). No I/O
22
+ * of its own; tested in isolation by feeding mock instances. The CLI
23
+ * `patchwork suggest` is a thin printer over this output.
24
+ */
25
+ import { computeCoOccurrence } from "./fp/activityAnalytics.js";
26
+ import { listTools } from "./recipes/toolRegistry.js";
27
+ const DEFAULT_CO_OCCURRENCE_WINDOW_MS = 5 * 60 * 1000;
28
+ const DEFAULT_CO_OCCURRENCE_MIN_COUNT = 5;
29
+ const DEFAULT_ACTIVITY_SINCE_MS = 7 * 24 * 60 * 60 * 1000;
30
+ const DEFAULT_TRUST_GRADUATION_MIN_RUNS = 10;
31
+ /**
32
+ * Compute the full suggestion set. Returns at most 30 suggestions
33
+ * (10 of each kind) sorted by salience within each bucket.
34
+ */
35
+ export function computeAutomationSuggestions(deps) {
36
+ const window = deps.coOccurrenceWindowMs ?? DEFAULT_CO_OCCURRENCE_WINDOW_MS;
37
+ const minCount = deps.coOccurrenceMinCount ?? DEFAULT_CO_OCCURRENCE_MIN_COUNT;
38
+ const sinceLookback = deps.activitySinceMs ?? DEFAULT_ACTIVITY_SINCE_MS;
39
+ const minRuns = deps.trustGraduationMinRuns ?? DEFAULT_TRUST_GRADUATION_MIN_RUNS;
40
+ const sinceMs = Date.now() - sinceLookback;
41
+ const recent = deps.activityLog.queryAll({ sinceMs });
42
+ const suggestions = [];
43
+ // (1) Co-occurring tool pairs that aren't already in a recipe.
44
+ // The agent's filter `!pairAlreadyInRecipe(p, runs)` requires us to
45
+ // know which (tool, tool) pairs ever appeared together inside a single
46
+ // recipe run. We synthesize that set from RecipeRun.stepResults.
47
+ const pairsInRecipes = deps.recipeRunLog
48
+ ? buildRecipePairSet(deps.recipeRunLog)
49
+ : new Set();
50
+ const coOccurringPairs = computeCoOccurrence(recent, window, 50)
51
+ .filter((p) => p.count >= minCount)
52
+ .filter((p) => !pairsInRecipes.has(p.pair));
53
+ for (const { pair, count } of coOccurringPairs.slice(0, 10)) {
54
+ const [a, b] = pair.split("|");
55
+ suggestions.push({
56
+ kind: "co_occurring_pair",
57
+ label: `You called ${a} and ${b} together ${count} times in the last 7 days. Create a recipe?`,
58
+ details: { pair: [a, b], count },
59
+ });
60
+ }
61
+ // (2) Installed but never used (in the lookback window).
62
+ const listToolNames = deps.listToolNamesFn ?? defaultListToolNames;
63
+ const installed = listToolNames();
64
+ if (installed.length > 0) {
65
+ const usedSet = new Set();
66
+ for (const e of recent) {
67
+ if (e.tool)
68
+ usedSet.add(e.tool);
69
+ }
70
+ const unused = installed.filter((t) => !usedSet.has(t));
71
+ if (unused.length > 0) {
72
+ // Single rolled-up suggestion with the count + a few examples;
73
+ // listing every unused tool would flood the output for fresh
74
+ // installs where ~150 of 170 tools have never been called.
75
+ const examples = unused.slice(0, 5);
76
+ const more = unused.length > 5 ? `, … (+${unused.length - 5} more)` : "";
77
+ suggestions.push({
78
+ kind: "installed_but_unused",
79
+ label: `${unused.length} installed tools haven't been used in the last 7 days. Examples: ${examples.join(", ")}${more}.`,
80
+ details: { unusedTools: unused.slice(0, 50) },
81
+ });
82
+ }
83
+ }
84
+ // (3) Recipe trust graduation — recipes with ≥ minRuns successful runs.
85
+ if (deps.recipeRunLog) {
86
+ const byRecipe = groupRunsByRecipe(deps.recipeRunLog);
87
+ const graduates = [...byRecipe.entries()]
88
+ .filter(([, runs]) => runs.length >= minRuns)
89
+ .filter(([, runs]) => runs.every((r) => r.status === "done"))
90
+ .sort((a, b) => b[1].length - a[1].length)
91
+ .slice(0, 10);
92
+ for (const [recipeName, runs] of graduates) {
93
+ suggestions.push({
94
+ kind: "recipe_trust_graduation",
95
+ label: `Recipe \`${recipeName}\` has succeeded ${runs.length}/${runs.length} times — consider trust graduation.`,
96
+ details: { recipeName, runCount: runs.length },
97
+ });
98
+ }
99
+ }
100
+ return suggestions;
101
+ }
102
+ function defaultListToolNames() {
103
+ return listTools().map((t) => t.id);
104
+ }
105
+ /**
106
+ * Build the set of (toolA, toolB) pairs that ever appeared together
107
+ * inside a single recipe run. Pairs are alphabetized to match
108
+ * `computeCoOccurrence`'s key shape ("a|b" with a < b).
109
+ *
110
+ * We pull from the most recent 500 runs (the default in-memory cap) —
111
+ * older runs are evicted from RAM and we don't pay for the disk scan
112
+ * here; if a tool pair only ever appeared in a 6-month-old run, the
113
+ * suggestion will fire as if the pair is new, which is a fine UX
114
+ * (the user has time to re-promote it).
115
+ */
116
+ function buildRecipePairSet(runLog) {
117
+ const pairs = new Set();
118
+ const runs = runLog.query({ limit: 500 });
119
+ for (const run of runs) {
120
+ if (!run.stepResults || run.stepResults.length < 2)
121
+ continue;
122
+ const tools = run.stepResults
123
+ .map((s) => s.tool)
124
+ .filter((t) => typeof t === "string" && t.length > 0);
125
+ for (let i = 0; i < tools.length; i++) {
126
+ for (let j = i + 1; j < tools.length; j++) {
127
+ const a = tools[i];
128
+ const b = tools[j];
129
+ if (!a || !b || a === b)
130
+ continue;
131
+ const key = a < b ? `${a}|${b}` : `${b}|${a}`;
132
+ pairs.add(key);
133
+ }
134
+ }
135
+ }
136
+ return pairs;
137
+ }
138
+ function groupRunsByRecipe(runLog) {
139
+ const grouped = new Map();
140
+ // We pull more than the default 100 since we're computing aggregate
141
+ // stats — 500 is the in-memory cap and matches the typical buffer.
142
+ const runs = runLog.query({ limit: 500 });
143
+ for (const run of runs) {
144
+ const list = grouped.get(run.recipeName) ?? [];
145
+ list.push(run);
146
+ grouped.set(run.recipeName, list);
147
+ }
148
+ return grouped;
149
+ }
150
+ //# sourceMappingURL=automationSuggestions.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"automationSuggestions.js","sourceRoot":"","sources":["../src/automationSuggestions.ts"],"names":[],"mappings":"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"}
package/dist/bridge.d.ts CHANGED
@@ -39,8 +39,10 @@ export declare class Bridge {
39
39
  private pluginTools;
40
40
  private pluginWatcher;
41
41
  private automationHooks;
42
+ private recipeOrchestration;
42
43
  private recipeScheduler;
43
44
  private recipeRunLog;
45
+ private recipeOrchestrator;
44
46
  private commitIssueLinkLog;
45
47
  private decisionTraceLog;
46
48
  /** Pre-computed digest of recent decisions, refreshed on each session connect. */
@@ -56,6 +58,7 @@ export declare class Bridge {
56
58
  /** ISO timestamp of last getProjectContext cache write — drives status-bar "context X min ago". */
57
59
  private _lastContextCachedAt;
58
60
  private wsHeartbeatInterval;
61
+ private tokenUsageTracker;
59
62
  constructor(config: Config);
60
63
  /** Push live bridge state to the extension for status-bar display. */
61
64
  private _emitLiveState;
package/dist/bridge.js CHANGED
@@ -11,27 +11,32 @@ import { getApprovalQueue } from "./approvalQueue.js";
11
11
  import { AutomationHooks, loadPolicy } from "./automation.js";
12
12
  import { loadOrCreateBridgeToken } from "./bridgeToken.js";
13
13
  import { repairBridgeToolsRulesIfStale } from "./bridgeToolsRules.js";
14
- import { createDriver } from "./claudeDriver.js";
15
14
  import { ClaudeOrchestrator } from "./claudeOrchestrator.js";
16
15
  import { CommitIssueLinkLog } from "./commitIssueLinkLog.js";
17
16
  import { DecisionTraceLog } from "./decisionTraceLog.js";
17
+ import { createDriver } from "./drivers/index.js";
18
18
  import { ExtensionClient } from "./extensionClient.js";
19
+ import { lockKillSwitchEnv } from "./featureFlags.js";
19
20
  import { FileLock } from "./fileLock.js";
20
21
  import { buildEnforcementReminder } from "./instructionsUtils.js";
21
22
  import { LockFileManager } from "./lockfile.js";
22
23
  import { Logger } from "./logger.js";
23
24
  import { OAuthServerImpl } from "./oauth.js";
25
+ import { loadConfig as loadPatchworkConfig } from "./patchworkConfig.js";
24
26
  import { loadPlugins, loadPluginsFull } from "./pluginLoader.js";
25
27
  import { PluginWatcher } from "./pluginWatcher.js";
28
+ import { isPreToolUseHookRegistered } from "./preToolUseHook.js";
26
29
  import { probeAll } from "./probe.js";
27
- import { RecipeScheduler } from "./recipes/scheduler.js";
28
- import { findWebhookRecipe, listInstalledRecipes, loadRecipePrompt, renderWebhookPrompt, saveRecipe, } from "./recipesHttp.js";
30
+ import { RecipeOrchestration } from "./recipeOrchestration.js";
31
+ import { warnAboutLegacyPermissionsSidecars } from "./recipes/migrationWarnings.js";
32
+ import { RecipeOrchestrator } from "./recipes/RecipeOrchestrator.js";
29
33
  import { classifyTool } from "./riskTier.js";
30
34
  import { RecipeRunLog } from "./runLog.js";
31
35
  import { Server } from "./server.js";
32
36
  import { SessionCheckpoint } from "./sessionCheckpoint.js";
33
37
  import { StreamableHttpHandler } from "./streamableHttp.js";
34
38
  import { initTelemetry, shutdownTelemetry } from "./telemetry.js";
39
+ import { TokenUsageTracker } from "./tokenUsageTracker.js";
35
40
  import { createCtxQueryTracesTool } from "./tools/ctxQueryTraces.js";
36
41
  import { readNote, writeNote } from "./tools/handoffNote.js";
37
42
  import { registerAllTools } from "./tools/index.js";
@@ -112,8 +117,10 @@ export class Bridge {
112
117
  pluginTools = [];
113
118
  pluginWatcher = null;
114
119
  automationHooks = undefined;
120
+ recipeOrchestration = null;
115
121
  recipeScheduler = null;
116
122
  recipeRunLog = null;
123
+ recipeOrchestrator = null;
117
124
  commitIssueLinkLog = null;
118
125
  decisionTraceLog = null;
119
126
  /** Pre-computed digest of recent decisions, refreshed on each session connect. */
@@ -129,6 +136,7 @@ export class Bridge {
129
136
  /** ISO timestamp of last getProjectContext cache write — drives status-bar "context X min ago". */
130
137
  _lastContextCachedAt = null;
131
138
  wsHeartbeatInterval = null;
139
+ tokenUsageTracker;
132
140
  constructor(config) {
133
141
  this.config = config;
134
142
  this.logger = new Logger(config.verbose, config.jsonl);
@@ -136,6 +144,7 @@ export class Bridge {
136
144
  const configDir = process.env.CLAUDE_CONFIG_DIR ?? path.join(os.homedir(), ".claude");
137
145
  this.authToken = config.fixedToken ?? loadOrCreateBridgeToken(configDir);
138
146
  this.server = new Server(this.authToken, this.logger, config.corsOrigins);
147
+ this.server.bridgeConfigPath = config.configFilePath ?? undefined;
139
148
  if (config.issuerUrl) {
140
149
  this.oauthServer = new OAuthServerImpl(this.authToken, config.issuerUrl, {
141
150
  configDir,
@@ -150,6 +159,10 @@ export class Bridge {
150
159
  this.logger.info(`Audit log: ${config.auditLogPath}`);
151
160
  }
152
161
  this.extensionClient = new ExtensionClient(this.logger);
162
+ this.tokenUsageTracker = new TokenUsageTracker({
163
+ workspace: config.workspace,
164
+ logger: { warn: (m) => this.logger.warn(m) },
165
+ });
153
166
  // Handle new Claude Code connections
154
167
  this.server.on("connection", async (ws) => {
155
168
  // Reject connections before probes are ready
@@ -181,6 +194,15 @@ export class Bridge {
181
194
  existing.graceTimer = null;
182
195
  existing.ws = ws;
183
196
  existing.wsAlive = true;
197
+ // Clean up old WS listener + reject stale pending elicitations
198
+ // before attaching the new socket. Without this, the previous
199
+ // listener accumulates and pending elicitations from the old
200
+ // client can resolve against the new connection's `activeWs`,
201
+ // surfacing prompts to a client that didn't request them.
202
+ // Intentionally NOT calling `detach()` — that would abort
203
+ // in-flight tool calls, but grace-period semantics preserve
204
+ // those across the reconnect.
205
+ existing.transport.detachSoft();
184
206
  existing.transport.attach(ws);
185
207
  ws.on("pong", () => {
186
208
  existing.wsAlive = true;
@@ -239,8 +261,26 @@ export class Bridge {
239
261
  transport.sessionId = sessionId;
240
262
  transport.setActivityLog(this.activityLog);
241
263
  transport.setToolRateLimit(this.config.toolRateLimit);
264
+ if (this.config.lazyTools)
265
+ transport.setLazyTools(true);
242
266
  if (this.server.approvalGate !== "off") {
243
267
  this.logger.info(`[patchwork] approval gate active: ${this.server.approvalGate} tier(s) require dashboard approval`);
268
+ // The approval gate only sees traffic if Claude Code's PreToolUse
269
+ // hook is registered to POST into /approvals. If it isn't, every
270
+ // CC tool call bypasses the bridge entirely — the queue stays
271
+ // empty, no `approval_decision` rows accumulate, and the entire
272
+ // personalSignals catalog has no input data. Silent foot-gun
273
+ // that wasted hours of investigation; surface it loudly.
274
+ try {
275
+ const ccSettingsPath = path.join(process.env.CLAUDE_CONFIG_DIR ?? path.join(os.homedir(), ".claude"), "settings.json");
276
+ if (!isPreToolUseHookRegistered(ccSettingsPath)) {
277
+ this.logger.warn?.(`[patchwork] WARNING: approval gate is on but no PreToolUse hook found in ${ccSettingsPath}. ` +
278
+ `Claude Code will bypass the approval queue. Run 'patchwork-init' to register the hook.`);
279
+ }
280
+ }
281
+ catch {
282
+ // Best-effort warning — never block startup on a hook check failure.
283
+ }
244
284
  transport.setApprovalGate(async ({ toolName, params, sessionId }) => {
245
285
  const tier = classifyTool(toolName);
246
286
  if (this.server.approvalGate === "off")
@@ -677,7 +717,11 @@ export class Bridge {
677
717
  async start() {
678
718
  // 0. Initialize OpenTelemetry (no-op when OTEL_EXPORTER_OTLP_ENDPOINT is unset)
679
719
  initTelemetry();
680
- // 0a. Auto-repair .claude/rules/bridge-tools.md if stale (present but missing the
720
+ // 0a. Snapshot env-derived kill-switch flags. After this, plugins or
721
+ // recipe steps mutating `process.env.PATCHWORK_FLAG_*` cannot disable an
722
+ // active emergency stop. Closes MED-2 from the 2026-04-28 audit.
723
+ lockKillSwitchEnv();
724
+ // 0b. Auto-repair .claude/rules/bridge-tools.md if stale (present but missing the
681
725
  // current version sentinel). Older package versions write stale files that may
682
726
  // lack new tool substitution rules. Repair is silent on success; falls back to
683
727
  // warn-only if the template is missing or the write fails.
@@ -685,6 +729,7 @@ export class Bridge {
685
729
  // 1. Probe available CLI tools (pass workspace so local node_modules/.bin is checked)
686
730
  this.probes = await probeAll(this.config.workspace);
687
731
  this.ready = true;
732
+ this.tokenUsageTracker.start();
688
733
  // 2. Load plugins (after probes, before accepting sessions)
689
734
  this.pluginTools = await loadPlugins(this.config.plugins, this.config, this.logger);
690
735
  if (this.config.pluginWatch && this.config.plugins.length > 0) {
@@ -703,8 +748,19 @@ export class Bridge {
703
748
  this.logger.info(`Available linters: ${probeList(["tsc", "eslint", "pyright", "ruff", "cargo", "go", "biome"])}`);
704
749
  this.logger.info(`Available test runners: ${probeList(["vitest", "jest", "pytest", "cargo", "go"])}`);
705
750
  // 2. Initialize Claude driver and orchestrator (if configured)
706
- if (this.config.claudeDriver !== "none") {
707
- const driver = createDriver(this.config.claudeDriver, this.config.claudeBinary, this.config.antBinary, (msg) => this.logger.info(msg));
751
+ if (this.config.driver !== "none") {
752
+ const driver = createDriver(this.config.driver, {
753
+ binary: this.config.claudeBinary,
754
+ antBinary: this.config.antBinary,
755
+ bridgeMcp: this.config.driver === "gemini"
756
+ ? () => this.port > 0
757
+ ? {
758
+ url: `http://127.0.0.1:${this.port}/mcp`,
759
+ authToken: this.authToken,
760
+ }
761
+ : undefined
762
+ : undefined,
763
+ }, (msg) => this.logger.info(msg));
708
764
  // Patchwork: enrichment link log is useful regardless of orchestrator.
709
765
  const patchworkDir = path.join(os.homedir(), ".patchwork");
710
766
  this.commitIssueLinkLog = new CommitIssueLinkLog({
@@ -764,17 +820,33 @@ export class Bridge {
764
820
  },
765
821
  });
766
822
  this.logger.info(`[bridge] Claude driver: ${driver.name}`);
767
- // Patchwork: start cron-trigger scheduler once the orchestrator exists.
823
+ // Recipe orchestrator — owns in-flight dedup across all entry paths.
824
+ this.recipeOrchestrator = new RecipeOrchestrator({
825
+ workdir: this.config.workspace,
826
+ });
827
+ // Patchwork: wire recipe server fns + build cron scheduler.
768
828
  const recipesDir = path.join(os.homedir(), ".patchwork", "recipes");
769
- this.recipeScheduler = new RecipeScheduler({
829
+ // One-shot scan for legacy `<name>.permissions.json` sidecars (alpha.36+
830
+ // no longer generates them). Skipped under NODE_ENV=test.
831
+ warnAboutLegacyPermissionsSidecars(recipesDir);
832
+ this.recipeOrchestration = new RecipeOrchestration({
833
+ server: this.server,
834
+ getOrchestrator: () => this.orchestrator,
835
+ recipeOrchestrator: this.recipeOrchestrator,
836
+ recipeRunLog: this.recipeRunLog,
837
+ activityLog: this.activityLog,
838
+ workdir: this.config.workspace,
839
+ logger: this.logger,
840
+ });
841
+ this.recipeOrchestration.wireServerFns();
842
+ this.recipeScheduler = RecipeOrchestration.buildScheduler({
770
843
  recipesDir,
771
- enqueue: (opts) => this.orchestrator.enqueue(opts),
844
+ runRecipeFn: async (name) => this.server.runRecipeFn?.(name),
845
+ enqueue: (opts) => this.orchestrator?.enqueue(opts) ?? "",
772
846
  logger: this.logger,
773
847
  });
774
- const scheduled = this.recipeScheduler.start();
775
- if (scheduled.length > 0) {
776
- this.logger.info(`[patchwork] scheduled ${scheduled.length} cron recipe${scheduled.length === 1 ? "" : "s"}`);
777
- }
848
+ // scheduler.start() deferred to after this.port is set (see below)
849
+ // so bridgeMcp callback has a valid port when first cron fires.
778
850
  }
779
851
  }
780
852
  if (this.config.automationEnabled) {
@@ -808,6 +880,7 @@ export class Bridge {
808
880
  extensionCircuitBreaker: this.extensionClient.getCircuitBreakerState(),
809
881
  extensionDisconnectCount: this.extensionDisconnectCount,
810
882
  recentActivity: this.activityLog.query({ last: 10 }),
883
+ tokens: this.tokenUsageTracker.getTotals(),
811
884
  };
812
885
  };
813
886
  this.server.metricsFn = () => this.activityLog.toPrometheus({
@@ -974,6 +1047,37 @@ export class Bridge {
974
1047
  };
975
1048
  };
976
1049
  this.server.streamFn = (listener) => this.activityLog.subscribe(listener);
1050
+ this.server.cancelTaskFn = (id) => this.orchestrator?.cancel(id, "user") ?? false;
1051
+ // Wire `/sessions` for the dashboard's Sessions page. The same Map is
1052
+ // already iterated by `statusFn` for the bridge-status payload — exposing
1053
+ // it as a list-only endpoint lets the dashboard render per-session cards
1054
+ // without coupling to the rest of the status payload. Without this the
1055
+ // page returns 404 forever (sessionsFn was declared but never assigned),
1056
+ // even when sessions are connected.
1057
+ this.server.sessionsFn = () => {
1058
+ const approvals = getApprovalQueue().list();
1059
+ const pendingBySession = new Map();
1060
+ for (const a of approvals) {
1061
+ if (!a.sessionId)
1062
+ continue;
1063
+ // Match by 8-char prefix because the dashboard's session id is
1064
+ // the same prefix shape (statusFn emits `s.id.slice(0, 8)`).
1065
+ const prefix = a.sessionId.slice(0, 8);
1066
+ pendingBySession.set(prefix, (pendingBySession.get(prefix) ?? 0) + 1);
1067
+ }
1068
+ const out = [];
1069
+ for (const s of this.sessions.values()) {
1070
+ const id = s.id.slice(0, 8);
1071
+ out.push({
1072
+ id,
1073
+ connectedAt: new Date(s.connectedAt).toISOString(),
1074
+ openedFileCount: s.openedFiles.size,
1075
+ pendingApprovals: pendingBySession.get(id) ?? 0,
1076
+ ...(s.remoteAddr ? { remoteAddr: s.remoteAddr } : {}),
1077
+ });
1078
+ }
1079
+ return out;
1080
+ };
977
1081
  this.server.tasksFn = () => ({
978
1082
  tasks: (this.orchestrator?.list() ?? []).map((t) => ({
979
1083
  taskId: t.id,
@@ -1000,134 +1104,21 @@ export class Bridge {
1000
1104
  this.server.approvalWebhookUrl =
1001
1105
  this.config.approvalWebhookUrl ?? undefined;
1002
1106
  this.server.onApprovalDecision = (event, meta) => this.activityLog.recordEvent(event, meta);
1003
- this.server.recipesFn = () => {
1004
- const recipesDir = path.join(os.homedir(), ".patchwork", "recipes");
1005
- return listInstalledRecipes(recipesDir);
1006
- };
1007
- this.server.saveRecipeFn = (draft) => {
1008
- const recipesDir = path.join(os.homedir(), ".patchwork", "recipes");
1009
- return saveRecipe(recipesDir, draft);
1010
- };
1011
- this.server.runsFn = (q) => {
1012
- if (!this.recipeRunLog)
1013
- return [];
1014
- return this.recipeRunLog.query({
1015
- ...(q.limit !== undefined && { limit: q.limit }),
1016
- ...(q.trigger !== undefined && {
1017
- trigger: q.trigger,
1018
- }),
1019
- ...(q.status !== undefined && {
1020
- status: q.status,
1021
- }),
1022
- ...(q.recipe !== undefined && { recipe: q.recipe }),
1023
- ...(q.after !== undefined && { after: q.after }),
1024
- });
1025
- };
1026
- this.server.sessionsFn = () => [...this.sessions.values()].map((s) => {
1027
- const tools = this.activityLog.querySessionTools(s.id, 1);
1028
- return {
1029
- id: s.id,
1030
- connectedAt: new Date(s.connectedAt).toISOString(),
1031
- openedFileCount: s.openedFiles.size,
1032
- pendingApprovals: getApprovalQueue()
1033
- .list()
1034
- .filter((a) => a.sessionId === s.id).length,
1035
- firstTool: tools[0]?.tool,
1036
- remoteAddr: s.remoteAddr,
1037
- };
1038
- });
1039
- this.server.sessionDetailFn = (id) => {
1040
- const s = this.sessions.get(id);
1041
- const summary = s
1042
- ? {
1043
- id: s.id,
1044
- connectedAt: new Date(s.connectedAt).toISOString(),
1045
- openedFileCount: s.openedFiles.size,
1046
- pendingApprovals: getApprovalQueue()
1047
- .list()
1048
- .filter((a) => a.sessionId === s.id).length,
1049
- }
1050
- : null;
1051
- const lifecycle = this.activityLog.querySessionLifecycle(id, 100);
1052
- const tools = this.activityLog.querySessionTools(id, 100);
1053
- const decisions = this.decisionTraceLog?.query({ sessionId: id, limit: 50 }) ?? [];
1054
- const approvals = getApprovalQueue()
1055
- .list()
1056
- .filter((a) => a.sessionId === id);
1057
- return {
1058
- summary,
1059
- lifecycle: lifecycle,
1060
- tools: tools,
1061
- decisions: decisions,
1062
- approvals: approvals,
1063
- };
1064
- };
1065
- this.server.webhookFn = async (hookPath, payload) => {
1066
- if (!this.orchestrator) {
1067
- return {
1068
- ok: false,
1069
- error: "orchestrator_unavailable",
1070
- };
1071
- }
1072
- const recipesDir = path.join(os.homedir(), ".patchwork", "recipes");
1073
- const match = findWebhookRecipe(recipesDir, hookPath);
1074
- if (!match) {
1075
- return { ok: false, error: "not_found" };
1076
- }
1077
- const loaded = loadRecipePrompt(recipesDir, match.name);
1078
- if (!loaded) {
1079
- return { ok: false, error: "recipe_file_missing" };
1080
- }
1081
- try {
1082
- const taskId = this.orchestrator.enqueue({
1083
- prompt: renderWebhookPrompt(loaded.prompt, payload),
1084
- triggerSource: `webhook:${match.name}`,
1085
- });
1086
- return { ok: true, taskId, name: match.name };
1087
- }
1088
- catch (err) {
1089
- return {
1090
- ok: false,
1091
- error: err instanceof Error ? err.message : String(err),
1092
- };
1093
- }
1094
- };
1095
- this.server.runRecipeFn = async (name, vars) => {
1096
- if (!this.orchestrator) {
1097
- return {
1098
- ok: false,
1099
- error: "Orchestrator unavailable — start bridge with --claude-driver subprocess",
1100
- };
1101
- }
1102
- const recipesDir = path.join(os.homedir(), ".patchwork", "recipes");
1103
- const loaded = loadRecipePrompt(recipesDir, name);
1104
- if (!loaded) {
1105
- return {
1106
- ok: false,
1107
- error: `Recipe "${name}" not found in ${recipesDir}`,
1108
- };
1109
- }
1110
- try {
1111
- let prompt = loaded.prompt;
1112
- if (vars && Object.keys(vars).length > 0) {
1113
- const varLines = Object.entries(vars)
1114
- .map(([k, v]) => `${k}=${v}`)
1115
- .join("\n");
1116
- prompt = `Variables:\n${varLines}\n\n${prompt}`;
1117
- }
1118
- const taskId = this.orchestrator.enqueue({
1119
- prompt,
1120
- triggerSource: `recipe:${name}`,
1121
- });
1122
- return { ok: true, taskId };
1123
- }
1124
- catch (err) {
1125
- return {
1126
- ok: false,
1127
- error: err instanceof Error ? err.message : String(err),
1128
- };
1129
- }
1130
- };
1107
+ // Wire activityLog into approvalHttp so passive risk personalization
1108
+ // signals (src/approvalSignals.ts) actually compute against the
1109
+ // user's history. Without this, the personalSignals catalog is
1110
+ // defined but inert — every approval queue entry has
1111
+ // personalSignals: undefined.
1112
+ this.server.activityLog = this.activityLog;
1113
+ // Same wire pattern for h6 (recipe-step trust). recipeRunLog may be
1114
+ // undefined when no orchestrator is configured; that's fine — h6
1115
+ // silently skips and the other 11 heuristics still compute.
1116
+ this.server.recipeRunLog = this.recipeRunLog ?? undefined;
1117
+ // Opt-in switch for personalSignals h10 (time-of-day anomaly).
1118
+ // Default false; user enables via --enable-time-of-day-anomaly or
1119
+ // ~/.patchwork/config.json's enableTimeOfDayAnomaly field.
1120
+ this.server.enableTimeOfDayAnomaly =
1121
+ this.config.enableTimeOfDayAnomaly ?? false;
1131
1122
  this.server.readyFn = () => {
1132
1123
  // Count tools from the first active session (all sessions share the same tool set)
1133
1124
  const anySession = [...this.sessions.values()][0];
@@ -1168,11 +1159,18 @@ export class Bridge {
1168
1159
  patchwork: {
1169
1160
  workspace: this.config.workspace,
1170
1161
  approvalGate: this.server.approvalGate,
1162
+ enableTimeOfDayAnomaly: this.server.enableTimeOfDayAnomaly,
1171
1163
  fullMode: this.config.fullMode,
1172
- claudeDriver: this.config.claudeDriver,
1164
+ driver: this.config.driver,
1165
+ model: loadPatchworkConfig().model,
1166
+ localEndpoint: loadPatchworkConfig().localEndpoint,
1167
+ localModel: loadPatchworkConfig().localModel,
1173
1168
  automationEnabled: this.config.automationEnabled,
1174
1169
  port: this.port,
1175
1170
  webhookUrl: this.server.approvalWebhookUrl ?? null,
1171
+ pushServiceUrl: this.server.pushServiceUrl ?? null,
1172
+ pushServiceToken: this.server.pushServiceToken ? "***" : null,
1173
+ pushServiceBaseUrl: this.server.pushServiceBaseUrl ?? null,
1176
1174
  },
1177
1175
  };
1178
1176
  };
@@ -1259,6 +1257,31 @@ export class Bridge {
1259
1257
  : null, async () => {
1260
1258
  await this.refreshRecentTracesDigest();
1261
1259
  return this.buildInstructions();
1260
+ },
1261
+ // Tail-end deps so `registerAllTools` registers the same tool set
1262
+ // on Streamable-HTTP sessions as on WebSocket. Without this object,
1263
+ // `ctxSaveTrace`, `ctxQueryTraces`, and any tool gated on the
1264
+ // remaining deps silently failed to register over Streamable HTTP
1265
+ // (caught dogfooding `ctx-loop-test` from a remote MCP client).
1266
+ {
1267
+ automationHooks: this.automationHooks,
1268
+ getDisconnectInfo: () => ({
1269
+ at: this.lastDisconnectAt,
1270
+ code: this.lastDisconnectCode,
1271
+ reason: this.lastDisconnectReason,
1272
+ }),
1273
+ onContextCacheUpdated: (generatedAt) => {
1274
+ this._lastContextCachedAt = generatedAt;
1275
+ this._emitLiveState();
1276
+ },
1277
+ getExtensionDisconnectCount: () => this.extensionDisconnectCount,
1278
+ ...(this.commitIssueLinkLog && {
1279
+ commitIssueLinkLog: this.commitIssueLinkLog,
1280
+ }),
1281
+ ...(this.recipeRunLog && { recipeRunLog: this.recipeRunLog }),
1282
+ ...(this.decisionTraceLog && {
1283
+ decisionTraceLog: this.decisionTraceLog,
1284
+ }),
1262
1285
  });
1263
1286
  this.server.httpMcpHandler = (req, res) => this.httpMcpHandler?.handle(req, res) ?? Promise.resolve();
1264
1287
  // 3. Check for stale lock files
@@ -1275,6 +1298,13 @@ export class Bridge {
1275
1298
  throw err;
1276
1299
  }
1277
1300
  this.port = port;
1301
+ // 4a-deferred. Start recipe scheduler now that port is known (bridgeMcp needs a valid port).
1302
+ if (this.recipeScheduler) {
1303
+ const scheduled = this.recipeScheduler.start();
1304
+ if (scheduled.length > 0) {
1305
+ this.logger.info(`[patchwork] scheduled ${scheduled.length} cron recipe${scheduled.length === 1 ? "" : "s"}`);
1306
+ }
1307
+ }
1278
1308
  // 4b. Start WebSocket keepalive heartbeat (keeps MCP session alive during long idle periods)
1279
1309
  this._startWsHeartbeat();
1280
1310
  // 4c. Load persisted tasks from previous sessions (best-effort)
@@ -1438,6 +1468,7 @@ export class Bridge {
1438
1468
  this.stopped = true;
1439
1469
  this.logger.info("Shutting down...");
1440
1470
  this._stopPeriodicSnapshots();
1471
+ this.tokenUsageTracker.stop();
1441
1472
  this.automationHooks?.destroy();
1442
1473
  this.recipeScheduler?.stop();
1443
1474
  this.recipeScheduler = null;
@@ -1471,7 +1502,7 @@ export class Bridge {
1471
1502
  if (this.checkpoint && this.port > 0) {
1472
1503
  try {
1473
1504
  await Promise.race([
1474
- Promise.resolve().then(() => this.checkpoint.write(this._buildCheckpoint(this.port))),
1505
+ Promise.resolve().then(() => this.checkpoint?.write(this._buildCheckpoint(this.port))),
1475
1506
  new Promise((resolve) => setTimeout(resolve, 3000)),
1476
1507
  ]);
1477
1508
  }