@stigmer/runner 3.11.0 → 3.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (239) hide show
  1. package/README.md +2 -0
  2. package/dist/.build-fingerprint +1 -1
  3. package/dist/activities/discover-mcp-server.d.ts +7 -0
  4. package/dist/activities/discover-mcp-server.js +6 -4
  5. package/dist/activities/discover-mcp-server.js.map +1 -1
  6. package/dist/activities/emit-event.d.ts +14 -2
  7. package/dist/activities/emit-event.js +52 -17
  8. package/dist/activities/emit-event.js.map +1 -1
  9. package/dist/activities/execute-cursor/attachment-resolver.d.ts +11 -0
  10. package/dist/activities/execute-cursor/attachment-resolver.js +8 -0
  11. package/dist/activities/execute-cursor/attachment-resolver.js.map +1 -1
  12. package/dist/activities/execute-cursor/blueprint-resolver.d.ts +1 -9
  13. package/dist/activities/execute-cursor/blueprint-resolver.js +0 -1
  14. package/dist/activities/execute-cursor/blueprint-resolver.js.map +1 -1
  15. package/dist/activities/execute-cursor/error-classifier.d.ts +19 -0
  16. package/dist/activities/execute-cursor/error-classifier.js +34 -3
  17. package/dist/activities/execute-cursor/error-classifier.js.map +1 -1
  18. package/dist/activities/execute-cursor/index.d.ts +52 -12
  19. package/dist/activities/execute-cursor/index.js +141 -67
  20. package/dist/activities/execute-cursor/index.js.map +1 -1
  21. package/dist/activities/execute-cursor/model-pricing-data.d.ts +2 -0
  22. package/dist/activities/execute-cursor/model-pricing-data.js +13 -3
  23. package/dist/activities/execute-cursor/model-pricing-data.js.map +1 -1
  24. package/dist/activities/execute-cursor/prompt-builder.d.ts +48 -11
  25. package/dist/activities/execute-cursor/prompt-builder.js +57 -10
  26. package/dist/activities/execute-cursor/prompt-builder.js.map +1 -1
  27. package/dist/activities/execute-cursor/turn-recovery.d.ts +52 -0
  28. package/dist/activities/execute-cursor/turn-recovery.js +193 -0
  29. package/dist/activities/execute-cursor/turn-recovery.js.map +1 -0
  30. package/dist/activities/execute-deep-agent/attachment-injector.d.ts +11 -0
  31. package/dist/activities/execute-deep-agent/attachment-injector.js +7 -1
  32. package/dist/activities/execute-deep-agent/attachment-injector.js.map +1 -1
  33. package/dist/activities/execute-deep-agent/index.js +0 -15
  34. package/dist/activities/execute-deep-agent/index.js.map +1 -1
  35. package/dist/activities/execute-deep-agent/mcp-gate.d.ts +0 -2
  36. package/dist/activities/execute-deep-agent/mcp-gate.js +0 -1
  37. package/dist/activities/execute-deep-agent/mcp-gate.js.map +1 -1
  38. package/dist/activities/execute-deep-agent/prompt-builder.d.ts +7 -8
  39. package/dist/activities/execute-deep-agent/prompt-builder.js +14 -7
  40. package/dist/activities/execute-deep-agent/prompt-builder.js.map +1 -1
  41. package/dist/activities/execute-deep-agent/setup.d.ts +0 -10
  42. package/dist/activities/execute-deep-agent/setup.js +22 -49
  43. package/dist/activities/execute-deep-agent/setup.js.map +1 -1
  44. package/dist/activities/execute-deep-agent/shell-env.d.ts +5 -1
  45. package/dist/activities/execute-deep-agent/shell-env.js +7 -9
  46. package/dist/activities/execute-deep-agent/shell-env.js.map +1 -1
  47. package/dist/activities/execute-deep-agent/subagent-transformer.js +7 -1
  48. package/dist/activities/execute-deep-agent/subagent-transformer.js.map +1 -1
  49. package/dist/activities/execute-deep-agent/subagent-wiring.d.ts +17 -3
  50. package/dist/activities/execute-deep-agent/subagent-wiring.js +14 -2
  51. package/dist/activities/execute-deep-agent/subagent-wiring.js.map +1 -1
  52. package/dist/activities/hydrate-workflow-execution.js +8 -5
  53. package/dist/activities/hydrate-workflow-execution.js.map +1 -1
  54. package/dist/activities/run-command.d.ts +5 -2
  55. package/dist/activities/run-command.js +22 -10
  56. package/dist/activities/run-command.js.map +1 -1
  57. package/dist/activities/run-env.d.ts +40 -0
  58. package/dist/activities/run-env.js +66 -0
  59. package/dist/activities/run-env.js.map +1 -0
  60. package/dist/bootstrap.js +5 -0
  61. package/dist/bootstrap.js.map +1 -1
  62. package/dist/client/stigmer-client.d.ts +83 -26
  63. package/dist/client/stigmer-client.js +88 -24
  64. package/dist/client/stigmer-client.js.map +1 -1
  65. package/dist/config.d.ts +6 -5
  66. package/dist/config.js.map +1 -1
  67. package/dist/encryption/config.d.ts +41 -9
  68. package/dist/encryption/config.js +63 -23
  69. package/dist/encryption/config.js.map +1 -1
  70. package/dist/middleware/index.d.ts +3 -0
  71. package/dist/middleware/index.js +7 -0
  72. package/dist/middleware/index.js.map +1 -1
  73. package/dist/middleware/path-normalization.d.ts +57 -0
  74. package/dist/middleware/path-normalization.js +109 -0
  75. package/dist/middleware/path-normalization.js.map +1 -0
  76. package/dist/middleware/types.d.ts +8 -0
  77. package/dist/payload-codecs.d.ts +2 -1
  78. package/dist/payload-codecs.js +7 -3
  79. package/dist/payload-codecs.js.map +1 -1
  80. package/dist/runner-manager.js +17 -3
  81. package/dist/runner-manager.js.map +1 -1
  82. package/dist/runner.js +7 -3
  83. package/dist/runner.js.map +1 -1
  84. package/dist/shared/artifact-storage.d.ts +34 -1
  85. package/dist/shared/artifact-storage.js +53 -10
  86. package/dist/shared/artifact-storage.js.map +1 -1
  87. package/dist/shared/attachment-download-urls.d.ts +62 -0
  88. package/dist/shared/attachment-download-urls.js +79 -0
  89. package/dist/shared/attachment-download-urls.js.map +1 -0
  90. package/dist/shared/channel-attachment.d.ts +1 -1
  91. package/dist/shared/channel-attachment.js +1 -1
  92. package/dist/shared/checkpointer/http-saver.d.ts +4 -20
  93. package/dist/shared/checkpointer/http-saver.js +15 -57
  94. package/dist/shared/checkpointer/http-saver.js.map +1 -1
  95. package/dist/shared/conversation-attachment.d.ts +2 -3
  96. package/dist/shared/conversation-attachment.js +2 -3
  97. package/dist/shared/conversation-attachment.js.map +1 -1
  98. package/dist/shared/conversation-catchup.d.ts +9 -2
  99. package/dist/shared/conversation-catchup.js +39 -6
  100. package/dist/shared/conversation-catchup.js.map +1 -1
  101. package/dist/shared/grpc-retry.d.ts +5 -1
  102. package/dist/shared/grpc-retry.js +5 -1
  103. package/dist/shared/grpc-retry.js.map +1 -1
  104. package/dist/shared/http-retry.d.ts +51 -9
  105. package/dist/shared/http-retry.js +60 -9
  106. package/dist/shared/http-retry.js.map +1 -1
  107. package/dist/shared/mcp-manager.d.ts +8 -4
  108. package/dist/shared/mcp-manager.js +30 -4
  109. package/dist/shared/mcp-manager.js.map +1 -1
  110. package/dist/shared/mcp-schema-sanitizer.d.ts +70 -0
  111. package/dist/shared/mcp-schema-sanitizer.js +197 -0
  112. package/dist/shared/mcp-schema-sanitizer.js.map +1 -0
  113. package/dist/shared/mcp-transport-guard.d.ts +2 -2
  114. package/dist/shared/mcp-transport-guard.js +2 -2
  115. package/dist/shared/model-client.d.ts +6 -0
  116. package/dist/shared/model-client.js +14 -3
  117. package/dist/shared/model-client.js.map +1 -1
  118. package/dist/shared/model-pricing-data.d.ts +2 -0
  119. package/dist/shared/model-pricing-data.js +13 -3
  120. package/dist/shared/model-pricing-data.js.map +1 -1
  121. package/dist/shared/model-registry.js +3 -2
  122. package/dist/shared/model-registry.js.map +1 -1
  123. package/dist/shared/plan-mode-permissions.d.ts +7 -0
  124. package/dist/shared/plan-mode-permissions.js +7 -0
  125. package/dist/shared/plan-mode-permissions.js.map +1 -1
  126. package/dist/shared/registry-endpoint.d.ts +20 -0
  127. package/dist/shared/registry-endpoint.js +25 -0
  128. package/dist/shared/registry-endpoint.js.map +1 -1
  129. package/dist/shared/runner-credential-keys.d.ts +24 -0
  130. package/dist/shared/runner-credential-keys.js +47 -0
  131. package/dist/shared/runner-credential-keys.js.map +1 -0
  132. package/dist/shared/synthesized-attachment.d.ts +4 -4
  133. package/dist/shared/synthesized-attachment.js +4 -4
  134. package/dist/workflow-engine/resolve.d.ts +19 -1
  135. package/dist/workflow-engine/resolve.js +37 -2
  136. package/dist/workflow-engine/resolve.js.map +1 -1
  137. package/dist/workflows/connect-mcp-server.js +2 -0
  138. package/dist/workflows/connect-mcp-server.js.map +1 -1
  139. package/dist/workflows/types.d.ts +8 -0
  140. package/package.json +3 -4
  141. package/src/__test-utils__/__tests__/vitest-global-setup.test.ts +37 -0
  142. package/src/__test-utils__/fake-artifact-storage.ts +10 -1
  143. package/src/__test-utils__/vitest-global-setup.ts +50 -0
  144. package/src/__tests__/bootstrap.test.ts +43 -0
  145. package/src/__tests__/encryption-codec.test.ts +53 -0
  146. package/src/__tests__/preflight.test.ts +15 -0
  147. package/src/activities/__tests__/discover-mcp-server.test.ts +39 -1
  148. package/src/activities/__tests__/error-classifier.test.ts +94 -2
  149. package/src/activities/__tests__/run-command.test.ts +221 -0
  150. package/src/activities/discover-mcp-server.ts +16 -2
  151. package/src/activities/emit-event.ts +65 -21
  152. package/src/activities/execute-cursor/__tests__/attachment-resolver.test.ts +63 -3
  153. package/src/activities/execute-cursor/__tests__/build-prompt.test.ts +173 -31
  154. package/src/activities/execute-cursor/__tests__/model-pricing.test.ts +62 -1
  155. package/src/activities/execute-cursor/__tests__/turn-recovery.test.ts +160 -0
  156. package/src/activities/execute-cursor/attachment-resolver.ts +24 -0
  157. package/src/activities/execute-cursor/blueprint-resolver.ts +1 -10
  158. package/src/activities/execute-cursor/error-classifier.ts +40 -3
  159. package/src/activities/execute-cursor/index.ts +175 -74
  160. package/src/activities/execute-cursor/model-pricing-data.ts +22 -3
  161. package/src/activities/execute-cursor/prompt-builder.ts +89 -22
  162. package/src/activities/execute-cursor/turn-recovery.ts +208 -0
  163. package/src/activities/execute-deep-agent/__tests__/attachment-injector.test.ts +105 -0
  164. package/src/activities/execute-deep-agent/__tests__/hitl-reject.test.ts +0 -2
  165. package/src/activities/execute-deep-agent/__tests__/hitl-resume-approve-all.test.ts +0 -1
  166. package/src/activities/execute-deep-agent/__tests__/hitl-resume-history.test.ts +0 -1
  167. package/src/activities/execute-deep-agent/__tests__/mcp-gate.test.ts +2 -7
  168. package/src/activities/execute-deep-agent/__tests__/plan-mode-path-normalization.test.ts +174 -0
  169. package/src/activities/execute-deep-agent/__tests__/prompt-builder.test.ts +71 -0
  170. package/src/activities/execute-deep-agent/__tests__/sequential-gate-resume.test.ts +0 -1
  171. package/src/activities/execute-deep-agent/__tests__/shell-env.test.ts +24 -10
  172. package/src/activities/execute-deep-agent/__tests__/subagent-plan-mode-permissions.test.ts +22 -17
  173. package/src/activities/execute-deep-agent/attachment-injector.ts +20 -1
  174. package/src/activities/execute-deep-agent/index.ts +0 -19
  175. package/src/activities/execute-deep-agent/mcp-gate.ts +0 -3
  176. package/src/activities/execute-deep-agent/prompt-builder.ts +27 -15
  177. package/src/activities/execute-deep-agent/setup.ts +22 -73
  178. package/src/activities/execute-deep-agent/shell-env.ts +8 -9
  179. package/src/activities/execute-deep-agent/subagent-transformer.ts +7 -1
  180. package/src/activities/execute-deep-agent/subagent-wiring.ts +26 -3
  181. package/src/activities/hydrate-workflow-execution.ts +8 -5
  182. package/src/activities/run-command.ts +25 -12
  183. package/src/activities/run-env.ts +79 -0
  184. package/src/bootstrap.ts +5 -0
  185. package/src/client/__tests__/stigmer-client.test.ts +104 -5
  186. package/src/client/stigmer-client.ts +128 -29
  187. package/src/config.ts +6 -5
  188. package/src/encryption/config.ts +87 -23
  189. package/src/middleware/__tests__/path-normalization.test.ts +140 -0
  190. package/src/middleware/index.ts +8 -0
  191. package/src/middleware/path-normalization.ts +125 -0
  192. package/src/middleware/types.ts +11 -0
  193. package/src/payload-codecs.ts +8 -2
  194. package/src/runner-manager.ts +21 -3
  195. package/src/runner.ts +7 -3
  196. package/src/shared/__tests__/artifact-storage.test.ts +154 -2
  197. package/src/shared/__tests__/attachment-download-urls.test.ts +70 -0
  198. package/src/shared/__tests__/channel-attachment.test.ts +2 -2
  199. package/src/shared/__tests__/conversation-catchup.test.ts +24 -0
  200. package/src/shared/__tests__/http-retry.test.ts +5 -4
  201. package/src/shared/__tests__/mcp-manager.test.ts +53 -13
  202. package/src/shared/__tests__/mcp-schema-sanitizer.test.ts +267 -0
  203. package/src/shared/__tests__/model-client.test.ts +46 -0
  204. package/src/shared/__tests__/model-pricing.test.ts +62 -2
  205. package/src/shared/__tests__/model-registry.test.ts +22 -0
  206. package/src/shared/__tests__/synthesized-attachment.test.ts +19 -41
  207. package/src/shared/artifact-storage.ts +86 -10
  208. package/src/shared/attachment-download-urls.ts +98 -0
  209. package/src/shared/channel-attachment.ts +1 -1
  210. package/src/shared/checkpointer/http-saver.ts +15 -60
  211. package/src/shared/conversation-attachment.ts +2 -3
  212. package/src/shared/conversation-catchup.ts +39 -6
  213. package/src/shared/grpc-retry.ts +5 -1
  214. package/src/shared/http-retry.ts +98 -9
  215. package/src/shared/mcp-manager.ts +35 -4
  216. package/src/shared/mcp-schema-sanitizer.ts +224 -0
  217. package/src/shared/mcp-transport-guard.ts +2 -2
  218. package/src/shared/model-client.ts +21 -3
  219. package/src/shared/model-pricing-data.ts +22 -3
  220. package/src/shared/model-registry.ts +7 -2
  221. package/src/shared/plan-mode-permissions.ts +7 -0
  222. package/src/shared/registry-endpoint.ts +27 -0
  223. package/src/shared/runner-credential-keys.ts +46 -0
  224. package/src/shared/synthesized-attachment.ts +4 -4
  225. package/src/workflow-engine/__tests__/tasks/emit-event.test.ts +127 -49
  226. package/src/workflow-engine/resolve.ts +48 -2
  227. package/src/workflows/__tests__/connect-mcp-server.test.ts +25 -0
  228. package/src/workflows/connect-mcp-server.ts +2 -0
  229. package/src/workflows/types.ts +8 -0
  230. package/dist/activities/execute-cursor/mcp-config.d.ts +0 -30
  231. package/dist/activities/execute-cursor/mcp-config.js +0 -39
  232. package/dist/activities/execute-cursor/mcp-config.js.map +0 -1
  233. package/dist/shared/datastore-attachment.d.ts +0 -99
  234. package/dist/shared/datastore-attachment.js +0 -184
  235. package/dist/shared/datastore-attachment.js.map +0 -1
  236. package/src/activities/execute-cursor/mcp-config.ts +0 -66
  237. package/src/activities/execute-deep-agent/__tests__/datastore-degradation.test.ts +0 -104
  238. package/src/shared/__tests__/datastore-attachment.test.ts +0 -293
  239. package/src/shared/datastore-attachment.ts +0 -209
@@ -55,10 +55,27 @@ export interface BuildChatModelOptions {
55
55
  * stays the caller's decision to avoid silently changing behavior.
56
56
  */
57
57
  readonly maxTokens?: number;
58
+ /**
59
+ * Per-request bound. When omitted, defaults to the operator's
60
+ * STIGMER_LLM_REQUEST_TIMEOUT_MS — resolved HERE, not per caller, so no
61
+ * call site can drop the bound by forgetting to plumb it (the sub-agent
62
+ * factory did exactly that; stigmer/stigmer#468). An explicit value wins.
63
+ */
58
64
  readonly timeoutMs?: number;
59
65
  readonly maxRetries?: number;
60
66
  }
61
67
 
68
+ /**
69
+ * The operator's request-timeout bound. Only a positive integer means
70
+ * "bound the request" — unset, non-numeric, zero, and negative all
71
+ * normalize to no bound. Deployment-static env, read here the same way
72
+ * provider API keys are.
73
+ */
74
+ function resolveDefaultTimeoutMs(): number | undefined {
75
+ const parsed = Number.parseInt(process.env.STIGMER_LLM_REQUEST_TIMEOUT_MS ?? "", 10);
76
+ return parsed > 0 ? parsed : undefined;
77
+ }
78
+
62
79
  export interface BuiltChatModel {
63
80
  readonly model: BaseChatModel;
64
81
  readonly provider: LlmProvider;
@@ -82,6 +99,7 @@ export async function buildChatModel(opts: BuildChatModelOptions): Promise<Built
82
99
  const resolved = await resolveToApiModelId(opts.modelName);
83
100
  const provider = inferProvider(resolved);
84
101
  const apiModelId = stripProviderPrefix(resolved);
102
+ const timeoutMs = opts.timeoutMs ?? resolveDefaultTimeoutMs();
85
103
 
86
104
  // Backend precedence by construction: a proxied call never consults the
87
105
  // backend var — the proxy owns provider routing (the factories' preflight
@@ -201,7 +219,7 @@ export async function buildChatModel(opts: BuildChatModelOptions): Promise<Built
201
219
  temperature: opts.temperature ?? 0,
202
220
  apiKey,
203
221
  ...(maxTokens ? { maxTokens } : {}),
204
- ...(opts.timeoutMs ? { maxRetries: opts.maxRetries ?? 0 } : {}),
222
+ ...(timeoutMs ? { maxRetries: opts.maxRetries ?? 0 } : {}),
205
223
  };
206
224
 
207
225
  // The request timeout lives in a different slot per wrapper: ChatOpenAI
@@ -215,7 +233,7 @@ export async function buildChatModel(opts: BuildChatModelOptions): Promise<Built
215
233
  // that is LangChain's own retry knob, distinct from the SDK-level
216
234
  // maxRetries the factories receive.
217
235
  const anthropicClientOptions = {
218
- ...(opts.timeoutMs ? { timeout: opts.timeoutMs } : {}),
236
+ ...(timeoutMs ? { timeout: timeoutMs } : {}),
219
237
  ...(baseUrl ? { baseURL: baseUrl } : {}),
220
238
  ...(headers ? { defaultHeaders: headers } : {}),
221
239
  };
@@ -231,7 +249,7 @@ export async function buildChatModel(opts: BuildChatModelOptions): Promise<Built
231
249
  ? new ChatOpenAI({
232
250
  model: apiModelId,
233
251
  ...common,
234
- ...(opts.timeoutMs ? { timeout: opts.timeoutMs } : {}),
252
+ ...(timeoutMs ? { timeout: timeoutMs } : {}),
235
253
  ...(baseUrl || headers
236
254
  ? {
237
255
  configuration: {
@@ -30,9 +30,18 @@ interface RegistryEntry {
30
30
  };
31
31
  }
32
32
 
33
- import { resolveModelRegistryUrl, buildRegistryHeaders } from "./registry-endpoint.js";
33
+ import {
34
+ resolveModelRegistryUrl,
35
+ buildRegistryHeaders,
36
+ REGISTRY_RETRY_POLICY,
37
+ } from "./registry-endpoint.js";
38
+ import { fetchWithRetry } from "./http-retry.js";
34
39
 
35
40
  const CACHE_TTL_MS = 3_600_000;
41
+ // Failed fetches are cached much shorter than successes (the model-registry.ts
42
+ // policy): a transient failure must not pin DEFAULT_PRICING — wrong rates for
43
+ // cost tracking — for a full hour.
44
+ const FAILURE_CACHE_TTL_MS = 60_000;
36
45
 
37
46
  export const DEFAULT_PRICING: ModelPricing = {
38
47
  model: "unknown",
@@ -66,7 +75,11 @@ function parsePricingTable(json: unknown): ModelPricing[] {
66
75
  }
67
76
 
68
77
  async function fetchFromApi(): Promise<readonly ModelPricing[]> {
69
- const res = await fetch(resolveModelRegistryUrl(), { headers: buildRegistryHeaders() });
78
+ const res = await fetchWithRetry(
79
+ resolveModelRegistryUrl(),
80
+ { headers: buildRegistryHeaders() },
81
+ REGISTRY_RETRY_POLICY,
82
+ );
70
83
  if (!res.ok) throw new Error(`Model registry fetch failed: ${res.status}`);
71
84
  const data: unknown = await res.json();
72
85
  const table = parsePricingTable(data);
@@ -93,7 +106,7 @@ export async function getPricingTable(): Promise<readonly ModelPricing[]> {
93
106
  `Failed to fetch model registry, using default pricing: ${err}`,
94
107
  );
95
108
  const fallback = [DEFAULT_PRICING];
96
- cache = { data: fallback, expiresAt: Date.now() + CACHE_TTL_MS };
109
+ cache = { data: fallback, expiresAt: Date.now() + FAILURE_CACHE_TTL_MS };
97
110
  return fallback;
98
111
  })
99
112
  .finally(() => {
@@ -102,3 +115,9 @@ export async function getPricingTable(): Promise<readonly ModelPricing[]> {
102
115
 
103
116
  return inflightFetch;
104
117
  }
118
+
119
+ /** Exposed for testing — resets the in-memory cache. */
120
+ export function _resetPricingCache(): void {
121
+ cache = null;
122
+ inflightFetch = null;
123
+ }
@@ -10,7 +10,12 @@
10
10
  * (getModelVisionCapability).
11
11
  */
12
12
 
13
- import { resolveModelRegistryUrl, buildRegistryHeaders } from "./registry-endpoint.js";
13
+ import {
14
+ resolveModelRegistryUrl,
15
+ buildRegistryHeaders,
16
+ REGISTRY_RETRY_POLICY,
17
+ } from "./registry-endpoint.js";
18
+ import { fetchWithRetry } from "./http-retry.js";
14
19
 
15
20
  const CACHE_TTL_MS = 3_600_000;
16
21
  // Failed fetches are cached much shorter than successes: a transient failure
@@ -68,7 +73,7 @@ function parseVisionCapability(capabilities: unknown): boolean | undefined {
68
73
 
69
74
  async function fetchRegistry(): Promise<readonly RegistryModel[]> {
70
75
  const url = resolveModelRegistryUrl();
71
- const res = await fetch(url, { headers: buildRegistryHeaders() });
76
+ const res = await fetchWithRetry(url, { headers: buildRegistryHeaders() }, REGISTRY_RETRY_POLICY);
72
77
  if (!res.ok) throw new Error(`Model registry fetch failed: ${res.status}`);
73
78
  const data: unknown = await res.json();
74
79
  return parseRegistry(data);
@@ -16,6 +16,13 @@
16
16
  * (issue #255). Kept as its own side-effect-free module so tests can pin the
17
17
  * production rules without dragging in setup.ts's import graph.
18
18
  *
19
+ * Rules travel with a companion: every graph that carries them also installs
20
+ * the path-normalization middleware (middleware/path-normalization.ts,
21
+ * issue #429), because deepagents' rule validation refuses workspace-relative
22
+ * paths outright — without the shim, prompt-compliant relative READS die in
23
+ * validation instead of just working. Both are derived from the same
24
+ * expression at each composition site so they cannot drift apart.
25
+ *
19
26
  * Invariant: never combine these rules with a shell-capable (sandbox)
20
27
  * backend — deepagents rejects that pairing at graph construction (see the
21
28
  * cas-capture-backend.ts header). Plan mode guarantees it by construction:
@@ -20,10 +20,37 @@
20
20
  */
21
21
 
22
22
  import { normalizeEndpoint } from "../config.js";
23
+ import type { FetchRetryPolicy } from "./http-retry.js";
23
24
 
24
25
  /** Default local stigmer-server origin — mirrors config.ts's local-mode default. */
25
26
  const DEFAULT_LOCAL_BACKEND = "http://localhost:7234";
26
27
 
28
+ /**
29
+ * Shared retry policy for the three registry fetch clients (model-registry.ts
30
+ * and both model-pricing-data.ts modules) — stigmer/stigmer#468.
31
+ *
32
+ * The timeout is the load-bearing half: each consumer deduplicates callers
33
+ * through a single in-flight promise, so one hung fetch strands every
34
+ * concurrent caller behind it — model-registry.ts's sits in front of every
35
+ * LLM client build. 10 s (not the checkpointer's 30 s) because the response
36
+ * is a small JSON document and the callers degrade gracefully; what they must
37
+ * never do is wait forever.
38
+ *
39
+ * The retry half matters because the degrade is not free: a failed registry
40
+ * fetch is cached (60 s empty registry → unresolved model ids that 404 at the
41
+ * provider; pricing falls back to defaults), so a budget spanning a
42
+ * pod-restart reroute converts that poisoned window into ~1.75 s of backoff.
43
+ * Worst case fully bounded: ~31 s of cold-cache stall when the control plane
44
+ * is genuinely down, versus an infinite hang before #468.
45
+ */
46
+ export const REGISTRY_RETRY_POLICY: FetchRetryPolicy = {
47
+ label: "registry-endpoint",
48
+ baseDelayMs: 250,
49
+ backoffFactor: 2,
50
+ maxRetries: 3,
51
+ requestTimeoutMs: 10_000,
52
+ };
53
+
27
54
  /**
28
55
  * Resolve the base URL (origin, no path) for model-registry and pricing
29
56
  * fetches. See module doc for the tier order.
@@ -0,0 +1,46 @@
1
+ /**
2
+ * Env var names of the runner's own credentials — the single source of truth
3
+ * for every surface that isolates agent-spawned processes from the runner's
4
+ * process env (issue #385).
5
+ *
6
+ * Inclusion rule: a name belongs here iff the runner reads it from its own
7
+ * `process.env` to authenticate the runner's own outbound calls. User-supplied
8
+ * credentials resolved from ExecutionContext (e.g. GITHUB_TOKEN, read only
9
+ * from mergedEnvVars) must NEVER be listed — the ExecutionContext overlay is
10
+ * their sanctioned delivery channel, and denying them here would break it.
11
+ *
12
+ * Consumers:
13
+ * - shell-env.ts: SHELL_ENV_DENYLIST for the native harness `execute` tool.
14
+ * - mcp-manager.test.ts: leak-tripwire canaries for MCP stdio subprocesses
15
+ * (that path passes NO runner env by construction; the test plants these
16
+ * names to prove none leak through).
17
+ *
18
+ * Ambient cloud-SDK chain vars (AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY,
19
+ * GOOGLE_APPLICATION_CREDENTIALS, ...) are deliberately absent: they are
20
+ * dual-use on local runners (operators expect agent shells to keep their
21
+ * ambient aws/gcloud auth), and on a local backend the shell can read the
22
+ * credential files off disk regardless (owner ruling on #385).
23
+ */
24
+ export const RUNNER_CREDENTIAL_ENV_KEYS: readonly string[] = [
25
+ // Derives HITL approval fingerprints (fingerprint-secret.ts) — an agent
26
+ // that reads this could forge approval receipts.
27
+ "STIGMER_RUNNER_HITL_SECRET",
28
+ // Stigmer control-plane auth (config.ts; rotated at runtime by
29
+ // runner-manager.ts). Executions receive theirs via ExecutionContext.
30
+ "STIGMER_TOKEN",
31
+ // Fallback bearer token for model-registry/pricing fetches when
32
+ // STIGMER_TOKEN is unset (registry-endpoint.ts).
33
+ "STIGMER_AUTH_TOKEN",
34
+ // Cursor harness credential (config.ts) — agent-spawned processes never
35
+ // need direct Cursor API access.
36
+ "CURSOR_API_KEY",
37
+ // Direct-mode Anthropic key for the native harness (model-client.ts).
38
+ "ANTHROPIC_API_KEY",
39
+ // Direct-mode OpenAI key for the native harness (model-client.ts).
40
+ "OPENAI_API_KEY",
41
+ // Azure AI Foundry backend key (model-client.ts).
42
+ "ANTHROPIC_FOUNDRY_API_KEY",
43
+ // Bedrock backend bearer credential, read by the AWS SDK's chain
44
+ // (llm-backend.ts documents it as a supported auth path).
45
+ "AWS_BEARER_TOKEN_BEDROCK",
46
+ ];
@@ -1,8 +1,8 @@
1
1
  /**
2
2
  * Shared mechanics of runner-synthesized MCP attachments — the pieces
3
- * the datastore records attachment (T05) and the channel messaging
4
- * attachment (proactive-messaging DD-006 D8) have in common, extracted
5
- * when the second attachment arrived.
3
+ * the channel messaging attachment (proactive-messaging DD-006 D8) and
4
+ * the conversation participation attachment (DD-008 D-c) have in
5
+ * common, extracted when the second attachment arrived.
6
6
  *
7
7
  * A synthesized attachment is a first-party MCP server entry the runner
8
8
  * builds itself (no McpServer resource, no Environment, no credential in
@@ -44,7 +44,7 @@ export interface SynthesizedAttachmentOptions {
44
44
  * Inject a synthesized attachment into a resolved server list — AFTER
45
45
  * resolve + backfill (see the module header). A user server shadowing
46
46
  * the reserved slug is replaced, loudly; `label` names the attachment
47
- * in that warning (e.g. "datastore records").
47
+ * in that warning (e.g. "channel messaging").
48
48
  */
49
49
  export function injectSynthesizedAttachment(
50
50
  resolvedServers: ResolvedMcpServer[],
@@ -1,32 +1,34 @@
1
1
  import { describe, it, expect, vi, afterEach, beforeEach } from "vitest";
2
- import { emitEventAction, type EmitEventConfig } from "../../../activities/emit-event.js";
3
-
4
- // ─── Temporal signal-delivery mocks ──────────────────────────────────────────
5
- // deliverSignal dynamically imports @temporalio/client and resolves the cluster
6
- // coordinates through loadConfig(). We mock both so the signal path is testable
7
- // without a live Temporal server, and so we can assert which address it dials.
8
- const mockConnect = vi.fn(async (_opts: { address: string }) => ({}));
9
- const mockSignal = vi.fn(async () => undefined);
10
- const mockGetHandle = vi.fn((_id: string) => ({ signal: mockSignal }));
11
- const mockClientCtor = vi.fn((_opts: { connection: unknown; namespace: string }) => ({
12
- workflow: { getHandle: mockGetHandle },
2
+ import { emitEventAction, type EmitEventConfig, type SignalDeliveryTarget } from "../../../activities/emit-event.js";
3
+
4
+ // ─── Server-mediated signal-delivery mocks ───────────────────────────────────
5
+ // deliverSignal routes through StigmerClient.sendWorkflowSignal (the server's
6
+ // SendSignal lane — oss#517). We mock the client so the signal path is testable
7
+ // without a live server, and so we can assert the addressing, the bounded call
8
+ // timeout, and the best-effort delivery_errors contract.
9
+ const mockSendWorkflowSignal = vi.fn(async (
10
+ _executionId: string,
11
+ _signalName: string,
12
+ _payload: unknown,
13
+ _options?: { timeoutMs?: number },
14
+ ) => ({}));
15
+ const mockClientCtor = vi.fn((_opts: { endpoint: string; token?: string | null }) => ({
16
+ sendWorkflowSignal: mockSendWorkflowSignal,
13
17
  }));
14
18
 
15
- vi.mock("@temporalio/client", () => ({
16
- Connection: { connect: (opts: { address: string }) => mockConnect(opts) },
17
- Client: vi.fn().mockImplementation((opts: { connection: unknown; namespace: string }) =>
18
- mockClientCtor(opts),
19
+ vi.mock("../../../client/stigmer-client.js", () => ({
20
+ StigmerClient: vi.fn().mockImplementation(
21
+ (opts: { endpoint: string; token?: string | null }) => mockClientCtor(opts),
19
22
  ),
20
23
  }));
21
24
 
22
- // Mutable so each test can vary what config.ts resolves. Mirrors the real
23
- // resolution where temporalAddress comes from TEMPORAL_SERVICE_ADDRESS.
24
- let mockTemporalAddress = "localhost:7233";
25
- let mockTemporalNamespace = "default";
25
+ // Mutable so each test can vary what config.ts resolves. deliverSignal builds
26
+ // its client from the canonical stigmerBackendEndpoint resolution.
27
+ let mockBackendEndpoint = "http://localhost:7234";
26
28
  vi.mock("../../../config.js", () => ({
27
29
  loadConfig: () => ({
28
- temporalAddress: mockTemporalAddress,
29
- temporalNamespace: mockTemporalNamespace,
30
+ stigmerBackendEndpoint: mockBackendEndpoint,
31
+ stigmerToken: null,
30
32
  }),
31
33
  }));
32
34
 
@@ -260,63 +262,139 @@ describe("emitEventAction", () => {
260
262
  });
261
263
  });
262
264
 
263
- describe("signal delivery (Temporal coordinates from config)", () => {
265
+ describe("signal delivery (server-mediated SendSignal lane)", () => {
264
266
  beforeEach(() => {
265
- mockConnect.mockClear();
266
- mockSignal.mockClear();
267
- mockGetHandle.mockClear();
267
+ mockSendWorkflowSignal.mockClear();
268
268
  mockClientCtor.mockClear();
269
- mockSignal.mockResolvedValue(undefined);
270
- mockTemporalAddress = "localhost:7233";
271
- mockTemporalNamespace = "default";
269
+ mockSendWorkflowSignal.mockResolvedValue({});
270
+ mockBackendEndpoint = "http://localhost:7234";
272
271
  });
273
272
 
274
- it("dials the Temporal address resolved by config, not a hardcoded localhost", async () => {
275
- // Regression for F1: deliverSignal previously read process.env.TEMPORAL_ADDRESS
276
- // and fell back to localhost — diverging from the canonical
277
- // TEMPORAL_SERVICE_ADDRESS the worker uses. It now goes through loadConfig().
278
- mockTemporalAddress = "stigmer-temporal-frontend:7233";
279
- mockTemporalNamespace = "stigmer-prod";
273
+ it("routes through the server resolved by config, addressed by execution_id", async () => {
274
+ mockBackendEndpoint = "https://api.stigmer.example.com";
280
275
 
281
276
  const config: EmitEventConfig = {
282
277
  event: { type: "approval.granted" },
283
278
  delivery: [
284
- { signal: { workflow_id: "wf-abc", signal_name: "approvalResolved" } },
279
+ { signal: { execution_id: "wfx_01ABC", signal_name: "approval_resolved" } },
285
280
  ],
286
281
  };
287
282
 
288
283
  const result = await emitEventAction(config, "exec-1");
289
284
 
290
- expect(mockConnect).toHaveBeenCalledWith({ address: "stigmer-temporal-frontend:7233" });
291
285
  expect(mockClientCtor).toHaveBeenCalledWith(
292
- expect.objectContaining({ namespace: "stigmer-prod" }),
286
+ expect.objectContaining({ endpoint: "https://api.stigmer.example.com" }),
293
287
  );
294
- expect(mockGetHandle).toHaveBeenCalledWith("wf-abc");
295
- expect(mockSignal).toHaveBeenCalledTimes(1);
296
- const [signalName, envelope] = mockSignal.mock.calls[0] as unknown as [
297
- string,
298
- Record<string, unknown>,
299
- ];
300
- expect(signalName).toBe("approvalResolved");
288
+ expect(mockSendWorkflowSignal).toHaveBeenCalledTimes(1);
289
+ const [executionId, signalName, envelope, options] =
290
+ mockSendWorkflowSignal.mock.calls[0] as unknown as [
291
+ string,
292
+ string,
293
+ Record<string, unknown>,
294
+ { timeoutMs?: number },
295
+ ];
296
+ expect(executionId).toBe("wfx_01ABC");
297
+ expect(signalName).toBe("approval_resolved");
301
298
  expect(envelope.type).toBe("approval.granted");
299
+ expect(envelope.specversion).toBe("1.0");
302
300
  expect(result.delivery_errors).toBeUndefined();
301
+ // Best-effort delivery must never consume the activity's 5m budget.
302
+ expect(options).toEqual({ timeoutMs: 30_000 });
303
303
  });
304
304
 
305
- it("keeps signal delivery best-effort: failures are collected, not thrown", async () => {
306
- mockSignal.mockRejectedValueOnce(new Error("temporal unavailable"));
305
+ it("keeps signal delivery best-effort: server refusals are collected, not thrown", async () => {
306
+ mockSendWorkflowSignal.mockRejectedValueOnce(
307
+ new Error("[failed_precondition] cannot send signal to execution in phase EXECUTION_COMPLETED"),
308
+ );
309
+
310
+ const config: EmitEventConfig = {
311
+ event: { type: "test" },
312
+ delivery: [
313
+ { signal: { execution_id: "wfx_done", signal_name: "ping" } },
314
+ ],
315
+ };
316
+
317
+ const result = await emitEventAction(config, "exec-1");
318
+
319
+ expect(result.delivery_errors).toHaveLength(1);
320
+ expect((result.delivery_errors as any)[0].target).toBe("signal:wfx_done/ping");
321
+ expect((result.delivery_errors as any)[0].error).toContain("failed_precondition");
322
+ });
307
323
 
324
+ it("refuses the pre-oss#517 workflow_id field by name, without calling the server", async () => {
308
325
  const config: EmitEventConfig = {
309
326
  event: { type: "test" },
310
327
  delivery: [
311
- { signal: { workflow_id: "wf-down", signal_name: "ping" } },
328
+ {
329
+ signal: {
330
+ workflow_id: "workflow-exec-wfx_01ABC",
331
+ signal_name: "ping",
332
+ } as unknown as SignalDeliveryTarget,
333
+ },
312
334
  ],
313
335
  };
314
336
 
315
337
  const result = await emitEventAction(config, "exec-1");
316
338
 
339
+ expect(mockSendWorkflowSignal).not.toHaveBeenCalled();
317
340
  expect(result.delivery_errors).toHaveLength(1);
318
- expect((result.delivery_errors as any)[0].target).toBe("signal:wf-down/ping");
319
- expect((result.delivery_errors as any)[0].error).toContain("temporal unavailable");
341
+ expect((result.delivery_errors as any)[0].error).toContain("'execution_id'");
342
+ expect((result.delivery_errors as any)[0].error).toContain("'workflow_id' is not supported");
343
+ });
344
+
345
+ it("refuses a signal target missing signal_name", async () => {
346
+ const config: EmitEventConfig = {
347
+ event: { type: "test" },
348
+ delivery: [
349
+ {
350
+ signal: {
351
+ execution_id: "wfx_01ABC",
352
+ } as unknown as SignalDeliveryTarget,
353
+ },
354
+ ],
355
+ };
356
+
357
+ const result = await emitEventAction(config, "exec-1");
358
+
359
+ expect(mockSendWorkflowSignal).not.toHaveBeenCalled();
360
+ expect(result.delivery_errors).toHaveLength(1);
361
+ expect((result.delivery_errors as any)[0].error).toContain("'signal_name'");
362
+ });
363
+
364
+ it("reuses one client across multiple signal targets", async () => {
365
+ const config: EmitEventConfig = {
366
+ event: { type: "fanout" },
367
+ delivery: [
368
+ { signal: { execution_id: "wfx_a", signal_name: "sig_a" } },
369
+ { signal: { execution_id: "wfx_b", signal_name: "sig_b" } },
370
+ ],
371
+ };
372
+
373
+ const result = await emitEventAction(config, "exec-1");
374
+
375
+ expect(mockClientCtor).toHaveBeenCalledTimes(1);
376
+ expect(mockSendWorkflowSignal).toHaveBeenCalledTimes(2);
377
+ expect(result.delivery_errors).toBeUndefined();
378
+ });
379
+
380
+ it("constructs no client for webhook-only delivery", async () => {
381
+ const originalFetch = globalThis.fetch;
382
+ globalThis.fetch = vi.fn(() =>
383
+ Promise.resolve(new Response(null, { status: 200 })),
384
+ ) as any;
385
+
386
+ try {
387
+ const config: EmitEventConfig = {
388
+ event: { type: "test" },
389
+ delivery: [{ webhook: { url: "https://events.example.com" } }],
390
+ };
391
+
392
+ await emitEventAction(config, "exec-1");
393
+
394
+ expect(mockClientCtor).not.toHaveBeenCalled();
395
+ } finally {
396
+ globalThis.fetch = originalFetch;
397
+ }
320
398
  });
321
399
  });
322
400
  });
@@ -158,6 +158,9 @@ function parsePath(path: string): (string | number)[] {
158
158
 
159
159
  const RUNTIME_PLACEHOLDER_RE = /\$\{\.(secrets|env_vars)\.\w+\}/;
160
160
 
161
+ /** Substitution form of {@link RUNTIME_PLACEHOLDER_RE} (global, capturing). */
162
+ const RUNTIME_PLACEHOLDER_SUB_RE = /\$\{\.(secrets|env_vars)\.(\w+)\}/g;
163
+
161
164
  /**
162
165
  * Checks whether a string contains a runtime placeholder that should
163
166
  * NOT be evaluated in the workflow (deterministic) phase. These are
@@ -172,7 +175,9 @@ export function isRuntimePlaceholder(value: string): boolean {
172
175
 
173
176
  /**
174
177
  * Resolves runtime placeholders (`${.secrets.KEY}`, `${.env_vars.KEY}`)
175
- * in a string using values from the runtime environment map.
178
+ * in a string using values from the runtime environment map. Missing
179
+ * keys resolve to `""` — callers that must fail loudly instead use
180
+ * {@link resolveRuntimePlaceholdersStrict}.
176
181
  *
177
182
  * This runs in activities only — never in the workflow sandbox.
178
183
  */
@@ -181,7 +186,7 @@ export function resolveRuntimePlaceholders(
181
186
  runtimeEnv: Record<string, unknown>,
182
187
  ): string {
183
188
  return value.replace(
184
- /\$\{\.(secrets|env_vars)\.(\w+)\}/g,
189
+ RUNTIME_PLACEHOLDER_SUB_RE,
185
190
  (_match, _ns: string, key: string) => {
186
191
  const resolved = runtimeEnv[key];
187
192
  return resolved !== undefined ? String(resolved) : "";
@@ -189,6 +194,47 @@ export function resolveRuntimePlaceholders(
189
194
  );
190
195
  }
191
196
 
197
+ /** Thrown by {@link resolveRuntimePlaceholdersStrict} for a missing key. */
198
+ export class RuntimePlaceholderResolutionError extends Error {
199
+ constructor(
200
+ public readonly variableName: string,
201
+ public readonly context?: string,
202
+ ) {
203
+ const where = context ? ` in ${context}` : "";
204
+ super(
205
+ `Unresolved runtime placeholder for "${variableName}"${where}: ` +
206
+ `variable is not present in the workflow's runtime environment`,
207
+ );
208
+ this.name = "RuntimePlaceholderResolutionError";
209
+ }
210
+ }
211
+
212
+ /**
213
+ * Strict variant of {@link resolveRuntimePlaceholders}: a placeholder
214
+ * whose key is missing from the runtime environment throws instead of
215
+ * resolving to `""` — a silently-empty credential produces cryptic
216
+ * downstream failures, so declared-value consumers (the run task's env
217
+ * contract) fail fast with the variable named.
218
+ *
219
+ * This runs in activities only — never in the workflow sandbox.
220
+ */
221
+ export function resolveRuntimePlaceholdersStrict(
222
+ value: string,
223
+ runtimeEnv: Record<string, unknown>,
224
+ context?: string,
225
+ ): string {
226
+ return value.replace(
227
+ RUNTIME_PLACEHOLDER_SUB_RE,
228
+ (_match, _ns: string, key: string) => {
229
+ const resolved = runtimeEnv[key];
230
+ if (resolved === undefined) {
231
+ throw new RuntimePlaceholderResolutionError(key, context);
232
+ }
233
+ return String(resolved);
234
+ },
235
+ );
236
+ }
237
+
192
238
  /**
193
239
  * Recursively resolves all runtime placeholders in a nested object.
194
240
  * Walks maps, arrays, and strings. Non-string leaves pass through.
@@ -63,6 +63,7 @@ describe("ConnectMcpServerWorkflow", () => {
63
63
  expect(mockDiscoverActivity).toHaveBeenCalledWith({
64
64
  mcpServerId: "mcp-123",
65
65
  executionContextId: "ctx-abc",
66
+ executionContextToken: null,
66
67
  invokerIdentityAccountId: "user-1",
67
68
  });
68
69
 
@@ -88,6 +89,30 @@ describe("ConnectMcpServerWorkflow", () => {
88
89
  expect(mockDiscoverActivity).toHaveBeenCalledWith({
89
90
  mcpServerId: "mcp-min",
90
91
  executionContextId: null,
92
+ executionContextToken: null,
93
+ invokerIdentityAccountId: null,
94
+ });
95
+ });
96
+
97
+ it("forwards the payload-carried EC token to discovery (oss#535)", async () => {
98
+ // The OSS handler mints an execution-scoped token into the workflow
99
+ // input; discovery presents it on the EC read to receive decrypted
100
+ // credentials from the redact-by-default OSS server.
101
+ const { connectMcpServer } = await import("../connect-mcp-server.js");
102
+
103
+ mockDiscoverActivity.mockResolvedValue(makeDiscoveryResult({ tools: [] }));
104
+ mockClassifyActivity.mockResolvedValue([]);
105
+
106
+ await connectMcpServer({
107
+ mcp_server_id: "mcp-tok",
108
+ execution_context_id: "ctx-tok",
109
+ execution_context_token: "scoped-ec-token",
110
+ });
111
+
112
+ expect(mockDiscoverActivity).toHaveBeenCalledWith({
113
+ mcpServerId: "mcp-tok",
114
+ executionContextId: "ctx-tok",
115
+ executionContextToken: "scoped-ec-token",
91
116
  invokerIdentityAccountId: null,
92
117
  });
93
118
  });
@@ -201,6 +201,7 @@ export async function connectMcpServer(
201
201
  const discovery = await discover.DiscoverMcpServerCapabilities({
202
202
  mcpServerId: input.mcp_server_id,
203
203
  executionContextId: input.execution_context_id ?? null,
204
+ executionContextToken: input.execution_context_token ?? null,
204
205
  invokerIdentityAccountId: input.invoker_identity_account_id ?? null,
205
206
  });
206
207
 
@@ -288,6 +289,7 @@ export async function discoverMcpServerLegacy(
288
289
  const discovery = await discover.DiscoverMcpServerCapabilities({
289
290
  mcpServerId: input.mcp_server_id,
290
291
  executionContextId: input.execution_context_id ?? null,
292
+ executionContextToken: input.execution_context_token ?? null,
291
293
  invokerIdentityAccountId: input.invoker_identity_account_id ?? null,
292
294
  });
293
295
 
@@ -19,6 +19,14 @@
19
19
  export interface ConnectMcpServerWorkflowInput {
20
20
  mcp_server_id: string;
21
21
  execution_context_id?: string | null;
22
+ /**
23
+ * Execution-scoped token for reading the connect ExecutionContext's
24
+ * decrypted credentials (oss#535). Populated by the OSS Go handler, whose
25
+ * EC read RPCs redact secrets for tokenless callers; absent on cloud,
26
+ * where the discovery activity's ambient connect_sandbox credential
27
+ * decrypts on its own.
28
+ */
29
+ execution_context_token?: string | null;
22
30
  invoker_identity_account_id?: string | null;
23
31
  }
24
32
 
@@ -1,30 +0,0 @@
1
- /**
2
- * Cursor-specific MCP config mapping.
3
- *
4
- * Converts harness-agnostic ResolvedMcpServer into the Cursor SDK's
5
- * McpServerConfig shape for Agent.create(). The resolution logic lives
6
- * in shared/mcp-resolver.ts; this module only handles the final transform.
7
- */
8
- import type { ResolvedMcpServer } from "../../shared/mcp-resolver.js";
9
- export type { ResolvedMcpServer, McpResolutionResult } from "../../shared/mcp-resolver.js";
10
- export { resolveMcpServers, extractMcpServerSlugs, PlaceholderResolutionError, } from "../../shared/mcp-resolver.js";
11
- /**
12
- * Cursor SDK MCP server config shape (matches @cursor/sdk McpServerConfig).
13
- * Defined here to avoid tight coupling to the SDK's internal types.
14
- */
15
- export type CursorMcpServerConfig = {
16
- type?: "stdio";
17
- command: string;
18
- args?: string[];
19
- env?: Record<string, string>;
20
- cwd?: string;
21
- } | {
22
- type?: "http" | "sse";
23
- url: string;
24
- headers?: Record<string, string>;
25
- };
26
- /**
27
- * Transform resolved MCP servers into the Cursor SDK's mcpServers config
28
- * for Agent.create().
29
- */
30
- export declare function toCursorMcpConfig(servers: ResolvedMcpServer[]): Record<string, CursorMcpServerConfig>;