@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
@@ -0,0 +1,229 @@
1
+ /**
2
+ * Session-keyed Cursor agent cache — keeps the SDK executor (and its stdio
3
+ * MCP server processes) alive across turns of the SAME session (#215).
4
+ *
5
+ * Why this exists: the SDK's local executor cache is refcounted and keyed
6
+ * by the full acquisition config (workingDirectory, hashed apiKey,
7
+ * settingSources, mcpServers, customSubagents). `agent.close()` releases
8
+ * the agent's lease; at refcount zero the executor is DISPOSED and every
9
+ * stdio MCP server is killed. The activity used to close on every terminal
10
+ * path, so each turn re-acquired the executor and re-spawned every MCP
11
+ * server inside `agent.send()` — a measured 2.2–3.2s per-turn tax
12
+ * (`turn_first_event`'s `send_returned` segment).
13
+ *
14
+ * Ownership model — exclusive checkout, explicit lifetime:
15
+ * - A finishing turn PARKS its healthy agent here (`cacheSessionAgent`);
16
+ * the next activity for the session CHECKS IT OUT (`takeCachedAgent`),
17
+ * removing it from the cache, so two concurrent activities can never
18
+ * share one Agent handle — the loser of the race resolves its own.
19
+ * - The cached agent is reused only when the acquisition FINGERPRINT
20
+ * matches. Any config drift (rotated credential, edited MCP servers,
21
+ * model change, different workspace) closes the parked agent and forces
22
+ * a fresh resolve — correctness by construction: a reused executor would
23
+ * otherwise keep serving the OLD config.
24
+ * - Failure paths never park: a suspect agent is closed where it failed
25
+ * (the pre-existing close sites), and `evictSessionAgent` clears any
26
+ * parked entry when a session's state is replaced out from under it.
27
+ * - Idle TTL + LRU cap bound memory on multi-session hosts (the desktop
28
+ * runner-manager hosts many sessions per process; a cloud sandbox is
29
+ * session-pinned and holds at most one entry). Worker shutdown closes
30
+ * everything (`closeAllCachedAgents`).
31
+ */
32
+
33
+ import { createHash } from "node:crypto";
34
+
35
+ /** The slice of SDKAgent this cache needs — close() releases the executor lease. */
36
+ export interface CacheableAgent {
37
+ readonly agentId: string;
38
+ close(): void;
39
+ }
40
+
41
+ interface CachedSessionAgent {
42
+ readonly agent: CacheableAgent;
43
+ readonly fingerprint: string;
44
+ readonly evictTimer: NodeJS.Timeout;
45
+ /** Insertion-order tiebreaker for the LRU cap. */
46
+ readonly parkedAt: number;
47
+ }
48
+
49
+ /**
50
+ * Default idle lifetime for a parked agent. Long enough to cover human
51
+ * think-time between turns and approval round-trips; short enough that an
52
+ * abandoned session does not pin MCP subprocesses for hours.
53
+ */
54
+ const DEFAULT_IDLE_TTL_MS = 30 * 60 * 1000;
55
+
56
+ /**
57
+ * Ceiling on concurrently parked agents (each holds an executor + its MCP
58
+ * subprocesses). Cloud sandboxes never approach it (one session per pod);
59
+ * it protects long-lived desktop runner-managers.
60
+ */
61
+ const MAX_PARKED_AGENTS = 32;
62
+
63
+ function resolveIdleTtlMs(): number {
64
+ const parsed = Number.parseInt(process.env.STIGMER_CURSOR_AGENT_CACHE_TTL_MS ?? "", 10);
65
+ return parsed > 0 ? parsed : DEFAULT_IDLE_TTL_MS;
66
+ }
67
+
68
+ const parkedAgents = new Map<string, CachedSessionAgent>();
69
+
70
+ function closeQuietly(agent: CacheableAgent): void {
71
+ try {
72
+ agent.close();
73
+ } catch {
74
+ /* best effort — the lease release is advisory on an already-dead agent */
75
+ }
76
+ }
77
+
78
+ /**
79
+ * Fingerprint of everything that determines whether a parked agent can
80
+ * serve the next turn as-is: the SDK executor cache key inputs PLUS the
81
+ * per-agent options resume() re-supplies (model selection, sub-agents).
82
+ * The API key contributes only as a hash — the fingerprint must never be
83
+ * a secret-bearing value (it appears in no logs, but defense in depth).
84
+ */
85
+ export function computeAgentFingerprint(createOptions: Record<string, unknown>): string {
86
+ const { apiKey, ...rest } = createOptions;
87
+ const material = {
88
+ ...rest,
89
+ apiKeyHash:
90
+ typeof apiKey === "string" && apiKey.length > 0
91
+ ? createHash("sha256").update(apiKey).digest("hex")
92
+ : undefined,
93
+ };
94
+ return createHash("sha256").update(stableStringify(material)).digest("hex");
95
+ }
96
+
97
+ /** Deterministic JSON: object keys sorted recursively (the SDK's own idiom). */
98
+ function stableStringify(value: unknown): string {
99
+ return JSON.stringify(sortKeys(value));
100
+ }
101
+
102
+ function sortKeys(value: unknown): unknown {
103
+ if (Array.isArray(value)) return value.map(sortKeys);
104
+ if (value !== null && typeof value === "object" && value.constructor === Object) {
105
+ return Object.fromEntries(
106
+ Object.keys(value as Record<string, unknown>)
107
+ .sort()
108
+ .map((k) => [k, sortKeys((value as Record<string, unknown>)[k])]),
109
+ );
110
+ }
111
+ return value;
112
+ }
113
+
114
+ /**
115
+ * Exclusive checkout: returns the parked agent for the session and removes
116
+ * it from the cache, or undefined when there is nothing reusable.
117
+ *
118
+ * A parked agent is reusable only when BOTH hold:
119
+ * - `fingerprint` matches (config identical to what the agent was built with);
120
+ * - `expectedAgentId`, when non-empty, matches the parked agent (the
121
+ * session's harnessStateId is the source of truth — a recovery in another
122
+ * activity may have replaced the agent since this one was parked).
123
+ *
124
+ * A mismatch on either closes the parked agent: it can never serve this
125
+ * session again, and holding it would only pin dead MCP processes.
126
+ */
127
+ export function takeCachedAgent(
128
+ sessionId: string,
129
+ fingerprint: string,
130
+ expectedAgentId: string,
131
+ ): CacheableAgent | undefined {
132
+ const entry = parkedAgents.get(sessionId);
133
+ if (!entry) return undefined;
134
+
135
+ parkedAgents.delete(sessionId);
136
+ clearTimeout(entry.evictTimer);
137
+
138
+ const agentMatches = expectedAgentId === "" || entry.agent.agentId === expectedAgentId;
139
+ if (entry.fingerprint !== fingerprint || !agentMatches) {
140
+ console.log(
141
+ `agent-session-cache: parked agent for session=${sessionId} not reusable ` +
142
+ `(fingerprintMatch=${entry.fingerprint === fingerprint}, agentIdMatch=${agentMatches}) — closing`,
143
+ );
144
+ closeQuietly(entry.agent);
145
+ return undefined;
146
+ }
147
+
148
+ return entry.agent;
149
+ }
150
+
151
+ /**
152
+ * Parks a healthy agent for the session's next turn. Replaces (and closes)
153
+ * any agent already parked for the session; evicts the oldest entry when
154
+ * the cap is reached.
155
+ */
156
+ export function cacheSessionAgent(
157
+ sessionId: string,
158
+ agent: CacheableAgent,
159
+ fingerprint: string,
160
+ ): void {
161
+ if (!sessionId) {
162
+ // No stable key to reuse by — release the lease as before the cache.
163
+ closeQuietly(agent);
164
+ return;
165
+ }
166
+
167
+ const displaced = parkedAgents.get(sessionId);
168
+ if (displaced) {
169
+ clearTimeout(displaced.evictTimer);
170
+ closeQuietly(displaced.agent);
171
+ }
172
+
173
+ if (parkedAgents.size >= MAX_PARKED_AGENTS) {
174
+ let oldestKey: string | undefined;
175
+ let oldestAt = Infinity;
176
+ for (const [key, entry] of parkedAgents) {
177
+ if (entry.parkedAt < oldestAt) {
178
+ oldestAt = entry.parkedAt;
179
+ oldestKey = key;
180
+ }
181
+ }
182
+ if (oldestKey !== undefined) evictSessionAgent(oldestKey);
183
+ }
184
+
185
+ const ttlMs = resolveIdleTtlMs();
186
+ const evictTimer = setTimeout(() => evictSessionAgent(sessionId), ttlMs);
187
+ // Never keep the process alive just to evict an idle agent.
188
+ evictTimer.unref?.();
189
+
190
+ parkedAgents.set(sessionId, {
191
+ agent,
192
+ fingerprint,
193
+ evictTimer,
194
+ parkedAt: Date.now(),
195
+ });
196
+ }
197
+
198
+ /** Closes and forgets the parked agent for a session, if any. */
199
+ export function evictSessionAgent(sessionId: string): void {
200
+ const entry = parkedAgents.get(sessionId);
201
+ if (!entry) return;
202
+ parkedAgents.delete(sessionId);
203
+ clearTimeout(entry.evictTimer);
204
+ closeQuietly(entry.agent);
205
+ console.log(`agent-session-cache: evicted parked agent for session=${sessionId}`);
206
+ }
207
+
208
+ /** Worker shutdown: release every parked lease so executors dispose cleanly. */
209
+ export function closeAllCachedAgents(): void {
210
+ for (const [sessionId, entry] of parkedAgents) {
211
+ clearTimeout(entry.evictTimer);
212
+ closeQuietly(entry.agent);
213
+ console.log(`agent-session-cache: closed parked agent for session=${sessionId} (shutdown)`);
214
+ }
215
+ parkedAgents.clear();
216
+ }
217
+
218
+ /** Test seam. */
219
+ export function _resetAgentSessionCacheForTests(): void {
220
+ for (const entry of parkedAgents.values()) {
221
+ clearTimeout(entry.evictTimer);
222
+ }
223
+ parkedAgents.clear();
224
+ }
225
+
226
+ /** Test seam. */
227
+ export function _parkedAgentCountForTests(): number {
228
+ return parkedAgents.size;
229
+ }
@@ -0,0 +1,107 @@
1
+ /**
2
+ * The Cursor harness's MCP output adapter: maps the shared resolver's
3
+ * {@link ResolvedMcpServer} servers into the Cursor SDK's mcpServers config
4
+ * for Agent.create(), plus the harness's env pre-flight check.
5
+ *
6
+ * Resolution itself lives in shared/mcp-resolver.ts (oss#387 consolidated the
7
+ * near-duplicate resolver this file used to carry) — this module is the
8
+ * symmetric twin of the deep-agent harness's toMcpClientConfig in
9
+ * shared/mcp-manager.ts: both consume the identical intermediate, only the
10
+ * final SDK serialization differs.
11
+ */
12
+
13
+ import type { McpServerUsage } from "@stigmer/protos/ai/stigmer/agentic/agent/v1/spec_pb";
14
+ import type { ResolvedMcpServer } from "../../shared/mcp-resolver.js";
15
+
16
+ /**
17
+ * Cursor SDK MCP server config shape (matches @cursor/sdk McpServerConfig).
18
+ * Defined here to avoid tight coupling to the SDK's internal types.
19
+ */
20
+ export type CursorMcpServerConfig =
21
+ | {
22
+ type?: "stdio";
23
+ command: string;
24
+ args?: string[];
25
+ env?: Record<string, string>;
26
+ cwd?: string;
27
+ }
28
+ | {
29
+ type?: "http" | "sse";
30
+ url: string;
31
+ headers?: Record<string, string>;
32
+ };
33
+
34
+ /**
35
+ * Transform resolved MCP servers into the Cursor SDK's mcpServers config
36
+ * for Agent.create().
37
+ *
38
+ * Call it ONCE, after the last mutation of the resolved-server list (backfill,
39
+ * synthesized-attachment injection): the config is a projection of that list,
40
+ * and building it early just creates a stale copy someone must remember to
41
+ * rebuild. The Cursor SDK config cannot hide tools, so enabledTools is NOT
42
+ * expressed here — the HITL hook enforces it (see ResolvedMcpServer.enabledTools).
43
+ */
44
+ export function toCursorMcpConfig(
45
+ servers: ResolvedMcpServer[],
46
+ ): Record<string, CursorMcpServerConfig> {
47
+ const result: Record<string, CursorMcpServerConfig> = {};
48
+
49
+ for (const server of servers) {
50
+ if (server.connectionType === "stdio") {
51
+ if (!server.command) continue;
52
+ result[server.slug] = {
53
+ type: "stdio",
54
+ command: server.command,
55
+ args: server.args,
56
+ env: server.env,
57
+ cwd: server.cwd,
58
+ };
59
+ } else {
60
+ if (!server.url) continue;
61
+ result[server.slug] = {
62
+ type: server.connectionType,
63
+ url: server.url,
64
+ headers: server.headers,
65
+ };
66
+ }
67
+ }
68
+
69
+ return result;
70
+ }
71
+
72
+ /**
73
+ * Validate that resolved MCP servers have their required env vars populated.
74
+ * Returns a list of warnings for servers with empty/missing env.
75
+ * Used as a pre-flight check before agent.send() to surface config issues early.
76
+ */
77
+ export function validateMcpServerEnv(
78
+ servers: ResolvedMcpServer[],
79
+ usages: McpServerUsage[],
80
+ ): string[] {
81
+ const warnings: string[] = [];
82
+
83
+ for (const usage of usages) {
84
+ const slug = usage.mcpServerRef?.slug;
85
+ if (!slug) continue;
86
+
87
+ const resolved = servers.find((s) => s.slug === slug);
88
+ if (!resolved) {
89
+ warnings.push(`MCP server '${slug}': failed to resolve (server may not exist or is inaccessible)`);
90
+ continue;
91
+ }
92
+
93
+ if (resolved.connectionType === "stdio" && resolved.env) {
94
+ const emptyKeys = Object.entries(resolved.env)
95
+ .filter(([, v]) => !v)
96
+ .map(([k]) => k);
97
+ if (emptyKeys.length > 0) {
98
+ warnings.push(
99
+ `MCP server '${slug}': env vars [${emptyKeys.join(", ")}] are empty — ` +
100
+ `server subprocess will likely fail to connect`,
101
+ );
102
+ }
103
+ }
104
+ }
105
+
106
+ return warnings;
107
+ }