@stigmer/runner 3.0.9-dev.20260616060535 → 3.1.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 (328) hide show
  1. package/dist/.build-fingerprint +1 -1
  2. package/dist/__test-utils__/approval-contract/types.d.ts +174 -0
  3. package/dist/__test-utils__/approval-contract/types.js +24 -0
  4. package/dist/__test-utils__/approval-contract/types.js.map +1 -0
  5. package/dist/activities/call-agent-status.d.ts +19 -1
  6. package/dist/activities/call-agent-status.js +64 -5
  7. package/dist/activities/call-agent-status.js.map +1 -1
  8. package/dist/activities/call-llm.js +19 -53
  9. package/dist/activities/call-llm.js.map +1 -1
  10. package/dist/activities/classify-tool-approvals.d.ts +24 -0
  11. package/dist/activities/classify-tool-approvals.js +69 -17
  12. package/dist/activities/classify-tool-approvals.js.map +1 -1
  13. package/dist/activities/discover-mcp-server.d.ts +7 -0
  14. package/dist/activities/discover-mcp-server.js +11 -1
  15. package/dist/activities/discover-mcp-server.js.map +1 -1
  16. package/dist/activities/execute-cursor/__test-utils__/cursor-hook-harness.d.ts +140 -0
  17. package/dist/activities/execute-cursor/__test-utils__/cursor-hook-harness.js +130 -0
  18. package/dist/activities/execute-cursor/__test-utils__/cursor-hook-harness.js.map +1 -0
  19. package/dist/activities/execute-cursor/__test-utils__/gateway-substrate.d.ts +18 -0
  20. package/dist/activities/execute-cursor/__test-utils__/gateway-substrate.js +123 -0
  21. package/dist/activities/execute-cursor/__test-utils__/gateway-substrate.js.map +1 -0
  22. package/dist/activities/execute-cursor/approval-policy.d.ts +17 -61
  23. package/dist/activities/execute-cursor/approval-policy.js +8 -120
  24. package/dist/activities/execute-cursor/approval-policy.js.map +1 -1
  25. package/dist/activities/execute-cursor/approval-state.d.ts +219 -9
  26. package/dist/activities/execute-cursor/approval-state.js +233 -17
  27. package/dist/activities/execute-cursor/approval-state.js.map +1 -1
  28. package/dist/activities/execute-cursor/capture-flow.d.ts +127 -0
  29. package/dist/activities/execute-cursor/capture-flow.js +234 -0
  30. package/dist/activities/execute-cursor/capture-flow.js.map +1 -0
  31. package/dist/activities/execute-cursor/cas-observations.d.ts +96 -0
  32. package/dist/activities/execute-cursor/cas-observations.js +184 -0
  33. package/dist/activities/execute-cursor/cas-observations.js.map +1 -0
  34. package/dist/activities/execute-cursor/command-provenance.d.ts +62 -0
  35. package/dist/activities/execute-cursor/command-provenance.js +128 -0
  36. package/dist/activities/execute-cursor/command-provenance.js.map +1 -0
  37. package/dist/activities/execute-cursor/exact-apply.d.ts +110 -0
  38. package/dist/activities/execute-cursor/exact-apply.js +204 -0
  39. package/dist/activities/execute-cursor/exact-apply.js.map +1 -0
  40. package/dist/activities/execute-cursor/hook-script.d.ts +53 -24
  41. package/dist/activities/execute-cursor/hook-script.js +310 -47
  42. package/dist/activities/execute-cursor/hook-script.js.map +1 -1
  43. package/dist/activities/execute-cursor/index.d.ts +8 -1
  44. package/dist/activities/execute-cursor/index.js +500 -64
  45. package/dist/activities/execute-cursor/index.js.map +1 -1
  46. package/dist/activities/execute-cursor/message-translator.d.ts +258 -11
  47. package/dist/activities/execute-cursor/message-translator.js +836 -90
  48. package/dist/activities/execute-cursor/message-translator.js.map +1 -1
  49. package/dist/activities/execute-cursor/prompt-builder.d.ts +20 -23
  50. package/dist/activities/execute-cursor/prompt-builder.js +74 -15
  51. package/dist/activities/execute-cursor/prompt-builder.js.map +1 -1
  52. package/dist/activities/execute-cursor/workspace-setup.d.ts +17 -0
  53. package/dist/activities/execute-cursor/workspace-setup.js +212 -33
  54. package/dist/activities/execute-cursor/workspace-setup.js.map +1 -1
  55. package/dist/activities/execute-deep-agent/__test-utils__/gateway-substrate.d.ts +19 -0
  56. package/dist/activities/execute-deep-agent/__test-utils__/gateway-substrate.js +143 -0
  57. package/dist/activities/execute-deep-agent/__test-utils__/gateway-substrate.js.map +1 -0
  58. package/dist/activities/execute-deep-agent/__test-utils__/scripted-model.d.ts +88 -0
  59. package/dist/activities/execute-deep-agent/__test-utils__/scripted-model.js +81 -0
  60. package/dist/activities/execute-deep-agent/__test-utils__/scripted-model.js.map +1 -0
  61. package/dist/activities/execute-deep-agent/approval-file-change.d.ts +47 -0
  62. package/dist/activities/execute-deep-agent/approval-file-change.js +68 -0
  63. package/dist/activities/execute-deep-agent/approval-file-change.js.map +1 -0
  64. package/dist/activities/execute-deep-agent/attachment-injector.d.ts +8 -1
  65. package/dist/activities/execute-deep-agent/attachment-injector.js +7 -7
  66. package/dist/activities/execute-deep-agent/attachment-injector.js.map +1 -1
  67. package/dist/activities/execute-deep-agent/cas-capture-backend.d.ts +42 -0
  68. package/dist/activities/execute-deep-agent/cas-capture-backend.js +47 -0
  69. package/dist/activities/execute-deep-agent/cas-capture-backend.js.map +1 -0
  70. package/dist/activities/execute-deep-agent/cas-capture-observer.d.ts +79 -0
  71. package/dist/activities/execute-deep-agent/cas-capture-observer.js +112 -0
  72. package/dist/activities/execute-deep-agent/cas-capture-observer.js.map +1 -0
  73. package/dist/activities/execute-deep-agent/hitl.d.ts +10 -0
  74. package/dist/activities/execute-deep-agent/hitl.js +5 -1
  75. package/dist/activities/execute-deep-agent/hitl.js.map +1 -1
  76. package/dist/activities/execute-deep-agent/index.d.ts +2 -1
  77. package/dist/activities/execute-deep-agent/index.js +370 -56
  78. package/dist/activities/execute-deep-agent/index.js.map +1 -1
  79. package/dist/activities/execute-deep-agent/inline-publisher.d.ts +7 -1
  80. package/dist/activities/execute-deep-agent/inline-publisher.js +23 -2
  81. package/dist/activities/execute-deep-agent/inline-publisher.js.map +1 -1
  82. package/dist/activities/execute-deep-agent/setup.d.ts +53 -2
  83. package/dist/activities/execute-deep-agent/setup.js +149 -92
  84. package/dist/activities/execute-deep-agent/setup.js.map +1 -1
  85. package/dist/activities/execute-deep-agent/stamp-flowed-rows.d.ts +36 -0
  86. package/dist/activities/execute-deep-agent/stamp-flowed-rows.js +56 -0
  87. package/dist/activities/execute-deep-agent/stamp-flowed-rows.js.map +1 -0
  88. package/dist/activities/execute-deep-agent/status-builder-shared.d.ts +34 -1
  89. package/dist/activities/execute-deep-agent/status-builder-shared.js +26 -25
  90. package/dist/activities/execute-deep-agent/status-builder-shared.js.map +1 -1
  91. package/dist/activities/execute-deep-agent/status-builder.d.ts +11 -5
  92. package/dist/activities/execute-deep-agent/status-builder.js +6 -2
  93. package/dist/activities/execute-deep-agent/status-builder.js.map +1 -1
  94. package/dist/activities/execute-deep-agent/streaming-side-effects.js +2 -19
  95. package/dist/activities/execute-deep-agent/streaming-side-effects.js.map +1 -1
  96. package/dist/activities/execute-deep-agent/streaming.js +3 -15
  97. package/dist/activities/execute-deep-agent/streaming.js.map +1 -1
  98. package/dist/activities/execute-deep-agent/subagent-transformer.d.ts +25 -7
  99. package/dist/activities/execute-deep-agent/subagent-transformer.js +23 -7
  100. package/dist/activities/execute-deep-agent/subagent-transformer.js.map +1 -1
  101. package/dist/activities/execute-deep-agent/subagent-wiring.d.ts +30 -3
  102. package/dist/activities/execute-deep-agent/subagent-wiring.js +29 -3
  103. package/dist/activities/execute-deep-agent/subagent-wiring.js.map +1 -1
  104. package/dist/activities/execute-deep-agent/v3-status-builder.js +6 -2
  105. package/dist/activities/execute-deep-agent/v3-status-builder.js.map +1 -1
  106. package/dist/claimcheck/payload-codec.js +9 -5
  107. package/dist/claimcheck/payload-codec.js.map +1 -1
  108. package/dist/client/stigmer-client.d.ts +2 -0
  109. package/dist/client/stigmer-client.js +2 -0
  110. package/dist/client/stigmer-client.js.map +1 -1
  111. package/dist/middleware/approval-gate.d.ts +85 -4
  112. package/dist/middleware/approval-gate.js +165 -38
  113. package/dist/middleware/approval-gate.js.map +1 -1
  114. package/dist/middleware/types.d.ts +2 -5
  115. package/dist/shared/activity-input.d.ts +43 -0
  116. package/dist/shared/activity-input.js +17 -0
  117. package/dist/shared/activity-input.js.map +1 -0
  118. package/dist/shared/approval-canonicalize.d.ts +19 -0
  119. package/dist/shared/approval-canonicalize.js +119 -0
  120. package/dist/shared/approval-canonicalize.js.map +1 -0
  121. package/dist/shared/approval-fingerprint.d.ts +106 -0
  122. package/dist/shared/approval-fingerprint.js +113 -0
  123. package/dist/shared/approval-fingerprint.js.map +1 -0
  124. package/dist/shared/approval-policy.d.ts +182 -12
  125. package/dist/shared/approval-policy.js +213 -27
  126. package/dist/shared/approval-policy.js.map +1 -1
  127. package/dist/shared/args-preview.d.ts +52 -0
  128. package/dist/shared/args-preview.js +93 -0
  129. package/dist/shared/args-preview.js.map +1 -0
  130. package/dist/shared/artifact-storage.d.ts +19 -1
  131. package/dist/shared/artifact-storage.js +48 -11
  132. package/dist/shared/artifact-storage.js.map +1 -1
  133. package/dist/shared/file-change.d.ts +44 -0
  134. package/dist/shared/file-change.js +57 -0
  135. package/dist/shared/file-change.js.map +1 -0
  136. package/dist/shared/file-tools.d.ts +107 -0
  137. package/dist/shared/file-tools.js +168 -0
  138. package/dist/shared/file-tools.js.map +1 -0
  139. package/dist/shared/filereview/capture.d.ts +202 -0
  140. package/dist/shared/filereview/capture.js +498 -0
  141. package/dist/shared/filereview/capture.js.map +1 -0
  142. package/dist/shared/filereview/cas-substrate.d.ts +190 -0
  143. package/dist/shared/filereview/cas-substrate.js +284 -0
  144. package/dist/shared/filereview/cas-substrate.js.map +1 -0
  145. package/dist/shared/filereview/digest.d.ts +40 -0
  146. package/dist/shared/filereview/digest.js +66 -0
  147. package/dist/shared/filereview/digest.js.map +1 -0
  148. package/dist/shared/filereview/events.d.ts +170 -0
  149. package/dist/shared/filereview/events.js +298 -0
  150. package/dist/shared/filereview/events.js.map +1 -0
  151. package/dist/shared/filereview/git-substrate.d.ts +175 -0
  152. package/dist/shared/filereview/git-substrate.js +439 -0
  153. package/dist/shared/filereview/git-substrate.js.map +1 -0
  154. package/dist/shared/filereview/index.d.ts +11 -0
  155. package/dist/shared/filereview/index.js +12 -0
  156. package/dist/shared/filereview/index.js.map +1 -0
  157. package/dist/shared/filereview/secret-paths.d.ts +63 -0
  158. package/dist/shared/filereview/secret-paths.js +105 -0
  159. package/dist/shared/filereview/secret-paths.js.map +1 -0
  160. package/dist/shared/fingerprint-secret.d.ts +26 -0
  161. package/dist/shared/fingerprint-secret.js +47 -0
  162. package/dist/shared/fingerprint-secret.js.map +1 -0
  163. package/dist/shared/model-client.d.ts +51 -0
  164. package/dist/shared/model-client.js +77 -0
  165. package/dist/shared/model-client.js.map +1 -0
  166. package/dist/shared/plan-artifact.js +0 -2
  167. package/dist/shared/plan-artifact.js.map +1 -1
  168. package/dist/shared/status-offload.d.ts +83 -9
  169. package/dist/shared/status-offload.js +399 -79
  170. package/dist/shared/status-offload.js.map +1 -1
  171. package/dist/shared/status.js +14 -1
  172. package/dist/shared/status.js.map +1 -1
  173. package/dist/shared/tool-kind.d.ts +19 -0
  174. package/dist/shared/tool-kind.js +13 -0
  175. package/dist/shared/tool-kind.js.map +1 -1
  176. package/dist/shared/tool-row.d.ts +88 -0
  177. package/dist/shared/tool-row.js +127 -0
  178. package/dist/shared/tool-row.js.map +1 -0
  179. package/dist/shared/workspace/platform-dir.d.ts +25 -0
  180. package/dist/shared/workspace/platform-dir.js +38 -2
  181. package/dist/shared/workspace/platform-dir.js.map +1 -1
  182. package/dist/workflows/call-agent-orchestrator.js +56 -7
  183. package/dist/workflows/call-agent-orchestrator.js.map +1 -1
  184. package/dist/workflows/connect-mcp-server.d.ts +50 -0
  185. package/dist/workflows/connect-mcp-server.js +136 -15
  186. package/dist/workflows/connect-mcp-server.js.map +1 -1
  187. package/dist/workflows/types.d.ts +8 -0
  188. package/package.json +2 -2
  189. package/src/__test-utils__/approval-contract/contract.ts +224 -0
  190. package/src/__test-utils__/approval-contract/types.ts +179 -0
  191. package/src/__test-utils__/fake-artifact-storage.ts +72 -0
  192. package/src/__tests__/approval-gateway-contract.test.ts +29 -0
  193. package/src/__tests__/claimcheck-codec.test.ts +16 -53
  194. package/src/__tests__/golden-e2e.test.ts +2 -0
  195. package/src/__tests__/runner-token-coordinator.test.ts +3 -3
  196. package/src/activities/__tests__/call-agent-status.test.ts +135 -0
  197. package/src/activities/__tests__/call-llm.test.ts +1 -1
  198. package/src/activities/__tests__/classify-tool-approvals.test.ts +208 -1
  199. package/src/activities/__tests__/discover-mcp-server.test.ts +30 -0
  200. package/src/activities/__tests__/workflow-event-activities.test.ts +2 -1
  201. package/src/activities/call-agent-status.ts +74 -4
  202. package/src/activities/call-llm.ts +18 -63
  203. package/src/activities/classify-tool-approvals.ts +101 -19
  204. package/src/activities/discover-mcp-server.ts +29 -1
  205. package/src/activities/execute-cursor/__test-utils__/cursor-hook-harness.ts +216 -0
  206. package/src/activities/execute-cursor/__test-utils__/gateway-substrate.ts +148 -0
  207. package/src/activities/execute-cursor/__tests__/approval-gate.test.ts +41 -9
  208. package/src/activities/execute-cursor/__tests__/approval-state.test.ts +292 -0
  209. package/src/activities/execute-cursor/__tests__/build-prompt.test.ts +68 -1
  210. package/src/activities/execute-cursor/__tests__/capture-flow.test.ts +1005 -0
  211. package/src/activities/execute-cursor/__tests__/cas-observations.test.ts +187 -0
  212. package/src/activities/execute-cursor/__tests__/coarse-fingerprint.test.ts +97 -0
  213. package/src/activities/execute-cursor/__tests__/command-provenance.test.ts +240 -0
  214. package/src/activities/execute-cursor/__tests__/deny-gate-exact-apply.test.ts +203 -0
  215. package/src/activities/execute-cursor/__tests__/exact-apply.test.ts +375 -0
  216. package/src/activities/execute-cursor/__tests__/hitl-ledger.test.ts +1294 -24
  217. package/src/activities/execute-cursor/__tests__/hitl-resume-history.test.ts +446 -0
  218. package/src/activities/execute-cursor/__tests__/hook-script.test.ts +384 -110
  219. package/src/activities/execute-cursor/__tests__/message-translator.test.ts +171 -25
  220. package/src/activities/execute-cursor/__tests__/sequential-gate-resume.test.ts +189 -0
  221. package/src/activities/execute-cursor/__tests__/tool-result-image.test.ts +44 -23
  222. package/src/activities/execute-cursor/__tests__/workspace-setup.test.ts +190 -10
  223. package/src/activities/execute-cursor/approval-policy.ts +28 -159
  224. package/src/activities/execute-cursor/approval-state.ts +366 -18
  225. package/src/activities/execute-cursor/capture-flow.ts +323 -0
  226. package/src/activities/execute-cursor/cas-observations.ts +204 -0
  227. package/src/activities/execute-cursor/command-provenance.ts +168 -0
  228. package/src/activities/execute-cursor/exact-apply.ts +253 -0
  229. package/src/activities/execute-cursor/hook-script.ts +317 -51
  230. package/src/activities/execute-cursor/index.ts +575 -67
  231. package/src/activities/execute-cursor/message-translator.ts +963 -89
  232. package/src/activities/execute-cursor/prompt-builder.ts +80 -14
  233. package/src/activities/execute-cursor/workspace-setup.ts +257 -42
  234. package/src/activities/execute-deep-agent/__test-utils__/gateway-substrate.ts +180 -0
  235. package/src/activities/execute-deep-agent/__test-utils__/scripted-model.ts +134 -0
  236. package/src/activities/execute-deep-agent/__tests__/approval-file-change.test.ts +84 -0
  237. package/src/activities/execute-deep-agent/__tests__/attachment-injector.test.ts +11 -24
  238. package/src/activities/execute-deep-agent/__tests__/cas-capture-backend.test.ts +64 -0
  239. package/src/activities/execute-deep-agent/__tests__/cas-capture-observer.test.ts +163 -0
  240. package/src/activities/execute-deep-agent/__tests__/hitl-integration.test.ts +8 -5
  241. package/src/activities/execute-deep-agent/__tests__/hitl-resume-approve-all.test.ts +342 -0
  242. package/src/activities/execute-deep-agent/__tests__/hitl-resume-history.test.ts +2 -5
  243. package/src/activities/execute-deep-agent/__tests__/inline-publisher.test.ts +31 -13
  244. package/src/activities/execute-deep-agent/__tests__/sequential-gate-resume.test.ts +349 -0
  245. package/src/activities/execute-deep-agent/__tests__/stamp-flowed-rows.test.ts +119 -0
  246. package/src/activities/execute-deep-agent/__tests__/status-builder.test.ts +12 -11
  247. package/src/activities/execute-deep-agent/__tests__/streaming-v3.test.ts +9 -9
  248. package/src/activities/execute-deep-agent/__tests__/subagent-approval-propagation.test.ts +160 -0
  249. package/src/activities/execute-deep-agent/__tests__/subagent-gitignored-capture.test.ts +213 -0
  250. package/src/activities/execute-deep-agent/__tests__/subagent-transformer.test.ts +3 -6
  251. package/src/activities/execute-deep-agent/__tests__/subagent-wiring.test.ts +84 -1
  252. package/src/activities/execute-deep-agent/__tests__/v3-status-builder.test.ts +4 -1
  253. package/src/activities/execute-deep-agent/approval-file-change.ts +80 -0
  254. package/src/activities/execute-deep-agent/attachment-injector.ts +20 -11
  255. package/src/activities/execute-deep-agent/cas-capture-backend.ts +66 -0
  256. package/src/activities/execute-deep-agent/cas-capture-observer.ts +125 -0
  257. package/src/activities/execute-deep-agent/hitl.ts +15 -1
  258. package/src/activities/execute-deep-agent/index.ts +434 -64
  259. package/src/activities/execute-deep-agent/inline-publisher.ts +27 -4
  260. package/src/activities/execute-deep-agent/setup.ts +223 -125
  261. package/src/activities/execute-deep-agent/stamp-flowed-rows.ts +64 -0
  262. package/src/activities/execute-deep-agent/status-builder-shared.ts +62 -23
  263. package/src/activities/execute-deep-agent/status-builder.ts +19 -7
  264. package/src/activities/execute-deep-agent/streaming-side-effects.ts +2 -16
  265. package/src/activities/execute-deep-agent/streaming.ts +3 -13
  266. package/src/activities/execute-deep-agent/subagent-transformer.ts +53 -13
  267. package/src/activities/execute-deep-agent/subagent-wiring.ts +50 -3
  268. package/src/activities/execute-deep-agent/v3-status-builder.ts +8 -2
  269. package/src/claimcheck/payload-codec.ts +8 -8
  270. package/src/client/stigmer-client.ts +9 -1
  271. package/src/middleware/__tests__/approval-gate.test.ts +488 -4
  272. package/src/middleware/approval-gate.ts +247 -38
  273. package/src/middleware/types.ts +5 -5
  274. package/src/shared/__tests__/activity-input.test.ts +78 -0
  275. package/src/shared/__tests__/approval-canonicalize.test.ts +106 -0
  276. package/src/shared/__tests__/approval-fingerprint.test.ts +115 -0
  277. package/src/shared/__tests__/approval-policy.test.ts +274 -40
  278. package/src/shared/__tests__/args-preview.test.ts +78 -0
  279. package/src/shared/__tests__/artifact-storage-extended.test.ts +62 -10
  280. package/src/shared/__tests__/artifact-storage.test.ts +123 -11
  281. package/src/shared/__tests__/file-change.test.ts +85 -0
  282. package/src/shared/__tests__/file-tools.test.ts +90 -0
  283. package/src/shared/__tests__/fingerprint-secret.test.ts +51 -0
  284. package/src/shared/__tests__/lease-scope-corpus.test.ts +56 -0
  285. package/src/shared/__tests__/model-client.test.ts +162 -0
  286. package/src/shared/__tests__/plan-artifact.test.ts +11 -26
  287. package/src/shared/__tests__/policy-source-corpus.test.ts +58 -0
  288. package/src/shared/__tests__/status-offload.test.ts +573 -16
  289. package/src/shared/__tests__/status.test.ts +4 -5
  290. package/src/shared/__tests__/tool-kind.test.ts +24 -1
  291. package/src/shared/__tests__/tool-row.test.ts +221 -0
  292. package/src/shared/activity-input.ts +57 -0
  293. package/src/shared/approval-canonicalize.ts +159 -0
  294. package/src/shared/approval-fingerprint.ts +148 -0
  295. package/src/shared/approval-policy.ts +303 -27
  296. package/src/shared/args-preview.ts +98 -0
  297. package/src/shared/artifact-storage.ts +62 -11
  298. package/src/shared/checkpointer/__tests__/http-saver.test.ts +1 -2
  299. package/src/shared/file-change.ts +64 -0
  300. package/src/shared/file-tools.ts +169 -0
  301. package/src/shared/filereview/__tests__/capture.test.ts +856 -0
  302. package/src/shared/filereview/__tests__/cas-substrate.test.ts +404 -0
  303. package/src/shared/filereview/__tests__/digest.test.ts +100 -0
  304. package/src/shared/filereview/__tests__/events.test.ts +245 -0
  305. package/src/shared/filereview/__tests__/git-substrate.test.ts +362 -0
  306. package/src/shared/filereview/__tests__/proxy-reconcile.test.ts +286 -0
  307. package/src/shared/filereview/__tests__/secret-paths.test.ts +121 -0
  308. package/src/shared/filereview/capture.ts +727 -0
  309. package/src/shared/filereview/cas-substrate.ts +401 -0
  310. package/src/shared/filereview/digest.ts +83 -0
  311. package/src/shared/filereview/events.ts +449 -0
  312. package/src/shared/filereview/git-substrate.ts +555 -0
  313. package/src/shared/filereview/index.ts +60 -0
  314. package/src/shared/filereview/secret-paths.ts +121 -0
  315. package/src/shared/fingerprint-secret.ts +53 -0
  316. package/src/shared/model-client.ts +122 -0
  317. package/src/shared/plan-artifact.ts +0 -2
  318. package/src/shared/status-offload.ts +433 -77
  319. package/src/shared/status.ts +13 -0
  320. package/src/shared/tool-kind.ts +33 -0
  321. package/src/shared/tool-row.ts +135 -0
  322. package/src/shared/workspace/platform-dir.ts +41 -2
  323. package/src/workflow-engine/__tests__/golden-execution.test.ts +35 -18
  324. package/src/workflow-engine/__tests__/tasks/try.test.ts +1 -1
  325. package/src/workflows/__tests__/connect-mcp-server.test.ts +304 -29
  326. package/src/workflows/call-agent-orchestrator.ts +53 -6
  327. package/src/workflows/connect-mcp-server.ts +179 -24
  328. package/src/workflows/types.ts +8 -0
@@ -168,6 +168,33 @@ describe("DiscoverMcpServer activity", () => {
168
168
  );
169
169
  });
170
170
 
171
+ it("returns previous tool definitions for incremental diffing", async () => {
172
+ const { extractPreviousState } = await import("../discover-mcp-server.js");
173
+ const server = makeMcpServer({
174
+ status: {
175
+ discoveredCapabilities: {
176
+ tools: [
177
+ { name: "search", description: "Search things", inputSchema: { type: "object" } },
178
+ { name: "delete_repo", description: "Delete a repo", inputSchema: null },
179
+ ],
180
+ resourceTemplates: [],
181
+ },
182
+ toolApprovals: [{ toolName: "delete_repo", message: "Delete" }],
183
+ },
184
+ });
185
+ const state = extractPreviousState(server);
186
+ expect(state.tools).toEqual([
187
+ { name: "search", description: "Search things", inputSchema: { type: "object" } },
188
+ { name: "delete_repo", description: "Delete a repo", inputSchema: null },
189
+ ]);
190
+ });
191
+
192
+ it("returns empty previous tools when server has no status", async () => {
193
+ const { extractPreviousState } = await import("../discover-mcp-server.js");
194
+ const state = extractPreviousState(makeMcpServer({ status: undefined }));
195
+ expect(state.tools).toEqual([]);
196
+ });
197
+
171
198
  it("extracts tool approvals from status", async () => {
172
199
  const { extractPreviousState } = await import("../discover-mcp-server.js");
173
200
  const server = makeMcpServer({
@@ -444,6 +471,9 @@ describe("DiscoverMcpServer activity", () => {
444
471
  expect(result.previousToolApprovals).toEqual([
445
472
  { toolName: "old_tool", requiresApproval: true, message: "Approve old_tool" },
446
473
  ]);
474
+ expect(result.previousTools).toEqual([
475
+ { name: "old_tool", description: "Old", inputSchema: null },
476
+ ]);
447
477
  expect(result.tools[0].name).toBe("new_tool");
448
478
  expect(result.newToolsFingerprint).toHaveLength(64);
449
479
  expect(result.newToolsFingerprint).not.toBe(result.previousToolsFingerprint);
@@ -329,7 +329,8 @@ describe("toProtoEvent", () => {
329
329
  attemptNumber: 1,
330
330
  };
331
331
  const proto = toProtoEvent(desc);
332
- expect(proto.payload.value?.taskKind).toBe(WorkflowTaskKind.agent_call);
332
+ if (proto.payload.case !== "taskStarted") throw new Error("unexpected");
333
+ expect(proto.payload.value.taskKind).toBe(WorkflowTaskKind.agent_call);
333
334
  });
334
335
  });
335
336
 
@@ -15,9 +15,9 @@
15
15
  import { StigmerClient } from "../client/stigmer-client.js";
16
16
  import { loadConfig } from "../config.js";
17
17
  import { create } from "@bufbuild/protobuf";
18
- import { WorkflowExecutionStatusSchema, WorkflowPendingApprovalSchema } from "@stigmer/protos/ai/stigmer/agentic/workflowexecution/v1/api_pb";
18
+ import { WorkflowExecutionStatusSchema, WorkflowPendingApprovalSchema, WorkflowPendingFileReviewSchema } from "@stigmer/protos/ai/stigmer/agentic/workflowexecution/v1/api_pb";
19
19
  import type { ChildApprovalNotification } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/approval_pb";
20
- import { ToolCallStatus } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/enum_pb";
20
+ import { ToolCallStatus, FileChangeSetStatus } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/enum_pb";
21
21
 
22
22
  function buildClient(): StigmerClient {
23
23
  const config = loadConfig();
@@ -34,12 +34,13 @@ export async function updateWorkflowTaskApprovalStatus(
34
34
  ): Promise<void> {
35
35
  if (!executionId) return;
36
36
 
37
+ const childExecId = notification.executionId;
37
38
  const client = buildClient();
38
39
  const pendingApprovals = (notification.pendingApprovals ?? []).map(
39
40
  (approval) =>
40
41
  create(WorkflowPendingApprovalSchema, {
41
42
  approval,
42
- childAgentExecutionId: notification.executionId,
43
+ childAgentExecutionId: childExecId,
43
44
  }),
44
45
  );
45
46
 
@@ -47,24 +48,91 @@ export async function updateWorkflowTaskApprovalStatus(
47
48
  pendingApprovals,
48
49
  });
49
50
 
51
+ // Per-child merge scoped to this child: the write replaces only this child's
52
+ // approvals and preserves every parallel sibling's entries.
50
53
  await client.updateWorkflowExecutionStatus(executionId, status, {
51
54
  updatePendingApprovals: true,
55
+ pendingUpdateChildAgentExecutionId: childExecId,
52
56
  });
53
57
  }
54
58
 
55
59
  export async function clearWorkflowApprovalStatus(
56
60
  executionId: string,
61
+ childExecutionId: string,
57
62
  ): Promise<void> {
58
- if (!executionId) return;
63
+ if (!executionId || !childExecutionId) return;
59
64
 
60
65
  const client = buildClient();
61
66
  const status = create(WorkflowExecutionStatusSchema, {
62
67
  pendingApprovals: [],
63
68
  });
64
69
 
70
+ // Scoped clear: empty list for this child clears only its approvals; sibling
71
+ // children still awaiting approval are untouched.
65
72
  await client.updateWorkflowExecutionStatus(executionId, status, {
66
73
  updatePendingApprovals: true,
74
+ pendingUpdateChildAgentExecutionId: childExecutionId,
75
+ });
76
+ }
77
+
78
+ /**
79
+ * Surfaces (or clears) a child agent's file-review gate on the parent workflow.
80
+ *
81
+ * Reference-only: writes a single WorkflowPendingFileReview naming the child and
82
+ * the change_set ids it currently has AWAITING_REVIEW — never the diffs, which
83
+ * stay single-sourced on the child. Passing an empty changeSetIds clears this
84
+ * child's entry (per-child merge, so parallel siblings are never disturbed).
85
+ */
86
+ export async function updateWorkflowFileReviewStatus(
87
+ executionId: string,
88
+ childExecutionId: string,
89
+ changeSetIds: string[],
90
+ ): Promise<void> {
91
+ if (!executionId || !childExecutionId) return;
92
+
93
+ const client = buildClient();
94
+ const pendingFileReviews =
95
+ changeSetIds.length > 0
96
+ ? [
97
+ create(WorkflowPendingFileReviewSchema, {
98
+ childAgentExecutionId: childExecutionId,
99
+ changeSetId: changeSetIds,
100
+ }),
101
+ ]
102
+ : [];
103
+
104
+ const status = create(WorkflowExecutionStatusSchema, {
105
+ pendingFileReviews,
67
106
  });
107
+
108
+ await client.updateWorkflowExecutionStatus(executionId, status, {
109
+ updatePendingFileReviews: true,
110
+ pendingUpdateChildAgentExecutionId: childExecutionId,
111
+ });
112
+ }
113
+
114
+ /**
115
+ * Reads the child agent execution and returns the ids of its change sets that
116
+ * are currently AWAITING_REVIEW. The gated child is non-terminal, so its last
117
+ * status write stored a populated file_change_sets projection; GET returns it
118
+ * (the same reason getAgentExecutionProgress reads status.messages).
119
+ */
120
+ export async function getAwaitingFileReviewChangeSetIds(
121
+ childExecutionId: string,
122
+ ): Promise<string[]> {
123
+ if (!childExecutionId) return [];
124
+
125
+ try {
126
+ const client = buildClient();
127
+ const execution = await client.getExecution(childExecutionId);
128
+ const changeSets = execution?.status?.fileChangeSets ?? [];
129
+ return changeSets
130
+ .filter((cs) => cs.status === FileChangeSetStatus.AWAITING_REVIEW)
131
+ .map((cs) => cs.id);
132
+ } catch (err) {
133
+ console.warn("Failed to fetch child file-review state (non-fatal):", String(err));
134
+ return [];
135
+ }
68
136
  }
69
137
 
70
138
  /**
@@ -125,6 +193,8 @@ export function createCallAgentStatusActivities() {
125
193
  return {
126
194
  UpdateWorkflowTaskApprovalStatus: updateWorkflowTaskApprovalStatus,
127
195
  ClearWorkflowApprovalStatus: clearWorkflowApprovalStatus,
196
+ UpdateWorkflowFileReviewStatus: updateWorkflowFileReviewStatus,
197
+ GetAwaitingFileReviewChangeSetIds: getAwaitingFileReviewChangeSetIds,
128
198
  GetAgentExecutionProgress: getAgentExecutionProgress,
129
199
  };
130
200
  }
@@ -16,8 +16,6 @@
16
16
  * Output: LlmCallResult
17
17
  */
18
18
 
19
- import { ChatAnthropic } from "@langchain/anthropic";
20
- import { ChatOpenAI } from "@langchain/openai";
21
19
  import { HumanMessage, SystemMessage } from "@langchain/core/messages";
22
20
  import type { BaseChatModel } from "@langchain/core/language_models/chat_models";
23
21
  import type { BaseMessageChunk } from "@langchain/core/messages";
@@ -28,12 +26,11 @@ import { jsonSchemaToZod } from "../shared/json-schema-to-zod.js";
28
26
  import {
29
27
  inferProvider,
30
28
  stripProviderPrefix,
31
- resolveProxyBaseUrl,
32
- buildProxyHeaders,
33
29
  type LlmProvider,
34
30
  } from "../shared/llm-proxy.js";
35
31
  import { computeLlmCostMicros, ensureLoaded as ensurePricingLoaded } from "../shared/model-pricing.js";
36
32
  import { resolveToApiModelId } from "../shared/model-registry.js";
33
+ import { buildChatModel } from "../shared/model-client.js";
37
34
 
38
35
  export interface LlmCallConfig {
39
36
  readonly model: string;
@@ -54,54 +51,6 @@ export interface LlmCallResult {
54
51
  readonly parse_error?: string;
55
52
  }
56
53
 
57
- function constructModel(
58
- provider: LlmProvider,
59
- modelId: string,
60
- config: LlmCallConfig,
61
- baseUrl?: string,
62
- headers?: Record<string, string>,
63
- ): BaseChatModel {
64
- if (provider === "openai") {
65
- const apiKey = baseUrl
66
- ? (headers?.Authorization?.replace("Bearer ", "") ?? "proxy-managed")
67
- : (process.env.OPENAI_API_KEY ?? "");
68
-
69
- return new ChatOpenAI({
70
- model: modelId,
71
- temperature: config.temperature ?? 0,
72
- maxTokens: config.max_tokens,
73
- ...(baseUrl || headers
74
- ? {
75
- configuration: {
76
- ...(baseUrl ? { baseURL: baseUrl } : {}),
77
- ...(headers ? { defaultHeaders: headers } : {}),
78
- },
79
- }
80
- : {}),
81
- apiKey,
82
- });
83
- }
84
-
85
- const apiKey = baseUrl
86
- ? (headers?.Authorization?.replace("Bearer ", "") ?? "proxy-managed")
87
- : (process.env.ANTHROPIC_API_KEY ?? "");
88
-
89
- return new ChatAnthropic({
90
- model: modelId,
91
- temperature: config.temperature ?? 0,
92
- maxTokens: config.max_tokens ?? 4096,
93
- ...(baseUrl || headers
94
- ? {
95
- clientOptions: {
96
- ...(baseUrl ? { baseURL: baseUrl } : {}),
97
- ...(headers ? { defaultHeaders: headers } : {}),
98
- },
99
- }
100
- : {}),
101
- apiKey,
102
- });
103
- }
104
-
105
54
  /**
106
55
  * Stream a chat model invocation and collect the response.
107
56
  *
@@ -274,22 +223,18 @@ export async function callLlmAction(
274
223
  `structured=${!!config.response_schema} execution=${executionId}`,
275
224
  );
276
225
 
277
- let baseUrl: string | undefined;
278
- let headers: Record<string, string> | undefined;
279
-
280
- if (proxyActive) {
281
- baseUrl = resolveProxyBaseUrl(proxyEndpoint!, provider);
282
- headers = buildProxyHeaders(stigmerToken!, { workflowExecutionId: executionId });
283
- } else if (provider === "openai") {
284
- if (!process.env.OPENAI_API_KEY) {
226
+ // Direct mode (no proxy) requires the provider's own API key. Validated here
227
+ // rather than in buildChatModel so the shared module stays free of Temporal
228
+ // failure types.
229
+ if (!proxyActive) {
230
+ if (provider === "openai" && !process.env.OPENAI_API_KEY) {
285
231
  throw ApplicationFailure.nonRetryable(
286
232
  `OPENAI_API_KEY is not set and no proxy is configured. ` +
287
233
  `Set the API key in your environment or connect to a Stigmer Cloud deployment.`,
288
234
  "LLM_MISSING_API_KEY",
289
235
  );
290
236
  }
291
- } else {
292
- if (!process.env.ANTHROPIC_API_KEY) {
237
+ if (provider === "anthropic" && !process.env.ANTHROPIC_API_KEY) {
293
238
  throw ApplicationFailure.nonRetryable(
294
239
  `ANTHROPIC_API_KEY is not set and no proxy is configured. ` +
295
240
  `Set the API key in your environment or connect to a Stigmer Cloud deployment.`,
@@ -298,7 +243,17 @@ export async function callLlmAction(
298
243
  }
299
244
  }
300
245
 
301
- const model = constructModel(provider, modelId, config, baseUrl, headers);
246
+ // Anthropic requires an explicit maxTokens; preserve the 4096 default here
247
+ // (buildChatModel intentionally imposes none). resolvedModel is already an
248
+ // API id, so buildChatModel's resolve step is a no-op for it.
249
+ const { model } = await buildChatModel({
250
+ modelName: resolvedModel,
251
+ proxyEndpoint: proxyActive ? proxyEndpoint : undefined,
252
+ stigmerToken: proxyActive ? stigmerToken : undefined,
253
+ headerScope: { workflowExecutionId: executionId },
254
+ temperature: config.temperature,
255
+ maxTokens: provider === "anthropic" ? (config.max_tokens ?? 4096) : config.max_tokens,
256
+ });
302
257
 
303
258
  const messages: (HumanMessage | SystemMessage)[] = [];
304
259
  if (config.system_prompt) {
@@ -19,11 +19,10 @@
19
19
  */
20
20
 
21
21
  import { z } from "zod";
22
- import { ChatOpenAI } from "@langchain/openai";
23
22
  import { SystemMessage, HumanMessage } from "@langchain/core/messages";
24
23
  import { activityStarted, activityFinished } from "../idle-watchdog.js";
25
24
  import { getSummarizationModel } from "../shared/model-registry.js";
26
- import { resolveProxyBaseUrl, buildProxyHeaders } from "../shared/llm-proxy.js";
25
+ import { buildChatModel } from "../shared/model-client.js";
27
26
  import type { Config } from "../config.js";
28
27
 
29
28
  const BATCH_SIZE = 40;
@@ -51,6 +50,15 @@ export interface ToolApprovalResult {
51
50
  tool_name: string;
52
51
  requires_approval: boolean;
53
52
  message: string;
53
+ /**
54
+ * True when the connect-time fail-closed tightener force-gated this tool from
55
+ * its destructiveHint annotation (see applyDestructiveHintTightener), not the
56
+ * classifier. Persisted on ToolApprovalPolicy.from_destructive_hint so the
57
+ * runner attributes the gate to the annotation
58
+ * (ApprovalPolicySource.ANNOTATION_DESTRUCTIVE_TIGHTEN) instead of the
59
+ * classifier default. Absent/false on classifier and human-pinned entries.
60
+ */
61
+ from_destructive_hint?: boolean;
54
62
  }
55
63
 
56
64
  // ─────────────────────────────────────────────────────────────────────────────
@@ -89,9 +97,12 @@ safe (auto-approve) or sensitive (requires human approval before execution).
89
97
 
90
98
  Classification rules:
91
99
 
92
- 1. READ-ONLY operations → requires_approval: false
93
- Examples: search, list, get, query, read, fetch, describe, count
94
- These only retrieve data and have no side effects.
100
+ 1. READ-ONLY / OBSERVATION operations → requires_approval: false
101
+ Examples: search, list, get, query, read, fetch, describe, count, view,
102
+ inspect, status, screenshot, snapshot, and "get state" style tools.
103
+ These only retrieve or observe data and have no side effects. When a tool
104
+ name begins with one of these verbs (e.g. get_app_state, list_apps), prefer
105
+ requires_approval: false unless its description clearly says it mutates.
95
106
 
96
107
  2. CREATE or MODIFY operations → requires_approval: true
97
108
  Examples: create, update, put, set, add, edit, modify, write, post, send
@@ -120,6 +131,19 @@ For tools that do NOT require approval, leave message empty.
120
131
 
121
132
  Output one classification per tool, maintaining the input order.`;
122
133
 
134
+ // ─────────────────────────────────────────────────────────────────────────────
135
+ // Read-only authority
136
+ // ─────────────────────────────────────────────────────────────────────────────
137
+ //
138
+ // Read-only auto-approval is owned SOLELY by the trusted LLM classifier above.
139
+ // There is deliberately no deterministic name-based relax here: a tool name is
140
+ // an untrusted, server-supplied signal, and relaxing a gate on it is the unsafe
141
+ // direction (e.g. `get_and_delete_stale_records` leads with `get` yet deletes).
142
+ // A prior name-prefix heuristic was removed for exactly this reason. Annotations
143
+ // are likewise never trusted to relax — the connect workflow uses `destructiveHint`
144
+ // only to TIGHTEN (see applyDestructiveHintTightener). Anything the classifier
145
+ // does not affirmatively clear stays gated (fail closed).
146
+
123
147
  // ─────────────────────────────────────────────────────────────────────────────
124
148
  // Core Classification Logic (no Temporal coupling)
125
149
  // ─────────────────────────────────────────────────────────────────────────────
@@ -168,7 +192,17 @@ export async function classifyTools(
168
192
  batchIdx,
169
193
  totalBatches: batches.length,
170
194
  });
171
- allApprovals.push(...batchResult);
195
+ // Reconcile against the input batch: a tool the model omitted must fail
196
+ // closed, never slip through un-gated. Partial output is as dangerous as
197
+ // an outage for the missing tools.
198
+ const { reconciled, failedClosedCount } = reconcileBatchClassifications(batch, batchResult);
199
+ if (failedClosedCount > 0) {
200
+ console.warn(
201
+ `[ClassifyToolApprovals] Batch ${batchIdx + 1}/${batches.length} for '${serverName}': ` +
202
+ `${failedClosedCount} tool(s) missing from classifier output — failing closed (requires_approval=true)`,
203
+ );
204
+ }
205
+ allApprovals.push(...reconciled);
172
206
  } catch (err) {
173
207
  console.error(
174
208
  `[ClassifyToolApprovals] Batch ${batchIdx + 1}/${batches.length} failed ` +
@@ -179,6 +213,10 @@ export async function classifyTools(
179
213
  }
180
214
  }
181
215
 
216
+ // The LLM classifier is the sole read-only authority: only tools it
217
+ // affirmatively cleared (requires_approval=false) are auto-approved. Tools it
218
+ // gated, omitted (reconciled to fail-closed), or that fell back on an outage
219
+ // all remain gated. No name-based relax runs here by design.
182
220
  const approved = allApprovals.filter((a) => a.requires_approval);
183
221
 
184
222
  console.log(
@@ -214,22 +252,22 @@ async function classifyBatch(params: ClassifyBatchParams): Promise<ToolApprovalR
214
252
 
215
253
  const maxTokens = Math.max(MIN_MAX_TOKENS, batch.length * MAX_TOKENS_PER_TOOL);
216
254
 
217
- const resolvedBaseUrl = resolveProxyBaseUrl(proxyEndpoint, "openai");
218
- const headers = stigmerToken
219
- ? buildProxyHeaders(stigmerToken, { mcpServerId: mcpServerId ?? undefined })
220
- : {};
221
-
222
- const llm = new ChatOpenAI({
223
- model,
224
- temperature: 0,
255
+ // Provider is inferred from the resolved economy model — for an Anthropic
256
+ // primary this routes to Claude, not the hardcoded OpenAI path it used to.
257
+ const { model: llm } = await buildChatModel({
258
+ modelName: model,
259
+ proxyEndpoint,
260
+ stigmerToken: stigmerToken ?? undefined,
261
+ headerScope: { mcpServerId: mcpServerId ?? undefined },
225
262
  maxTokens,
226
- configuration: {
227
- baseURL: resolvedBaseUrl,
228
- defaultHeaders: headers,
229
- },
230
263
  });
231
264
 
232
- const structuredLlm = llm.withStructuredOutput(ClassifyToolApprovalsOutputSchema);
265
+ // Explicit type param: BaseChatModel.withStructuredOutput widens to
266
+ // Record<string, any>, unlike the concrete SDK overloads, so pin the schema's
267
+ // output type here.
268
+ const structuredLlm = llm.withStructuredOutput<ClassifyToolApprovalsOutput>(
269
+ ClassifyToolApprovalsOutputSchema,
270
+ );
233
271
 
234
272
  const toolsPayload = buildToolsPayload(batch);
235
273
  const userPrompt =
@@ -264,7 +302,51 @@ async function classifyBatch(params: ClassifyBatchParams): Promise<ToolApprovalR
264
302
  // Helpers
265
303
  // ─────────────────────────────────────────────────────────────────────────────
266
304
 
305
+ /**
306
+ * Reconcile a batch's LLM classifications against the tools that were actually
307
+ * sent. The canonical result is built from the INPUT batch, never the raw model
308
+ * output, so the classifier can never silently disarm a gate by omission:
309
+ *
310
+ * - A tool the model classified is kept as-is (its requires_approval + message).
311
+ * - A tool the model OMITTED fails closed (requires_approval=true) — a missing
312
+ * decision is treated exactly like an outage for that one tool.
313
+ * - A name the model returned that was never in the batch (a hallucinated or
314
+ * duplicated entry) is dropped — only real tools earn a policy.
315
+ */
316
+ export function reconcileBatchClassifications(
317
+ batch: ToolDescriptor[],
318
+ llmResults: ToolApprovalResult[],
319
+ ): { reconciled: ToolApprovalResult[]; failedClosedCount: number } {
320
+ const byName = new Map<string, ToolApprovalResult>();
321
+ for (const r of llmResults) {
322
+ if (r.tool_name) byName.set(r.tool_name, r);
323
+ }
324
+
325
+ const reconciled: ToolApprovalResult[] = [];
326
+ let failedClosedCount = 0;
327
+ for (const tool of batch) {
328
+ const classified = byName.get(tool.name);
329
+ if (classified) {
330
+ reconciled.push(classified);
331
+ continue;
332
+ }
333
+ reconciled.push({
334
+ tool_name: tool.name,
335
+ requires_approval: true,
336
+ message: `Execute ${tool.name}`,
337
+ });
338
+ failedClosedCount++;
339
+ }
340
+
341
+ return { reconciled, failedClosedCount };
342
+ }
343
+
267
344
  export function fallbackApprovals(tools: ToolDescriptor[]): ToolApprovalResult[] {
345
+ // Full fail-closed: when the classifier batch throws, every tool in it is
346
+ // gated. With no trusted classification we cannot safely auto-approve anything
347
+ // — a name is an untrusted signal — so the entire batch requires approval
348
+ // until a reconnect re-classifies it. The accepted tradeoff is that, during a
349
+ // rare classifier outage, benign read tools briefly prompt for approval.
268
350
  return tools.map((tool) => ({
269
351
  tool_name: tool.name,
270
352
  requires_approval: true,
@@ -47,6 +47,18 @@ export interface DiscoveredToolResult {
47
47
  name: string;
48
48
  description: string;
49
49
  inputSchema?: Record<string, unknown> | null;
50
+ // MCP server-supplied behaviour hints. UNTRUSTED — the MCP spec is explicit
51
+ // that clients must never make tool-use decisions on annotations from
52
+ // untrusted servers. We therefore consume them only to TIGHTEN gating
53
+ // (destructiveHint → force-gate), never to relax it. Kept in-memory for the
54
+ // connect workflow's tightener; deliberately excluded from `toolsFingerprint`
55
+ // / `toolSignature` so incremental-classification reuse stays content-stable.
56
+ annotations?: DiscoveredToolAnnotations | null;
57
+ }
58
+
59
+ export interface DiscoveredToolAnnotations {
60
+ readOnlyHint?: boolean;
61
+ destructiveHint?: boolean;
50
62
  }
51
63
 
52
64
  export interface DiscoveredResourceTemplateResult {
@@ -62,6 +74,11 @@ export interface DiscoverMcpServerOutput {
62
74
  previousToolsFingerprint: string;
63
75
  previousToolApprovals: ToolApprovalDict[];
64
76
  newToolsFingerprint: string;
77
+ // Full definitions of the tools discovered on the previous connect, read from
78
+ // status.discovered_capabilities. The connect workflow diffs these against the
79
+ // freshly discovered tools to reuse prior approval decisions for unchanged
80
+ // tools and (re)classify only the new or changed ones. Empty on first connect.
81
+ previousTools: DiscoveredToolResult[];
65
82
  }
66
83
 
67
84
  export interface ToolApprovalDict {
@@ -97,11 +114,12 @@ export function toolsFingerprint(tools: DiscoveredToolResult[]): string {
97
114
  interface PreviousState {
98
115
  fingerprint: string;
99
116
  toolApprovals: ToolApprovalDict[];
117
+ tools: DiscoveredToolResult[];
100
118
  }
101
119
 
102
120
  export function extractPreviousState(mcpServer: McpServer): PreviousState {
103
121
  const status = mcpServer.status;
104
- if (!status) return { fingerprint: "", toolApprovals: [] };
122
+ if (!status) return { fingerprint: "", toolApprovals: [], tools: [] };
105
123
 
106
124
  const caps = status.discoveredCapabilities;
107
125
  const prevTools: DiscoveredToolResult[] = [];
@@ -131,6 +149,7 @@ export function extractPreviousState(mcpServer: McpServer): PreviousState {
131
149
  return {
132
150
  fingerprint: toolsFingerprint(prevTools),
133
151
  toolApprovals,
152
+ tools: prevTools,
134
153
  };
135
154
  }
136
155
 
@@ -244,6 +263,7 @@ export async function discoverMcpServer(
244
263
  previousToolsFingerprint: previousState.fingerprint,
245
264
  previousToolApprovals: previousState.toolApprovals,
246
265
  newToolsFingerprint: newFp,
266
+ previousTools: previousState.tools,
247
267
  };
248
268
  }
249
269
 
@@ -323,6 +343,14 @@ async function connectAndDiscover(
323
343
  inputSchema: tool.inputSchema
324
344
  ? (tool.inputSchema as Record<string, unknown>)
325
345
  : null,
346
+ // Capture only the two hints the tightener uses. Stored verbatim from
347
+ // the server (untrusted): used to tighten, never to relax.
348
+ annotations: tool.annotations
349
+ ? {
350
+ readOnlyHint: tool.annotations.readOnlyHint,
351
+ destructiveHint: tool.annotations.destructiveHint,
352
+ }
353
+ : null,
326
354
  });
327
355
  }
328
356