@stigmer/runner 3.12.4 → 3.12.6

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 (180) hide show
  1. package/dist/.build-fingerprint +1 -1
  2. package/dist/activities/call-agent.js +19 -6
  3. package/dist/activities/call-agent.js.map +1 -1
  4. package/dist/activities/execute-cursor/cursor-mcp-config.d.ts +45 -0
  5. package/dist/activities/execute-cursor/cursor-mcp-config.js +76 -0
  6. package/dist/activities/execute-cursor/cursor-mcp-config.js.map +1 -0
  7. package/dist/activities/execute-cursor/error-classifier.d.ts +9 -0
  8. package/dist/activities/execute-cursor/error-classifier.js +30 -1
  9. package/dist/activities/execute-cursor/error-classifier.js.map +1 -1
  10. package/dist/activities/execute-cursor/index.d.ts +10 -1
  11. package/dist/activities/execute-cursor/index.js +62 -36
  12. package/dist/activities/execute-cursor/index.js.map +1 -1
  13. package/dist/activities/execute-cursor/prompt-builder.d.ts +13 -2
  14. package/dist/activities/execute-cursor/prompt-builder.js +11 -0
  15. package/dist/activities/execute-cursor/prompt-builder.js.map +1 -1
  16. package/dist/activities/execute-cursor/service-tier.d.ts +38 -29
  17. package/dist/activities/execute-cursor/service-tier.js +92 -63
  18. package/dist/activities/execute-cursor/service-tier.js.map +1 -1
  19. package/dist/activities/execute-cursor/session-lifecycle.d.ts +1 -1
  20. package/dist/activities/execute-cursor/skill-resolver.d.ts +2 -21
  21. package/dist/activities/execute-cursor/skill-resolver.js +5 -108
  22. package/dist/activities/execute-cursor/skill-resolver.js.map +1 -1
  23. package/dist/activities/execute-cursor/usage-accumulator.d.ts +16 -2
  24. package/dist/activities/execute-cursor/usage-accumulator.js +12 -2
  25. package/dist/activities/execute-cursor/usage-accumulator.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 +17 -16
  38. package/dist/activities/execute-deep-agent/setup.js.map +1 -1
  39. package/dist/activities/execute-deep-agent/subagent-transformer.js +17 -11
  40. package/dist/activities/execute-deep-agent/subagent-transformer.js.map +1 -1
  41. package/dist/activities/execute-deep-agent/subagent-wiring.d.ts +3 -3
  42. package/dist/index.d.ts +1 -1
  43. package/dist/index.js +1 -1
  44. package/dist/middleware/path-normalization.d.ts +49 -49
  45. package/dist/middleware/path-normalization.js +73 -84
  46. package/dist/middleware/path-normalization.js.map +1 -1
  47. package/dist/middleware/types.d.ts +3 -3
  48. package/dist/runner-manager.d.ts +1 -1
  49. package/dist/runner-manager.js +22 -15
  50. package/dist/runner-manager.js.map +1 -1
  51. package/dist/runner.js +11 -0
  52. package/dist/runner.js.map +1 -1
  53. package/dist/shared/args-preview.d.ts +8 -0
  54. package/dist/shared/args-preview.js +14 -3
  55. package/dist/shared/args-preview.js.map +1 -1
  56. package/dist/shared/artifact-storage.d.ts +11 -1
  57. package/dist/shared/artifact-storage.js +10 -1
  58. package/dist/shared/artifact-storage.js.map +1 -1
  59. package/dist/shared/caller-identity.d.ts +10 -7
  60. package/dist/shared/caller-identity.js +10 -7
  61. package/dist/shared/caller-identity.js.map +1 -1
  62. package/dist/shared/declared-preferences.d.ts +47 -0
  63. package/dist/shared/declared-preferences.js +64 -0
  64. package/dist/shared/declared-preferences.js.map +1 -0
  65. package/dist/shared/mcp-enabled-tools.d.ts +2 -3
  66. package/dist/shared/mcp-enabled-tools.js +2 -3
  67. package/dist/shared/mcp-enabled-tools.js.map +1 -1
  68. package/dist/shared/mcp-resolver.d.ts +20 -15
  69. package/dist/shared/mcp-resolver.js +11 -12
  70. package/dist/shared/mcp-resolver.js.map +1 -1
  71. package/dist/shared/placeholder-resolver.d.ts +9 -2
  72. package/dist/shared/placeholder-resolver.js +9 -2
  73. package/dist/shared/placeholder-resolver.js.map +1 -1
  74. package/dist/shared/plan-mode-permissions.d.ts +26 -46
  75. package/dist/shared/plan-mode-permissions.js +27 -55
  76. package/dist/shared/plan-mode-permissions.js.map +1 -1
  77. package/dist/shared/skill-mount.d.ts +89 -0
  78. package/dist/shared/skill-mount.js +142 -0
  79. package/dist/shared/skill-mount.js.map +1 -0
  80. package/dist/shared/skill-writer.d.ts +28 -26
  81. package/dist/shared/skill-writer.js +79 -102
  82. package/dist/shared/skill-writer.js.map +1 -1
  83. package/dist/shared/thinking-mode.d.ts +35 -0
  84. package/dist/shared/thinking-mode.js +43 -0
  85. package/dist/shared/thinking-mode.js.map +1 -0
  86. package/dist/shared/worker-shutdown.d.ts +67 -0
  87. package/dist/shared/worker-shutdown.js +79 -0
  88. package/dist/shared/worker-shutdown.js.map +1 -0
  89. package/dist/shared/workspace/types.d.ts +3 -2
  90. package/dist/workflow-engine/loader.js +82 -6
  91. package/dist/workflow-engine/loader.js.map +1 -1
  92. package/dist/workflow-engine/tasks/human-input.d.ts +2 -1
  93. package/dist/workflow-engine/tasks/human-input.js +31 -3
  94. package/dist/workflow-engine/tasks/human-input.js.map +1 -1
  95. package/dist/workflow-engine/types.d.ts +17 -3
  96. package/dist/workflow-engine/types.js.map +1 -1
  97. package/dist/workflows/human-input-orchestrator.d.ts +4 -0
  98. package/dist/workflows/human-input-orchestrator.js +13 -0
  99. package/dist/workflows/human-input-orchestrator.js.map +1 -1
  100. package/package.json +15 -11
  101. package/src/activities/call-agent.ts +20 -6
  102. package/src/activities/execute-cursor/__tests__/build-prompt.test.ts +50 -0
  103. package/src/activities/execute-cursor/__tests__/cursor-mcp-config.test.ts +111 -0
  104. package/src/activities/execute-cursor/__tests__/error-classifier-billing.test.ts +67 -0
  105. package/src/activities/execute-cursor/__tests__/service-tier.test.ts +67 -7
  106. package/src/activities/execute-cursor/__tests__/skill-resolver.test.ts +3 -94
  107. package/src/activities/execute-cursor/__tests__/usage-accumulator.test.ts +33 -1
  108. package/src/activities/execute-cursor/cursor-mcp-config.ts +107 -0
  109. package/src/activities/execute-cursor/error-classifier.ts +42 -1
  110. package/src/activities/execute-cursor/index.ts +78 -41
  111. package/src/activities/execute-cursor/prompt-builder.ts +29 -2
  112. package/src/activities/execute-cursor/service-tier.ts +94 -63
  113. package/src/activities/execute-cursor/session-lifecycle.ts +1 -1
  114. package/src/activities/execute-cursor/skill-resolver.ts +10 -137
  115. package/src/activities/execute-cursor/usage-accumulator.ts +11 -1
  116. package/src/activities/execute-deep-agent/__tests__/approval-file-change.test.ts +15 -0
  117. package/src/activities/execute-deep-agent/__tests__/cas-capture-backend.test.ts +75 -0
  118. package/src/activities/execute-deep-agent/__tests__/hitl-reject.test.ts +1 -0
  119. package/src/activities/execute-deep-agent/__tests__/hitl-resume-approve-all.test.ts +1 -0
  120. package/src/activities/execute-deep-agent/__tests__/hitl-resume-history.test.ts +1 -0
  121. package/src/activities/execute-deep-agent/__tests__/index.test.ts +1 -0
  122. package/src/activities/execute-deep-agent/__tests__/plan-mode-path-normalization.test.ts +42 -29
  123. package/src/activities/execute-deep-agent/__tests__/prompt-builder.test.ts +52 -0
  124. package/src/activities/execute-deep-agent/__tests__/sequential-gate-resume.test.ts +1 -0
  125. package/src/activities/execute-deep-agent/__tests__/subagent-plan-mode-permissions.test.ts +11 -8
  126. package/src/activities/execute-deep-agent/approval-file-change.ts +11 -1
  127. package/src/activities/execute-deep-agent/cas-capture-backend.ts +20 -3
  128. package/src/activities/execute-deep-agent/index.ts +57 -5
  129. package/src/activities/execute-deep-agent/prompt-builder.ts +22 -0
  130. package/src/activities/execute-deep-agent/setup.ts +21 -19
  131. package/src/activities/execute-deep-agent/subagent-transformer.ts +19 -12
  132. package/src/activities/execute-deep-agent/subagent-wiring.ts +3 -3
  133. package/src/index.ts +1 -1
  134. package/src/middleware/__tests__/path-normalization.test.ts +32 -32
  135. package/src/middleware/path-normalization.ts +78 -90
  136. package/src/middleware/types.ts +3 -3
  137. package/src/runner-manager.ts +26 -16
  138. package/src/runner.ts +14 -0
  139. package/src/shared/__tests__/artifact-storage.test.ts +28 -0
  140. package/src/shared/__tests__/bedrock-seam.test.ts +5 -5
  141. package/src/shared/__tests__/declared-preferences.test.ts +109 -0
  142. package/src/shared/__tests__/foundry-seam.test.ts +5 -5
  143. package/src/shared/__tests__/plan-mode-permissions.test.ts +15 -44
  144. package/src/shared/__tests__/skill-mount.test.ts +238 -0
  145. package/src/shared/__tests__/skill-writer.test.ts +151 -128
  146. package/src/shared/__tests__/vertex-adapter.test.ts +30 -3
  147. package/src/shared/__tests__/vertex-seam.test.ts +5 -5
  148. package/src/shared/__tests__/worker-shutdown.test.ts +101 -0
  149. package/src/shared/args-preview.ts +17 -3
  150. package/src/shared/artifact-storage.ts +20 -1
  151. package/src/shared/caller-identity.ts +10 -7
  152. package/src/shared/declared-preferences.ts +84 -0
  153. package/src/shared/mcp-enabled-tools.ts +2 -3
  154. package/src/shared/mcp-resolver.ts +20 -20
  155. package/src/shared/placeholder-resolver.ts +9 -2
  156. package/src/shared/plan-mode-permissions.ts +27 -58
  157. package/src/shared/skill-mount.ts +179 -0
  158. package/src/shared/skill-writer.ts +96 -130
  159. package/src/shared/thinking-mode.ts +52 -0
  160. package/src/shared/worker-shutdown.ts +99 -0
  161. package/src/shared/workspace/types.ts +3 -2
  162. package/src/workflow-engine/__tests__/loader.test.ts +95 -0
  163. package/src/workflow-engine/__tests__/tasks/human-input.test.ts +156 -0
  164. package/src/workflow-engine/loader.ts +97 -6
  165. package/src/workflow-engine/tasks/human-input.ts +41 -6
  166. package/src/workflow-engine/types.ts +18 -3
  167. package/src/workflows/human-input-orchestrator.ts +20 -2
  168. package/dist/activities/execute-cursor/connect-backfill.d.ts +0 -19
  169. package/dist/activities/execute-cursor/connect-backfill.js +0 -27
  170. package/dist/activities/execute-cursor/connect-backfill.js.map +0 -1
  171. package/dist/activities/execute-cursor/mcp-resolver.d.ts +0 -120
  172. package/dist/activities/execute-cursor/mcp-resolver.js +0 -194
  173. package/dist/activities/execute-cursor/mcp-resolver.js.map +0 -1
  174. package/dist/activities/execute-cursor/placeholder-resolver.d.ts +0 -34
  175. package/dist/activities/execute-cursor/placeholder-resolver.js +0 -82
  176. package/dist/activities/execute-cursor/placeholder-resolver.js.map +0 -1
  177. package/src/activities/execute-cursor/__tests__/mcp-resolver.test.ts +0 -125
  178. package/src/activities/execute-cursor/connect-backfill.ts +0 -52
  179. package/src/activities/execute-cursor/mcp-resolver.ts +0 -324
  180. package/src/activities/execute-cursor/placeholder-resolver.ts +0 -109
@@ -104,3 +104,70 @@ describe("error-classifier billing category", () => {
104
104
  expect(result.category).toBe("billing");
105
105
  });
106
106
  });
107
+
108
+ describe("D4 platform attribution of billing errors (proxy mode)", () => {
109
+ // The exact message the 2026-08-15 pool-drain incident put in front of a
110
+ // customer: Cursor's team-usage-limit prose relayed raw, telling them to
111
+ // "reach out to an admin" of a Cursor team they cannot see.
112
+ const CURSOR_USAGE_LIMIT_MESSAGE =
113
+ "Your team has reached its usage limit. Please reach out to an admin to " +
114
+ "enable on-demand usage, or return on 8/20/2026 when your limit resets.";
115
+
116
+ beforeEach(() => {
117
+ vi.spyOn(console, "log").mockImplementation(() => {});
118
+ });
119
+ afterEach(() => {
120
+ vi.restoreAllMocks();
121
+ });
122
+
123
+ it("rewords managed-key billing errors with platform attribution", () => {
124
+ const result = synthesizeError({
125
+ ...base(),
126
+ streamErrorMessage: CURSOR_USAGE_LIMIT_MESSAGE,
127
+ proxyMode: true,
128
+ });
129
+ expect(result.category).toBe("billing");
130
+ expect(result.retryable).toBe(false);
131
+ expect(result.message).toContain("Stigmer platform");
132
+ expect(result.message).toContain("credits were not charged");
133
+ expect(result.message).toContain("STIGMER_PLATFORM_MODEL_CAPACITY");
134
+ // Cursor's limit-reset date must survive, quoted, not erased.
135
+ expect(result.message).toContain("return on 8/20/2026");
136
+ });
137
+
138
+ it("leaves BYO-key (direct mode) billing errors untouched", () => {
139
+ // A self-hoster's drained personal account: the raw message IS the
140
+ // actionable one — never hide it behind platform attribution.
141
+ const result = synthesizeError({
142
+ ...base(),
143
+ streamErrorMessage: CURSOR_USAGE_LIMIT_MESSAGE,
144
+ proxyMode: false,
145
+ });
146
+ expect(result.category).toBe("billing");
147
+ expect(result.message).toBe(CURSOR_USAGE_LIMIT_MESSAGE);
148
+ expect(result.message).not.toContain("STIGMER_PLATFORM_MODEL_CAPACITY");
149
+ });
150
+
151
+ it("does not double-wrap a message the proxy already rewrote", () => {
152
+ const proxyRewritten =
153
+ "The Stigmer platform's Cursor capacity is temporarily exhausted. " +
154
+ "[code: STIGMER_PLATFORM_MODEL_CAPACITY]";
155
+ const result = synthesizeError({
156
+ ...base(),
157
+ streamErrorMessage: proxyRewritten,
158
+ proxyMode: true,
159
+ });
160
+ expect(result.category).toBe("billing");
161
+ expect(result.message).toBe(proxyRewritten);
162
+ });
163
+
164
+ it("never rewords non-billing categories in proxy mode", () => {
165
+ const result = synthesizeError({
166
+ ...base(),
167
+ streamErrorMessage: "rate limit exceeded, retry after 2s",
168
+ proxyMode: true,
169
+ });
170
+ expect(result.category).toBe("rate-limit");
171
+ expect(result.message).not.toContain("Stigmer platform");
172
+ });
173
+ });
@@ -1,11 +1,12 @@
1
1
  import { describe, it, expect, vi, beforeEach } from "vitest";
2
- import { ServiceTier } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/enum_pb";
2
+ import { ServiceTier, ThinkingMode } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/enum_pb";
3
3
 
4
4
  /**
5
- * Verifies the service-tier → Cursor variant-parameter translation (#357):
6
- * the runner must always send an explicit selection whose price-bearing
7
- * parameters are a deterministic function of the requested tier — never
8
- * the catalog's (account-influenced) default variant.
5
+ * Verifies the variant-attribute → Cursor variant-parameter translation
6
+ * (#357 service tier, #772 thinking mode): the runner must always send an
7
+ * explicit selection whose user-selectable parameters are a deterministic
8
+ * function of the requested attributes — never the catalog's
9
+ * (account-influenced) default variant.
9
10
  *
10
11
  * Catalog fixtures mirror the real shapes observed 2026-08-06: composer
11
12
  * has a `fast` bool (default fast=true), haiku has a `thinking` bool
@@ -23,6 +24,7 @@ import {
23
24
  resetCatalogCacheForTests,
24
25
  } from "../service-tier.js";
25
26
  import { resolveEffectiveServiceTier } from "../../../shared/service-tier.js";
27
+ import { resolveEffectiveThinkingMode } from "../../../shared/thinking-mode.js";
26
28
 
27
29
  const CATALOG = [
28
30
  {
@@ -66,8 +68,12 @@ const CATALOG = [
66
68
  },
67
69
  ];
68
70
 
69
- function opts(modelId: string, tier: ServiceTier.STANDARD | ServiceTier.FAST) {
70
- return { apiKey: "key-1", modelId, tier, executionId: "aex_test" };
71
+ function opts(
72
+ modelId: string,
73
+ tier: ServiceTier.STANDARD | ServiceTier.FAST,
74
+ thinking: ThinkingMode.DISABLED | ThinkingMode.ENABLED = ThinkingMode.DISABLED,
75
+ ) {
76
+ return { apiKey: "key-1", modelId, tier, thinking, executionId: "aex_test" };
71
77
  }
72
78
 
73
79
  beforeEach(() => {
@@ -88,6 +94,18 @@ describe("resolveEffectiveServiceTier", () => {
88
94
  });
89
95
  });
90
96
 
97
+ describe("resolveEffectiveThinkingMode", () => {
98
+ it("resolves UNSPECIFIED to DISABLED — never the account default", () => {
99
+ expect(resolveEffectiveThinkingMode(ThinkingMode.UNSPECIFIED)).toBe(ThinkingMode.DISABLED);
100
+ expect(resolveEffectiveThinkingMode(undefined)).toBe(ThinkingMode.DISABLED);
101
+ });
102
+
103
+ it("preserves explicit DISABLED and ENABLED", () => {
104
+ expect(resolveEffectiveThinkingMode(ThinkingMode.DISABLED)).toBe(ThinkingMode.DISABLED);
105
+ expect(resolveEffectiveThinkingMode(ThinkingMode.ENABLED)).toBe(ThinkingMode.ENABLED);
106
+ });
107
+ });
108
+
91
109
  describe("resolveServiceTierParams", () => {
92
110
  it("STANDARD pins fast=false on a fast-capable model", async () => {
93
111
  const params = await resolveServiceTierParams(opts("composer-2.5", ServiceTier.STANDARD));
@@ -123,6 +141,48 @@ describe("resolveServiceTierParams", () => {
123
141
  ]);
124
142
  });
125
143
 
144
+ it("ENABLED pins thinking=true on a thinking-capable model (#772)", async () => {
145
+ const params = await resolveServiceTierParams(
146
+ opts("claude-haiku-4-5", ServiceTier.STANDARD, ThinkingMode.ENABLED),
147
+ );
148
+ expect(params).toEqual([{ id: "thinking", value: "true" }]);
149
+ });
150
+
151
+ it("ENABLED on a model with no thinking parameter fails loudly, never a silent base variant", async () => {
152
+ await expect(
153
+ resolveServiceTierParams(opts("composer-2.5", ServiceTier.STANDARD, ThinkingMode.ENABLED)),
154
+ ).rejects.toThrow(/no "thinking" parameter/);
155
+ });
156
+
157
+ it("thinking combines freely with the fast tier — both pinned true, sorted", async () => {
158
+ const params = await resolveServiceTierParams(
159
+ opts("claude-opus-4-8", ServiceTier.FAST, ThinkingMode.ENABLED),
160
+ );
161
+ expect(params).toEqual([
162
+ { id: "fast", value: "true" },
163
+ { id: "thinking", value: "true" },
164
+ ]);
165
+ });
166
+
167
+ it("Auto + ENABLED is a loud failure (no variant dimensions to pin)", async () => {
168
+ await expect(
169
+ resolveServiceTierParams(opts("default", ServiceTier.STANDARD, ThinkingMode.ENABLED)),
170
+ ).rejects.toThrow(/requires a pinned model/);
171
+ });
172
+
173
+ it("unknown model + ENABLED fails loudly, never degrades", async () => {
174
+ await expect(
175
+ resolveServiceTierParams(opts("not-a-model", ServiceTier.STANDARD, ThinkingMode.ENABLED)),
176
+ ).rejects.toThrow(/does not list that model/);
177
+ });
178
+
179
+ it("catalog fetch failure + ENABLED fails loudly, never degrades", async () => {
180
+ listMock.mockRejectedValue(new Error("proxy down"));
181
+ await expect(
182
+ resolveServiceTierParams(opts("claude-haiku-4-5", ServiceTier.STANDARD, ThinkingMode.ENABLED)),
183
+ ).rejects.toThrow(/catalog fetch failed/);
184
+ });
185
+
126
186
  it("resolves models referenced by alias", async () => {
127
187
  const params = await resolveServiceTierParams(opts("composer", ServiceTier.STANDARD));
128
188
  expect(params).toEqual([{ id: "fast", value: "false" }]);
@@ -3,7 +3,7 @@ import { mkdtempSync, readFileSync, existsSync, rmSync, writeFileSync } from "no
3
3
  import { join } from "node:path";
4
4
  import { tmpdir } from "node:os";
5
5
  import { ConnectError, Code } from "@connectrpc/connect";
6
- import { resolveSkills, downloadArtifact } from "../skill-resolver.js";
6
+ import { resolveSkills } from "../skill-resolver.js";
7
7
  import { buildZip } from "../../../__test-utils__/zip-fixtures.js";
8
8
 
9
9
  /** A server that predates the transfer lane (#675) answers the mint RPC
@@ -390,96 +390,5 @@ describe("resolveSkills — artifact extraction", () => {
390
390
  });
391
391
  });
392
392
 
393
- // ─── downloadArtifact transfer lane routing (#675) ─────────────────────
394
-
395
- describe("downloadArtifact — transfer lane routing", () => {
396
- afterEach(() => {
397
- vi.unstubAllGlobals();
398
- });
399
-
400
- function makeClient(overrides: Record<string, any> = {}) {
401
- return {
402
- getSkillArtifact: vi.fn().mockResolvedValue({ artifact: new Uint8Array(0) }),
403
- getSkillArtifactDownloadUrl: unimplementedMint(),
404
- ...overrides,
405
- } as any;
406
- }
407
-
408
- it("fetches bytes over HTTP when the server mints a download URL", async () => {
409
- const bytes = new Uint8Array([1, 2, 3, 4, 5]);
410
- const client = makeClient({
411
- getSkillArtifactDownloadUrl: vi.fn().mockResolvedValue({
412
- url: "http://localhost:7234/v1/skill-artifacts/skills/abc.zip",
413
- sizeBytes: 5n,
414
- ttlSeconds: 0,
415
- }),
416
- });
417
- const fetchMock = vi.fn().mockResolvedValue({
418
- ok: true,
419
- arrayBuffer: async () => bytes.buffer,
420
- });
421
- vi.stubGlobal("fetch", fetchMock);
422
-
423
- const got = await downloadArtifact(client, "skills/abc.zip");
424
-
425
- expect(got).toEqual(bytes);
426
- expect(fetchMock).toHaveBeenCalledWith("http://localhost:7234/v1/skill-artifacts/skills/abc.zip");
427
- // The unary lane (10MB-capped) must not be touched when the URL lane works.
428
- expect(client.getSkillArtifact).not.toHaveBeenCalled();
429
- });
430
-
431
- it("falls back to the unary RPC when the server predates the lane", async () => {
432
- const bytes = new Uint8Array([9, 9]);
433
- const client = makeClient({
434
- getSkillArtifact: vi.fn().mockResolvedValue({ artifact: bytes }),
435
- });
436
- vi.stubGlobal("fetch", vi.fn()); // must never be called
437
-
438
- const got = await downloadArtifact(client, "skills/abc.zip");
439
-
440
- expect(got).toEqual(bytes);
441
- expect(client.getSkillArtifact).toHaveBeenCalledWith("skills/abc.zip");
442
- expect(fetch).not.toHaveBeenCalled();
443
- });
444
-
445
- it("does NOT fall back on non-Unimplemented mint failures", async () => {
446
- const client = makeClient({
447
- getSkillArtifactDownloadUrl: vi.fn().mockRejectedValue(
448
- new ConnectError("boom", Code.Internal),
449
- ),
450
- });
451
-
452
- await expect(downloadArtifact(client, "skills/abc.zip")).rejects.toThrow("boom");
453
- // Falling back here would mask real server faults behind the capped lane.
454
- expect(client.getSkillArtifact).not.toHaveBeenCalled();
455
- });
456
-
457
- it("rejects truncated fetches via the minted size", async () => {
458
- const client = makeClient({
459
- getSkillArtifactDownloadUrl: vi.fn().mockResolvedValue({
460
- url: "http://localhost:7234/v1/skill-artifacts/skills/abc.zip",
461
- sizeBytes: 100n,
462
- ttlSeconds: 0,
463
- }),
464
- });
465
- vi.stubGlobal("fetch", vi.fn().mockResolvedValue({
466
- ok: true,
467
- arrayBuffer: async () => new Uint8Array([1, 2, 3]).buffer,
468
- }));
469
-
470
- await expect(downloadArtifact(client, "skills/abc.zip")).rejects.toThrow(/truncated/);
471
- });
472
-
473
- it("surfaces HTTP failures with the status code", async () => {
474
- const client = makeClient({
475
- getSkillArtifactDownloadUrl: vi.fn().mockResolvedValue({
476
- url: "http://localhost:7234/v1/skill-artifacts/skills/gone.zip",
477
- sizeBytes: 0n,
478
- ttlSeconds: 0,
479
- }),
480
- });
481
- vi.stubGlobal("fetch", vi.fn().mockResolvedValue({ ok: false, status: 404 }));
482
-
483
- await expect(downloadArtifact(client, "skills/gone.zip")).rejects.toThrow(/HTTP 404/);
484
- });
485
- });
393
+ // downloadArtifact's transfer-lane routing tests moved with the code to
394
+ // shared/__tests__/skill-mount.test.ts (issue #337 extraction).
@@ -1,5 +1,5 @@
1
1
  import { describe, it, expect, vi, beforeAll } from "vitest";
2
- import { ServiceTier } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/enum_pb";
2
+ import { ServiceTier, ThinkingMode } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/enum_pb";
3
3
 
4
4
  import { UsageAccumulator } from "../usage-accumulator.js";
5
5
 
@@ -129,4 +129,36 @@ describe("UsageAccumulator", () => {
129
129
  expect(unspecified.snapshot().estimatedCostUsd)
130
130
  .toBe(standard.snapshot().estimatedCostUsd);
131
131
  });
132
+
133
+ it("records the requested thinking mode into the snapshot (#772 audit trail)", () => {
134
+ const acc = new UsageAccumulator(
135
+ "claude-haiku-4-5",
136
+ ServiceTier.STANDARD,
137
+ [{ id: "thinking", value: "true" }],
138
+ ThinkingMode.ENABLED,
139
+ );
140
+ acc.addTurn({ inputTokens: 10, outputTokens: 5 });
141
+ const snap = acc.snapshot();
142
+ expect(snap.requestedThinkingMode).toBe(ThinkingMode.ENABLED);
143
+ expect(snap.requestedModelParams).toBe('[{"id":"thinking","value":"true"}]');
144
+ });
145
+
146
+ it("thinking never changes the estimate — per-token price-neutral (#772)", () => {
147
+ // Ledger-verified 2026-08-15: thinking wire ids bill exactly base
148
+ // per-token rates; the extra cost of thinking is more output tokens,
149
+ // which the accumulator already counts as they arrive.
150
+ const turn = { inputTokens: 1_000_000, outputTokens: 1_000_000 };
151
+
152
+ const disabled = new UsageAccumulator(
153
+ "composer-2.5", ServiceTier.STANDARD, [], ThinkingMode.DISABLED,
154
+ );
155
+ disabled.addTurn(turn);
156
+ const enabled = new UsageAccumulator(
157
+ "composer-2.5", ServiceTier.STANDARD, [], ThinkingMode.ENABLED,
158
+ );
159
+ enabled.addTurn(turn);
160
+
161
+ expect(enabled.snapshot().estimatedCostUsd)
162
+ .toBe(disabled.snapshot().estimatedCostUsd);
163
+ });
132
164
  });
@@ -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
+ }
@@ -21,6 +21,7 @@
21
21
  */
22
22
 
23
23
  import type { CapturedRejection } from "./rejection-capture.js";
24
+ import { PLATFORM_CAPACITY_SENTINEL } from "../../shared/model-error.js";
24
25
 
25
26
  export type ErrorCategory =
26
27
  | "auth"
@@ -212,6 +213,15 @@ interface SynthesizeErrorOpts {
212
213
  durationMs?: number;
213
214
  /** Number of messages received from the stream (0 = no response at all). */
214
215
  messageCount?: number;
216
+ /**
217
+ * True when the execution key is platform-managed (the run rides the
218
+ * Stigmer proxy). Enables the D4 attribution of the platform provider
219
+ * error contract (see shared/model-error.ts): billing errors on a
220
+ * platform key must never tell the customer to fix an account they do
221
+ * not own. BYO-key runs leave this false — there the raw Cursor message
222
+ * IS the actionable one (it is the user's own account).
223
+ */
224
+ proxyMode?: boolean;
215
225
  }
216
226
 
217
227
  /**
@@ -244,7 +254,38 @@ export function synthesizeError(opts: SynthesizeErrorOpts): ClassifiedError {
244
254
  return { ...classified, category: "agent-stale", retryable: true };
245
255
  }
246
256
 
247
- return classified;
257
+ return attributePlatformBilling(classified, opts.proxyMode === true);
258
+ }
259
+
260
+ /**
261
+ * D4 attribution (platform provider error contract, Cursor surface): a
262
+ * billing error on a platform-managed key is the PLATFORM's fault — the
263
+ * customer's org credits are fine, and Cursor's raw prose ("reach out to
264
+ * an admin to enable on-demand usage") points at a Cursor dashboard they
265
+ * do not own. Reword with platform attribution, quoting the original so
266
+ * Cursor's limit-reset date survives.
267
+ *
268
+ * <p>Two cases pass through untouched: messages already carrying the
269
+ * sentinel (the proxy's end-stream rewrite landed — this is the runner-side
270
+ * fallback for the message-bearing in-stream error path the proxy relays
271
+ * verbatim), and BYO-key runs (the raw message is about the user's own
272
+ * account and must never be hidden).
273
+ */
274
+ function attributePlatformBilling(
275
+ classified: ClassifiedError,
276
+ proxyMode: boolean,
277
+ ): ClassifiedError {
278
+ if (classified.category !== "billing" || !proxyMode) return classified;
279
+ if (classified.message.includes(PLATFORM_CAPACITY_SENTINEL)) return classified;
280
+ return {
281
+ ...classified,
282
+ message:
283
+ `The Stigmer platform's Cursor capacity is temporarily exhausted. ` +
284
+ `This is a platform-side issue - your organization's credits were not ` +
285
+ `charged for this call. Ask your platform operator to restock Cursor ` +
286
+ `execution keys. Provider message: "${classified.message}" ` +
287
+ `[code: ${PLATFORM_CAPACITY_SENTINEL}]`,
288
+ };
248
289
  }
249
290
 
250
291
  /**