@stigmer/runner 3.12.3 → 3.12.5

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 (227) hide show
  1. package/dist/.build-fingerprint +1 -1
  2. package/dist/activities/call-http.js +12 -0
  3. package/dist/activities/call-http.js.map +1 -1
  4. package/dist/activities/call-llm.d.ts +18 -0
  5. package/dist/activities/call-llm.js +56 -2
  6. package/dist/activities/call-llm.js.map +1 -1
  7. package/dist/activities/execute-cursor/agent-session-cache.d.ts +72 -0
  8. package/dist/activities/execute-cursor/agent-session-cache.js +186 -0
  9. package/dist/activities/execute-cursor/agent-session-cache.js.map +1 -0
  10. package/dist/activities/execute-cursor/cursor-mcp-config.d.ts +45 -0
  11. package/dist/activities/execute-cursor/cursor-mcp-config.js +76 -0
  12. package/dist/activities/execute-cursor/cursor-mcp-config.js.map +1 -0
  13. package/dist/activities/execute-cursor/index.d.ts +10 -1
  14. package/dist/activities/execute-cursor/index.js +108 -57
  15. package/dist/activities/execute-cursor/index.js.map +1 -1
  16. package/dist/activities/execute-cursor/prompt-builder.d.ts +13 -2
  17. package/dist/activities/execute-cursor/prompt-builder.js +11 -0
  18. package/dist/activities/execute-cursor/prompt-builder.js.map +1 -1
  19. package/dist/activities/execute-cursor/service-tier.d.ts +5 -15
  20. package/dist/activities/execute-cursor/service-tier.js +5 -21
  21. package/dist/activities/execute-cursor/service-tier.js.map +1 -1
  22. package/dist/activities/execute-cursor/session-lifecycle.d.ts +1 -1
  23. package/dist/activities/execute-cursor/skill-resolver.d.ts +2 -21
  24. package/dist/activities/execute-cursor/skill-resolver.js +5 -108
  25. package/dist/activities/execute-cursor/skill-resolver.js.map +1 -1
  26. package/dist/activities/execute-deep-agent/approval-file-change.d.ts +9 -0
  27. package/dist/activities/execute-deep-agent/approval-file-change.js +2 -1
  28. package/dist/activities/execute-deep-agent/approval-file-change.js.map +1 -1
  29. package/dist/activities/execute-deep-agent/cas-capture-backend.d.ts +18 -1
  30. package/dist/activities/execute-deep-agent/cas-capture-backend.js +20 -3
  31. package/dist/activities/execute-deep-agent/cas-capture-backend.js.map +1 -1
  32. package/dist/activities/execute-deep-agent/index.js +57 -5
  33. package/dist/activities/execute-deep-agent/index.js.map +1 -1
  34. package/dist/activities/execute-deep-agent/prompt-builder.d.ts +9 -0
  35. package/dist/activities/execute-deep-agent/prompt-builder.js +10 -0
  36. package/dist/activities/execute-deep-agent/prompt-builder.js.map +1 -1
  37. package/dist/activities/execute-deep-agent/setup.js +31 -16
  38. package/dist/activities/execute-deep-agent/setup.js.map +1 -1
  39. package/dist/activities/execute-deep-agent/shell-env.d.ts +8 -5
  40. package/dist/activities/execute-deep-agent/shell-env.js +10 -7
  41. package/dist/activities/execute-deep-agent/shell-env.js.map +1 -1
  42. package/dist/activities/execute-deep-agent/subagent-transformer.js +17 -11
  43. package/dist/activities/execute-deep-agent/subagent-transformer.js.map +1 -1
  44. package/dist/activities/execute-deep-agent/subagent-wiring.d.ts +3 -3
  45. package/dist/config.js +10 -5
  46. package/dist/config.js.map +1 -1
  47. package/dist/encryption/config.js +7 -2
  48. package/dist/encryption/config.js.map +1 -1
  49. package/dist/index.d.ts +1 -1
  50. package/dist/index.js +1 -1
  51. package/dist/main.js +12 -6
  52. package/dist/main.js.map +1 -1
  53. package/dist/middleware/path-normalization.d.ts +49 -49
  54. package/dist/middleware/path-normalization.js +73 -84
  55. package/dist/middleware/path-normalization.js.map +1 -1
  56. package/dist/middleware/types.d.ts +3 -3
  57. package/dist/payload-codecs.js +2 -1
  58. package/dist/payload-codecs.js.map +1 -1
  59. package/dist/runner-manager.d.ts +1 -1
  60. package/dist/runner-manager.js +42 -22
  61. package/dist/runner-manager.js.map +1 -1
  62. package/dist/runner.js +30 -6
  63. package/dist/runner.js.map +1 -1
  64. package/dist/shared/args-preview.d.ts +8 -0
  65. package/dist/shared/args-preview.js +14 -3
  66. package/dist/shared/args-preview.js.map +1 -1
  67. package/dist/shared/artifact-storage.d.ts +11 -1
  68. package/dist/shared/artifact-storage.js +10 -1
  69. package/dist/shared/artifact-storage.js.map +1 -1
  70. package/dist/shared/declared-preferences.d.ts +47 -0
  71. package/dist/shared/declared-preferences.js +64 -0
  72. package/dist/shared/declared-preferences.js.map +1 -0
  73. package/dist/shared/fingerprint-secret.d.ts +3 -2
  74. package/dist/shared/fingerprint-secret.js +5 -3
  75. package/dist/shared/fingerprint-secret.js.map +1 -1
  76. package/dist/shared/llm-backend.js +8 -1
  77. package/dist/shared/llm-backend.js.map +1 -1
  78. package/dist/shared/mcp-enabled-tools.d.ts +2 -3
  79. package/dist/shared/mcp-enabled-tools.js +2 -3
  80. package/dist/shared/mcp-enabled-tools.js.map +1 -1
  81. package/dist/shared/mcp-resolver.d.ts +20 -15
  82. package/dist/shared/mcp-resolver.js +11 -12
  83. package/dist/shared/mcp-resolver.js.map +1 -1
  84. package/dist/shared/model-client.d.ts +15 -0
  85. package/dist/shared/model-client.js +57 -13
  86. package/dist/shared/model-client.js.map +1 -1
  87. package/dist/shared/placeholder-resolver.d.ts +9 -2
  88. package/dist/shared/placeholder-resolver.js +9 -2
  89. package/dist/shared/placeholder-resolver.js.map +1 -1
  90. package/dist/shared/plan-mode-permissions.d.ts +26 -46
  91. package/dist/shared/plan-mode-permissions.js +27 -55
  92. package/dist/shared/plan-mode-permissions.js.map +1 -1
  93. package/dist/shared/registry-endpoint.d.ts +5 -0
  94. package/dist/shared/registry-endpoint.js +7 -1
  95. package/dist/shared/registry-endpoint.js.map +1 -1
  96. package/dist/shared/runner-credential-keys.d.ts +26 -1
  97. package/dist/shared/runner-credential-keys.js +34 -1
  98. package/dist/shared/runner-credential-keys.js.map +1 -1
  99. package/dist/shared/runner-credential-store.d.ts +77 -0
  100. package/dist/shared/runner-credential-store.js +111 -0
  101. package/dist/shared/runner-credential-store.js.map +1 -0
  102. package/dist/shared/service-tier.d.ts +55 -0
  103. package/dist/shared/service-tier.js +67 -0
  104. package/dist/shared/service-tier.js.map +1 -0
  105. package/dist/shared/skill-mount.d.ts +89 -0
  106. package/dist/shared/skill-mount.js +142 -0
  107. package/dist/shared/skill-mount.js.map +1 -0
  108. package/dist/shared/skill-writer.d.ts +28 -26
  109. package/dist/shared/skill-writer.js +79 -102
  110. package/dist/shared/skill-writer.js.map +1 -1
  111. package/dist/shared/worker-shutdown.d.ts +67 -0
  112. package/dist/shared/worker-shutdown.js +79 -0
  113. package/dist/shared/worker-shutdown.js.map +1 -0
  114. package/dist/shared/workspace/types.d.ts +3 -2
  115. package/dist/shared/zip-extract.d.ts +10 -3
  116. package/dist/shared/zip-extract.js +10 -3
  117. package/dist/shared/zip-extract.js.map +1 -1
  118. package/dist/workflow-engine/loader.js +38 -1
  119. package/dist/workflow-engine/loader.js.map +1 -1
  120. package/dist/workflow-engine/tasks/call-function.d.ts +14 -0
  121. package/dist/workflow-engine/tasks/call-function.js +49 -5
  122. package/dist/workflow-engine/tasks/call-function.js.map +1 -1
  123. package/dist/workflow-engine/tasks/human-input.js +23 -2
  124. package/dist/workflow-engine/tasks/human-input.js.map +1 -1
  125. package/dist/workflow-engine/types.d.ts +6 -0
  126. package/dist/workflow-engine/types.js.map +1 -1
  127. package/dist/workflows/engine-core.js +36 -8
  128. package/dist/workflows/engine-core.js.map +1 -1
  129. package/package.json +15 -11
  130. package/src/activities/__tests__/call-http.test.ts +36 -0
  131. package/src/activities/__tests__/call-llm.test.ts +77 -0
  132. package/src/activities/call-http.ts +17 -0
  133. package/src/activities/call-llm.ts +78 -2
  134. package/src/activities/execute-cursor/__tests__/agent-session-cache.test.ts +220 -0
  135. package/src/activities/execute-cursor/__tests__/build-prompt.test.ts +50 -0
  136. package/src/activities/execute-cursor/__tests__/cursor-mcp-config.test.ts +111 -0
  137. package/src/activities/execute-cursor/__tests__/service-tier.test.ts +1 -1
  138. package/src/activities/execute-cursor/__tests__/skill-resolver.test.ts +3 -94
  139. package/src/activities/execute-cursor/agent-session-cache.ts +229 -0
  140. package/src/activities/execute-cursor/cursor-mcp-config.ts +107 -0
  141. package/src/activities/execute-cursor/index.ts +129 -55
  142. package/src/activities/execute-cursor/prompt-builder.ts +29 -2
  143. package/src/activities/execute-cursor/service-tier.ts +5 -29
  144. package/src/activities/execute-cursor/session-lifecycle.ts +1 -1
  145. package/src/activities/execute-cursor/skill-resolver.ts +10 -137
  146. package/src/activities/execute-deep-agent/__tests__/approval-file-change.test.ts +15 -0
  147. package/src/activities/execute-deep-agent/__tests__/cas-capture-backend.test.ts +75 -0
  148. package/src/activities/execute-deep-agent/__tests__/hitl-reject.test.ts +1 -0
  149. package/src/activities/execute-deep-agent/__tests__/hitl-resume-approve-all.test.ts +1 -0
  150. package/src/activities/execute-deep-agent/__tests__/hitl-resume-history.test.ts +1 -0
  151. package/src/activities/execute-deep-agent/__tests__/index.test.ts +1 -0
  152. package/src/activities/execute-deep-agent/__tests__/plan-mode-path-normalization.test.ts +42 -29
  153. package/src/activities/execute-deep-agent/__tests__/prompt-builder.test.ts +52 -0
  154. package/src/activities/execute-deep-agent/__tests__/sequential-gate-resume.test.ts +1 -0
  155. package/src/activities/execute-deep-agent/__tests__/subagent-plan-mode-permissions.test.ts +11 -8
  156. package/src/activities/execute-deep-agent/approval-file-change.ts +11 -1
  157. package/src/activities/execute-deep-agent/cas-capture-backend.ts +20 -3
  158. package/src/activities/execute-deep-agent/index.ts +57 -5
  159. package/src/activities/execute-deep-agent/prompt-builder.ts +22 -0
  160. package/src/activities/execute-deep-agent/setup.ts +36 -19
  161. package/src/activities/execute-deep-agent/shell-env.ts +10 -7
  162. package/src/activities/execute-deep-agent/subagent-transformer.ts +19 -12
  163. package/src/activities/execute-deep-agent/subagent-wiring.ts +3 -3
  164. package/src/config.ts +10 -5
  165. package/src/encryption/config.ts +8 -2
  166. package/src/index.ts +1 -1
  167. package/src/main.ts +16 -6
  168. package/src/middleware/__tests__/path-normalization.test.ts +32 -32
  169. package/src/middleware/path-normalization.ts +78 -90
  170. package/src/middleware/types.ts +3 -3
  171. package/src/payload-codecs.ts +2 -1
  172. package/src/runner-manager.ts +55 -22
  173. package/src/runner.ts +39 -6
  174. package/src/shared/__tests__/artifact-storage.test.ts +28 -0
  175. package/src/shared/__tests__/bedrock-seam.test.ts +5 -5
  176. package/src/shared/__tests__/declared-preferences.test.ts +109 -0
  177. package/src/shared/__tests__/foundry-seam.test.ts +5 -5
  178. package/src/shared/__tests__/model-client.test.ts +99 -0
  179. package/src/shared/__tests__/plan-mode-permissions.test.ts +15 -44
  180. package/src/shared/__tests__/runner-credential-store.test.ts +155 -0
  181. package/src/shared/__tests__/skill-mount.test.ts +238 -0
  182. package/src/shared/__tests__/skill-writer.test.ts +151 -128
  183. package/src/shared/__tests__/vertex-adapter.test.ts +30 -3
  184. package/src/shared/__tests__/vertex-seam.test.ts +5 -5
  185. package/src/shared/__tests__/worker-shutdown.test.ts +101 -0
  186. package/src/shared/__tests__/zip-extract.test.ts +46 -11
  187. package/src/shared/args-preview.ts +17 -3
  188. package/src/shared/artifact-storage.ts +20 -1
  189. package/src/shared/declared-preferences.ts +84 -0
  190. package/src/shared/fingerprint-secret.ts +5 -3
  191. package/src/shared/llm-backend.ts +7 -1
  192. package/src/shared/mcp-enabled-tools.ts +2 -3
  193. package/src/shared/mcp-resolver.ts +20 -20
  194. package/src/shared/model-client.ts +76 -13
  195. package/src/shared/placeholder-resolver.ts +9 -2
  196. package/src/shared/plan-mode-permissions.ts +27 -58
  197. package/src/shared/registry-endpoint.ts +9 -1
  198. package/src/shared/runner-credential-keys.ts +36 -1
  199. package/src/shared/runner-credential-store.ts +115 -0
  200. package/src/shared/service-tier.ts +78 -0
  201. package/src/shared/skill-mount.ts +179 -0
  202. package/src/shared/skill-writer.ts +96 -130
  203. package/src/shared/worker-shutdown.ts +99 -0
  204. package/src/shared/workspace/types.ts +3 -2
  205. package/src/shared/zip-extract.ts +14 -7
  206. package/src/workflow-engine/__tests__/golden-execution.test.ts +20 -1
  207. package/src/workflow-engine/__tests__/loader.test.ts +51 -0
  208. package/src/workflow-engine/__tests__/tasks/call-function.test.ts +105 -0
  209. package/src/workflow-engine/__tests__/tasks/human-input.test.ts +117 -0
  210. package/src/workflow-engine/loader.ts +46 -1
  211. package/src/workflow-engine/tasks/call-function.ts +74 -13
  212. package/src/workflow-engine/tasks/human-input.ts +33 -5
  213. package/src/workflow-engine/types.ts +6 -0
  214. package/src/workflows/engine-core.ts +39 -8
  215. package/dist/activities/execute-cursor/connect-backfill.d.ts +0 -19
  216. package/dist/activities/execute-cursor/connect-backfill.js +0 -27
  217. package/dist/activities/execute-cursor/connect-backfill.js.map +0 -1
  218. package/dist/activities/execute-cursor/mcp-resolver.d.ts +0 -120
  219. package/dist/activities/execute-cursor/mcp-resolver.js +0 -194
  220. package/dist/activities/execute-cursor/mcp-resolver.js.map +0 -1
  221. package/dist/activities/execute-cursor/placeholder-resolver.d.ts +0 -34
  222. package/dist/activities/execute-cursor/placeholder-resolver.js +0 -82
  223. package/dist/activities/execute-cursor/placeholder-resolver.js.map +0 -1
  224. package/src/activities/execute-cursor/__tests__/mcp-resolver.test.ts +0 -125
  225. package/src/activities/execute-cursor/connect-backfill.ts +0 -52
  226. package/src/activities/execute-cursor/mcp-resolver.ts +0 -324
  227. package/src/activities/execute-cursor/placeholder-resolver.ts +0 -109
@@ -34,6 +34,7 @@ import { ExecutionPhase, FileChangeSetStatus, InteractionMode, MessageType, Appr
34
34
  import { StigmerClient } from "../../client/stigmer-client.js";
35
35
  import { describeExecutionError } from "../../shared/model-error.js";
36
36
  import { resolveAgentWithTransportRecovery } from "./session-lifecycle.js";
37
+ import { cacheSessionAgent, computeAgentFingerprint, takeCachedAgent } from "./agent-session-cache.js";
37
38
  import { CursorMode } from "@stigmer/protos/ai/stigmer/agentic/session/v1/enum_pb";
38
39
  import { determineCursorMode, isCloudMode } from "./cursor-mode.js";
39
40
  import { MessageAccumulator, cancelInProgressSubAgentProtos, collapseRedundantToolCallTwins } from "./message-translator.js";
@@ -44,6 +45,7 @@ import { readConversationCatchup } from "../../shared/conversation-catchup.js";
44
45
  import { readSenderIdentity } from "../../shared/sender-identity.js";
45
46
  import { injectCallerIdentityEnv, resolveCallerIdentity, } from "../../shared/caller-identity.js";
46
47
  import { readSessionContext } from "../../shared/session-context.js";
48
+ import { readDeclaredPreferences } from "../../shared/declared-preferences.js";
47
49
  import { withholdSecretContentFromMessages } from "../../shared/tool-row.js";
48
50
  import { StallTimeoutError, formatStallFailure } from "../../shared/stall-watchdog.js";
49
51
  import { resolveUsableArtifactStorage, loadArtifactStorageConfig } from "../../shared/artifact-storage.js";
@@ -54,7 +56,8 @@ import { DeltaEnricher } from "./delta-enricher.js";
54
56
  import { TodoTracker } from "./todo-tracker.js";
55
57
  import { StreamingUpdateScheduler, loadStreamingConfig } from "../../shared/streaming-scheduler.js";
56
58
  import { createCursorEventRecorder } from "./cursor-event-recorder.js";
57
- import { resolveMcpServers, toCursorMcpConfig, validateMcpServerEnv } from "./mcp-resolver.js";
59
+ import { resolveMcpServers } from "../../shared/mcp-resolver.js";
60
+ import { toCursorMcpConfig, validateMcpServerEnv } from "./cursor-mcp-config.js";
58
61
  import { resolveMcpTransportPosture } from "../../shared/mcp-transport-guard.js";
59
62
  import { discoverChannelMessaging, synthesizeChannelAttachment, } from "../../shared/channel-attachment.js";
60
63
  import { readChannelConversationId, synthesizeConversationAttachment, } from "../../shared/conversation-attachment.js";
@@ -62,7 +65,7 @@ import { injectSynthesizedAttachment } from "../../shared/synthesized-attachment
62
65
  import { mergeApprovalPolicies } from "./approval-policy.js";
63
66
  import { deriveActiveLeases, isUnattendedApprovalMode } from "../../shared/approval-policy.js";
64
67
  import { enabledToolsBySlug } from "../../shared/mcp-enabled-tools.js";
65
- import { backfillMcpServersIfNeeded } from "./connect-backfill.js";
68
+ import { backfillMcpServersIfNeeded } from "../../shared/connect-backfill.js";
66
69
  import { resolveExecutionEnv } from "./env-resolver.js";
67
70
  import { resolveBlueprint } from "./blueprint-resolver.js";
68
71
  import { buildCursorSubAgentDefinitions } from "./subagent-config.js";
@@ -91,7 +94,8 @@ import { statusProtoWriter } from "../../shared/execution-status-writer.js";
91
94
  import { setInterceptorExecutionId, runWithExecutionContext } from "./fetch-interceptor.js";
92
95
  import { closeProxySessions } from "./http2-interceptor.js";
93
96
  import { resolveModelId, ensureLoaded as ensurePricingLoaded } from "./model-pricing.js";
94
- import { resolveEffectiveServiceTier, resolveServiceTierParams } from "./service-tier.js";
97
+ import { resolveEffectiveServiceTier } from "../../shared/service-tier.js";
98
+ import { resolveServiceTierParams } from "./service-tier.js";
95
99
  import { UsageAccumulator } from "./usage-accumulator.js";
96
100
  import { StreamingUsageSummarySchema } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/usage_pb";
97
101
  import { activityStarted, activityFinished } from "../../idle-watchdog.js";
@@ -101,7 +105,7 @@ import { synthesizeError, formatClassifiedError, shouldRetryWithFreshAgent, extr
101
105
  import { createAgent, createCloudAgent } from "./session-lifecycle.js";
102
106
  import { setMaxListeners } from "node:events";
103
107
  import { startHeartbeat } from "../../shared/heartbeat.js";
104
- import { getShutdownSignalForQueue } from "../../runner-manager.js";
108
+ import { classifyTurnInterruption, getShutdownSignalForQueue } from "../../shared/worker-shutdown.js";
105
109
  /**
106
110
  * Creates the activity functions bound to the runner config.
107
111
  * Returned object is passed to Temporal Worker.create({ activities }).
@@ -524,12 +528,15 @@ turnSeq) {
524
528
  await reportSetupProgress(client, executionId, "Resolving MCP servers");
525
529
  const transportPosture = resolveMcpTransportPosture(config.mode);
526
530
  const mcpEnvVars = injectCallerIdentityEnv(envVars, resolveCallerIdentity(blueprint.sessionSpec.metadata, session.status?.audit?.specAudit?.createdBy), sessionId);
527
- let mcpResolution = await resolveMcpServers(client, blueprint.mergedMcpServerUsages, mcpEnvVars, transportPosture);
531
+ // The resolved-server list mutates through backfill and attachment
532
+ // injection below; the Cursor SDK config is projected from it exactly
533
+ // once, after the last mutation (see toCursorMcpConfig).
534
+ let resolvedMcpServers = (await resolveMcpServers(client, blueprint.mergedMcpServerUsages, mcpEnvVars, transportPosture)).resolvedServers;
528
535
  setupTiming.mark("resolve_mcp_servers");
529
536
  // Phase 4a: Connect backfill for undiscovered MCP servers
530
537
  heartbeatPhase = "resolving_mcp_servers";
531
538
  const sessionOrg = session.metadata?.org ?? "";
532
- mcpResolution = await backfillMcpServersIfNeeded(client, mcpResolution, blueprint.mergedMcpServerUsages, mcpEnvVars, sessionOrg, transportPosture, heartbeat, secretKeys);
539
+ resolvedMcpServers = await backfillMcpServersIfNeeded(client, resolvedMcpServers, blueprint.mergedMcpServerUsages, mcpEnvVars, sessionOrg, transportPosture, heartbeat, secretKeys);
533
540
  setupTiming.mark("backfill_mcp");
534
541
  // The synthesized attachments' credential story (DD-006 D4): the
535
542
  // exchanged token authenticates the discovery reads per-call (a
@@ -572,11 +579,7 @@ turnSeq) {
572
579
  backendEndpoint: config.stigmerBackendEndpoint,
573
580
  });
574
581
  if (attachment) {
575
- const resolvedServers = injectSynthesizedAttachment(mcpResolution.resolvedServers, attachment, "channel messaging");
576
- mcpResolution = {
577
- resolvedServers,
578
- cursorConfig: toCursorMcpConfig(resolvedServers),
579
- };
582
+ resolvedMcpServers = injectSynthesizedAttachment(resolvedMcpServers, attachment, "channel messaging");
580
583
  }
581
584
  }
582
585
  // Phase 4a4: Synthesize the conversation participation attachment
@@ -592,13 +595,11 @@ turnSeq) {
592
595
  backendEndpoint: config.stigmerBackendEndpoint,
593
596
  });
594
597
  if (conversationAttachment) {
595
- const resolvedServers = injectSynthesizedAttachment(mcpResolution.resolvedServers, conversationAttachment, "conversation participation");
596
- mcpResolution = {
597
- resolvedServers,
598
- cursorConfig: toCursorMcpConfig(resolvedServers),
599
- };
598
+ resolvedMcpServers = injectSynthesizedAttachment(resolvedMcpServers, conversationAttachment, "conversation participation");
600
599
  }
601
- const mcpConfig = mcpResolution.cursorConfig;
600
+ // The one projection point: every mutation above is now visible in the
601
+ // Cursor SDK config by construction (no per-mutation rebuild to forget).
602
+ const mcpConfig = toCursorMcpConfig(resolvedMcpServers);
602
603
  // Phase 4b: Merge approval policies from all layers.
603
604
  //
604
605
  // Two bypasses (see ActiveLeases, shared with the native harness): the
@@ -612,10 +613,10 @@ turnSeq) {
612
613
  // Layer-3 overrides ride each resolved server from its merged usage —
613
614
  // see ResolvedMcpServer.toolApprovalOverrides (issue #349) — so there
614
615
  // is no separate override input to pass here.
615
- const mergedPolicies = mergeApprovalPolicies(mcpResolution.resolvedServers, leases);
616
+ const mergedPolicies = mergeApprovalPolicies(resolvedMcpServers, leases);
616
617
  heartbeat();
617
618
  // Phase 4c: Validate MCP server env health (diagnostic, non-blocking)
618
- const mcpWarnings = validateMcpServerEnv(mcpResolution.resolvedServers, blueprint.mergedMcpServerUsages, envVars);
619
+ const mcpWarnings = validateMcpServerEnv(resolvedMcpServers, blueprint.mergedMcpServerUsages);
619
620
  if (mcpWarnings.length > 0) {
620
621
  console.warn(`ExecuteCursor MCP pre-flight warnings: execution=${executionId}\n` +
621
622
  mcpWarnings.map((w) => ` - ${w}`).join("\n"));
@@ -758,7 +759,7 @@ turnSeq) {
758
759
  // servers' allow-lists, enforced by the hook's "disabled" arm ahead of
759
760
  // every approval bypass. The Cursor SDK config cannot hide a server's
760
761
  // tools, so this deny-at-call is the harness's enforcement.
761
- enabledToolsBySlug(mcpResolution.resolvedServers));
762
+ enabledToolsBySlug(resolvedMcpServers));
762
763
  const hitlGate = await installHitlGate({
763
764
  workspaceRoot: primaryWorkspaceDir,
764
765
  hitlDir,
@@ -882,20 +883,47 @@ turnSeq) {
882
883
  // is the largest user-visible setup segment; this split keeps its
883
884
  // historical meaning — the SDK call was already 98%+ of it).
884
885
  setupTiming.mark("prepare_agent");
885
- let resolution = await resolveAgentWithTransportRecovery({
886
- harnessStateId: threadId,
887
- createOptions,
888
- mode: agentMode,
889
- timeoutMs: config.agentResolveTimeoutMs,
890
- buildTimeoutMessage: (finalAttempt) => `Cursor agent ${threadId ? "resume" : "create"} timed out after ${resolveTimeoutSeconds}s ` +
891
- `(${config.proxyEndpoint ? `via proxy ${config.proxyEndpoint}` : "direct Cursor API connection"}). ` +
892
- `The transport connection is likely dead. ` +
893
- (finalAttempt
894
- ? `An automatic retry on a fresh transport connection also timed out. ` +
895
- `Retry the message later; if this persists, check proxy and network health.`
896
- : `Resetting the transport and retrying automatically.`),
897
- resetTransport: closeProxySessions,
898
- });
886
+ // Phase 8a: Reuse the previous turn's agent when this session parked one
887
+ // (#215). A checkout hit skips Agent.resume() AND — the real win — keeps
888
+ // the SDK executor lease alive, so agent.send() below re-acquires the
889
+ // warm executor instead of re-spawning every stdio MCP server (the
890
+ // measured 2.2–3.2s `send_returned` tax). The fingerprint covers the
891
+ // full acquisition config, so any drift (rotated credential, edited MCP
892
+ // servers, model change) falls through to a fresh resolve.
893
+ const agentFingerprint = computeAgentFingerprint(createOptions);
894
+ const parkedAgent = takeCachedAgent(sessionId, agentFingerprint, threadId ?? "");
895
+ let resolution;
896
+ if (parkedAgent) {
897
+ console.log(`ExecuteCursor reusing parked session agent: execution=${executionId}, ` +
898
+ `session=${sessionId}, agentId=${parkedAgent.agentId}`);
899
+ resolution = {
900
+ agent: parkedAgent,
901
+ agentId: parkedAgent.agentId,
902
+ isNew: false,
903
+ resumed: true,
904
+ mode: agentMode,
905
+ // The parked handle IS the live conversation — every consumer of
906
+ // "resumed_successfully" (prompt selection, poisoned-handle
907
+ // recovery eligibility) wants exactly those semantics.
908
+ reason: "resumed_successfully",
909
+ };
910
+ }
911
+ else {
912
+ resolution = await resolveAgentWithTransportRecovery({
913
+ harnessStateId: threadId,
914
+ createOptions,
915
+ mode: agentMode,
916
+ timeoutMs: config.agentResolveTimeoutMs,
917
+ buildTimeoutMessage: (finalAttempt) => `Cursor agent ${threadId ? "resume" : "create"} timed out after ${resolveTimeoutSeconds}s ` +
918
+ `(${config.proxyEndpoint ? `via proxy ${config.proxyEndpoint}` : "direct Cursor API connection"}). ` +
919
+ `The transport connection is likely dead. ` +
920
+ (finalAttempt
921
+ ? `An automatic retry on a fresh transport connection also timed out. ` +
922
+ `Retry the message later; if this persists, check proxy and network health.`
923
+ : `Resetting the transport and retrying automatically.`),
924
+ resetTransport: closeProxySessions,
925
+ });
926
+ }
899
927
  console.log(`ExecuteCursor agent resolved: execution=${executionId}, ` +
900
928
  `reason=${resolution.reason}, mode=${resolution.mode}, ` +
901
929
  `agentId=${resolution.agentId}, resumed=${resolution.resumed}` +
@@ -960,6 +988,7 @@ turnSeq) {
960
988
  contextBridge: readContextBridge(blueprint.sessionSpec.metadata),
961
989
  senderIdentity: readSenderIdentity(blueprint.sessionSpec.metadata),
962
990
  sessionContext: readSessionContext(blueprint.sessionSpec.metadata),
991
+ declaredPreferences: readDeclaredPreferences(spec.declaredPreferences),
963
992
  conversationCatchup: readConversationCatchup(spec.conversationCatchup),
964
993
  // The turn's recorded transcript, seeded from the persisted execution
965
994
  // on a reinvocation (Phase 3). Consumed only by the HITL-recovery
@@ -1159,14 +1188,22 @@ turnSeq) {
1159
1188
  // classifies a shutdown from the shutdown signal directly (in
1160
1189
  // resolvePreBoundaryTerminal). The heartbeat timer may set `cancelled` before
1161
1190
  // the AbortSignal microtask propagates; the direct signal check catches that.
1162
- const isShutdown = periodicHeartbeat.workerShutdown || (shutdownSignal?.aborted ?? false);
1163
- if (isShutdown) {
1191
+ // The decision table (including #776's grace-window guard: an aborted
1192
+ // shutdown signal with NO interruption evidence stays "none") lives in
1193
+ // classifyTurnInterruption — shared/worker-shutdown.ts.
1194
+ const interruption = classifyTurnInterruption({
1195
+ heartbeatCancelled: periodicHeartbeat.cancelled,
1196
+ heartbeatWorkerShutdown: periodicHeartbeat.workerShutdown,
1197
+ cancellationSignalAborted: Context.current().cancellationSignal.aborted,
1198
+ shutdownSignalAborted: shutdownSignal?.aborted ?? false,
1199
+ });
1200
+ if (interruption === "worker-shutdown") {
1164
1201
  turnState.pauseDetected = false;
1165
1202
  }
1166
- else if (periodicHeartbeat.cancelled) {
1203
+ else if (interruption === "pause") {
1167
1204
  turnState.pauseDetected = true;
1168
1205
  }
1169
- workerShutdownDetected = isShutdown;
1206
+ workerShutdownDetected = interruption === "worker-shutdown";
1170
1207
  // Post-stream finalize, shared by the primary turn and both recovery retries:
1171
1208
  // finalize the transcript + streaming flags, mark any in-flight sub-agent
1172
1209
  // CANCELLED on an aborted turn, snapshot usage, flush the recorder, and
@@ -1234,18 +1271,21 @@ turnSeq) {
1234
1271
  timestamp: utcTimestamp(),
1235
1272
  }));
1236
1273
  await persist(status);
1237
- try {
1238
- resolution.agent.close();
1239
- }
1240
- catch { /* best effort */ }
1274
+ // Clean terminal: the conversation continues on the next message,
1275
+ // so park the healthy agent for that turn (#215).
1276
+ cacheSessionAgent(sessionId ?? "", resolution.agent, agentFingerprint);
1241
1277
  console.warn(`ExecuteCursor terminated (cost cap): execution=${executionId}, ` +
1242
1278
  `estimatedCostUsd=${estimated.toFixed(4)}, maxCostUsd=${maxCostUsd.toFixed(2)}`);
1243
1279
  return { kind: "return" };
1244
1280
  }
1245
- // Worker shutdown: the runner-manager aborted the shutdown signal. NOT a
1281
+ // Worker shutdown: the runner/manager aborted the shutdown signal. NOT a
1246
1282
  // user pause. Checked via the shutdown signal directly so a retry (whose
1247
- // periodic heartbeat is already stopped) still classifies it correctly.
1248
- if (workerShutdownDetected || (shutdownSignal?.aborted ?? false)) {
1283
+ // periodic heartbeat is already stopped) still classifies it correctly
1284
+ // but only alongside a delivered cancellation: a retry that completed
1285
+ // normally inside the drain grace window must stay a completion (#776's
1286
+ // grace-window guard, mirroring the primary's `interrupted` gate).
1287
+ if (workerShutdownDetected ||
1288
+ ((shutdownSignal?.aborted ?? false) && Context.current().cancellationSignal.aborted)) {
1249
1289
  status.phase = ExecutionPhase.EXECUTION_FAILED;
1250
1290
  status.error = "Execution interrupted: runner worker was shut down. Retry or resume.";
1251
1291
  status.completedAt = utcTimestamp();
@@ -1296,10 +1336,8 @@ turnSeq) {
1296
1336
  timestamp: utcTimestamp(),
1297
1337
  }));
1298
1338
  await persist(status);
1299
- try {
1300
- resolution.agent.close();
1301
- }
1302
- catch { /* best effort */ }
1339
+ // Clean terminal — park for the session's next turn (#215).
1340
+ cacheSessionAgent(sessionId ?? "", resolution.agent, agentFingerprint);
1303
1341
  console.log(`ExecuteCursor completed (platform stop): execution=${executionId}`);
1304
1342
  return { kind: "return" };
1305
1343
  }
@@ -1343,6 +1381,12 @@ turnSeq) {
1343
1381
  const enterApprovalPause = async (boundary) => {
1344
1382
  status.phase = ExecutionPhase.EXECUTION_WAITING_FOR_APPROVAL;
1345
1383
  await persist(status);
1384
+ // The approval-resume reinvocation is the cache's best case: park the
1385
+ // agent so the resumed turn skips the full executor rebuild (#215).
1386
+ // (This path previously dropped the handle without close() — the
1387
+ // lease leaked; parking makes the lifetime explicit.) An absent
1388
+ // sessionId falls back to "" — the cache closes the lease immediately.
1389
+ cacheSessionAgent(sessionId ?? "", resolution.agent, agentFingerprint);
1346
1390
  console.log(`ExecuteCursor returning WAITING_FOR_APPROVAL: ${boundary.deniedToolCallCount} gated tool(s), ` +
1347
1391
  `${boundary.capturedChangeCount} file card(s) pending`);
1348
1392
  return slimStatus(status);
@@ -1542,6 +1586,7 @@ turnSeq) {
1542
1586
  contextBridge: readContextBridge(blueprint.sessionSpec.metadata),
1543
1587
  senderIdentity: readSenderIdentity(blueprint.sessionSpec.metadata),
1544
1588
  sessionContext: readSessionContext(blueprint.sessionSpec.metadata),
1589
+ declaredPreferences: readDeclaredPreferences(spec.declaredPreferences),
1545
1590
  conversationCatchup: readConversationCatchup(spec.conversationCatchup),
1546
1591
  // Composed fresh (not reused from Phase 10): the failed primary
1547
1592
  // stream may have appended partial work onto status.messages,
@@ -1755,11 +1800,12 @@ turnSeq) {
1755
1800
  console.log(`ExecuteCursor completed: execution=${executionId}, phase=${ExecutionPhase[status.phase]}, ` +
1756
1801
  `hasStructuredOutput=${structuredOutput !== undefined}` +
1757
1802
  (status.error ? `, error=${status.error}` : ""));
1758
- // Release SDK executor lease to prevent cache buildup across workflow tasks
1759
- try {
1760
- resolution.agent.close();
1761
- }
1762
- catch { /* best effort */ }
1803
+ // Park the agent (with its executor lease) for the session's next turn
1804
+ // instead of closing it — the idle TTL / shutdown hooks in
1805
+ // agent-session-cache own the eventual release, so cache buildup across
1806
+ // sessions stays bounded while turns of ONE session stop paying the
1807
+ // executor + MCP re-spawn tax (#215).
1808
+ cacheSessionAgent(sessionId ?? "", resolution.agent, agentFingerprint);
1763
1809
  const slim = slimStatus(status);
1764
1810
  if (finalText !== undefined) {
1765
1811
  slim.final_text = finalText;
@@ -1772,9 +1818,12 @@ turnSeq) {
1772
1818
  catch (err) {
1773
1819
  periodicHeartbeat?.stop();
1774
1820
  if (err instanceof CancelledFailure) {
1775
- // workerShutdownDetected means the runner-manager signaled shutdown
1776
- // before the worker drained. This is infrastructure failure, not pause.
1777
- if (workerShutdownDetected) {
1821
+ // Worker shutdown is infrastructure failure, not pause. The direct
1822
+ // signal check covers a CancelledFailure thrown BEFORE the post-stream
1823
+ // classification ran (workerShutdownDetected still false); no extra
1824
+ // interruption-evidence gate is needed here — the caught
1825
+ // CancelledFailure IS the evidence (#776).
1826
+ if (workerShutdownDetected || (shutdownSignal?.aborted ?? false)) {
1778
1827
  console.log(`ExecuteCursor cancelled (worker shutdown) for execution ${executionId}`);
1779
1828
  status.phase = ExecutionPhase.EXECUTION_FAILED;
1780
1829
  status.error = "Execution interrupted: runner worker was shut down. Retry or resume.";
@@ -2076,6 +2125,7 @@ export function buildPrompt(input) {
2076
2125
  contextBridge: input.contextBridge,
2077
2126
  senderIdentity: input.senderIdentity,
2078
2127
  sessionContext: input.sessionContext,
2128
+ declaredPreferences: input.declaredPreferences,
2079
2129
  conversationCatchup,
2080
2130
  }, {
2081
2131
  turnDigest: input.turnRecoveryDigest,
@@ -2134,6 +2184,7 @@ export function buildPrompt(input) {
2134
2184
  contextBridge: input.contextBridge,
2135
2185
  senderIdentity: input.senderIdentity,
2136
2186
  sessionContext: input.sessionContext,
2187
+ declaredPreferences: input.declaredPreferences,
2137
2188
  conversationCatchup,
2138
2189
  });
2139
2190
  }