@stigmer/runner 3.11.0 → 3.12.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 (239) hide show
  1. package/README.md +2 -0
  2. package/dist/.build-fingerprint +1 -1
  3. package/dist/activities/discover-mcp-server.d.ts +7 -0
  4. package/dist/activities/discover-mcp-server.js +6 -4
  5. package/dist/activities/discover-mcp-server.js.map +1 -1
  6. package/dist/activities/emit-event.d.ts +14 -2
  7. package/dist/activities/emit-event.js +52 -17
  8. package/dist/activities/emit-event.js.map +1 -1
  9. package/dist/activities/execute-cursor/attachment-resolver.d.ts +11 -0
  10. package/dist/activities/execute-cursor/attachment-resolver.js +8 -0
  11. package/dist/activities/execute-cursor/attachment-resolver.js.map +1 -1
  12. package/dist/activities/execute-cursor/blueprint-resolver.d.ts +1 -9
  13. package/dist/activities/execute-cursor/blueprint-resolver.js +0 -1
  14. package/dist/activities/execute-cursor/blueprint-resolver.js.map +1 -1
  15. package/dist/activities/execute-cursor/error-classifier.d.ts +19 -0
  16. package/dist/activities/execute-cursor/error-classifier.js +34 -3
  17. package/dist/activities/execute-cursor/error-classifier.js.map +1 -1
  18. package/dist/activities/execute-cursor/index.d.ts +52 -12
  19. package/dist/activities/execute-cursor/index.js +141 -67
  20. package/dist/activities/execute-cursor/index.js.map +1 -1
  21. package/dist/activities/execute-cursor/model-pricing-data.d.ts +2 -0
  22. package/dist/activities/execute-cursor/model-pricing-data.js +13 -3
  23. package/dist/activities/execute-cursor/model-pricing-data.js.map +1 -1
  24. package/dist/activities/execute-cursor/prompt-builder.d.ts +48 -11
  25. package/dist/activities/execute-cursor/prompt-builder.js +57 -10
  26. package/dist/activities/execute-cursor/prompt-builder.js.map +1 -1
  27. package/dist/activities/execute-cursor/turn-recovery.d.ts +52 -0
  28. package/dist/activities/execute-cursor/turn-recovery.js +193 -0
  29. package/dist/activities/execute-cursor/turn-recovery.js.map +1 -0
  30. package/dist/activities/execute-deep-agent/attachment-injector.d.ts +11 -0
  31. package/dist/activities/execute-deep-agent/attachment-injector.js +7 -1
  32. package/dist/activities/execute-deep-agent/attachment-injector.js.map +1 -1
  33. package/dist/activities/execute-deep-agent/index.js +0 -15
  34. package/dist/activities/execute-deep-agent/index.js.map +1 -1
  35. package/dist/activities/execute-deep-agent/mcp-gate.d.ts +0 -2
  36. package/dist/activities/execute-deep-agent/mcp-gate.js +0 -1
  37. package/dist/activities/execute-deep-agent/mcp-gate.js.map +1 -1
  38. package/dist/activities/execute-deep-agent/prompt-builder.d.ts +7 -8
  39. package/dist/activities/execute-deep-agent/prompt-builder.js +14 -7
  40. package/dist/activities/execute-deep-agent/prompt-builder.js.map +1 -1
  41. package/dist/activities/execute-deep-agent/setup.d.ts +0 -10
  42. package/dist/activities/execute-deep-agent/setup.js +22 -49
  43. package/dist/activities/execute-deep-agent/setup.js.map +1 -1
  44. package/dist/activities/execute-deep-agent/shell-env.d.ts +5 -1
  45. package/dist/activities/execute-deep-agent/shell-env.js +7 -9
  46. package/dist/activities/execute-deep-agent/shell-env.js.map +1 -1
  47. package/dist/activities/execute-deep-agent/subagent-transformer.js +7 -1
  48. package/dist/activities/execute-deep-agent/subagent-transformer.js.map +1 -1
  49. package/dist/activities/execute-deep-agent/subagent-wiring.d.ts +17 -3
  50. package/dist/activities/execute-deep-agent/subagent-wiring.js +14 -2
  51. package/dist/activities/execute-deep-agent/subagent-wiring.js.map +1 -1
  52. package/dist/activities/hydrate-workflow-execution.js +8 -5
  53. package/dist/activities/hydrate-workflow-execution.js.map +1 -1
  54. package/dist/activities/run-command.d.ts +5 -2
  55. package/dist/activities/run-command.js +22 -10
  56. package/dist/activities/run-command.js.map +1 -1
  57. package/dist/activities/run-env.d.ts +40 -0
  58. package/dist/activities/run-env.js +66 -0
  59. package/dist/activities/run-env.js.map +1 -0
  60. package/dist/bootstrap.js +5 -0
  61. package/dist/bootstrap.js.map +1 -1
  62. package/dist/client/stigmer-client.d.ts +83 -26
  63. package/dist/client/stigmer-client.js +88 -24
  64. package/dist/client/stigmer-client.js.map +1 -1
  65. package/dist/config.d.ts +6 -5
  66. package/dist/config.js.map +1 -1
  67. package/dist/encryption/config.d.ts +41 -9
  68. package/dist/encryption/config.js +63 -23
  69. package/dist/encryption/config.js.map +1 -1
  70. package/dist/middleware/index.d.ts +3 -0
  71. package/dist/middleware/index.js +7 -0
  72. package/dist/middleware/index.js.map +1 -1
  73. package/dist/middleware/path-normalization.d.ts +57 -0
  74. package/dist/middleware/path-normalization.js +109 -0
  75. package/dist/middleware/path-normalization.js.map +1 -0
  76. package/dist/middleware/types.d.ts +8 -0
  77. package/dist/payload-codecs.d.ts +2 -1
  78. package/dist/payload-codecs.js +7 -3
  79. package/dist/payload-codecs.js.map +1 -1
  80. package/dist/runner-manager.js +17 -3
  81. package/dist/runner-manager.js.map +1 -1
  82. package/dist/runner.js +7 -3
  83. package/dist/runner.js.map +1 -1
  84. package/dist/shared/artifact-storage.d.ts +34 -1
  85. package/dist/shared/artifact-storage.js +53 -10
  86. package/dist/shared/artifact-storage.js.map +1 -1
  87. package/dist/shared/attachment-download-urls.d.ts +62 -0
  88. package/dist/shared/attachment-download-urls.js +79 -0
  89. package/dist/shared/attachment-download-urls.js.map +1 -0
  90. package/dist/shared/channel-attachment.d.ts +1 -1
  91. package/dist/shared/channel-attachment.js +1 -1
  92. package/dist/shared/checkpointer/http-saver.d.ts +4 -20
  93. package/dist/shared/checkpointer/http-saver.js +15 -57
  94. package/dist/shared/checkpointer/http-saver.js.map +1 -1
  95. package/dist/shared/conversation-attachment.d.ts +2 -3
  96. package/dist/shared/conversation-attachment.js +2 -3
  97. package/dist/shared/conversation-attachment.js.map +1 -1
  98. package/dist/shared/conversation-catchup.d.ts +9 -2
  99. package/dist/shared/conversation-catchup.js +39 -6
  100. package/dist/shared/conversation-catchup.js.map +1 -1
  101. package/dist/shared/grpc-retry.d.ts +5 -1
  102. package/dist/shared/grpc-retry.js +5 -1
  103. package/dist/shared/grpc-retry.js.map +1 -1
  104. package/dist/shared/http-retry.d.ts +51 -9
  105. package/dist/shared/http-retry.js +60 -9
  106. package/dist/shared/http-retry.js.map +1 -1
  107. package/dist/shared/mcp-manager.d.ts +8 -4
  108. package/dist/shared/mcp-manager.js +30 -4
  109. package/dist/shared/mcp-manager.js.map +1 -1
  110. package/dist/shared/mcp-schema-sanitizer.d.ts +70 -0
  111. package/dist/shared/mcp-schema-sanitizer.js +197 -0
  112. package/dist/shared/mcp-schema-sanitizer.js.map +1 -0
  113. package/dist/shared/mcp-transport-guard.d.ts +2 -2
  114. package/dist/shared/mcp-transport-guard.js +2 -2
  115. package/dist/shared/model-client.d.ts +6 -0
  116. package/dist/shared/model-client.js +14 -3
  117. package/dist/shared/model-client.js.map +1 -1
  118. package/dist/shared/model-pricing-data.d.ts +2 -0
  119. package/dist/shared/model-pricing-data.js +13 -3
  120. package/dist/shared/model-pricing-data.js.map +1 -1
  121. package/dist/shared/model-registry.js +3 -2
  122. package/dist/shared/model-registry.js.map +1 -1
  123. package/dist/shared/plan-mode-permissions.d.ts +7 -0
  124. package/dist/shared/plan-mode-permissions.js +7 -0
  125. package/dist/shared/plan-mode-permissions.js.map +1 -1
  126. package/dist/shared/registry-endpoint.d.ts +20 -0
  127. package/dist/shared/registry-endpoint.js +25 -0
  128. package/dist/shared/registry-endpoint.js.map +1 -1
  129. package/dist/shared/runner-credential-keys.d.ts +24 -0
  130. package/dist/shared/runner-credential-keys.js +47 -0
  131. package/dist/shared/runner-credential-keys.js.map +1 -0
  132. package/dist/shared/synthesized-attachment.d.ts +4 -4
  133. package/dist/shared/synthesized-attachment.js +4 -4
  134. package/dist/workflow-engine/resolve.d.ts +19 -1
  135. package/dist/workflow-engine/resolve.js +37 -2
  136. package/dist/workflow-engine/resolve.js.map +1 -1
  137. package/dist/workflows/connect-mcp-server.js +2 -0
  138. package/dist/workflows/connect-mcp-server.js.map +1 -1
  139. package/dist/workflows/types.d.ts +8 -0
  140. package/package.json +3 -4
  141. package/src/__test-utils__/__tests__/vitest-global-setup.test.ts +37 -0
  142. package/src/__test-utils__/fake-artifact-storage.ts +10 -1
  143. package/src/__test-utils__/vitest-global-setup.ts +50 -0
  144. package/src/__tests__/bootstrap.test.ts +43 -0
  145. package/src/__tests__/encryption-codec.test.ts +53 -0
  146. package/src/__tests__/preflight.test.ts +15 -0
  147. package/src/activities/__tests__/discover-mcp-server.test.ts +39 -1
  148. package/src/activities/__tests__/error-classifier.test.ts +94 -2
  149. package/src/activities/__tests__/run-command.test.ts +221 -0
  150. package/src/activities/discover-mcp-server.ts +16 -2
  151. package/src/activities/emit-event.ts +65 -21
  152. package/src/activities/execute-cursor/__tests__/attachment-resolver.test.ts +63 -3
  153. package/src/activities/execute-cursor/__tests__/build-prompt.test.ts +173 -31
  154. package/src/activities/execute-cursor/__tests__/model-pricing.test.ts +62 -1
  155. package/src/activities/execute-cursor/__tests__/turn-recovery.test.ts +160 -0
  156. package/src/activities/execute-cursor/attachment-resolver.ts +24 -0
  157. package/src/activities/execute-cursor/blueprint-resolver.ts +1 -10
  158. package/src/activities/execute-cursor/error-classifier.ts +40 -3
  159. package/src/activities/execute-cursor/index.ts +175 -74
  160. package/src/activities/execute-cursor/model-pricing-data.ts +22 -3
  161. package/src/activities/execute-cursor/prompt-builder.ts +89 -22
  162. package/src/activities/execute-cursor/turn-recovery.ts +208 -0
  163. package/src/activities/execute-deep-agent/__tests__/attachment-injector.test.ts +105 -0
  164. package/src/activities/execute-deep-agent/__tests__/hitl-reject.test.ts +0 -2
  165. package/src/activities/execute-deep-agent/__tests__/hitl-resume-approve-all.test.ts +0 -1
  166. package/src/activities/execute-deep-agent/__tests__/hitl-resume-history.test.ts +0 -1
  167. package/src/activities/execute-deep-agent/__tests__/mcp-gate.test.ts +2 -7
  168. package/src/activities/execute-deep-agent/__tests__/plan-mode-path-normalization.test.ts +174 -0
  169. package/src/activities/execute-deep-agent/__tests__/prompt-builder.test.ts +71 -0
  170. package/src/activities/execute-deep-agent/__tests__/sequential-gate-resume.test.ts +0 -1
  171. package/src/activities/execute-deep-agent/__tests__/shell-env.test.ts +24 -10
  172. package/src/activities/execute-deep-agent/__tests__/subagent-plan-mode-permissions.test.ts +22 -17
  173. package/src/activities/execute-deep-agent/attachment-injector.ts +20 -1
  174. package/src/activities/execute-deep-agent/index.ts +0 -19
  175. package/src/activities/execute-deep-agent/mcp-gate.ts +0 -3
  176. package/src/activities/execute-deep-agent/prompt-builder.ts +27 -15
  177. package/src/activities/execute-deep-agent/setup.ts +22 -73
  178. package/src/activities/execute-deep-agent/shell-env.ts +8 -9
  179. package/src/activities/execute-deep-agent/subagent-transformer.ts +7 -1
  180. package/src/activities/execute-deep-agent/subagent-wiring.ts +26 -3
  181. package/src/activities/hydrate-workflow-execution.ts +8 -5
  182. package/src/activities/run-command.ts +25 -12
  183. package/src/activities/run-env.ts +79 -0
  184. package/src/bootstrap.ts +5 -0
  185. package/src/client/__tests__/stigmer-client.test.ts +104 -5
  186. package/src/client/stigmer-client.ts +128 -29
  187. package/src/config.ts +6 -5
  188. package/src/encryption/config.ts +87 -23
  189. package/src/middleware/__tests__/path-normalization.test.ts +140 -0
  190. package/src/middleware/index.ts +8 -0
  191. package/src/middleware/path-normalization.ts +125 -0
  192. package/src/middleware/types.ts +11 -0
  193. package/src/payload-codecs.ts +8 -2
  194. package/src/runner-manager.ts +21 -3
  195. package/src/runner.ts +7 -3
  196. package/src/shared/__tests__/artifact-storage.test.ts +154 -2
  197. package/src/shared/__tests__/attachment-download-urls.test.ts +70 -0
  198. package/src/shared/__tests__/channel-attachment.test.ts +2 -2
  199. package/src/shared/__tests__/conversation-catchup.test.ts +24 -0
  200. package/src/shared/__tests__/http-retry.test.ts +5 -4
  201. package/src/shared/__tests__/mcp-manager.test.ts +53 -13
  202. package/src/shared/__tests__/mcp-schema-sanitizer.test.ts +267 -0
  203. package/src/shared/__tests__/model-client.test.ts +46 -0
  204. package/src/shared/__tests__/model-pricing.test.ts +62 -2
  205. package/src/shared/__tests__/model-registry.test.ts +22 -0
  206. package/src/shared/__tests__/synthesized-attachment.test.ts +19 -41
  207. package/src/shared/artifact-storage.ts +86 -10
  208. package/src/shared/attachment-download-urls.ts +98 -0
  209. package/src/shared/channel-attachment.ts +1 -1
  210. package/src/shared/checkpointer/http-saver.ts +15 -60
  211. package/src/shared/conversation-attachment.ts +2 -3
  212. package/src/shared/conversation-catchup.ts +39 -6
  213. package/src/shared/grpc-retry.ts +5 -1
  214. package/src/shared/http-retry.ts +98 -9
  215. package/src/shared/mcp-manager.ts +35 -4
  216. package/src/shared/mcp-schema-sanitizer.ts +224 -0
  217. package/src/shared/mcp-transport-guard.ts +2 -2
  218. package/src/shared/model-client.ts +21 -3
  219. package/src/shared/model-pricing-data.ts +22 -3
  220. package/src/shared/model-registry.ts +7 -2
  221. package/src/shared/plan-mode-permissions.ts +7 -0
  222. package/src/shared/registry-endpoint.ts +27 -0
  223. package/src/shared/runner-credential-keys.ts +46 -0
  224. package/src/shared/synthesized-attachment.ts +4 -4
  225. package/src/workflow-engine/__tests__/tasks/emit-event.test.ts +127 -49
  226. package/src/workflow-engine/resolve.ts +48 -2
  227. package/src/workflows/__tests__/connect-mcp-server.test.ts +25 -0
  228. package/src/workflows/connect-mcp-server.ts +2 -0
  229. package/src/workflows/types.ts +8 -0
  230. package/dist/activities/execute-cursor/mcp-config.d.ts +0 -30
  231. package/dist/activities/execute-cursor/mcp-config.js +0 -39
  232. package/dist/activities/execute-cursor/mcp-config.js.map +0 -1
  233. package/dist/shared/datastore-attachment.d.ts +0 -99
  234. package/dist/shared/datastore-attachment.js +0 -184
  235. package/dist/shared/datastore-attachment.js.map +0 -1
  236. package/src/activities/execute-cursor/mcp-config.ts +0 -66
  237. package/src/activities/execute-deep-agent/__tests__/datastore-degradation.test.ts +0 -104
  238. package/src/shared/__tests__/datastore-attachment.test.ts +0 -293
  239. package/src/shared/datastore-attachment.ts +0 -209
@@ -27,7 +27,7 @@
27
27
  */
28
28
  import { InteractionMode, ApprovalAction } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/enum_pb";
29
29
  import type { Config } from "../../config.js";
30
- import type { AgentResolution } from "./session-lifecycle.js";
30
+ import type { AgentResolution, AgentResolutionReason } from "./session-lifecycle.js";
31
31
  import { type ExecuteActivityInput } from "../../shared/activity-input.js";
32
32
  /**
33
33
  * Creates the activity functions bound to the runner config.
@@ -42,8 +42,6 @@ export interface BuildPromptInput {
42
42
  instructions: string;
43
43
  userMessage: string;
44
44
  skills: import("./prompt-builder.js").SkillMetadata[];
45
- /** Datastores attached via `datastore_usages` — the `<available_datastores>` section. */
46
- datastoreUsages?: import("@stigmer/protos/ai/stigmer/agentic/agent/v1/spec_pb").DatastoreUsage[];
47
45
  /**
48
46
  * Serving proactive channels + their templates (the DD-006 D2
49
47
  * discovery read) — the `<available_channel_templates>` section.
@@ -63,6 +61,12 @@ export interface BuildPromptInput {
63
61
  * catchup — it rides both the enhanced prompt and a resumed turn's prefix.
64
62
  */
65
63
  vision?: import("./prompt-builder.js").VisionPromptInfo;
64
+ /**
65
+ * What kind of URL the turn's storage backend mints (issue #532) — keys
66
+ * the input-files hand-off wording. Sourced from the resolved
67
+ * artifactStorage's self-description; absent when no storage resolved.
68
+ */
69
+ downloadUrlKind?: import("../../shared/attachment-download-urls.js").DownloadUrlKind;
66
70
  pendingApprovals: import("@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/approval_pb").PendingApproval[];
67
71
  /**
68
72
  * Approved whole-file writes the runner already applied itself (exact-apply).
@@ -107,6 +111,15 @@ export interface BuildPromptInput {
107
111
  * usually blank.
108
112
  */
109
113
  conversationCatchup?: string;
114
+ /**
115
+ * The turn's recorded transcript rendered as digest lines
116
+ * (turn-recovery.ts), composed from `status.messages` at the call site.
117
+ * Consumed ONLY by the HITL-recovery shape — a fresh agent that replaced
118
+ * a lost one mid-HITL needs the story of the work it no longer remembers
119
+ * (issue #366); every other shape either has native context or no prior
120
+ * work to tell.
121
+ */
122
+ turnRecoveryDigest?: string;
110
123
  }
111
124
  /**
112
125
  * Select and build the appropriate prompt based on resolution reason and
@@ -117,20 +130,47 @@ export interface BuildPromptInput {
117
130
  * volume, or cloud server-side state) — there is no separate continuation
118
131
  * store. The prompt therefore depends only on how the agent was resolved:
119
132
  *
120
- * 1. HITL reinvocation -> buildReinvocationPrompt (approval decisions;
121
- * the resumed agent's native context carries
122
- * the prior conversation)
123
- * 2. resumed_successfully -> raw userMessage (native context carries it)
124
- * 3. first execution / fresh -> buildEnhancedPrompt (full instructions +
133
+ * 1. HITL reinvocation, -> buildReinvocationPrompt (approval decisions
134
+ * resumed agent only; the resumed agent's native context
135
+ * carries the prior conversation)
136
+ * 2. HITL reinvocation, -> buildHitlRecoveryPrompt (full context +
137
+ * fresh agent after the turn's recorded transcript + decisions;
138
+ * resume failure the replacement agent's conversation is
139
+ * empty, and both fresh-agent crossings —
140
+ * resolution-time resume failure and mid-send
141
+ * poisoned-handle recovery — land here by
142
+ * keying on the reason, issue #366)
143
+ * 3. resumed_successfully -> raw userMessage (native context carries it)
144
+ * 4. first execution / fresh -> buildEnhancedPrompt (full instructions +
125
145
  * agent after resume failure skills; no prior conversation to inherit)
126
146
  */
127
147
  /**
128
148
  * Whether this activity invocation is a HITL re-invocation — the turn resumes
129
149
  * an agent purely to convey approval decisions, carrying NO user message.
130
- * The single discriminator for everything that must ride with the user's
131
- * message and nothing else: the reinvocation prompt shape (below) and the
132
- * vision payload (images accompany the message; a resumed agent already holds
133
- * them in its native conversation).
150
+ * Discriminates the two surfaces that depend on the agent already holding
151
+ * this turn's content natively the prompt shape (below) and the primary
152
+ * send's vision payload but never alone: both pair it with
153
+ * `resolution.reason`, because a FRESH agent mid-HITL holds nothing and
154
+ * needs the full re-delivery (issue #366).
134
155
  */
135
156
  export declare function isHitlReinvocation(approvalDecisions: Map<string, ApprovalAction> | undefined): approvalDecisions is Map<string, ApprovalAction>;
157
+ /**
158
+ * Whether the PRIMARY send delivers the turn's vision payload. The invariant
159
+ * is "images accompany the user's turn message, wherever the conversation
160
+ * does not already hold them" — so the only send that skips them is a HITL
161
+ * re-invocation of a successfully RESUMED agent, whose native conversation
162
+ * carries the images from the original send. A fresh agent mid-HITL
163
+ * (resolution-time resume failure — issue #366's vision corollary) holds
164
+ * nothing and needs the re-delivery. The mid-send recovery retries always
165
+ * run on a fresh agent, so their send sites carry the payload
166
+ * unconditionally rather than consulting this.
167
+ */
168
+ export declare function primarySendCarriesImages(approvalDecisions: Map<string, ApprovalAction> | undefined, reason: AgentResolutionReason): boolean;
169
+ /**
170
+ * Append the structured-output contract to a prompt when the execution
171
+ * requests one. A per-turn directive (the buildFromPlan rule): it must ride
172
+ * every prompt this turn sends — the primary send AND the poisoned-handle
173
+ * recovery rebuild, which previously lost it (issue #366 ride-along).
174
+ */
175
+ export declare function appendStructuredOutputDirective(basePrompt: string, schema: Record<string, unknown> | undefined): string;
136
176
  export declare function buildPrompt(input: BuildPromptInput): string;
@@ -56,7 +56,6 @@ import { StreamingUpdateScheduler, loadStreamingConfig } from "../../shared/stre
56
56
  import { createCursorEventRecorder } from "./cursor-event-recorder.js";
57
57
  import { resolveMcpServers, toCursorMcpConfig, validateMcpServerEnv } from "./mcp-resolver.js";
58
58
  import { resolveMcpTransportPosture } from "../../shared/mcp-transport-guard.js";
59
- import { synthesizeDatastoreAttachment } from "../../shared/datastore-attachment.js";
60
59
  import { discoverChannelMessaging, synthesizeChannelAttachment, } from "../../shared/channel-attachment.js";
61
60
  import { readChannelConversationId, synthesizeConversationAttachment, } from "../../shared/conversation-attachment.js";
62
61
  import { injectSynthesizedAttachment } from "../../shared/synthesized-attachment.js";
@@ -70,7 +69,8 @@ import { buildCursorSubAgentDefinitions } from "./subagent-config.js";
70
69
  import { resolveSkills } from "./skill-resolver.js";
71
70
  import { removeStigmerSymlink } from "../../shared/workspace/stigmer-link.js";
72
71
  import { resolveAttachments } from "./attachment-resolver.js";
73
- import { buildEnhancedPrompt, buildReinvocationPrompt, formatConversationCatchupSection, formatInputFiles, formatInteractionModePrefix, formatImplementPlanSection } from "./prompt-builder.js";
72
+ import { buildEnhancedPrompt, buildHitlRecoveryPrompt, buildReinvocationPrompt, formatConversationCatchupSection, formatInputFiles, formatInteractionModePrefix, formatImplementPlanSection } from "./prompt-builder.js";
73
+ import { composeTurnRecoveryDigest } from "./turn-recovery.js";
74
74
  import { installHitlGate, removeHitlGate } from "./workspace-setup.js";
75
75
  import { ensureHitlDir } from "../../shared/workspace/platform-dir.js";
76
76
  import { acquireWorkspaceLock, WorkspaceLockCancelledError, WorkspaceLockTimeoutError, } from "../../shared/workspace/workspace-lock.js";
@@ -554,32 +554,16 @@ turnSeq) {
554
554
  const attachmentCredential = exchangedRunnerToken
555
555
  ?? config.stigmerTokenRef?.current
556
556
  ?? config.stigmerToken;
557
- // Phase 4a2: Synthesize the datastore records attachment (T05).
558
- // Deliberately AFTER resolve + backfill: the attachment has no
559
- // McpServerUsage and reports discovered capabilities, so the
560
- // backfill's destructiveHint tightener can never force-gate
561
- // delete_record (which on channels would be silently skipped).
562
- // Empty approval maps keep it approval-free by construction.
563
- if (blueprint.datastoreUsages.length > 0) {
564
- const attachment = synthesizeDatastoreAttachment(blueprint.datastoreUsages, {
565
- bridgeEndpoint: config.mcpBridgeEndpoint,
566
- credential: attachmentCredential,
567
- backendEndpoint: config.stigmerBackendEndpoint,
568
- });
569
- if (attachment) {
570
- const resolvedServers = injectSynthesizedAttachment(mcpResolution.resolvedServers, attachment, "datastore records");
571
- mcpResolution = {
572
- resolvedServers,
573
- cursorConfig: toCursorMcpConfig(resolvedServers),
574
- };
575
- }
576
- }
577
- // Phase 4a3: Synthesize the channel messaging attachment (DD-006
578
- // D7/D8), the records attachment's twin. The discovery read is the
579
- // attachment decision — the control plane runs the SAME candidate
580
- // computation the send authorization uses — and every failure mode
581
- // (no channel, OSS, registry down, pre-3a control plane) degrades
582
- // to honest absence: no tool, no section, execution unharmed.
557
+ // Phase 4a2: Synthesize the channel messaging attachment (DD-006
558
+ // D7/D8). Deliberately AFTER resolve + backfill: the attachment has
559
+ // no McpServerUsage and reports discovered capabilities, so the
560
+ // backfill's destructiveHint tightener can never force-gate its
561
+ // tools; empty approval maps keep it approval-free by construction.
562
+ // The discovery read is the attachment decision — the control plane
563
+ // runs the SAME candidate computation the send authorization uses —
564
+ // and every failure mode (no channel, OSS, registry down, pre-3a
565
+ // control plane) degrades to honest absence: no tool, no section,
566
+ // execution unharmed.
583
567
  const channelMessaging = await discoverChannelMessaging(client, exchangedRunnerToken);
584
568
  if (channelMessaging.length > 0) {
585
569
  const attachment = synthesizeChannelAttachment(channelMessaging, {
@@ -667,6 +651,7 @@ turnSeq) {
667
651
  const attachmentEntries = attachmentResults.map((a) => ({
668
652
  path: a.relativePath,
669
653
  ...(a.renamedFrom !== undefined ? { renamedFrom: a.renamedFrom } : {}),
654
+ ...(a.downloadUrl !== undefined ? { downloadUrl: a.downloadUrl } : {}),
670
655
  }));
671
656
  // Vision facts, derived once from the single resolution result: the
672
657
  // images the model will see inline (in attachment order) and the ones
@@ -961,13 +946,13 @@ turnSeq) {
961
946
  instructions: blueprint.instructions,
962
947
  userMessage: spec.message,
963
948
  skills: skillMetadata,
964
- datastoreUsages: blueprint.datastoreUsages,
965
949
  channelMessaging,
966
950
  subAgents: blueprint.subAgents,
967
951
  workspaceDirs: blueprint.workspaceDirs,
968
952
  workspaceFileRefs: spec.workspaceFileRefs ?? [],
969
953
  attachments: attachmentEntries,
970
954
  vision: visionPromptInfo,
955
+ downloadUrlKind: artifactStorage?.downloadUrlKind,
971
956
  pendingApprovals: adjudicatedApprovals,
972
957
  appliedToolCallIds,
973
958
  interactionMode,
@@ -976,25 +961,36 @@ turnSeq) {
976
961
  senderIdentity: readSenderIdentity(blueprint.sessionSpec.metadata),
977
962
  sessionContext: readSessionContext(blueprint.sessionSpec.metadata),
978
963
  conversationCatchup: readConversationCatchup(spec.conversationCatchup),
964
+ // The turn's recorded transcript, seeded from the persisted execution
965
+ // on a reinvocation (Phase 3). Consumed only by the HITL-recovery
966
+ // shape — reached from HERE when the stored handle failed to resume
967
+ // at resolution time (issue #366 crossing 2).
968
+ turnRecoveryDigest: isReinvocation
969
+ ? composeTurnRecoveryDigest(status.messages)
970
+ : undefined,
979
971
  });
980
- // Phase 10a: Inject structured output instruction for Cursor harness
981
- let effectivePrompt = prompt;
982
- if (structuredOutputSchema) {
983
- const schemaStr = JSON.stringify(structuredOutputSchema, null, 2);
984
- effectivePrompt += `\n\n---\nCRITICAL OUTPUT REQUIREMENT:\nYour final response MUST be a single valid JSON object (no markdown, no commentary, no code fences) that matches this schema:\n${schemaStr}\n\nRespond with ONLY the JSON object. Nothing else.`;
985
- }
972
+ // Phase 10a: Inject the structured output instruction for the Cursor
973
+ // harness. A per-turn directive, so like buildFromPlan it must ride every
974
+ // prompt this turn sends — the primary AND the poisoned-handle recovery
975
+ // rebuild (the transport retry re-sends effectivePrompt and inherits it).
976
+ const withStructuredOutputDirective = (basePrompt) => appendStructuredOutputDirective(basePrompt, structuredOutputSchema);
977
+ const effectivePrompt = withStructuredOutputDirective(prompt);
986
978
  // Phase 10a1: The turn's vision payload. The invariant is "images
987
- // accompany the user's turn message where the message goes, they go":
988
- // every send that delivers this turn's message carries them (the primary
989
- // send and both fresh-agent recovery retries, whose empty conversations
990
- // genuinely need the re-send), while a HITL re-invocation whose prompt
991
- // carries no user message and whose resumed agent already holds the
992
- // images in its native conversation carries none. Computed ONCE here so
993
- // all send sites agree by construction.
994
- const turnImages = isHitlReinvocation(approvalDecisions)
995
- ? []
996
- : toCursorImages(visionImages);
997
- const toSendMessage = (sendPrompt) => turnImages.length > 0 ? { text: sendPrompt, images: turnImages } : sendPrompt;
979
+ // accompany the user's turn message, wherever the conversation does not
980
+ // already hold them" (primarySendCarriesImages): the ONLY send that
981
+ // skips them is a HITL re-invocation of a successfully RESUMED agent,
982
+ // whose native conversation carries the images from the original send.
983
+ // Every send that starts an empty conversation re-delivers them the
984
+ // ordinary first/fresh-agent primary send, the HITL primary send after a
985
+ // resolution-time resume failure, and both mid-send recovery retries
986
+ // (which always run on a fresh agent, so their sites pass turnImages
987
+ // unconditionally). Attachments re-resolve on every invocation
988
+ // (Phase 5b), so the bytes are in hand even on a re-invocation.
989
+ const turnImages = toCursorImages(visionImages);
990
+ const primarySendImages = primarySendCarriesImages(approvalDecisions, resolution.reason)
991
+ ? turnImages
992
+ : [];
993
+ const toSendMessage = (sendPrompt, images) => images.length > 0 ? { text: sendPrompt, images } : sendPrompt;
998
994
  // Phase 10a2: Log Stigmer preamble size for context trimming diagnostics
999
995
  const promptChars = effectivePrompt.length;
1000
996
  const promptEstimatedTokens = Math.ceil(promptChars / 4);
@@ -1095,7 +1091,7 @@ turnSeq) {
1095
1091
  // The stall watchdog is armed inside consumeCursorTurnStream (it needs the
1096
1092
  // run to cancel), stored on turnState.stallWatchdog so this shared onDelta can
1097
1093
  // reset it and the activity's finally can stop it as a backstop.
1098
- const run = await resolution.agent.send(toSendMessage(effectivePrompt), {
1094
+ const run = await resolution.agent.send(toSendMessage(effectivePrompt, primarySendImages), {
1099
1095
  onDelta: (event) => {
1100
1096
  if (!turnFirstEventEmitted) {
1101
1097
  turnFirstEventEmitted = true;
@@ -1412,10 +1408,11 @@ turnSeq) {
1412
1408
  // poisoned-handle path leaked the fresh agent — it closed the stale one.)
1413
1409
  resolution = { ...resolution, agent: freshAgent, agentId: freshAgent.agentId, isNew: true };
1414
1410
  turnState.streamErrorMessage = undefined;
1415
- // The retry carries the turn's images too (same toSendMessage): the
1416
- // fresh agent's conversation is empty, so skipping them here would
1417
- // silently lose the user's photo on a recovered turn.
1418
- const retryRun = await freshAgent.send(toSendMessage(retryPrompt), {
1411
+ // The retry always carries the turn's full image payload never the
1412
+ // primary send's HITL-trimmed set: the fresh agent's conversation is
1413
+ // empty, so skipping them here would silently lose the user's photo on
1414
+ // a recovered turn (issue #366's vision corollary).
1415
+ const retryRun = await freshAgent.send(toSendMessage(retryPrompt, turnImages), {
1419
1416
  onDelta: makeCursorTurnOnDelta(onDeltaDeps),
1420
1417
  });
1421
1418
  await consumeCursorTurnStream(retryRun, streamDeps);
@@ -1524,14 +1521,18 @@ turnSeq) {
1524
1521
  instructions: blueprint.instructions,
1525
1522
  userMessage: spec.message,
1526
1523
  skills: skillMetadata,
1527
- datastoreUsages: blueprint.datastoreUsages,
1528
1524
  channelMessaging,
1529
1525
  subAgents: blueprint.subAgents,
1530
1526
  workspaceDirs: blueprint.workspaceDirs,
1531
1527
  workspaceFileRefs: spec.workspaceFileRefs ?? [],
1532
1528
  attachments: attachmentEntries,
1533
1529
  vision: visionPromptInfo,
1530
+ downloadUrlKind: artifactStorage?.downloadUrlKind,
1534
1531
  pendingApprovals: adjudicatedApprovals,
1532
+ // Without the applied set, the HITL-recovery prompt would tell
1533
+ // the fresh agent to carry out writes the runner already
1534
+ // exact-applied (the primary call at Phase 10 passes it too).
1535
+ appliedToolCallIds,
1535
1536
  interactionMode,
1536
1537
  // buildFromPlan was silently dropped here until T03 Sitting 3 —
1537
1538
  // a build turn that hit handle recovery lost its directive. The
@@ -1542,6 +1543,10 @@ turnSeq) {
1542
1543
  senderIdentity: readSenderIdentity(blueprint.sessionSpec.metadata),
1543
1544
  sessionContext: readSessionContext(blueprint.sessionSpec.metadata),
1544
1545
  conversationCatchup: readConversationCatchup(spec.conversationCatchup),
1546
+ // Composed fresh (not reused from Phase 10): the failed primary
1547
+ // stream may have appended partial work onto status.messages,
1548
+ // and the replacement agent should know about that too.
1549
+ turnRecoveryDigest: composeTurnRecoveryDigest(status.messages),
1545
1550
  });
1546
1551
  console.log(`ExecuteCursor retry with fresh agent: execution=${executionId}, ` +
1547
1552
  `newAgentId=${freshAgent.agentId}`);
@@ -1554,7 +1559,11 @@ turnSeq) {
1554
1559
  catch (updateErr) {
1555
1560
  console.warn("Failed to update session with fresh agentId (non-fatal):", updateErr);
1556
1561
  }
1557
- const outcome = await runRecoveryStream(freshAgent, freshPrompt);
1562
+ // Same per-turn directive rule as buildFromPlan above: a
1563
+ // structured-output turn keeps its output contract on the rebuilt
1564
+ // prompt (the transport retry re-sends effectivePrompt and
1565
+ // inherits it without help).
1566
+ const outcome = await runRecoveryStream(freshAgent, withStructuredOutputDirective(freshPrompt));
1558
1567
  if (!outcome.proceeded) {
1559
1568
  if (outcome.terminal.kind === "return")
1560
1569
  return slimStatus(status);
@@ -1986,30 +1995,95 @@ function seedCursorTranscriptFromExecution(status, execution) {
1986
1995
  * volume, or cloud server-side state) — there is no separate continuation
1987
1996
  * store. The prompt therefore depends only on how the agent was resolved:
1988
1997
  *
1989
- * 1. HITL reinvocation -> buildReinvocationPrompt (approval decisions;
1990
- * the resumed agent's native context carries
1991
- * the prior conversation)
1992
- * 2. resumed_successfully -> raw userMessage (native context carries it)
1993
- * 3. first execution / fresh -> buildEnhancedPrompt (full instructions +
1998
+ * 1. HITL reinvocation, -> buildReinvocationPrompt (approval decisions
1999
+ * resumed agent only; the resumed agent's native context
2000
+ * carries the prior conversation)
2001
+ * 2. HITL reinvocation, -> buildHitlRecoveryPrompt (full context +
2002
+ * fresh agent after the turn's recorded transcript + decisions;
2003
+ * resume failure the replacement agent's conversation is
2004
+ * empty, and both fresh-agent crossings —
2005
+ * resolution-time resume failure and mid-send
2006
+ * poisoned-handle recovery — land here by
2007
+ * keying on the reason, issue #366)
2008
+ * 3. resumed_successfully -> raw userMessage (native context carries it)
2009
+ * 4. first execution / fresh -> buildEnhancedPrompt (full instructions +
1994
2010
  * agent after resume failure skills; no prior conversation to inherit)
1995
2011
  */
1996
2012
  /**
1997
2013
  * Whether this activity invocation is a HITL re-invocation — the turn resumes
1998
2014
  * an agent purely to convey approval decisions, carrying NO user message.
1999
- * The single discriminator for everything that must ride with the user's
2000
- * message and nothing else: the reinvocation prompt shape (below) and the
2001
- * vision payload (images accompany the message; a resumed agent already holds
2002
- * them in its native conversation).
2015
+ * Discriminates the two surfaces that depend on the agent already holding
2016
+ * this turn's content natively the prompt shape (below) and the primary
2017
+ * send's vision payload but never alone: both pair it with
2018
+ * `resolution.reason`, because a FRESH agent mid-HITL holds nothing and
2019
+ * needs the full re-delivery (issue #366).
2003
2020
  */
2004
2021
  export function isHitlReinvocation(approvalDecisions) {
2005
2022
  return approvalDecisions !== undefined && approvalDecisions.size > 0;
2006
2023
  }
2024
+ /**
2025
+ * Whether the PRIMARY send delivers the turn's vision payload. The invariant
2026
+ * is "images accompany the user's turn message, wherever the conversation
2027
+ * does not already hold them" — so the only send that skips them is a HITL
2028
+ * re-invocation of a successfully RESUMED agent, whose native conversation
2029
+ * carries the images from the original send. A fresh agent mid-HITL
2030
+ * (resolution-time resume failure — issue #366's vision corollary) holds
2031
+ * nothing and needs the re-delivery. The mid-send recovery retries always
2032
+ * run on a fresh agent, so their send sites carry the payload
2033
+ * unconditionally rather than consulting this.
2034
+ */
2035
+ export function primarySendCarriesImages(approvalDecisions, reason) {
2036
+ return !(isHitlReinvocation(approvalDecisions) && reason === "resumed_successfully");
2037
+ }
2038
+ /**
2039
+ * Append the structured-output contract to a prompt when the execution
2040
+ * requests one. A per-turn directive (the buildFromPlan rule): it must ride
2041
+ * every prompt this turn sends — the primary send AND the poisoned-handle
2042
+ * recovery rebuild, which previously lost it (issue #366 ride-along).
2043
+ */
2044
+ export function appendStructuredOutputDirective(basePrompt, schema) {
2045
+ if (!schema)
2046
+ return basePrompt;
2047
+ const schemaStr = JSON.stringify(schema, null, 2);
2048
+ return basePrompt + `\n\n---\nCRITICAL OUTPUT REQUIREMENT:\nYour final response MUST be a single valid JSON object (no markdown, no commentary, no code fences) that matches this schema:\n${schemaStr}\n\nRespond with ONLY the JSON object. Nothing else.`;
2049
+ }
2007
2050
  export function buildPrompt(input) {
2008
2051
  const { resolution, approvalDecisions, instructions, userMessage, skills, subAgents, workspaceDirs, workspaceFileRefs, attachments, interactionMode, buildFromPlan, conversationCatchup, } = input;
2009
- // HITL reinvocation: the agent is resumed, so its native context carries the
2010
- // prior conversation; the reinvocation prompt conveys the approval decisions
2011
- // (and which approved writes the runner already exact-applied).
2052
+ // HITL reinvocation: the decisions-only prompt is correct ONLY while the
2053
+ // agent's native context still carries the prior conversation which only
2054
+ // resumed_successfully guarantees. Any other reason means a fresh agent
2055
+ // mid-HITL (in practice created_after_resume_failure: the stored handle
2056
+ // failed to resume, or a poisoned handle was replaced mid-send), which
2057
+ // gets the full recovery shape instead — enhanced context + the turn's
2058
+ // recorded transcript + the same decisions — because the bare decisions on
2059
+ // an empty conversation strand the agent with instructions and no story,
2060
+ // and the session inherits that amnesia permanently (issue #366).
2012
2061
  if (isHitlReinvocation(approvalDecisions)) {
2062
+ if (resolution.reason !== "resumed_successfully") {
2063
+ return buildHitlRecoveryPrompt({
2064
+ instructions,
2065
+ userMessage,
2066
+ skills,
2067
+ channelMessaging: input.channelMessaging ?? [],
2068
+ subAgents,
2069
+ workspaceDirs,
2070
+ workspaceFileRefs,
2071
+ attachments,
2072
+ vision: input.vision,
2073
+ downloadUrlKind: input.downloadUrlKind,
2074
+ interactionMode,
2075
+ buildFromPlan,
2076
+ contextBridge: input.contextBridge,
2077
+ senderIdentity: input.senderIdentity,
2078
+ sessionContext: input.sessionContext,
2079
+ conversationCatchup,
2080
+ }, {
2081
+ turnDigest: input.turnRecoveryDigest,
2082
+ pendingApprovals: input.pendingApprovals,
2083
+ approvalDecisions,
2084
+ appliedToolCallIds: input.appliedToolCallIds,
2085
+ });
2086
+ }
2013
2087
  return buildReinvocationPrompt(input.pendingApprovals, approvalDecisions, input.appliedToolCallIds);
2014
2088
  }
2015
2089
  // A successfully resumed agent carries its own conversation context via the
@@ -2031,7 +2105,7 @@ export function buildPrompt(input) {
2031
2105
  formatInteractionModePrefix(interactionMode),
2032
2106
  formatImplementPlanSection(buildFromPlan, attachments),
2033
2107
  attachments.length > 0
2034
- ? formatInputFiles(attachments, input.vision)
2108
+ ? formatInputFiles(attachments, input.vision, input.downloadUrlKind)
2035
2109
  : undefined,
2036
2110
  conversationCatchup !== undefined
2037
2111
  ? formatConversationCatchupSection(conversationCatchup)
@@ -2048,13 +2122,13 @@ export function buildPrompt(input) {
2048
2122
  instructions,
2049
2123
  userMessage,
2050
2124
  skills,
2051
- datastoreUsages: input.datastoreUsages ?? [],
2052
2125
  channelMessaging: input.channelMessaging ?? [],
2053
2126
  subAgents,
2054
2127
  workspaceDirs,
2055
2128
  workspaceFileRefs,
2056
2129
  attachments,
2057
2130
  vision: input.vision,
2131
+ downloadUrlKind: input.downloadUrlKind,
2058
2132
  interactionMode,
2059
2133
  buildFromPlan,
2060
2134
  contextBridge: input.contextBridge,