@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.
- package/dist/.build-fingerprint +1 -1
- package/dist/activities/call-agent.js +19 -6
- package/dist/activities/call-agent.js.map +1 -1
- package/dist/activities/execute-cursor/cursor-mcp-config.d.ts +45 -0
- package/dist/activities/execute-cursor/cursor-mcp-config.js +76 -0
- package/dist/activities/execute-cursor/cursor-mcp-config.js.map +1 -0
- package/dist/activities/execute-cursor/error-classifier.d.ts +9 -0
- package/dist/activities/execute-cursor/error-classifier.js +30 -1
- package/dist/activities/execute-cursor/error-classifier.js.map +1 -1
- package/dist/activities/execute-cursor/index.d.ts +10 -1
- package/dist/activities/execute-cursor/index.js +62 -36
- package/dist/activities/execute-cursor/index.js.map +1 -1
- package/dist/activities/execute-cursor/prompt-builder.d.ts +13 -2
- package/dist/activities/execute-cursor/prompt-builder.js +11 -0
- package/dist/activities/execute-cursor/prompt-builder.js.map +1 -1
- package/dist/activities/execute-cursor/service-tier.d.ts +38 -29
- package/dist/activities/execute-cursor/service-tier.js +92 -63
- package/dist/activities/execute-cursor/service-tier.js.map +1 -1
- package/dist/activities/execute-cursor/session-lifecycle.d.ts +1 -1
- package/dist/activities/execute-cursor/skill-resolver.d.ts +2 -21
- package/dist/activities/execute-cursor/skill-resolver.js +5 -108
- package/dist/activities/execute-cursor/skill-resolver.js.map +1 -1
- package/dist/activities/execute-cursor/usage-accumulator.d.ts +16 -2
- package/dist/activities/execute-cursor/usage-accumulator.js +12 -2
- package/dist/activities/execute-cursor/usage-accumulator.js.map +1 -1
- package/dist/activities/execute-deep-agent/approval-file-change.d.ts +9 -0
- package/dist/activities/execute-deep-agent/approval-file-change.js +2 -1
- package/dist/activities/execute-deep-agent/approval-file-change.js.map +1 -1
- package/dist/activities/execute-deep-agent/cas-capture-backend.d.ts +18 -1
- package/dist/activities/execute-deep-agent/cas-capture-backend.js +20 -3
- package/dist/activities/execute-deep-agent/cas-capture-backend.js.map +1 -1
- package/dist/activities/execute-deep-agent/index.js +57 -5
- package/dist/activities/execute-deep-agent/index.js.map +1 -1
- package/dist/activities/execute-deep-agent/prompt-builder.d.ts +9 -0
- package/dist/activities/execute-deep-agent/prompt-builder.js +10 -0
- package/dist/activities/execute-deep-agent/prompt-builder.js.map +1 -1
- package/dist/activities/execute-deep-agent/setup.js +17 -16
- package/dist/activities/execute-deep-agent/setup.js.map +1 -1
- package/dist/activities/execute-deep-agent/subagent-transformer.js +17 -11
- package/dist/activities/execute-deep-agent/subagent-transformer.js.map +1 -1
- package/dist/activities/execute-deep-agent/subagent-wiring.d.ts +3 -3
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/middleware/path-normalization.d.ts +49 -49
- package/dist/middleware/path-normalization.js +73 -84
- package/dist/middleware/path-normalization.js.map +1 -1
- package/dist/middleware/types.d.ts +3 -3
- package/dist/runner-manager.d.ts +1 -1
- package/dist/runner-manager.js +22 -15
- package/dist/runner-manager.js.map +1 -1
- package/dist/runner.js +11 -0
- package/dist/runner.js.map +1 -1
- package/dist/shared/args-preview.d.ts +8 -0
- package/dist/shared/args-preview.js +14 -3
- package/dist/shared/args-preview.js.map +1 -1
- package/dist/shared/artifact-storage.d.ts +11 -1
- package/dist/shared/artifact-storage.js +10 -1
- package/dist/shared/artifact-storage.js.map +1 -1
- package/dist/shared/caller-identity.d.ts +10 -7
- package/dist/shared/caller-identity.js +10 -7
- package/dist/shared/caller-identity.js.map +1 -1
- package/dist/shared/declared-preferences.d.ts +47 -0
- package/dist/shared/declared-preferences.js +64 -0
- package/dist/shared/declared-preferences.js.map +1 -0
- package/dist/shared/mcp-enabled-tools.d.ts +2 -3
- package/dist/shared/mcp-enabled-tools.js +2 -3
- package/dist/shared/mcp-enabled-tools.js.map +1 -1
- package/dist/shared/mcp-resolver.d.ts +20 -15
- package/dist/shared/mcp-resolver.js +11 -12
- package/dist/shared/mcp-resolver.js.map +1 -1
- package/dist/shared/placeholder-resolver.d.ts +9 -2
- package/dist/shared/placeholder-resolver.js +9 -2
- package/dist/shared/placeholder-resolver.js.map +1 -1
- package/dist/shared/plan-mode-permissions.d.ts +26 -46
- package/dist/shared/plan-mode-permissions.js +27 -55
- package/dist/shared/plan-mode-permissions.js.map +1 -1
- package/dist/shared/skill-mount.d.ts +89 -0
- package/dist/shared/skill-mount.js +142 -0
- package/dist/shared/skill-mount.js.map +1 -0
- package/dist/shared/skill-writer.d.ts +28 -26
- package/dist/shared/skill-writer.js +79 -102
- package/dist/shared/skill-writer.js.map +1 -1
- package/dist/shared/thinking-mode.d.ts +35 -0
- package/dist/shared/thinking-mode.js +43 -0
- package/dist/shared/thinking-mode.js.map +1 -0
- package/dist/shared/worker-shutdown.d.ts +67 -0
- package/dist/shared/worker-shutdown.js +79 -0
- package/dist/shared/worker-shutdown.js.map +1 -0
- package/dist/shared/workspace/types.d.ts +3 -2
- package/dist/workflow-engine/loader.js +82 -6
- package/dist/workflow-engine/loader.js.map +1 -1
- package/dist/workflow-engine/tasks/human-input.d.ts +2 -1
- package/dist/workflow-engine/tasks/human-input.js +31 -3
- package/dist/workflow-engine/tasks/human-input.js.map +1 -1
- package/dist/workflow-engine/types.d.ts +17 -3
- package/dist/workflow-engine/types.js.map +1 -1
- package/dist/workflows/human-input-orchestrator.d.ts +4 -0
- package/dist/workflows/human-input-orchestrator.js +13 -0
- package/dist/workflows/human-input-orchestrator.js.map +1 -1
- package/package.json +15 -11
- package/src/activities/call-agent.ts +20 -6
- package/src/activities/execute-cursor/__tests__/build-prompt.test.ts +50 -0
- package/src/activities/execute-cursor/__tests__/cursor-mcp-config.test.ts +111 -0
- package/src/activities/execute-cursor/__tests__/error-classifier-billing.test.ts +67 -0
- package/src/activities/execute-cursor/__tests__/service-tier.test.ts +67 -7
- package/src/activities/execute-cursor/__tests__/skill-resolver.test.ts +3 -94
- package/src/activities/execute-cursor/__tests__/usage-accumulator.test.ts +33 -1
- package/src/activities/execute-cursor/cursor-mcp-config.ts +107 -0
- package/src/activities/execute-cursor/error-classifier.ts +42 -1
- package/src/activities/execute-cursor/index.ts +78 -41
- package/src/activities/execute-cursor/prompt-builder.ts +29 -2
- package/src/activities/execute-cursor/service-tier.ts +94 -63
- package/src/activities/execute-cursor/session-lifecycle.ts +1 -1
- package/src/activities/execute-cursor/skill-resolver.ts +10 -137
- package/src/activities/execute-cursor/usage-accumulator.ts +11 -1
- package/src/activities/execute-deep-agent/__tests__/approval-file-change.test.ts +15 -0
- package/src/activities/execute-deep-agent/__tests__/cas-capture-backend.test.ts +75 -0
- package/src/activities/execute-deep-agent/__tests__/hitl-reject.test.ts +1 -0
- package/src/activities/execute-deep-agent/__tests__/hitl-resume-approve-all.test.ts +1 -0
- package/src/activities/execute-deep-agent/__tests__/hitl-resume-history.test.ts +1 -0
- package/src/activities/execute-deep-agent/__tests__/index.test.ts +1 -0
- package/src/activities/execute-deep-agent/__tests__/plan-mode-path-normalization.test.ts +42 -29
- package/src/activities/execute-deep-agent/__tests__/prompt-builder.test.ts +52 -0
- package/src/activities/execute-deep-agent/__tests__/sequential-gate-resume.test.ts +1 -0
- package/src/activities/execute-deep-agent/__tests__/subagent-plan-mode-permissions.test.ts +11 -8
- package/src/activities/execute-deep-agent/approval-file-change.ts +11 -1
- package/src/activities/execute-deep-agent/cas-capture-backend.ts +20 -3
- package/src/activities/execute-deep-agent/index.ts +57 -5
- package/src/activities/execute-deep-agent/prompt-builder.ts +22 -0
- package/src/activities/execute-deep-agent/setup.ts +21 -19
- package/src/activities/execute-deep-agent/subagent-transformer.ts +19 -12
- package/src/activities/execute-deep-agent/subagent-wiring.ts +3 -3
- package/src/index.ts +1 -1
- package/src/middleware/__tests__/path-normalization.test.ts +32 -32
- package/src/middleware/path-normalization.ts +78 -90
- package/src/middleware/types.ts +3 -3
- package/src/runner-manager.ts +26 -16
- package/src/runner.ts +14 -0
- package/src/shared/__tests__/artifact-storage.test.ts +28 -0
- package/src/shared/__tests__/bedrock-seam.test.ts +5 -5
- package/src/shared/__tests__/declared-preferences.test.ts +109 -0
- package/src/shared/__tests__/foundry-seam.test.ts +5 -5
- package/src/shared/__tests__/plan-mode-permissions.test.ts +15 -44
- package/src/shared/__tests__/skill-mount.test.ts +238 -0
- package/src/shared/__tests__/skill-writer.test.ts +151 -128
- package/src/shared/__tests__/vertex-adapter.test.ts +30 -3
- package/src/shared/__tests__/vertex-seam.test.ts +5 -5
- package/src/shared/__tests__/worker-shutdown.test.ts +101 -0
- package/src/shared/args-preview.ts +17 -3
- package/src/shared/artifact-storage.ts +20 -1
- package/src/shared/caller-identity.ts +10 -7
- package/src/shared/declared-preferences.ts +84 -0
- package/src/shared/mcp-enabled-tools.ts +2 -3
- package/src/shared/mcp-resolver.ts +20 -20
- package/src/shared/placeholder-resolver.ts +9 -2
- package/src/shared/plan-mode-permissions.ts +27 -58
- package/src/shared/skill-mount.ts +179 -0
- package/src/shared/skill-writer.ts +96 -130
- package/src/shared/thinking-mode.ts +52 -0
- package/src/shared/worker-shutdown.ts +99 -0
- package/src/shared/workspace/types.ts +3 -2
- package/src/workflow-engine/__tests__/loader.test.ts +95 -0
- package/src/workflow-engine/__tests__/tasks/human-input.test.ts +156 -0
- package/src/workflow-engine/loader.ts +97 -6
- package/src/workflow-engine/tasks/human-input.ts +41 -6
- package/src/workflow-engine/types.ts +18 -3
- package/src/workflows/human-input-orchestrator.ts +20 -2
- package/dist/activities/execute-cursor/connect-backfill.d.ts +0 -19
- package/dist/activities/execute-cursor/connect-backfill.js +0 -27
- package/dist/activities/execute-cursor/connect-backfill.js.map +0 -1
- package/dist/activities/execute-cursor/mcp-resolver.d.ts +0 -120
- package/dist/activities/execute-cursor/mcp-resolver.js +0 -194
- package/dist/activities/execute-cursor/mcp-resolver.js.map +0 -1
- package/dist/activities/execute-cursor/placeholder-resolver.d.ts +0 -34
- package/dist/activities/execute-cursor/placeholder-resolver.js +0 -82
- package/dist/activities/execute-cursor/placeholder-resolver.js.map +0 -1
- package/src/activities/execute-cursor/__tests__/mcp-resolver.test.ts +0 -125
- package/src/activities/execute-cursor/connect-backfill.ts +0 -52
- package/src/activities/execute-cursor/mcp-resolver.ts +0 -324
- 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
|
|
6
|
-
* the runner must always send an
|
|
7
|
-
* parameters are a deterministic
|
|
8
|
-
* the catalog's
|
|
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(
|
|
70
|
-
|
|
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
|
|
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
|
-
//
|
|
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
|
/**
|