@stigmer/runner 3.10.0 → 3.11.1-dev.20260812192248

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 (355) hide show
  1. package/README.md +14 -1
  2. package/dist/.build-fingerprint +1 -1
  3. package/dist/activities/call-llm.js +9 -10
  4. package/dist/activities/call-llm.js.map +1 -1
  5. package/dist/activities/classify-tool-approvals.d.ts +2 -1
  6. package/dist/activities/classify-tool-approvals.js +28 -2
  7. package/dist/activities/classify-tool-approvals.js.map +1 -1
  8. package/dist/activities/discover-mcp-server.d.ts +39 -0
  9. package/dist/activities/discover-mcp-server.js +165 -28
  10. package/dist/activities/discover-mcp-server.js.map +1 -1
  11. package/dist/activities/emit-event.d.ts +14 -2
  12. package/dist/activities/emit-event.js +52 -17
  13. package/dist/activities/emit-event.js.map +1 -1
  14. package/dist/activities/execute-cursor/__test-utils__/cursor-hook-harness.d.ts +8 -0
  15. package/dist/activities/execute-cursor/__test-utils__/cursor-hook-harness.js +1 -1
  16. package/dist/activities/execute-cursor/__test-utils__/cursor-hook-harness.js.map +1 -1
  17. package/dist/activities/execute-cursor/approval-state.d.ts +28 -2
  18. package/dist/activities/execute-cursor/approval-state.js +7 -1
  19. package/dist/activities/execute-cursor/approval-state.js.map +1 -1
  20. package/dist/activities/execute-cursor/attachment-resolver.d.ts +14 -0
  21. package/dist/activities/execute-cursor/attachment-resolver.js +18 -4
  22. package/dist/activities/execute-cursor/attachment-resolver.js.map +1 -1
  23. package/dist/activities/execute-cursor/blueprint-resolver.d.ts +1 -9
  24. package/dist/activities/execute-cursor/blueprint-resolver.js +6 -22
  25. package/dist/activities/execute-cursor/blueprint-resolver.js.map +1 -1
  26. package/dist/activities/execute-cursor/env-resolver.js +3 -1
  27. package/dist/activities/execute-cursor/env-resolver.js.map +1 -1
  28. package/dist/activities/execute-cursor/error-classifier.d.ts +59 -3
  29. package/dist/activities/execute-cursor/error-classifier.js +115 -6
  30. package/dist/activities/execute-cursor/error-classifier.js.map +1 -1
  31. package/dist/activities/execute-cursor/extract-structured-output.d.ts +29 -0
  32. package/dist/activities/execute-cursor/extract-structured-output.js +58 -0
  33. package/dist/activities/execute-cursor/extract-structured-output.js.map +1 -0
  34. package/dist/activities/execute-cursor/hook-script.d.ts +14 -3
  35. package/dist/activities/execute-cursor/hook-script.js +72 -10
  36. package/dist/activities/execute-cursor/hook-script.js.map +1 -1
  37. package/dist/activities/execute-cursor/index.d.ts +51 -11
  38. package/dist/activities/execute-cursor/index.js +177 -93
  39. package/dist/activities/execute-cursor/index.js.map +1 -1
  40. package/dist/activities/execute-cursor/mcp-resolver.d.ts +24 -1
  41. package/dist/activities/execute-cursor/mcp-resolver.js +5 -2
  42. package/dist/activities/execute-cursor/mcp-resolver.js.map +1 -1
  43. package/dist/activities/execute-cursor/model-pricing-data.d.ts +2 -0
  44. package/dist/activities/execute-cursor/model-pricing-data.js +13 -3
  45. package/dist/activities/execute-cursor/model-pricing-data.js.map +1 -1
  46. package/dist/activities/execute-cursor/prompt-builder.d.ts +51 -4
  47. package/dist/activities/execute-cursor/prompt-builder.js +49 -7
  48. package/dist/activities/execute-cursor/prompt-builder.js.map +1 -1
  49. package/dist/activities/execute-cursor/turn-recovery.d.ts +52 -0
  50. package/dist/activities/execute-cursor/turn-recovery.js +193 -0
  51. package/dist/activities/execute-cursor/turn-recovery.js.map +1 -0
  52. package/dist/activities/execute-cursor/turn-stream.js +4 -1
  53. package/dist/activities/execute-cursor/turn-stream.js.map +1 -1
  54. package/dist/activities/execute-deep-agent/attachment-injector.d.ts +18 -1
  55. package/dist/activities/execute-deep-agent/attachment-injector.js +68 -23
  56. package/dist/activities/execute-deep-agent/attachment-injector.js.map +1 -1
  57. package/dist/activities/execute-deep-agent/environment.js +3 -1
  58. package/dist/activities/execute-deep-agent/environment.js.map +1 -1
  59. package/dist/activities/execute-deep-agent/index.js +15 -0
  60. package/dist/activities/execute-deep-agent/index.js.map +1 -1
  61. package/dist/activities/execute-deep-agent/prompt-builder.d.ts +7 -7
  62. package/dist/activities/execute-deep-agent/prompt-builder.js +8 -2
  63. package/dist/activities/execute-deep-agent/prompt-builder.js.map +1 -1
  64. package/dist/activities/execute-deep-agent/setup.d.ts +10 -0
  65. package/dist/activities/execute-deep-agent/setup.js +65 -24
  66. package/dist/activities/execute-deep-agent/setup.js.map +1 -1
  67. package/dist/activities/execute-deep-agent/shell-env.d.ts +5 -1
  68. package/dist/activities/execute-deep-agent/shell-env.js +7 -9
  69. package/dist/activities/execute-deep-agent/shell-env.js.map +1 -1
  70. package/dist/activities/execute-deep-agent/subagent-transformer.d.ts +18 -1
  71. package/dist/activities/execute-deep-agent/subagent-transformer.js +15 -2
  72. package/dist/activities/execute-deep-agent/subagent-transformer.js.map +1 -1
  73. package/dist/activities/execute-deep-agent/subagent-wiring.d.ts +27 -6
  74. package/dist/activities/execute-deep-agent/subagent-wiring.js +26 -5
  75. package/dist/activities/execute-deep-agent/subagent-wiring.js.map +1 -1
  76. package/dist/activities/hydrate-workflow-execution.js +8 -3
  77. package/dist/activities/hydrate-workflow-execution.js.map +1 -1
  78. package/dist/activities/run-command.d.ts +5 -2
  79. package/dist/activities/run-command.js +22 -10
  80. package/dist/activities/run-command.js.map +1 -1
  81. package/dist/activities/run-env.d.ts +40 -0
  82. package/dist/activities/run-env.js +66 -0
  83. package/dist/activities/run-env.js.map +1 -0
  84. package/dist/activities/workflow-event-activities.d.ts +28 -10
  85. package/dist/activities/workflow-event-activities.js +87 -58
  86. package/dist/activities/workflow-event-activities.js.map +1 -1
  87. package/dist/bootstrap.js +5 -0
  88. package/dist/bootstrap.js.map +1 -1
  89. package/dist/claimcheck/payload-codec.js +21 -1
  90. package/dist/claimcheck/payload-codec.js.map +1 -1
  91. package/dist/client/stigmer-client.d.ts +83 -21
  92. package/dist/client/stigmer-client.js +106 -29
  93. package/dist/client/stigmer-client.js.map +1 -1
  94. package/dist/encryption/config.d.ts +64 -0
  95. package/dist/encryption/config.js +108 -0
  96. package/dist/encryption/config.js.map +1 -0
  97. package/dist/encryption/index.d.ts +3 -0
  98. package/dist/encryption/index.js +3 -0
  99. package/dist/encryption/index.js.map +1 -0
  100. package/dist/encryption/payload-codec.d.ts +41 -0
  101. package/dist/encryption/payload-codec.js +130 -0
  102. package/dist/encryption/payload-codec.js.map +1 -0
  103. package/dist/middleware/index.d.ts +3 -0
  104. package/dist/middleware/index.js +7 -0
  105. package/dist/middleware/index.js.map +1 -1
  106. package/dist/middleware/path-normalization.d.ts +57 -0
  107. package/dist/middleware/path-normalization.js +109 -0
  108. package/dist/middleware/path-normalization.js.map +1 -0
  109. package/dist/middleware/types.d.ts +8 -0
  110. package/dist/payload-codecs.d.ts +17 -0
  111. package/dist/payload-codecs.js +42 -0
  112. package/dist/payload-codecs.js.map +1 -0
  113. package/dist/preflight.d.ts +31 -0
  114. package/dist/preflight.js +43 -0
  115. package/dist/preflight.js.map +1 -1
  116. package/dist/runner-manager.js +21 -17
  117. package/dist/runner-manager.js.map +1 -1
  118. package/dist/runner.js +11 -18
  119. package/dist/runner.js.map +1 -1
  120. package/dist/shared/approval-policy.d.ts +9 -3
  121. package/dist/shared/approval-policy.js +15 -6
  122. package/dist/shared/approval-policy.js.map +1 -1
  123. package/dist/shared/artifact-storage.d.ts +23 -1
  124. package/dist/shared/artifact-storage.js +51 -10
  125. package/dist/shared/artifact-storage.js.map +1 -1
  126. package/dist/shared/attachment-naming.d.ts +53 -0
  127. package/dist/shared/attachment-naming.js +59 -0
  128. package/dist/shared/attachment-naming.js.map +1 -0
  129. package/dist/shared/caller-identity.d.ts +23 -2
  130. package/dist/shared/caller-identity.js +36 -5
  131. package/dist/shared/caller-identity.js.map +1 -1
  132. package/dist/shared/channel-attachment.js +1 -0
  133. package/dist/shared/channel-attachment.js.map +1 -1
  134. package/dist/shared/checkpointer/http-saver.d.ts +26 -1
  135. package/dist/shared/checkpointer/http-saver.js +54 -8
  136. package/dist/shared/checkpointer/http-saver.js.map +1 -1
  137. package/dist/shared/conversation-attachment.js +1 -0
  138. package/dist/shared/conversation-attachment.js.map +1 -1
  139. package/dist/shared/conversation-catchup.d.ts +9 -2
  140. package/dist/shared/conversation-catchup.js +39 -6
  141. package/dist/shared/conversation-catchup.js.map +1 -1
  142. package/dist/shared/datastore-attachment.d.ts +50 -7
  143. package/dist/shared/datastore-attachment.js +93 -11
  144. package/dist/shared/datastore-attachment.js.map +1 -1
  145. package/dist/shared/grpc-retry.d.ts +5 -1
  146. package/dist/shared/grpc-retry.js +5 -1
  147. package/dist/shared/grpc-retry.js.map +1 -1
  148. package/dist/shared/http-retry.d.ts +85 -0
  149. package/dist/shared/http-retry.js +101 -0
  150. package/dist/shared/http-retry.js.map +1 -0
  151. package/dist/shared/llm-backend.d.ts +275 -0
  152. package/dist/shared/llm-backend.js +425 -0
  153. package/dist/shared/llm-backend.js.map +1 -0
  154. package/dist/shared/llm-proxy.d.ts +8 -0
  155. package/dist/shared/llm-proxy.js +15 -0
  156. package/dist/shared/llm-proxy.js.map +1 -1
  157. package/dist/shared/mcp-enabled-tools.d.ts +57 -0
  158. package/dist/shared/mcp-enabled-tools.js +86 -0
  159. package/dist/shared/mcp-enabled-tools.js.map +1 -0
  160. package/dist/shared/mcp-manager.d.ts +11 -5
  161. package/dist/shared/mcp-manager.js +47 -8
  162. package/dist/shared/mcp-manager.js.map +1 -1
  163. package/dist/shared/mcp-resolver.d.ts +39 -2
  164. package/dist/shared/mcp-resolver.js +38 -2
  165. package/dist/shared/mcp-resolver.js.map +1 -1
  166. package/dist/shared/mcp-schema-sanitizer.d.ts +70 -0
  167. package/dist/shared/mcp-schema-sanitizer.js +197 -0
  168. package/dist/shared/mcp-schema-sanitizer.js.map +1 -0
  169. package/dist/shared/model-client.d.ts +18 -5
  170. package/dist/shared/model-client.js +149 -18
  171. package/dist/shared/model-client.js.map +1 -1
  172. package/dist/shared/model-error.js +198 -5
  173. package/dist/shared/model-error.js.map +1 -1
  174. package/dist/shared/model-pricing-data.d.ts +2 -0
  175. package/dist/shared/model-pricing-data.js +13 -3
  176. package/dist/shared/model-pricing-data.js.map +1 -1
  177. package/dist/shared/model-registry.js +3 -2
  178. package/dist/shared/model-registry.js.map +1 -1
  179. package/dist/shared/plan-mode-permissions.d.ts +33 -0
  180. package/dist/shared/plan-mode-permissions.js +35 -0
  181. package/dist/shared/plan-mode-permissions.js.map +1 -0
  182. package/dist/shared/registry-endpoint.d.ts +20 -0
  183. package/dist/shared/registry-endpoint.js +25 -0
  184. package/dist/shared/registry-endpoint.js.map +1 -1
  185. package/dist/shared/runner-credential-keys.d.ts +24 -0
  186. package/dist/shared/runner-credential-keys.js +47 -0
  187. package/dist/shared/runner-credential-keys.js.map +1 -0
  188. package/dist/worker.d.ts +2 -1
  189. package/dist/worker.js +2 -4
  190. package/dist/worker.js.map +1 -1
  191. package/dist/workflow-engine/resolve.d.ts +19 -1
  192. package/dist/workflow-engine/resolve.js +37 -2
  193. package/dist/workflow-engine/resolve.js.map +1 -1
  194. package/dist/workflow-engine/types.d.ts +18 -0
  195. package/dist/workflow-engine/types.js.map +1 -1
  196. package/dist/workflows/call-agent-orchestrator.d.ts +9 -0
  197. package/dist/workflows/call-agent-orchestrator.js +1 -0
  198. package/dist/workflows/call-agent-orchestrator.js.map +1 -1
  199. package/dist/workflows/connect-mcp-server.js +9 -0
  200. package/dist/workflows/connect-mcp-server.js.map +1 -1
  201. package/dist/workflows/engine-core.js +23 -2
  202. package/dist/workflows/engine-core.js.map +1 -1
  203. package/dist/workflows/execute-from-execution.d.ts +1 -1
  204. package/dist/workflows/execute-from-execution.js +11 -1
  205. package/dist/workflows/execute-from-execution.js.map +1 -1
  206. package/dist/workflows/types.d.ts +8 -0
  207. package/package.json +9 -4
  208. package/src/__test-utils__/__tests__/vitest-global-setup.test.ts +37 -0
  209. package/src/__test-utils__/vitest-global-setup.ts +50 -0
  210. package/src/__tests__/bootstrap.test.ts +43 -0
  211. package/src/__tests__/claimcheck-codec.test.ts +36 -0
  212. package/src/__tests__/encryption-codec.test.ts +287 -0
  213. package/src/__tests__/fixtures/encrypted-payload-fixture.json +15 -0
  214. package/src/__tests__/history-encryption-e2e.test.ts +243 -0
  215. package/src/__tests__/preflight.test.ts +65 -2
  216. package/src/activities/__tests__/call-llm.test.ts +75 -0
  217. package/src/activities/__tests__/classify-tool-approvals.test.ts +117 -1
  218. package/src/activities/__tests__/discover-mcp-server.hang.test.ts +103 -0
  219. package/src/activities/__tests__/discover-mcp-server.test.ts +242 -1
  220. package/src/activities/__tests__/error-classifier.test.ts +94 -2
  221. package/src/activities/__tests__/run-command.test.ts +221 -0
  222. package/src/activities/__tests__/workflow-event-activities.test.ts +107 -8
  223. package/src/activities/call-llm.ts +9 -16
  224. package/src/activities/classify-tool-approvals.ts +34 -4
  225. package/src/activities/discover-mcp-server.ts +203 -31
  226. package/src/activities/emit-event.ts +65 -21
  227. package/src/activities/execute-cursor/__test-utils__/cursor-hook-harness.ts +9 -0
  228. package/src/activities/execute-cursor/__tests__/approval-gate.test.ts +14 -0
  229. package/src/activities/execute-cursor/__tests__/attachment-resolver.test.ts +53 -0
  230. package/src/activities/execute-cursor/__tests__/build-prompt.test.ts +151 -16
  231. package/src/activities/execute-cursor/__tests__/error-classifier-extraction.test.ts +208 -0
  232. package/src/activities/execute-cursor/__tests__/extract-structured-output.test.ts +120 -0
  233. package/src/activities/execute-cursor/__tests__/hook-script.test.ts +93 -0
  234. package/src/activities/execute-cursor/__tests__/mcp-resolver.test.ts +125 -0
  235. package/src/activities/execute-cursor/__tests__/model-pricing.test.ts +62 -1
  236. package/src/activities/execute-cursor/__tests__/prompt-builder-delegation.test.ts +1 -1
  237. package/src/activities/execute-cursor/__tests__/turn-recovery.test.ts +160 -0
  238. package/src/activities/execute-cursor/__tests__/turn-stream.test.ts +13 -0
  239. package/src/activities/execute-cursor/approval-state.ts +30 -1
  240. package/src/activities/execute-cursor/attachment-resolver.ts +31 -3
  241. package/src/activities/execute-cursor/blueprint-resolver.ts +7 -27
  242. package/src/activities/execute-cursor/env-resolver.ts +3 -1
  243. package/src/activities/execute-cursor/error-classifier.ts +131 -7
  244. package/src/activities/execute-cursor/extract-structured-output.ts +72 -0
  245. package/src/activities/execute-cursor/hook-script.ts +74 -10
  246. package/src/activities/execute-cursor/index.ts +209 -109
  247. package/src/activities/execute-cursor/mcp-resolver.ts +36 -2
  248. package/src/activities/execute-cursor/model-pricing-data.ts +22 -3
  249. package/src/activities/execute-cursor/prompt-builder.ts +85 -9
  250. package/src/activities/execute-cursor/turn-recovery.ts +208 -0
  251. package/src/activities/execute-cursor/turn-stream.ts +5 -2
  252. package/src/activities/execute-deep-agent/__tests__/attachment-injector.test.ts +110 -8
  253. package/src/activities/execute-deep-agent/__tests__/datastore-degradation.test.ts +104 -0
  254. package/src/activities/execute-deep-agent/__tests__/hitl-reject.test.ts +2 -0
  255. package/src/activities/execute-deep-agent/__tests__/hitl-resume-approve-all.test.ts +1 -0
  256. package/src/activities/execute-deep-agent/__tests__/hitl-resume-history.test.ts +1 -0
  257. package/src/activities/execute-deep-agent/__tests__/plan-mode-path-normalization.test.ts +174 -0
  258. package/src/activities/execute-deep-agent/__tests__/prompt-builder.test.ts +34 -5
  259. package/src/activities/execute-deep-agent/__tests__/sequential-gate-resume.test.ts +1 -0
  260. package/src/activities/execute-deep-agent/__tests__/shell-env.test.ts +24 -10
  261. package/src/activities/execute-deep-agent/__tests__/subagent-plan-mode-permissions.test.ts +178 -0
  262. package/src/activities/execute-deep-agent/__tests__/subagent-wiring.test.ts +12 -7
  263. package/src/activities/execute-deep-agent/attachment-injector.ts +94 -30
  264. package/src/activities/execute-deep-agent/environment.ts +3 -1
  265. package/src/activities/execute-deep-agent/index.ts +20 -0
  266. package/src/activities/execute-deep-agent/prompt-builder.ts +20 -10
  267. package/src/activities/execute-deep-agent/setup.ts +85 -29
  268. package/src/activities/execute-deep-agent/shell-env.ts +8 -9
  269. package/src/activities/execute-deep-agent/subagent-transformer.ts +30 -2
  270. package/src/activities/execute-deep-agent/subagent-wiring.ts +39 -6
  271. package/src/activities/hydrate-workflow-execution.ts +8 -3
  272. package/src/activities/run-command.ts +25 -12
  273. package/src/activities/run-env.ts +79 -0
  274. package/src/activities/workflow-event-activities.ts +96 -69
  275. package/src/bootstrap.ts +5 -0
  276. package/src/claimcheck/payload-codec.ts +33 -1
  277. package/src/client/__tests__/stigmer-client.test.ts +110 -11
  278. package/src/client/stigmer-client.ts +150 -37
  279. package/src/encryption/config.ts +155 -0
  280. package/src/encryption/index.ts +3 -0
  281. package/src/encryption/payload-codec.ts +152 -0
  282. package/src/middleware/__tests__/path-normalization.test.ts +140 -0
  283. package/src/middleware/index.ts +8 -0
  284. package/src/middleware/path-normalization.ts +125 -0
  285. package/src/middleware/types.ts +11 -0
  286. package/src/payload-codecs.ts +62 -0
  287. package/src/preflight.ts +45 -0
  288. package/src/runner-manager.ts +26 -26
  289. package/src/runner.ts +12 -27
  290. package/src/shared/__tests__/approval-policy.test.ts +82 -39
  291. package/src/shared/__tests__/artifact-storage.test.ts +145 -2
  292. package/src/shared/__tests__/attachment-naming.test.ts +159 -0
  293. package/src/shared/__tests__/bedrock-adapter.test.ts +213 -0
  294. package/src/shared/__tests__/bedrock-seam.test.ts +390 -0
  295. package/src/shared/__tests__/caller-identity.test.ts +25 -0
  296. package/src/shared/__tests__/channel-attachment.test.ts +1 -1
  297. package/src/shared/__tests__/connect-backfill.test.ts +1 -0
  298. package/src/shared/__tests__/conversation-attachment.test.ts +1 -1
  299. package/src/shared/__tests__/conversation-catchup.test.ts +24 -0
  300. package/src/shared/__tests__/datastore-attachment.test.ts +129 -1
  301. package/src/shared/__tests__/foundry-adapter.test.ts +276 -0
  302. package/src/shared/__tests__/foundry-seam.test.ts +482 -0
  303. package/src/shared/__tests__/http-retry.test.ts +68 -0
  304. package/src/shared/__tests__/llm-backend.test.ts +616 -0
  305. package/src/shared/__tests__/mcp-enabled-tools.test.ts +86 -0
  306. package/src/shared/__tests__/mcp-manager.test.ts +137 -15
  307. package/src/shared/__tests__/mcp-resolver.test.ts +146 -3
  308. package/src/shared/__tests__/mcp-schema-sanitizer.test.ts +267 -0
  309. package/src/shared/__tests__/model-client.test.ts +200 -0
  310. package/src/shared/__tests__/model-error.test.ts +289 -1
  311. package/src/shared/__tests__/model-pricing.test.ts +62 -2
  312. package/src/shared/__tests__/model-registry.test.ts +22 -0
  313. package/src/shared/__tests__/synthesized-attachment.test.ts +1 -0
  314. package/src/shared/__tests__/vertex-adapter.test.ts +169 -0
  315. package/src/shared/__tests__/vertex-seam.test.ts +295 -0
  316. package/src/shared/approval-policy.ts +14 -7
  317. package/src/shared/artifact-storage.ts +75 -10
  318. package/src/shared/attachment-naming.ts +78 -0
  319. package/src/shared/caller-identity.ts +40 -5
  320. package/src/shared/channel-attachment.ts +1 -0
  321. package/src/shared/checkpointer/__tests__/http-saver.test.ts +196 -1
  322. package/src/shared/checkpointer/http-saver.ts +71 -8
  323. package/src/shared/conversation-attachment.ts +1 -0
  324. package/src/shared/conversation-catchup.ts +39 -6
  325. package/src/shared/datastore-attachment.ts +106 -11
  326. package/src/shared/grpc-retry.ts +5 -1
  327. package/src/shared/http-retry.ts +139 -0
  328. package/src/shared/llm-backend.ts +544 -0
  329. package/src/shared/llm-proxy.ts +15 -0
  330. package/src/shared/mcp-enabled-tools.ts +105 -0
  331. package/src/shared/mcp-manager.ts +56 -8
  332. package/src/shared/mcp-resolver.ts +73 -2
  333. package/src/shared/mcp-schema-sanitizer.ts +224 -0
  334. package/src/shared/model-client.ts +179 -19
  335. package/src/shared/model-error.ts +222 -4
  336. package/src/shared/model-pricing-data.ts +22 -3
  337. package/src/shared/model-registry.ts +7 -2
  338. package/src/shared/plan-mode-permissions.ts +37 -0
  339. package/src/shared/registry-endpoint.ts +27 -0
  340. package/src/shared/runner-credential-keys.ts +46 -0
  341. package/src/worker.ts +4 -5
  342. package/src/workflow-engine/__tests__/tasks/emit-event.test.ts +127 -49
  343. package/src/workflow-engine/resolve.ts +48 -2
  344. package/src/workflow-engine/types.ts +18 -0
  345. package/src/workflows/__tests__/connect-mcp-server.test.ts +25 -0
  346. package/src/workflows/__tests__/execute-serverless-workflow.test.ts +68 -2
  347. package/src/workflows/call-agent-orchestrator.ts +10 -0
  348. package/src/workflows/connect-mcp-server.ts +9 -0
  349. package/src/workflows/engine-core.ts +23 -2
  350. package/src/workflows/execute-from-execution.ts +12 -2
  351. package/src/workflows/types.ts +8 -0
  352. package/dist/activities/execute-cursor/mcp-config.d.ts +0 -30
  353. package/dist/activities/execute-cursor/mcp-config.js +0 -39
  354. package/dist/activities/execute-cursor/mcp-config.js.map +0 -1
  355. package/src/activities/execute-cursor/mcp-config.ts +0 -66
@@ -12,14 +12,22 @@
12
12
  * Error model: fail-hard. Any attachment failure aborts the entire injection
13
13
  * and propagates a descriptive error. Attachments are explicit user inputs —
14
14
  * running with partial inputs produces silently incorrect results.
15
+ *
16
+ * Duplicate names are NOT a failure (issue #364): a default-derived mount
17
+ * path that collides is renamed with the platform's `stem-2.ext` semantics
18
+ * (shared/attachment-naming.ts) and the rename is disclosed in the prompt's
19
+ * Input Files section. Only two attachments EXPLICITLY pinning the same
20
+ * `mountPath` still abort — that is a user contradiction no rename can
21
+ * honestly resolve.
15
22
  */
16
23
 
17
24
  import { readFile } from "node:fs/promises";
18
25
  import { createInflateRaw } from "node:zlib";
19
- import { basename, posix } from "node:path";
26
+ import { posix } from "node:path";
20
27
  import type { Attachment } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/spec_pb";
21
28
  import type { WorkspaceBackend } from "../../shared/workspace/types.js";
22
29
  import type { ArtifactStorage } from "../../shared/artifact-storage.js";
30
+ import { allocateUniqueName } from "../../shared/attachment-naming.js";
23
31
  import type {
24
32
  VisionBudget,
25
33
  VisionDegradedReason,
@@ -38,9 +46,17 @@ export { MAX_ZIP_FILES, MAX_ZIP_EXTRACTED_SIZE };
38
46
  // ── Types ────────────────────────────────────────────────────────────
39
47
 
40
48
  export interface InjectedFile {
49
+ /** The final on-disk basename — after any duplicate rename, so it always
50
+ * agrees with {@link path} and with the vision payload's image label. */
41
51
  readonly filename: string;
42
52
  readonly path: string;
43
53
  readonly sizeBytes: number;
54
+ /**
55
+ * The attachment's original filename, present only when a duplicate name
56
+ * was renamed (shared/attachment-naming.ts) — rendered as disclosure in
57
+ * the prompt's Input Files section.
58
+ */
59
+ readonly renamedFrom?: string;
44
60
  /** Present when the attachment was accepted into the turn's vision payload. */
45
61
  readonly vision?: VisionImage;
46
62
  /**
@@ -257,7 +273,9 @@ export function validateZipForExtraction(
257
273
  /**
258
274
  * Download, validate, and inject all attachments into the workspace.
259
275
  *
260
- * Performs mount path collision detection before any downloads begin.
276
+ * Resolves all mount paths before any downloads begin: duplicate
277
+ * default-derived names are renamed (never fatal — see module doc), and an
278
+ * explicit-mountPath contradiction throws before any bytes move.
261
279
  * On any failure, throws AttachmentInjectionError with an actionable message.
262
280
  */
263
281
  export async function injectAttachments(opts: InjectAttachmentsOptions): Promise<InjectedFile[]> {
@@ -273,17 +291,24 @@ export async function injectAttachments(opts: InjectAttachmentsOptions): Promise
273
291
 
274
292
  for (const attachment of attachments) {
275
293
  const content = await downloadAttachment(attachment, storage, isLocalMode);
276
- const mountPath = mountPaths.get(attachment)!;
294
+ const { path: mountPath, renamedFrom } = mountPaths.get(attachment)!;
277
295
 
278
296
  if (attachment.extract) {
279
297
  const entries = validateZipForExtraction(content, attachment.filename);
280
298
  const extracted = await extractZipToWorkspace(
281
299
  content, entries, mountPath, backend,
282
300
  );
301
+ // A renamed mount DIR is visible through every extracted path; the
302
+ // entries themselves were not renamed, so they carry no renamedFrom
303
+ // (per-file disclosure would misattribute the rename).
283
304
  injectedFiles.push(...extracted);
284
305
  } else {
285
306
  await backend.writeFileBuffer(mountPath, content);
286
- const filename = attachment.filename || basename(mountPath);
307
+ // The final basename is the canonical filename: after a duplicate
308
+ // rename the original attachment.filename no longer names the file on
309
+ // disk, and the vision label must match what the prompt lists or the
310
+ // agent sees two images with one indistinguishable name.
311
+ const filename = posix.basename(mountPath);
287
312
  // The bytes are already in hand for the workspace write — offer them to
288
313
  // the vision budget before they go out of scope (the sniff decides
289
314
  // eligibility; the budget owns every size/count rule).
@@ -292,6 +317,7 @@ export async function injectAttachments(opts: InjectAttachmentsOptions): Promise
292
317
  filename,
293
318
  path: mountPath,
294
319
  sizeBytes: content.length,
320
+ ...(renamedFrom !== undefined ? { renamedFrom } : {}),
295
321
  ...(vision?.kind === "accepted" ? { vision: vision.image } : {}),
296
322
  ...(vision?.kind === "degraded" ? { visionDegraded: vision.reason } : {}),
297
323
  });
@@ -307,14 +333,24 @@ export async function injectAttachments(opts: InjectAttachmentsOptions): Promise
307
333
 
308
334
  // ── Internal Helpers ─────────────────────────────────────────────────
309
335
 
336
+ interface ResolvedMountPath {
337
+ readonly path: string;
338
+ /** Present when a default-derived name was uniquified (issue #364). */
339
+ readonly renamedFrom?: string;
340
+ }
341
+
310
342
  function resolveMountPaths(
311
343
  attachments: readonly Attachment[],
312
- ): Map<Attachment, string> {
313
- const result = new Map<Attachment, string>();
344
+ ): Map<Attachment, ResolvedMountPath> {
345
+ const result = new Map<Attachment, ResolvedMountPath>();
314
346
  const pathToAttachment = new Map<string, Attachment>();
315
347
 
348
+ // Pass 1: explicit mount paths claim their exact targets first. Two
349
+ // attachments explicitly pinning the SAME path is a user contradiction no
350
+ // rename can honestly resolve — keep rejecting with the actionable message.
316
351
  for (const attachment of attachments) {
317
- const mountPath = resolveMountPath(attachment);
352
+ if (!attachment.mountPath) continue;
353
+ const mountPath = resolveExplicitMountPath(attachment);
318
354
  const existing = pathToAttachment.get(mountPath);
319
355
 
320
356
  if (existing) {
@@ -326,35 +362,63 @@ function resolveMountPaths(
326
362
  }
327
363
 
328
364
  pathToAttachment.set(mountPath, attachment);
329
- result.set(attachment, mountPath);
365
+ result.set(attachment, { path: mountPath });
366
+ }
367
+
368
+ // Pass 2: default-derived names (`.stigmer/inputs/{filename}`) uniquify
369
+ // around everything already taken — other defaults AND explicit paths that
370
+ // landed inside the inputs prefix — instead of failing the execution
371
+ // (issue #364). The taken-set is seeded with the basenames the explicit
372
+ // pass claimed directly under the prefix.
373
+ const takenNames = new Set<string>();
374
+ for (const path of pathToAttachment.keys()) {
375
+ if (path.startsWith(`${DEFAULT_INPUTS_PREFIX}/`)) {
376
+ const rest = path.slice(DEFAULT_INPUTS_PREFIX.length + 1);
377
+ if (rest.length > 0 && !rest.includes("/")) takenNames.add(rest);
378
+ }
379
+ }
380
+ for (const attachment of attachments) {
381
+ if (attachment.mountPath) continue;
382
+ const { name, renamedFrom } = allocateUniqueName(
383
+ deriveDefaultFilename(attachment),
384
+ takenNames,
385
+ );
386
+ const path = `${DEFAULT_INPUTS_PREFIX}/${name}`;
387
+ pathToAttachment.set(path, attachment);
388
+ result.set(
389
+ attachment,
390
+ renamedFrom !== undefined ? { path, renamedFrom } : { path },
391
+ );
330
392
  }
331
393
 
332
394
  return result;
333
395
  }
334
396
 
335
- function resolveMountPath(attachment: Attachment): string {
336
- if (attachment.mountPath) {
337
- const cleaned = attachment.mountPath.replace(/^\/+/, "");
338
- if (cleaned.length === 0) {
339
- throw new AttachmentInjectionError(
340
- attachment.filename,
341
- "mountPath resolves to an empty path after removing leading slashes",
342
- );
343
- }
344
- // A caller-supplied mount path is untrusted. Stripping leading slashes does
345
- // not stop `..` segments from climbing out of the workspace root on the
346
- // non-`.stigmer/` branch (the `.stigmer/`-routed branch is already guarded
347
- // by LocalWorkspaceBackend.resolvePath). Reject any path that normalizes to
348
- // an escape before it reaches the backend write.
349
- if (escapesRoot(cleaned)) {
350
- throw new AttachmentInjectionError(
351
- attachment.filename,
352
- `mount path '${attachment.mountPath}' escapes the workspace root`,
353
- );
354
- }
355
- return cleaned;
397
+ function resolveExplicitMountPath(attachment: Attachment): string {
398
+ const cleaned = attachment.mountPath.replace(/^\/+/, "");
399
+ if (cleaned.length === 0) {
400
+ throw new AttachmentInjectionError(
401
+ attachment.filename,
402
+ "mountPath resolves to an empty path after removing leading slashes",
403
+ );
356
404
  }
405
+ // A caller-supplied mount path is untrusted. Stripping leading slashes does
406
+ // not stop `..` segments from climbing out of the workspace root on the
407
+ // non-`.stigmer/` branch (the `.stigmer/`-routed branch is already guarded
408
+ // by LocalWorkspaceBackend.resolvePath). Reject any path that normalizes to
409
+ // an escape before it reaches the backend write.
410
+ if (escapesRoot(cleaned)) {
411
+ throw new AttachmentInjectionError(
412
+ attachment.filename,
413
+ `mount path '${attachment.mountPath}' escapes the workspace root`,
414
+ );
415
+ }
416
+ return cleaned;
417
+ }
357
418
 
419
+ // Derives the single-component filename an attachment without an explicit
420
+ // mountPath materializes under (the name that pass 2 uniquifies).
421
+ function deriveDefaultFilename(attachment: Attachment): string {
358
422
  const rawName = attachment.filename || deriveFilename(attachment.storageKey);
359
423
  if (!rawName) {
360
424
  throw new AttachmentInjectionError(
@@ -373,7 +437,7 @@ function resolveMountPath(attachment: Attachment): string {
373
437
  );
374
438
  }
375
439
 
376
- return `${DEFAULT_INPUTS_PREFIX}/${filename}`;
440
+ return filename;
377
441
  }
378
442
 
379
443
  // escapesRoot reports whether a workspace-relative mount path would climb out
@@ -30,7 +30,9 @@ export async function resolveEnvironment(
30
30
  ): Promise<EnvironmentResult> {
31
31
  // A desktop runner exchanges its bootstrap credential for a token scoped to
32
32
  // this execution's session, so cloud's decrypt gate binds the read (#156).
33
- // No-op for cloud sandbox and OSS runners.
33
+ // No-op for cloud sandbox and OSS runners. A failed exchange throws and
34
+ // fails the activity: the bootstrap credential no longer decrypts
35
+ // (stigmer-cloud#218), so proceeding would resolve redacted placeholders.
34
36
  const scopedToken = await client.acquireScopedRunnerToken({
35
37
  agentExecutionId: executionId,
36
38
  });
@@ -32,6 +32,7 @@ import {
32
32
  } from "../../shared/workspace/stigmer-link.js";
33
33
  import type { ToolOutputOffloadContext } from "../../shared/status-offload.js";
34
34
  import { publishPlanArtifact } from "../../shared/plan-artifact.js";
35
+ import { formatDatastoreDegradationNotice } from "../../shared/datastore-attachment.js";
35
36
  import { classifyTool } from "../../shared/tool-kind.js";
36
37
  import {
37
38
  POLICY_ENGINE_VERSION,
@@ -151,6 +152,25 @@ export function createDeepAgentActivities(config: Config) {
151
152
  const initialStatus = shouldSeedFromPersistedTranscript(setup.execution)
152
153
  ? seedStatusFromExecution(setup.execution)
153
154
  : create(AgentExecutionStatusSchema, {});
155
+
156
+ // Datastore degradation disclosure, operator half (issue #325): the
157
+ // prompt already tells the agent to disclose the outage; this makes
158
+ // it visible on the execution record without reading a transcript.
159
+ // Appended to initialStatus BEFORE streaming (not via a status
160
+ // builder): both the v2 and v3 stream paths construct their own
161
+ // builder around this same proto and only ever append messages, so
162
+ // this row survives whichever path runs and rides every persist.
163
+ if (setup.datastoreToolsMissing.length > 0) {
164
+ initialStatus.messages.push(create(AgentMessageSchema, {
165
+ type: MessageType.MESSAGE_SYSTEM,
166
+ content: formatDatastoreDegradationNotice(
167
+ setup.agent.spec?.datastoreUsages?.length ?? 0,
168
+ setup.datastoreToolsMissing,
169
+ ),
170
+ timestamp: utcTimestamp(),
171
+ }));
172
+ }
173
+
154
174
  const statusBuilder = new StatusBuilder(executionId, initialStatus);
155
175
 
156
176
  statusBuilder.setApprovalProvider({
@@ -26,6 +26,7 @@ import {
26
26
  buildImplementPlanDirective,
27
27
  findApprovedPlanPath,
28
28
  } from "../../shared/implement-plan-prompt.js";
29
+ import type { InjectedFile } from "./attachment-injector.js";
29
30
 
30
31
  const RESPONSE_RULES = `
31
32
 
@@ -99,8 +100,11 @@ export interface PromptBuilderInput {
99
100
  containerRoot: string;
100
101
  skillsPromptSection: string;
101
102
  /**
102
- * The `<available_datastores>` section (shared/datastore-attachment.ts
103
- * formatDatastoresSection); empty when the agent uses no datastores.
103
+ * The datastores section (shared/datastore-attachment.ts
104
+ * formatDatastoresSection): `<available_datastores>` when the connected
105
+ * record-tool roster is complete, `<unavailable_datastores>` when setup's
106
+ * reconciliation found record tools missing (issue #325). Empty when the
107
+ * agent uses no datastores.
104
108
  */
105
109
  datastoresPromptSection?: string;
106
110
  /**
@@ -162,11 +166,10 @@ export interface PromptBuilderInput {
162
166
  sessionContext?: string;
163
167
  }
164
168
 
165
- export interface InjectedFile {
166
- filename: string;
167
- path: string;
168
- size?: number | null;
169
- }
169
+ // The prompt renders the injector's own result type — a local structural twin
170
+ // once lived here and silently dropped the size field (`size` vs `sizeBytes`),
171
+ // so the "(N bytes)" annotation never rendered. One type, one truth.
172
+ export type { InjectedFile } from "./attachment-injector.js";
170
173
 
171
174
  /**
172
175
  * Which images ride the user message inline (in send order) and which
@@ -396,7 +399,7 @@ function buildReferencedFilesSection(
396
399
  }
397
400
 
398
401
  function buildInjectedFilesSection(
399
- files: InjectedFile[],
402
+ files: readonly InjectedFile[],
400
403
  vision?: VisionPromptInfo,
401
404
  ): string {
402
405
  let section = "\n\n## Input Files\n\n";
@@ -410,8 +413,15 @@ function buildInjectedFilesSection(
410
413
  "Do NOT modify or delete these files.\n\n";
411
414
 
412
415
  for (const f of files) {
413
- const sizeInfo = f.size != null ? ` (${f.size} bytes)` : "";
414
- section += `- \`${f.path}\`${sizeInfo}\n`;
416
+ const sizeInfo = ` (${f.sizeBytes} bytes)`;
417
+ // A duplicate-renamed file (attachment-naming.ts) discloses its original
418
+ // name so the agent can connect "the two report.pdfs" in the user's
419
+ // message to distinct files on disk.
420
+ const renameInfo =
421
+ f.renamedFrom !== undefined
422
+ ? ` (renamed from duplicate '${f.renamedFrom}')`
423
+ : "";
424
+ section += `- \`${f.path}\`${sizeInfo}${renameInfo}\n`;
415
425
  }
416
426
 
417
427
  // The vision lines (shared wording, attachment-vision.ts) tell the model
@@ -8,7 +8,7 @@
8
8
  * the streaming phase needs.
9
9
  */
10
10
 
11
- import { createDeepAgent, type FilesystemPermission } from "deepagents";
11
+ import { createDeepAgent } from "deepagents";
12
12
  import { InteractionMode } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/enum_pb";
13
13
  import type { BaseCheckpointSaver } from "@langchain/langgraph-checkpoint";
14
14
  import { z } from "zod";
@@ -33,11 +33,13 @@ import {
33
33
  } from "../../shared/caller-identity.js";
34
34
  import { readSessionContext } from "../../shared/session-context.js";
35
35
  import { connectMcpServers, type McpConnectionResult } from "../../shared/mcp-manager.js";
36
- import { resolveMcpServers } from "../../shared/mcp-resolver.js";
36
+ import { mergeMcpServerUsages, resolveMcpServers } from "../../shared/mcp-resolver.js";
37
37
  import { resolveMcpTransportPosture } from "../../shared/mcp-transport-guard.js";
38
38
  import { backfillMcpServersIfNeeded } from "../../shared/connect-backfill.js";
39
39
  import {
40
+ DATASTORE_ATTACHMENT_SLUG,
40
41
  formatDatastoresSection,
42
+ missingRecordTools,
41
43
  synthesizeDatastoreAttachment,
42
44
  } from "../../shared/datastore-attachment.js";
43
45
  import {
@@ -56,6 +58,7 @@ import { LocalWorkspaceBackend } from "../../shared/workspace/local-backend.js";
56
58
  import type { WorkspaceBackend, ProvisionResult } from "../../shared/workspace/types.js";
57
59
  import { createCasCaptureBackend } from "./cas-capture-backend.js";
58
60
  import { buildShellEnv } from "./shell-env.js";
61
+ import { PLAN_MODE_PERMISSIONS } from "../../shared/plan-mode-permissions.js";
59
62
  import { CasCaptureObserver } from "./cas-capture-observer.js";
60
63
  import { isGitWorkTree, isPathCapturable } from "../../shared/filereview/git-substrate.js";
61
64
  import { deriveCaptureMode } from "../../shared/filereview/capture.js";
@@ -126,6 +129,16 @@ export interface SetupResult {
126
129
  readonly session: Session;
127
130
  readonly workspaceBackend: WorkspaceBackend;
128
131
  readonly mcpConnection: McpConnectionResult | null;
132
+ /**
133
+ * Record tools declared via datastore_usages but absent from the connected
134
+ * stigmer-records roster this turn (issue #325). Empty = healthy (also when
135
+ * the agent uses no datastores). Non-empty selects the degraded prompt
136
+ * section in Step 8, and the activity surfaces it to operators as a
137
+ * MESSAGE_SYSTEM row — the vision-facts pattern: setup computes the honest
138
+ * struct, the caller discloses. Recomputed every turn, so a healed outage
139
+ * stops disclosing on the next turn.
140
+ */
141
+ readonly datastoreToolsMissing: readonly string[];
129
142
  readonly mergedEnvVars: Record<string, string>;
130
143
  readonly secretKeys: ReadonlySet<string>;
131
144
  readonly modelName: string;
@@ -348,20 +361,36 @@ export async function performSetup(deps: SetupDependencies): Promise<SetupResult
348
361
  : async () => true,
349
362
  });
350
363
 
351
- // Step 7: Resolve and connect MCP servers
352
- const mcpServerUsages = [
353
- ...(agent.spec!.mcpServerUsages || []),
354
- ...(session.spec!.mcpServerUsages || []),
355
- ];
364
+ // Step 7: Resolve and connect MCP servers. Session-wins-per-slug merge
365
+ // (the Cursor harness's blueprint semantics, shared so a duplicate slug
366
+ // resolves identically in both harnesses — one usage per server, whose
367
+ // enabled_tools is the one the connect-time filter honors).
368
+ const mcpServerUsages = mergeMcpServerUsages(
369
+ agent.spec!.mcpServerUsages || [],
370
+ session.spec!.mcpServerUsages || [],
371
+ );
356
372
  const datastoreUsages = agent.spec!.datastoreUsages || [];
357
373
 
358
374
  // The synthesized attachments' credential story (DD-006 D4): the
359
375
  // exchanged token authenticates the discovery reads per-call (the
360
376
  // messaging reach refuses a desktop runner's ambient embedded_runner
361
377
  // credential; undefined lets the ambient credential apply). The
362
- // attachment header falls back to the ambient credential.
363
- const exchangedRunnerToken =
364
- await client.acquireScopedRunnerToken({ agentExecutionId: executionId });
378
+ // attachment header falls back to the ambient credential. Unlike the
379
+ // env read (which hard-fails on a broken exchange — secrets are
380
+ // load-bearing there), this exchange is opportunistic: every consumer
381
+ // below degrades to an empty answer by contract, and the server
382
+ // refuses the ambient fallback safely, so a failed exchange must not
383
+ // kill the run.
384
+ let exchangedRunnerToken: string | undefined;
385
+ try {
386
+ exchangedRunnerToken =
387
+ await client.acquireScopedRunnerToken({ agentExecutionId: executionId });
388
+ } catch (err) {
389
+ console.warn(
390
+ "[execute-deep-agent] Scoped-token exchange failed for attachment/discovery " +
391
+ `reads; degrading to the ambient credential: ${err instanceof Error ? err.message : err}`,
392
+ );
393
+ }
365
394
  const attachmentCredential = exchangedRunnerToken
366
395
  ?? config.stigmerTokenRef?.current
367
396
  ?? config.stigmerToken;
@@ -556,6 +585,28 @@ export async function performSetup(deps: SetupDependencies): Promise<SetupResult
556
585
  }
557
586
  timing.mark("inject_attachments");
558
587
 
588
+ // Reconcile the declared datastores against the record tools actually
589
+ // connected (issue #325): the records roster always serves all five
590
+ // tools, so any absence means the store is degraded, and the prompt
591
+ // must say so instead of promising tools the agent does not have. The
592
+ // connected roster is post-connect truth — when usages exist, MCP
593
+ // connect always ran above (a thrown connect already failed the run).
594
+ const datastoreToolsMissing = datastoreUsages.length > 0 && mcpConnection
595
+ ? missingRecordTools(
596
+ (mcpConnection.serverToolMap[DATASTORE_ATTACHMENT_SLUG] ?? [])
597
+ .map((t) => t.name),
598
+ )
599
+ : [];
600
+ if (datastoreToolsMissing.length > 0) {
601
+ console.warn(
602
+ `[setup] Datastore record tools degraded: declared ` +
603
+ `${datastoreUsages.length} datastore usage(s) but the ` +
604
+ `'${DATASTORE_ATTACHMENT_SLUG}' roster is missing ` +
605
+ `[${datastoreToolsMissing.join(", ")}] — the prompt discloses the ` +
606
+ `outage instead of the available-datastores section.`,
607
+ );
608
+ }
609
+
559
610
  // Step 8: Build enhanced system prompt
560
611
  const systemPrompt = buildEnhancedSystemPrompt({
561
612
  instructions,
@@ -563,7 +614,7 @@ export async function performSetup(deps: SetupDependencies): Promise<SetupResult
563
614
  containerRoot: workspaceBackend.rootDir,
564
615
  skillsPromptSection,
565
616
  datastoresPromptSection: datastoreUsages.length > 0
566
- ? formatDatastoresSection(datastoreUsages)
617
+ ? formatDatastoresSection(datastoreUsages, datastoreToolsMissing)
567
618
  : undefined,
568
619
  // "" (nothing sendable) threads as undefined: the tool alone still
569
620
  // serves text sends inside a 24-hour window (DD-006 D6).
@@ -584,14 +635,14 @@ export async function performSetup(deps: SetupDependencies): Promise<SetupResult
584
635
  // Step 9: Construct the LLM model. Resolution to the provider API id
585
636
  // happens inside buildChatModel; modelName stays the registry id for
586
637
  // pricing, the native-thinking heuristic, and sub-agent inheritance.
587
- const requestTimeoutMs =
588
- parseInt(process.env.STIGMER_LLM_REQUEST_TIMEOUT_MS ?? "0") || undefined;
638
+ // The operator's STIGMER_LLM_REQUEST_TIMEOUT_MS bound is applied inside
639
+ // buildChatModel (#468) the sub-agent modelFactory below silently
640
+ // dropped it when each caller parsed the env itself.
589
641
  const { model } = await buildChatModel({
590
642
  modelName,
591
643
  proxyEndpoint: config.proxyEndpoint ?? undefined,
592
644
  stigmerToken: config.stigmerToken ?? undefined,
593
645
  headerScope: { executionId },
594
- timeoutMs: requestTimeoutMs,
595
646
  });
596
647
  timing.mark("build_model");
597
648
 
@@ -601,6 +652,12 @@ export async function performSetup(deps: SetupDependencies): Promise<SetupResult
601
652
  const execConfig = execution.spec!.executionConfig;
602
653
  const isPlanMode = execConfig?.interactionMode === InteractionMode.PLAN;
603
654
  const shellEnv = isPlanMode ? undefined : buildShellEnv(envResult.mergedEnvVars);
655
+ // Plan mode's filesystem permission rules, hoisted once: the parent graph,
656
+ // every sub-agent graph, AND the path-normalization middleware (issue #429)
657
+ // all derive from this single value, so a rule-bearing graph can never miss
658
+ // the shim that keeps prompt-compliant relative paths from dying in rule
659
+ // validation (`path must be absolute`).
660
+ const planModePermissions = isPlanMode ? PLAN_MODE_PERMISSIONS : undefined;
604
661
 
605
662
  const toolServerMap = new Map<string, string>();
606
663
  if (mcpConnection) {
@@ -621,12 +678,11 @@ export async function performSetup(deps: SetupDependencies): Promise<SetupResult
621
678
  // gate. Both flow into sub-agents via the shared config below.
622
679
  const leases = deriveActiveLeases(execution);
623
680
  const globalBypass = leases.global;
624
- const agentOverrides = agent.spec!.mcpServerUsages?.flatMap(
625
- u => u.toolApprovalOverrides ?? [],
626
- ) ?? [];
681
+ // Layer-3 overrides ride each resolved server from its (session-wins
682
+ // merged) usage — see ResolvedMcpServer.toolApprovalOverrides (issue
683
+ // #349) so there is no separate override input to pass here.
627
684
  const approvalPolicies = mergeApprovalPolicies(
628
685
  resolvedMcpServers?.resolvedServers ?? [],
629
- agentOverrides,
630
686
  leases,
631
687
  );
632
688
 
@@ -707,6 +763,9 @@ export async function performSetup(deps: SetupDependencies): Promise<SetupResult
707
763
  } : null,
708
764
  otelSpans: { toolServerMap },
709
765
  approvalGate: approvalGateConfig,
766
+ pathNormalization: planModePermissions
767
+ ? { rootDir: workspaceBackend.rootDir }
768
+ : null,
710
769
  });
711
770
  timing.mark("build_middleware");
712
771
 
@@ -767,6 +826,12 @@ export async function performSetup(deps: SetupDependencies): Promise<SetupResult
767
826
  // Presence of shellEnv is the shell-capability switch for sub-agent
768
827
  // backends too (undefined in plan mode; see buildShellEnv above).
769
828
  shellEnv,
829
+ // Plan mode's deny-all-writes rule, mirrored onto every sub-agent graph
830
+ // (issue #255) — pre-built CompiledSubAgents never inherit the parent's
831
+ // permissions, so read-only-by-construction must be baked in here.
832
+ // compileSubagents derives each sub-agent's path-normalization shim
833
+ // from this same value (issue #429).
834
+ ...(planModePermissions ? { permissions: planModePermissions } : {}),
770
835
  });
771
836
  timing.mark("compile_subagents");
772
837
  }
@@ -780,16 +845,6 @@ export async function performSetup(deps: SetupDependencies): Promise<SetupResult
780
845
  responseFormat = jsonSchemaToZod(outputSchema as unknown as Record<string, unknown>);
781
846
  }
782
847
 
783
- // Plan mode is read-only. Deny every filesystem write operation at the tool
784
- // level so write_file/edit_file/etc. cannot mutate the workspace — enforcing
785
- // the InteractionMode.PLAN contract by construction, not by prompt. Rules are
786
- // first-match-wins with a permissive default, so a single deny-all-writes
787
- // rule is sufficient. (The Cursor harness enforces plan mode via its prompt
788
- // prefix; the native harness enforces it here.)
789
- const planModePermissions: FilesystemPermission[] = [
790
- { operations: ["write"], paths: ["/**"], mode: "deny" },
791
- ];
792
-
793
848
  // File capture point. Apply-then-review is universal (Slice 2b), so the
794
849
  // CAS-observing backend: git-tracked edits flow to disk (the turn-boundary
795
850
  // git diff is authoritative), and the shared CAS observer records the pre-turn
@@ -810,7 +865,7 @@ export async function performSetup(deps: SetupDependencies): Promise<SetupResult
810
865
  middleware: middleware as any,
811
866
  subagents: compiledSubagents ?? undefined,
812
867
  ...(responseFormat ? { responseFormat } : {}),
813
- ...(isPlanMode ? { permissions: planModePermissions } : {}),
868
+ ...(planModePermissions ? { permissions: planModePermissions } : {}),
814
869
  } as Parameters<typeof createDeepAgent>[0]);
815
870
 
816
871
  // Step 11: Prepare invocation input and config. The conversation catchup
@@ -881,6 +936,7 @@ export async function performSetup(deps: SetupDependencies): Promise<SetupResult
881
936
  session,
882
937
  workspaceBackend,
883
938
  mcpConnection,
939
+ datastoreToolsMissing,
884
940
  mergedEnvVars: envResult.mergedEnvVars,
885
941
  secretKeys: envResult.secretKeys,
886
942
  modelName,
@@ -6,15 +6,14 @@
6
6
  * never once at process start.
7
7
  */
8
8
 
9
- /** Runner-internal keys that must never reach agent shell commands. */
10
- export const SHELL_ENV_DENYLIST: readonly string[] = [
11
- // Derives HITL approval fingerprints — an agent that reads this could forge receipts.
12
- "STIGMER_RUNNER_HITL_SECRET",
13
- // Cursor harness credential; shell commands do not need direct Cursor API access.
14
- "CURSOR_API_KEY",
15
- // Stigmer control-plane auth; shell commands use ExecutionContext overlay instead.
16
- "STIGMER_TOKEN",
17
- ];
9
+ import { RUNNER_CREDENTIAL_ENV_KEYS } from "../../shared/runner-credential-keys.js";
10
+
11
+ /**
12
+ * Runner-internal keys that must never reach agent shell commands: every
13
+ * credential the runner holds for its own outbound calls (issue #385). The
14
+ * names — and the rule for adding one — live in runner-credential-keys.ts.
15
+ */
16
+ export const SHELL_ENV_DENYLIST: readonly string[] = RUNNER_CREDENTIAL_ENV_KEYS;
18
17
 
19
18
  /**
20
19
  * Build the environment map passed to deepagents' LocalShellBackend.