@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
@@ -41,6 +41,7 @@ import type { Config } from "../../config.js";
41
41
  import { StigmerClient } from "../../client/stigmer-client.js";
42
42
  import { describeExecutionError } from "../../shared/model-error.js";
43
43
  import { resolveAgentWithTransportRecovery } from "./session-lifecycle.js";
44
+ import { cacheSessionAgent, computeAgentFingerprint, takeCachedAgent } from "./agent-session-cache.js";
44
45
  import type { AgentResolution, AgentResolutionReason, CreateAgentOptions, CreateCloudAgentOptions } from "./session-lifecycle.js";
45
46
  import { CursorMode } from "@stigmer/protos/ai/stigmer/agentic/session/v1/enum_pb";
46
47
  import { determineCursorMode, isCloudMode } from "./cursor-mode.js";
@@ -55,6 +56,7 @@ import {
55
56
  resolveCallerIdentity,
56
57
  } from "../../shared/caller-identity.js";
57
58
  import { readSessionContext } from "../../shared/session-context.js";
59
+ import { readDeclaredPreferences } from "../../shared/declared-preferences.js";
58
60
  import { withholdSecretContentFromMessages } from "../../shared/tool-row.js";
59
61
  import { StallTimeoutError, formatStallFailure } from "../../shared/stall-watchdog.js";
60
62
  import { resolveUsableArtifactStorage, loadArtifactStorageConfig, type ArtifactStorage } from "../../shared/artifact-storage.js";
@@ -70,7 +72,8 @@ import { DeltaEnricher } from "./delta-enricher.js";
70
72
  import { TodoTracker } from "./todo-tracker.js";
71
73
  import { StreamingUpdateScheduler, loadStreamingConfig } from "../../shared/streaming-scheduler.js";
72
74
  import { createCursorEventRecorder } from "./cursor-event-recorder.js";
73
- import { resolveMcpServers, toCursorMcpConfig, validateMcpServerEnv } from "./mcp-resolver.js";
75
+ import { resolveMcpServers } from "../../shared/mcp-resolver.js";
76
+ import { toCursorMcpConfig, validateMcpServerEnv } from "./cursor-mcp-config.js";
74
77
  import { resolveMcpTransportPosture } from "../../shared/mcp-transport-guard.js";
75
78
  import {
76
79
  discoverChannelMessaging,
@@ -84,7 +87,7 @@ import { injectSynthesizedAttachment } from "../../shared/synthesized-attachment
84
87
  import { mergeApprovalPolicies } from "./approval-policy.js";
85
88
  import { deriveActiveLeases, isUnattendedApprovalMode } from "../../shared/approval-policy.js";
86
89
  import { enabledToolsBySlug } from "../../shared/mcp-enabled-tools.js";
87
- import { backfillMcpServersIfNeeded } from "./connect-backfill.js";
90
+ import { backfillMcpServersIfNeeded } from "../../shared/connect-backfill.js";
88
91
  import { resolveExecutionEnv } from "./env-resolver.js";
89
92
  import { resolveBlueprint } from "./blueprint-resolver.js";
90
93
  import { buildCursorSubAgentDefinitions } from "./subagent-config.js";
@@ -134,7 +137,8 @@ import { statusProtoWriter } from "../../shared/execution-status-writer.js";
134
137
  import { setInterceptorExecutionId, runWithExecutionContext } from "./fetch-interceptor.js";
135
138
  import { closeProxySessions } from "./http2-interceptor.js";
136
139
  import { resolveModelId, ensureLoaded as ensurePricingLoaded } from "./model-pricing.js";
137
- import { resolveEffectiveServiceTier, resolveServiceTierParams } from "./service-tier.js";
140
+ import { resolveEffectiveServiceTier } from "../../shared/service-tier.js";
141
+ import { resolveServiceTierParams } from "./service-tier.js";
138
142
  import { UsageAccumulator } from "./usage-accumulator.js";
139
143
  import { StreamingUsageSummarySchema } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/usage_pb";
140
144
  import { activityStarted, activityFinished } from "../../idle-watchdog.js";
@@ -145,7 +149,7 @@ import type { ClassifiedError } from "./error-classifier.js";
145
149
  import { createAgent, createCloudAgent } from "./session-lifecycle.js";
146
150
  import { setMaxListeners } from "node:events";
147
151
  import { startHeartbeat } from "../../shared/heartbeat.js";
148
- import { getShutdownSignalForQueue } from "../../runner-manager.js";
152
+ import { classifyTurnInterruption, getShutdownSignalForQueue } from "../../shared/worker-shutdown.js";
149
153
 
150
154
  /**
151
155
  * Creates the activity functions bound to the runner config.
@@ -625,16 +629,19 @@ async function executeCursorInner(
625
629
  ),
626
630
  sessionId,
627
631
  );
628
- let mcpResolution = await resolveMcpServers(
632
+ // The resolved-server list mutates through backfill and attachment
633
+ // injection below; the Cursor SDK config is projected from it exactly
634
+ // once, after the last mutation (see toCursorMcpConfig).
635
+ let resolvedMcpServers = (await resolveMcpServers(
629
636
  client, blueprint.mergedMcpServerUsages, mcpEnvVars, transportPosture,
630
- );
637
+ )).resolvedServers;
631
638
  setupTiming.mark("resolve_mcp_servers");
632
639
 
633
640
  // Phase 4a: Connect backfill for undiscovered MCP servers
634
641
  heartbeatPhase = "resolving_mcp_servers";
635
642
  const sessionOrg = session.metadata?.org ?? "";
636
- mcpResolution = await backfillMcpServersIfNeeded(
637
- client, mcpResolution, blueprint.mergedMcpServerUsages, mcpEnvVars, sessionOrg,
643
+ resolvedMcpServers = await backfillMcpServersIfNeeded(
644
+ client, resolvedMcpServers, blueprint.mergedMcpServerUsages, mcpEnvVars, sessionOrg,
638
645
  transportPosture, heartbeat, secretKeys,
639
646
  );
640
647
  setupTiming.mark("backfill_mcp");
@@ -682,13 +689,9 @@ async function executeCursorInner(
682
689
  backendEndpoint: config.stigmerBackendEndpoint,
683
690
  });
684
691
  if (attachment) {
685
- const resolvedServers = injectSynthesizedAttachment(
686
- mcpResolution.resolvedServers, attachment, "channel messaging",
692
+ resolvedMcpServers = injectSynthesizedAttachment(
693
+ resolvedMcpServers, attachment, "channel messaging",
687
694
  );
688
- mcpResolution = {
689
- resolvedServers,
690
- cursorConfig: toCursorMcpConfig(resolvedServers),
691
- };
692
695
  }
693
696
  }
694
697
 
@@ -708,15 +711,13 @@ async function executeCursorInner(
708
711
  },
709
712
  );
710
713
  if (conversationAttachment) {
711
- const resolvedServers = injectSynthesizedAttachment(
712
- mcpResolution.resolvedServers, conversationAttachment, "conversation participation",
714
+ resolvedMcpServers = injectSynthesizedAttachment(
715
+ resolvedMcpServers, conversationAttachment, "conversation participation",
713
716
  );
714
- mcpResolution = {
715
- resolvedServers,
716
- cursorConfig: toCursorMcpConfig(resolvedServers),
717
- };
718
717
  }
719
- const mcpConfig = mcpResolution.cursorConfig;
718
+ // The one projection point: every mutation above is now visible in the
719
+ // Cursor SDK config by construction (no per-mutation rebuild to forget).
720
+ const mcpConfig = toCursorMcpConfig(resolvedMcpServers);
720
721
 
721
722
  // Phase 4b: Merge approval policies from all layers.
722
723
  //
@@ -732,16 +733,15 @@ async function executeCursorInner(
732
733
  // see ResolvedMcpServer.toolApprovalOverrides (issue #349) — so there
733
734
  // is no separate override input to pass here.
734
735
  const mergedPolicies = mergeApprovalPolicies(
735
- mcpResolution.resolvedServers,
736
+ resolvedMcpServers,
736
737
  leases,
737
738
  );
738
739
  heartbeat();
739
740
 
740
741
  // Phase 4c: Validate MCP server env health (diagnostic, non-blocking)
741
742
  const mcpWarnings = validateMcpServerEnv(
742
- mcpResolution.resolvedServers,
743
+ resolvedMcpServers,
743
744
  blueprint.mergedMcpServerUsages,
744
- envVars,
745
745
  );
746
746
  if (mcpWarnings.length > 0) {
747
747
  console.warn(
@@ -908,7 +908,7 @@ async function executeCursorInner(
908
908
  // servers' allow-lists, enforced by the hook's "disabled" arm ahead of
909
909
  // every approval bypass. The Cursor SDK config cannot hide a server's
910
910
  // tools, so this deny-at-call is the harness's enforcement.
911
- enabledToolsBySlug(mcpResolution.resolvedServers),
911
+ enabledToolsBySlug(resolvedMcpServers),
912
912
  );
913
913
  const hitlGate = await installHitlGate({
914
914
  workspaceRoot: primaryWorkspaceDir,
@@ -1052,21 +1052,52 @@ async function executeCursorInner(
1052
1052
  // is the largest user-visible setup segment; this split keeps its
1053
1053
  // historical meaning — the SDK call was already 98%+ of it).
1054
1054
  setupTiming.mark("prepare_agent");
1055
- let resolution: AgentResolution = await resolveAgentWithTransportRecovery({
1056
- harnessStateId: threadId,
1057
- createOptions,
1058
- mode: agentMode,
1059
- timeoutMs: config.agentResolveTimeoutMs,
1060
- buildTimeoutMessage: (finalAttempt) =>
1061
- `Cursor agent ${threadId ? "resume" : "create"} timed out after ${resolveTimeoutSeconds}s ` +
1062
- `(${config.proxyEndpoint ? `via proxy ${config.proxyEndpoint}` : "direct Cursor API connection"}). ` +
1063
- `The transport connection is likely dead. ` +
1064
- (finalAttempt
1065
- ? `An automatic retry on a fresh transport connection also timed out. ` +
1066
- `Retry the message later; if this persists, check proxy and network health.`
1067
- : `Resetting the transport and retrying automatically.`),
1068
- resetTransport: closeProxySessions,
1069
- });
1055
+
1056
+ // Phase 8a: Reuse the previous turn's agent when this session parked one
1057
+ // (#215). A checkout hit skips Agent.resume() AND — the real win — keeps
1058
+ // the SDK executor lease alive, so agent.send() below re-acquires the
1059
+ // warm executor instead of re-spawning every stdio MCP server (the
1060
+ // measured 2.2–3.2s `send_returned` tax). The fingerprint covers the
1061
+ // full acquisition config, so any drift (rotated credential, edited MCP
1062
+ // servers, model change) falls through to a fresh resolve.
1063
+ const agentFingerprint = computeAgentFingerprint(
1064
+ createOptions as unknown as Record<string, unknown>,
1065
+ );
1066
+ const parkedAgent = takeCachedAgent(sessionId, agentFingerprint, threadId ?? "");
1067
+ let resolution: AgentResolution;
1068
+ if (parkedAgent) {
1069
+ console.log(
1070
+ `ExecuteCursor reusing parked session agent: execution=${executionId}, ` +
1071
+ `session=${sessionId}, agentId=${parkedAgent.agentId}`,
1072
+ );
1073
+ resolution = {
1074
+ agent: parkedAgent as AgentResolution["agent"],
1075
+ agentId: parkedAgent.agentId,
1076
+ isNew: false,
1077
+ resumed: true,
1078
+ mode: agentMode,
1079
+ // The parked handle IS the live conversation — every consumer of
1080
+ // "resumed_successfully" (prompt selection, poisoned-handle
1081
+ // recovery eligibility) wants exactly those semantics.
1082
+ reason: "resumed_successfully",
1083
+ };
1084
+ } else {
1085
+ resolution = await resolveAgentWithTransportRecovery({
1086
+ harnessStateId: threadId,
1087
+ createOptions,
1088
+ mode: agentMode,
1089
+ timeoutMs: config.agentResolveTimeoutMs,
1090
+ buildTimeoutMessage: (finalAttempt) =>
1091
+ `Cursor agent ${threadId ? "resume" : "create"} timed out after ${resolveTimeoutSeconds}s ` +
1092
+ `(${config.proxyEndpoint ? `via proxy ${config.proxyEndpoint}` : "direct Cursor API connection"}). ` +
1093
+ `The transport connection is likely dead. ` +
1094
+ (finalAttempt
1095
+ ? `An automatic retry on a fresh transport connection also timed out. ` +
1096
+ `Retry the message later; if this persists, check proxy and network health.`
1097
+ : `Resetting the transport and retrying automatically.`),
1098
+ resetTransport: closeProxySessions,
1099
+ });
1100
+ }
1070
1101
 
1071
1102
  console.log(
1072
1103
  `ExecuteCursor agent resolved: execution=${executionId}, ` +
@@ -1142,6 +1173,7 @@ async function executeCursorInner(
1142
1173
  contextBridge: readContextBridge(blueprint.sessionSpec.metadata),
1143
1174
  senderIdentity: readSenderIdentity(blueprint.sessionSpec.metadata),
1144
1175
  sessionContext: readSessionContext(blueprint.sessionSpec.metadata),
1176
+ declaredPreferences: readDeclaredPreferences(spec.declaredPreferences),
1145
1177
  conversationCatchup: readConversationCatchup(spec.conversationCatchup),
1146
1178
  // The turn's recorded transcript, seeded from the persisted execution
1147
1179
  // on a reinvocation (Phase 3). Consumed only by the HITL-recovery
@@ -1368,13 +1400,21 @@ async function executeCursorInner(
1368
1400
  // classifies a shutdown from the shutdown signal directly (in
1369
1401
  // resolvePreBoundaryTerminal). The heartbeat timer may set `cancelled` before
1370
1402
  // the AbortSignal microtask propagates; the direct signal check catches that.
1371
- const isShutdown = periodicHeartbeat.workerShutdown || (shutdownSignal?.aborted ?? false);
1372
- if (isShutdown) {
1403
+ // The decision table (including #776's grace-window guard: an aborted
1404
+ // shutdown signal with NO interruption evidence stays "none") lives in
1405
+ // classifyTurnInterruption — shared/worker-shutdown.ts.
1406
+ const interruption = classifyTurnInterruption({
1407
+ heartbeatCancelled: periodicHeartbeat.cancelled,
1408
+ heartbeatWorkerShutdown: periodicHeartbeat.workerShutdown,
1409
+ cancellationSignalAborted: Context.current().cancellationSignal.aborted,
1410
+ shutdownSignalAborted: shutdownSignal?.aborted ?? false,
1411
+ });
1412
+ if (interruption === "worker-shutdown") {
1373
1413
  turnState.pauseDetected = false;
1374
- } else if (periodicHeartbeat.cancelled) {
1414
+ } else if (interruption === "pause") {
1375
1415
  turnState.pauseDetected = true;
1376
1416
  }
1377
- workerShutdownDetected = isShutdown;
1417
+ workerShutdownDetected = interruption === "worker-shutdown";
1378
1418
 
1379
1419
  // Post-stream finalize, shared by the primary turn and both recovery retries:
1380
1420
  // finalize the transcript + streaming flags, mark any in-flight sub-agent
@@ -1462,7 +1502,9 @@ async function executeCursorInner(
1462
1502
  timestamp: utcTimestamp(),
1463
1503
  }));
1464
1504
  await persist(status);
1465
- try { resolution.agent.close(); } catch { /* best effort */ }
1505
+ // Clean terminal: the conversation continues on the next message,
1506
+ // so park the healthy agent for that turn (#215).
1507
+ cacheSessionAgent(sessionId ?? "", resolution.agent, agentFingerprint);
1466
1508
  console.warn(
1467
1509
  `ExecuteCursor terminated (cost cap): execution=${executionId}, ` +
1468
1510
  `estimatedCostUsd=${estimated.toFixed(4)}, maxCostUsd=${maxCostUsd.toFixed(2)}`,
@@ -1470,10 +1512,16 @@ async function executeCursorInner(
1470
1512
  return { kind: "return" };
1471
1513
  }
1472
1514
 
1473
- // Worker shutdown: the runner-manager aborted the shutdown signal. NOT a
1515
+ // Worker shutdown: the runner/manager aborted the shutdown signal. NOT a
1474
1516
  // user pause. Checked via the shutdown signal directly so a retry (whose
1475
- // periodic heartbeat is already stopped) still classifies it correctly.
1476
- if (workerShutdownDetected || (shutdownSignal?.aborted ?? false)) {
1517
+ // periodic heartbeat is already stopped) still classifies it correctly
1518
+ // but only alongside a delivered cancellation: a retry that completed
1519
+ // normally inside the drain grace window must stay a completion (#776's
1520
+ // grace-window guard, mirroring the primary's `interrupted` gate).
1521
+ if (
1522
+ workerShutdownDetected ||
1523
+ ((shutdownSignal?.aborted ?? false) && Context.current().cancellationSignal.aborted)
1524
+ ) {
1477
1525
  status.phase = ExecutionPhase.EXECUTION_FAILED;
1478
1526
  status.error = "Execution interrupted: runner worker was shut down. Retry or resume.";
1479
1527
  status.completedAt = utcTimestamp();
@@ -1527,7 +1575,8 @@ async function executeCursorInner(
1527
1575
  timestamp: utcTimestamp(),
1528
1576
  }));
1529
1577
  await persist(status);
1530
- try { resolution.agent.close(); } catch { /* best effort */ }
1578
+ // Clean terminal park for the session's next turn (#215).
1579
+ cacheSessionAgent(sessionId ?? "", resolution.agent, agentFingerprint);
1531
1580
  console.log(`ExecuteCursor completed (platform stop): execution=${executionId}`);
1532
1581
  return { kind: "return" };
1533
1582
  }
@@ -1573,6 +1622,12 @@ async function executeCursorInner(
1573
1622
  const enterApprovalPause = async (boundary: TurnBoundaryResult) => {
1574
1623
  status.phase = ExecutionPhase.EXECUTION_WAITING_FOR_APPROVAL;
1575
1624
  await persist(status);
1625
+ // The approval-resume reinvocation is the cache's best case: park the
1626
+ // agent so the resumed turn skips the full executor rebuild (#215).
1627
+ // (This path previously dropped the handle without close() — the
1628
+ // lease leaked; parking makes the lifetime explicit.) An absent
1629
+ // sessionId falls back to "" — the cache closes the lease immediately.
1630
+ cacheSessionAgent(sessionId ?? "", resolution.agent, agentFingerprint);
1576
1631
  console.log(
1577
1632
  `ExecuteCursor returning WAITING_FOR_APPROVAL: ${boundary.deniedToolCallCount} gated tool(s), ` +
1578
1633
  `${boundary.capturedChangeCount} file card(s) pending`,
@@ -1823,6 +1878,7 @@ async function executeCursorInner(
1823
1878
  contextBridge: readContextBridge(blueprint.sessionSpec.metadata),
1824
1879
  senderIdentity: readSenderIdentity(blueprint.sessionSpec.metadata),
1825
1880
  sessionContext: readSessionContext(blueprint.sessionSpec.metadata),
1881
+ declaredPreferences: readDeclaredPreferences(spec.declaredPreferences),
1826
1882
  conversationCatchup: readConversationCatchup(spec.conversationCatchup),
1827
1883
  // Composed fresh (not reused from Phase 10): the failed primary
1828
1884
  // stream may have appended partial work onto status.messages,
@@ -2088,8 +2144,12 @@ async function executeCursorInner(
2088
2144
  (status.error ? `, error=${status.error}` : ""),
2089
2145
  );
2090
2146
 
2091
- // Release SDK executor lease to prevent cache buildup across workflow tasks
2092
- try { resolution.agent.close(); } catch { /* best effort */ }
2147
+ // Park the agent (with its executor lease) for the session's next turn
2148
+ // instead of closing it the idle TTL / shutdown hooks in
2149
+ // agent-session-cache own the eventual release, so cache buildup across
2150
+ // sessions stays bounded while turns of ONE session stop paying the
2151
+ // executor + MCP re-spawn tax (#215).
2152
+ cacheSessionAgent(sessionId ?? "", resolution.agent, agentFingerprint);
2093
2153
 
2094
2154
  const slim = slimStatus(status) as Record<string, unknown>;
2095
2155
  if (finalText !== undefined) {
@@ -2104,9 +2164,12 @@ async function executeCursorInner(
2104
2164
  periodicHeartbeat?.stop();
2105
2165
 
2106
2166
  if (err instanceof CancelledFailure) {
2107
- // workerShutdownDetected means the runner-manager signaled shutdown
2108
- // before the worker drained. This is infrastructure failure, not pause.
2109
- if (workerShutdownDetected) {
2167
+ // Worker shutdown is infrastructure failure, not pause. The direct
2168
+ // signal check covers a CancelledFailure thrown BEFORE the post-stream
2169
+ // classification ran (workerShutdownDetected still false); no extra
2170
+ // interruption-evidence gate is needed here — the caught
2171
+ // CancelledFailure IS the evidence (#776).
2172
+ if (workerShutdownDetected || (shutdownSignal?.aborted ?? false)) {
2110
2173
  console.log(`ExecuteCursor cancelled (worker shutdown) for execution ${executionId}`);
2111
2174
  status.phase = ExecutionPhase.EXECUTION_FAILED;
2112
2175
  status.error = "Execution interrupted: runner worker was shut down. Retry or resume.";
@@ -2405,10 +2468,19 @@ export interface BuildPromptInput {
2405
2468
  * turn.
2406
2469
  */
2407
2470
  sessionContext?: string;
2471
+ /**
2472
+ * Platform-declared standing preferences from the execution spec's
2473
+ * `declared_preferences` (stigmer/stigmer#293). Like the bridge, only
2474
+ * the enhanced-prompt path consumes it — deliberately frozen per Cursor
2475
+ * session (DD-002 D3): the first turn delivers it into the agent's own
2476
+ * conversation store, and repeating it on resumed turns would bloat the
2477
+ * store with identical content.
2478
+ */
2479
+ declaredPreferences?: import("../../shared/declared-preferences.js").DeclaredPreferencesContent;
2408
2480
  /**
2409
2481
  * Conversation catchup from the execution spec's `conversation_catchup`
2410
2482
  * (cloud DD-006): what happened on the channel conversation that the
2411
- * agent has not seen. PER-TURN, so unlike the three standing values
2483
+ * agent has not seen. PER-TURN, so unlike the standing values
2412
2484
  * above it rides BOTH prompt paths — the enhanced prompt and a resumed
2413
2485
  * turn's prefix (the `interaction_mode` shape). Handback lands
2414
2486
  * mid-session on a resumed agent: the resumed path is the one that
@@ -2543,6 +2615,7 @@ export function buildPrompt(input: BuildPromptInput): string {
2543
2615
  contextBridge: input.contextBridge,
2544
2616
  senderIdentity: input.senderIdentity,
2545
2617
  sessionContext: input.sessionContext,
2618
+ declaredPreferences: input.declaredPreferences,
2546
2619
  conversationCatchup,
2547
2620
  },
2548
2621
  {
@@ -2609,6 +2682,7 @@ export function buildPrompt(input: BuildPromptInput): string {
2609
2682
  contextBridge: input.contextBridge,
2610
2683
  senderIdentity: input.senderIdentity,
2611
2684
  sessionContext: input.sessionContext,
2685
+ declaredPreferences: input.declaredPreferences,
2612
2686
  conversationCatchup,
2613
2687
  });
2614
2688
  }
@@ -31,6 +31,10 @@ import {
31
31
  type SenderIdentity,
32
32
  } from "../../shared/sender-identity.js";
33
33
  import { formatSessionContextText } from "../../shared/session-context.js";
34
+ import {
35
+ formatDeclaredPreferencesText,
36
+ type DeclaredPreferencesContent,
37
+ } from "../../shared/declared-preferences.js";
34
38
  import {
35
39
  visionDisclosureLines,
36
40
  type NotViewableEntry,
@@ -147,11 +151,20 @@ export interface EnhancedPromptOptions {
147
151
  * store — the context is constant for the session's lifetime.
148
152
  */
149
153
  sessionContext?: string;
154
+ /**
155
+ * Platform-declared standing preferences (stigmer/stigmer#293): the org's
156
+ * and user's standing context, server-snapshotted onto the execution
157
+ * spec's `declared_preferences` at create. Like the bridge, it lands in
158
+ * the first message and persists in the cursor agent's own conversation
159
+ * store — deliberately frozen per Cursor session (DD-002 D3): repeating
160
+ * it every resumed turn would bloat the store with identical content.
161
+ */
162
+ declaredPreferences?: DeclaredPreferencesContent;
150
163
  /**
151
164
  * Conversation catchup (cloud DD-006): what happened on the channel
152
165
  * conversation that the agent has not seen, read from the execution
153
- * spec's `conversation_catchup`. PER-TURN, unlike the three standing
154
- * siblings above: it rides BOTH prompt paths — this enhanced prompt and
166
+ * spec's `conversation_catchup`. PER-TURN, unlike the standing siblings
167
+ * above: it rides BOTH prompt paths — this enhanced prompt and
155
168
  * a resumed turn's prefix (the `interaction_mode` shape) — because
156
169
  * handback lands mid-session on a resumed agent, the exact case the
157
170
  * metadata lane cannot reach.
@@ -232,6 +245,14 @@ export function buildEnhancedPrompt(options: EnhancedPromptOptions): string {
232
245
  sections.push(formatSenderIdentitySection(options.senderIdentity));
233
246
  }
234
247
 
248
+ // Platform-declared standing facts precede embedder-supplied context
249
+ // (DD-002 D3): both are standing background, but the declared preferences
250
+ // are platform-authored while session context is the embedder's overlay —
251
+ // the more specific overlay reads later and naturally refines.
252
+ if (options.declaredPreferences) {
253
+ sections.push(formatDeclaredPreferencesSection(options.declaredPreferences));
254
+ }
255
+
235
256
  // Standing facts about the user (session context) come before the
236
257
  // carried conversation (bridge): the bridge may refer back to them.
237
258
  if (options.sessionContext) {
@@ -445,6 +466,12 @@ export function formatSenderIdentitySection(identity: SenderIdentity): string {
445
466
  return `<conversation_sender>\n${formatSenderIdentityText(identity)}\n</conversation_sender>`;
446
467
  }
447
468
 
469
+ export function formatDeclaredPreferencesSection(
470
+ preferences: DeclaredPreferencesContent,
471
+ ): string {
472
+ return `<declared_preferences>\n${formatDeclaredPreferencesText(preferences)}\n</declared_preferences>`;
473
+ }
474
+
448
475
  export function formatSessionContextSection(context: string): string {
449
476
  return `<session_context>\n${formatSessionContextText(context)}\n</session_context>`;
450
477
  }
@@ -20,19 +20,17 @@
20
20
  * rides the same proxy fetch-interceptor as every other SDK call, so it
21
21
  * works identically in proxy and direct modes.
22
22
  *
23
- * UNSPECIFIED resolves to STANDARD here and ONLY here every upstream
24
- * layer preserves the caller's raw enum so "user chose standard" stays
25
- * distinguishable from "platform default" all the way to the ledger.
23
+ * The harness-neutral halves the tier enum semantics, and the single
24
+ * UNSPECIFIED→STANDARD resolution point live in
25
+ * `shared/service-tier.ts` since #361 extended tiers to the native
26
+ * harness; this module keeps only the Cursor-catalog translation.
26
27
  */
27
28
 
28
29
  import { Cursor } from "@cursor/sdk";
29
30
  import type { ModelListItem, ModelParameterValue } from "@cursor/sdk";
30
31
  import { ServiceTier } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/enum_pb";
31
32
 
32
- /**
33
- * The effective tier after platform-default resolution: never UNSPECIFIED.
34
- */
35
- export type EffectiveServiceTier = ServiceTier.STANDARD | ServiceTier.FAST;
33
+ import { serviceTierLabel, type EffectiveServiceTier } from "../../shared/service-tier.js";
36
34
 
37
35
  /**
38
36
  * Catalog ids that mean "Cursor picks the model" (Auto). Auto's single
@@ -72,28 +70,6 @@ export function resetCatalogCacheForTests(): void {
72
70
  inflightCatalogFetch = null;
73
71
  }
74
72
 
75
- /**
76
- * Resolve the configured tier to its effective value. The single place in
77
- * the platform where UNSPECIFIED becomes STANDARD.
78
- */
79
- export function resolveEffectiveServiceTier(
80
- configured: ServiceTier | undefined,
81
- ): EffectiveServiceTier {
82
- return configured === ServiceTier.FAST ? ServiceTier.FAST : ServiceTier.STANDARD;
83
- }
84
-
85
- /** Human-readable tier label for logs and error messages. */
86
- export function serviceTierLabel(tier: ServiceTier): string {
87
- switch (tier) {
88
- case ServiceTier.FAST:
89
- return "fast";
90
- case ServiceTier.STANDARD:
91
- return "standard";
92
- default:
93
- return "unspecified";
94
- }
95
- }
96
-
97
73
  async function listCatalogModels(apiKey: string): Promise<readonly ModelListItem[]> {
98
74
  const now = Date.now();
99
75
  if (catalogCache && catalogCache.apiKey === apiKey && catalogCache.expiresAt > now) {
@@ -47,7 +47,7 @@ import type {
47
47
  ModelParameterValue,
48
48
  } from "@cursor/sdk";
49
49
  import { withTimeout, TimeoutError } from "../../shared/with-timeout.js";
50
- import type { CursorMcpServerConfig } from "./mcp-resolver.js";
50
+ import type { CursorMcpServerConfig } from "./cursor-mcp-config.js";
51
51
 
52
52
  // ---------------------------------------------------------------------------
53
53
  // Constants