@stigmer/runner 3.12.5 → 3.12.7
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-status.d.ts +14 -2
- package/dist/activities/call-agent-status.js +24 -7
- package/dist/activities/call-agent-status.js.map +1 -1
- package/dist/activities/call-agent.js +19 -6
- package/dist/activities/call-agent.js.map +1 -1
- 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 -0
- package/dist/activities/execute-cursor/index.js +20 -7
- package/dist/activities/execute-cursor/index.js.map +1 -1
- package/dist/activities/execute-cursor/prompt-builder.d.ts +12 -0
- 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/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/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 +2 -0
- package/dist/activities/execute-deep-agent/setup.js.map +1 -1
- package/dist/config.d.ts +10 -0
- package/dist/config.js +3 -0
- package/dist/config.js.map +1 -1
- package/dist/main.js +3 -0
- package/dist/main.js.map +1 -1
- package/dist/runner-manager.d.ts +2 -0
- package/dist/runner-manager.js +1 -0
- package/dist/runner-manager.js.map +1 -1
- package/dist/runner.d.ts +2 -0
- package/dist/runner.js +33 -6
- package/dist/runner.js.map +1 -1
- package/dist/shared/artifact-storage.js +7 -4
- 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/recalled-memories.d.ts +55 -0
- package/dist/shared/recalled-memories.js +70 -0
- package/dist/shared/recalled-memories.js.map +1 -0
- 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/workflow-engine/loader.js +52 -13
- 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 +8 -1
- 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/call-agent-orchestrator.d.ts +14 -2
- package/dist/workflows/call-agent-orchestrator.js +55 -18
- package/dist/workflows/call-agent-orchestrator.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 +2 -2
- package/src/__tests__/golden-e2e.test.ts +1 -1
- package/src/activities/__tests__/call-agent-status.test.ts +30 -5
- package/src/activities/__tests__/classify-tool-approvals.test.ts +1 -0
- package/src/activities/__tests__/discover-mcp-server.test.ts +1 -0
- package/src/activities/call-agent-status.ts +25 -8
- package/src/activities/call-agent.ts +20 -6
- package/src/activities/execute-cursor/__tests__/build-prompt.test.ts +79 -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__/usage-accumulator.test.ts +33 -1
- package/src/activities/execute-cursor/error-classifier.ts +42 -1
- package/src/activities/execute-cursor/index.ts +30 -6
- package/src/activities/execute-cursor/prompt-builder.ts +28 -0
- package/src/activities/execute-cursor/service-tier.ts +94 -63
- package/src/activities/execute-cursor/usage-accumulator.ts +11 -1
- 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__/prompt-builder.test.ts +48 -0
- package/src/activities/execute-deep-agent/__tests__/sequential-gate-resume.test.ts +1 -0
- package/src/activities/execute-deep-agent/prompt-builder.ts +22 -0
- package/src/activities/execute-deep-agent/setup.ts +4 -0
- package/src/config.ts +13 -0
- package/src/main.ts +3 -0
- package/src/runner-manager.ts +5 -0
- package/src/runner.ts +42 -6
- package/src/shared/__tests__/artifact-storage.test.ts +21 -0
- package/src/shared/__tests__/recalled-memories.test.ts +88 -0
- package/src/shared/artifact-storage.ts +9 -4
- package/src/shared/caller-identity.ts +10 -7
- package/src/shared/recalled-memories.ts +90 -0
- package/src/shared/thinking-mode.ts +52 -0
- package/src/workflow-engine/__tests__/loader.test.ts +47 -3
- package/src/workflow-engine/__tests__/tasks/human-input.test.ts +39 -0
- package/src/workflow-engine/loader.ts +63 -17
- package/src/workflow-engine/tasks/human-input.ts +8 -1
- package/src/workflow-engine/types.ts +18 -3
- package/src/workflows/call-agent-orchestrator.ts +60 -24
- package/src/workflows/human-input-orchestrator.ts +20 -2
|
@@ -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
|
/**
|
|
@@ -57,6 +57,7 @@ import {
|
|
|
57
57
|
} from "../../shared/caller-identity.js";
|
|
58
58
|
import { readSessionContext } from "../../shared/session-context.js";
|
|
59
59
|
import { readDeclaredPreferences } from "../../shared/declared-preferences.js";
|
|
60
|
+
import { readRecalledMemories } from "../../shared/recalled-memories.js";
|
|
60
61
|
import { withholdSecretContentFromMessages } from "../../shared/tool-row.js";
|
|
61
62
|
import { StallTimeoutError, formatStallFailure } from "../../shared/stall-watchdog.js";
|
|
62
63
|
import { resolveUsableArtifactStorage, loadArtifactStorageConfig, type ArtifactStorage } from "../../shared/artifact-storage.js";
|
|
@@ -138,6 +139,7 @@ import { setInterceptorExecutionId, runWithExecutionContext } from "./fetch-inte
|
|
|
138
139
|
import { closeProxySessions } from "./http2-interceptor.js";
|
|
139
140
|
import { resolveModelId, ensureLoaded as ensurePricingLoaded } from "./model-pricing.js";
|
|
140
141
|
import { resolveEffectiveServiceTier } from "../../shared/service-tier.js";
|
|
142
|
+
import { resolveEffectiveThinkingMode } from "../../shared/thinking-mode.js";
|
|
141
143
|
import { resolveServiceTierParams } from "./service-tier.js";
|
|
142
144
|
import { UsageAccumulator } from "./usage-accumulator.js";
|
|
143
145
|
import { StreamingUsageSummarySchema } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/usage_pb";
|
|
@@ -960,9 +962,10 @@ async function executeCursorInner(
|
|
|
960
962
|
await ensurePricingLoaded();
|
|
961
963
|
setupTiming.mark("load_pricing");
|
|
962
964
|
|
|
963
|
-
// Phase 6: Validate model selection and resolve the
|
|
964
|
-
// UNSPECIFIED → STANDARD
|
|
965
|
-
// upstream layer preserves the
|
|
965
|
+
// Phase 6: Validate model selection and resolve the variant attributes.
|
|
966
|
+
// UNSPECIFIED → STANDARD (#357) and UNSPECIFIED → DISABLED (#772)
|
|
967
|
+
// resolve here and nowhere else: every upstream layer preserves the
|
|
968
|
+
// caller's raw enum values.
|
|
966
969
|
const requestedModel = spec.executionConfig?.modelName || "default";
|
|
967
970
|
const validatedModel = resolveModelId(requestedModel);
|
|
968
971
|
if (validatedModel !== requestedModel) {
|
|
@@ -971,6 +974,7 @@ async function executeCursorInner(
|
|
|
971
974
|
);
|
|
972
975
|
}
|
|
973
976
|
const requestedServiceTier = resolveEffectiveServiceTier(spec.executionConfig?.serviceTier);
|
|
977
|
+
const requestedThinkingMode = resolveEffectiveThinkingMode(spec.executionConfig?.thinkingMode);
|
|
974
978
|
|
|
975
979
|
heartbeat();
|
|
976
980
|
|
|
@@ -1005,13 +1009,15 @@ async function executeCursorInner(
|
|
|
1005
1009
|
);
|
|
1006
1010
|
}
|
|
1007
1011
|
|
|
1008
|
-
// Translate the tier into the explicit variant params
|
|
1009
|
-
// create/resume. Never a bare { id }: the catalog's
|
|
1010
|
-
// account-influenced and picks the
|
|
1012
|
+
// Translate the tier + thinking mode into the explicit variant params
|
|
1013
|
+
// sent with every create/resume. Never a bare { id }: the catalog's
|
|
1014
|
+
// default variant is account-influenced and picks the served variant
|
|
1015
|
+
// (#357 fast pricing, #772 thinking).
|
|
1011
1016
|
const modelParams = await resolveServiceTierParams({
|
|
1012
1017
|
apiKey: effectiveApiKey,
|
|
1013
1018
|
modelId: validatedModel,
|
|
1014
1019
|
tier: requestedServiceTier,
|
|
1020
|
+
thinking: requestedThinkingMode,
|
|
1015
1021
|
executionId,
|
|
1016
1022
|
});
|
|
1017
1023
|
|
|
@@ -1174,6 +1180,7 @@ async function executeCursorInner(
|
|
|
1174
1180
|
senderIdentity: readSenderIdentity(blueprint.sessionSpec.metadata),
|
|
1175
1181
|
sessionContext: readSessionContext(blueprint.sessionSpec.metadata),
|
|
1176
1182
|
declaredPreferences: readDeclaredPreferences(spec.declaredPreferences),
|
|
1183
|
+
recalledMemories: readRecalledMemories(spec.recalledMemories),
|
|
1177
1184
|
conversationCatchup: readConversationCatchup(spec.conversationCatchup),
|
|
1178
1185
|
// The turn's recorded transcript, seeded from the persisted execution
|
|
1179
1186
|
// on a reinvocation (Phase 3). Consumed only by the HITL-recovery
|
|
@@ -1228,6 +1235,7 @@ async function executeCursorInner(
|
|
|
1228
1235
|
validatedModel,
|
|
1229
1236
|
requestedServiceTier,
|
|
1230
1237
|
modelParams,
|
|
1238
|
+
requestedThinkingMode,
|
|
1231
1239
|
);
|
|
1232
1240
|
|
|
1233
1241
|
// Phase 10c: Start OTel turn span. Coarse-grained — spans the whole turn
|
|
@@ -1819,6 +1827,7 @@ async function executeCursorInner(
|
|
|
1819
1827
|
fallbackContext: { model: validatedModel, mode: agentMode, agentId: resolution.agentId },
|
|
1820
1828
|
durationMs: (result as unknown as Record<string, unknown>).durationMs as number | undefined,
|
|
1821
1829
|
messageCount: status.messages.length,
|
|
1830
|
+
proxyMode: !!config.proxyEndpoint,
|
|
1822
1831
|
});
|
|
1823
1832
|
|
|
1824
1833
|
console.error(
|
|
@@ -1879,6 +1888,7 @@ async function executeCursorInner(
|
|
|
1879
1888
|
senderIdentity: readSenderIdentity(blueprint.sessionSpec.metadata),
|
|
1880
1889
|
sessionContext: readSessionContext(blueprint.sessionSpec.metadata),
|
|
1881
1890
|
declaredPreferences: readDeclaredPreferences(spec.declaredPreferences),
|
|
1891
|
+
recalledMemories: readRecalledMemories(spec.recalledMemories),
|
|
1882
1892
|
conversationCatchup: readConversationCatchup(spec.conversationCatchup),
|
|
1883
1893
|
// Composed fresh (not reused from Phase 10): the failed primary
|
|
1884
1894
|
// stream may have appended partial work onto status.messages,
|
|
@@ -1958,6 +1968,7 @@ async function executeCursorInner(
|
|
|
1958
1968
|
conversationErrorText: retryConversationErrorText,
|
|
1959
1969
|
isResumedHandle: false,
|
|
1960
1970
|
fallbackContext: { model: validatedModel, mode: agentMode, agentId: freshAgent.agentId },
|
|
1971
|
+
proxyMode: !!config.proxyEndpoint,
|
|
1961
1972
|
});
|
|
1962
1973
|
|
|
1963
1974
|
status.phase = ExecutionPhase.EXECUTION_FAILED;
|
|
@@ -2259,6 +2270,7 @@ async function executeCursorInner(
|
|
|
2259
2270
|
capturedRejection: getCapturedRejection(executionId),
|
|
2260
2271
|
isResumedHandle: false,
|
|
2261
2272
|
fallbackContext: errorContext,
|
|
2273
|
+
proxyMode: !!config.proxyEndpoint,
|
|
2262
2274
|
});
|
|
2263
2275
|
clearCapturedRejection(executionId);
|
|
2264
2276
|
status.phase = ExecutionPhase.EXECUTION_FAILED;
|
|
@@ -2477,6 +2489,16 @@ export interface BuildPromptInput {
|
|
|
2477
2489
|
* store with identical content.
|
|
2478
2490
|
*/
|
|
2479
2491
|
declaredPreferences?: import("../../shared/declared-preferences.js").DeclaredPreferencesContent;
|
|
2492
|
+
/**
|
|
2493
|
+
* The subject's confirmed memories from the execution spec's
|
|
2494
|
+
* `recalled_memories` (stigmer/stigmer#293 Phase 2, DD-006). Like the
|
|
2495
|
+
* preferences, only the enhanced-prompt path consumes it — deliberately
|
|
2496
|
+
* frozen per Cursor session (DD-002 D3, inherited by DD-006 D4): the
|
|
2497
|
+
* first turn delivers it into the agent's own conversation store, and
|
|
2498
|
+
* repeating it on resumed turns would bloat the store with identical
|
|
2499
|
+
* content.
|
|
2500
|
+
*/
|
|
2501
|
+
recalledMemories?: import("../../shared/recalled-memories.js").RecalledMemoriesContent;
|
|
2480
2502
|
/**
|
|
2481
2503
|
* Conversation catchup from the execution spec's `conversation_catchup`
|
|
2482
2504
|
* (cloud DD-006): what happened on the channel conversation that the
|
|
@@ -2616,6 +2638,7 @@ export function buildPrompt(input: BuildPromptInput): string {
|
|
|
2616
2638
|
senderIdentity: input.senderIdentity,
|
|
2617
2639
|
sessionContext: input.sessionContext,
|
|
2618
2640
|
declaredPreferences: input.declaredPreferences,
|
|
2641
|
+
recalledMemories: input.recalledMemories,
|
|
2619
2642
|
conversationCatchup,
|
|
2620
2643
|
},
|
|
2621
2644
|
{
|
|
@@ -2683,6 +2706,7 @@ export function buildPrompt(input: BuildPromptInput): string {
|
|
|
2683
2706
|
senderIdentity: input.senderIdentity,
|
|
2684
2707
|
sessionContext: input.sessionContext,
|
|
2685
2708
|
declaredPreferences: input.declaredPreferences,
|
|
2709
|
+
recalledMemories: input.recalledMemories,
|
|
2686
2710
|
conversationCatchup,
|
|
2687
2711
|
});
|
|
2688
2712
|
}
|
|
@@ -35,6 +35,10 @@ import {
|
|
|
35
35
|
formatDeclaredPreferencesText,
|
|
36
36
|
type DeclaredPreferencesContent,
|
|
37
37
|
} from "../../shared/declared-preferences.js";
|
|
38
|
+
import {
|
|
39
|
+
formatRecalledMemoriesText,
|
|
40
|
+
type RecalledMemoriesContent,
|
|
41
|
+
} from "../../shared/recalled-memories.js";
|
|
38
42
|
import {
|
|
39
43
|
visionDisclosureLines,
|
|
40
44
|
type NotViewableEntry,
|
|
@@ -160,6 +164,16 @@ export interface EnhancedPromptOptions {
|
|
|
160
164
|
* it every resumed turn would bloat the store with identical content.
|
|
161
165
|
*/
|
|
162
166
|
declaredPreferences?: DeclaredPreferencesContent;
|
|
167
|
+
/**
|
|
168
|
+
* The subject's confirmed memories (stigmer/stigmer#293 Phase 2, DD-006):
|
|
169
|
+
* consent-gated facts server-snapshotted onto the execution spec's
|
|
170
|
+
* `recalled_memories` at create. Like the preferences, it lands in the
|
|
171
|
+
* first message and persists in the cursor agent's own conversation
|
|
172
|
+
* store — deliberately frozen per Cursor session (DD-002 D3, inherited
|
|
173
|
+
* by DD-006 D4): repeating it every resumed turn would bloat the store
|
|
174
|
+
* with identical content.
|
|
175
|
+
*/
|
|
176
|
+
recalledMemories?: RecalledMemoriesContent;
|
|
163
177
|
/**
|
|
164
178
|
* Conversation catchup (cloud DD-006): what happened on the channel
|
|
165
179
|
* conversation that the agent has not seen, read from the execution
|
|
@@ -253,6 +267,14 @@ export function buildEnhancedPrompt(options: EnhancedPromptOptions): string {
|
|
|
253
267
|
sections.push(formatDeclaredPreferencesSection(options.declaredPreferences));
|
|
254
268
|
}
|
|
255
269
|
|
|
270
|
+
// Declared-by-humans precedes learned-and-confirmed (DD-006 D4): both
|
|
271
|
+
// are platform-authored standing background, but a preference is the
|
|
272
|
+
// user's exact words while a memory is an agent's confirmed inference —
|
|
273
|
+
// the exact statement reads first.
|
|
274
|
+
if (options.recalledMemories) {
|
|
275
|
+
sections.push(formatRecalledMemoriesSection(options.recalledMemories));
|
|
276
|
+
}
|
|
277
|
+
|
|
256
278
|
// Standing facts about the user (session context) come before the
|
|
257
279
|
// carried conversation (bridge): the bridge may refer back to them.
|
|
258
280
|
if (options.sessionContext) {
|
|
@@ -472,6 +494,12 @@ export function formatDeclaredPreferencesSection(
|
|
|
472
494
|
return `<declared_preferences>\n${formatDeclaredPreferencesText(preferences)}\n</declared_preferences>`;
|
|
473
495
|
}
|
|
474
496
|
|
|
497
|
+
export function formatRecalledMemoriesSection(
|
|
498
|
+
memories: RecalledMemoriesContent,
|
|
499
|
+
): string {
|
|
500
|
+
return `<recalled_memories>\n${formatRecalledMemoriesText(memories)}\n</recalled_memories>`;
|
|
501
|
+
}
|
|
502
|
+
|
|
475
503
|
export function formatSessionContextSection(context: string): string {
|
|
476
504
|
return `<session_context>\n${formatSessionContextText(context)}\n</session_context>`;
|
|
477
505
|
}
|
|
@@ -1,18 +1,23 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Variant-attribute → Cursor variant-parameter translation
|
|
3
|
+
* (stigmer/stigmer#357 service tier, #772 thinking mode).
|
|
3
4
|
*
|
|
4
5
|
* The platform contract: an execution's model selection is ALWAYS explicit.
|
|
5
|
-
* A bare `{ id }` lets the Cursor catalog's default variant decide the
|
|
6
|
-
* (observed 2026-08-06: composer-2.5 defaults to fast=true at ~4x
|
|
7
|
-
* rates, claude-haiku-4-5 to thinking=true), and that default follows
|
|
8
|
-
* out-of-band account setting. This module pins every
|
|
9
|
-
* parameter the model declares, so the
|
|
10
|
-
* function of
|
|
6
|
+
* A bare `{ id }` lets the Cursor catalog's default variant decide the
|
|
7
|
+
* variant (observed 2026-08-06: composer-2.5 defaults to fast=true at ~4x
|
|
8
|
+
* base rates, claude-haiku-4-5 to thinking=true), and that default follows
|
|
9
|
+
* an out-of-band account setting. This module pins every user-selectable
|
|
10
|
+
* variant parameter the model declares, so the served variant is a
|
|
11
|
+
* deterministic function of the execution config:
|
|
11
12
|
*
|
|
12
|
-
* -
|
|
13
|
-
*
|
|
14
|
-
* -
|
|
15
|
-
*
|
|
13
|
+
* - fast: pinned from ExecutionConfig.service_tier (FAST → true).
|
|
14
|
+
* Price-bearing — the fast variant bills at pricingVariants.fast rates.
|
|
15
|
+
* - thinking: pinned from ExecutionConfig.thinking_mode (ENABLED → true).
|
|
16
|
+
* Per-token price-neutral (ledger-verified 2026-08-15: thinking wire ids
|
|
17
|
+
* bill exactly base rates; thinking+fast bills exactly the fast rate) —
|
|
18
|
+
* pinned anyway because the served variant must never follow the account
|
|
19
|
+
* default, and ENABLED turns consume more output (reasoning) tokens.
|
|
20
|
+
* - Parameters that are neither (e.g. effort) are deliberately NOT pinned —
|
|
16
21
|
* they follow the catalog default and do not change the bill.
|
|
17
22
|
*
|
|
18
23
|
* Parameter bundles come from Cursor.models.list() (worker-cached): the
|
|
@@ -20,17 +25,19 @@
|
|
|
20
25
|
* rides the same proxy fetch-interceptor as every other SDK call, so it
|
|
21
26
|
* works identically in proxy and direct modes.
|
|
22
27
|
*
|
|
23
|
-
* The harness-neutral halves — the
|
|
24
|
-
* UNSPECIFIED→
|
|
25
|
-
*
|
|
26
|
-
*
|
|
28
|
+
* The harness-neutral halves — the enum semantics and the single
|
|
29
|
+
* UNSPECIFIED→default resolution points — live in `shared/service-tier.ts`
|
|
30
|
+
* (since #361 extended tiers to the native harness) and
|
|
31
|
+
* `shared/thinking-mode.ts`; this module keeps only the Cursor-catalog
|
|
32
|
+
* translation.
|
|
27
33
|
*/
|
|
28
34
|
|
|
29
35
|
import { Cursor } from "@cursor/sdk";
|
|
30
36
|
import type { ModelListItem, ModelParameterValue } from "@cursor/sdk";
|
|
31
|
-
import { ServiceTier } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/enum_pb";
|
|
37
|
+
import { ServiceTier, ThinkingMode } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/enum_pb";
|
|
32
38
|
|
|
33
39
|
import { serviceTierLabel, type EffectiveServiceTier } from "../../shared/service-tier.js";
|
|
40
|
+
import { thinkingModeLabel, type EffectiveThinkingMode } from "../../shared/thinking-mode.js";
|
|
34
41
|
|
|
35
42
|
/**
|
|
36
43
|
* Catalog ids that mean "Cursor picks the model" (Auto). Auto's single
|
|
@@ -42,9 +49,11 @@ import { serviceTierLabel, type EffectiveServiceTier } from "../../shared/servic
|
|
|
42
49
|
const AUTO_MODEL_IDS = new Set(["default", "auto"]);
|
|
43
50
|
|
|
44
51
|
/**
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
*
|
|
52
|
+
* The user-selectable variant parameter ids. Pinning exactly these keeps
|
|
53
|
+
* the served variant deterministic while leaving effort knobs on their
|
|
54
|
+
* catalog defaults. `fast` changes the per-token price; `thinking` is
|
|
55
|
+
* price-neutral but changes token consumption and must never follow the
|
|
56
|
+
* account default. Sourced from the Cursor catalog survey
|
|
48
57
|
* (stigmer-cloud _projects/2026-08/20260806.04.model-service-tier).
|
|
49
58
|
*/
|
|
50
59
|
const FAST_PARAM_ID = "fast";
|
|
@@ -108,46 +117,55 @@ export interface ResolveServiceTierParamsOptions {
|
|
|
108
117
|
/** Validated model id the execution runs on (may be "default" for Auto). */
|
|
109
118
|
readonly modelId: string;
|
|
110
119
|
readonly tier: EffectiveServiceTier;
|
|
120
|
+
readonly thinking: EffectiveThinkingMode;
|
|
111
121
|
/** For log correlation only. */
|
|
112
122
|
readonly executionId: string;
|
|
113
123
|
}
|
|
114
124
|
|
|
115
125
|
/**
|
|
116
|
-
* Translate the effective tier into the explicit variant
|
|
117
|
-
* with every Agent.create/resume for this execution.
|
|
126
|
+
* Translate the effective tier + thinking mode into the explicit variant
|
|
127
|
+
* parameters to send with every Agent.create/resume for this execution.
|
|
118
128
|
*
|
|
119
|
-
* Fail-closed posture:
|
|
120
|
-
* never a silent downgrade —
|
|
121
|
-
*
|
|
122
|
-
* must be heard about, not
|
|
129
|
+
* Fail-closed posture: an ACTIVE selection (FAST tier, ENABLED thinking)
|
|
130
|
+
* with no pinnable dimension is an error, never a silent downgrade —
|
|
131
|
+
* create-time validation makes this unreachable unless the registry and the
|
|
132
|
+
* provider catalog have drifted, and that drift must be heard about, not
|
|
133
|
+
* absorbed.
|
|
123
134
|
*
|
|
124
|
-
* STANDARD degrades to empty params on
|
|
125
|
-
* the execution — but be clear about
|
|
126
|
-
*
|
|
127
|
-
*
|
|
128
|
-
* exists to prevent). Failing every
|
|
129
|
-
* endpoint blips would be the worse trade; the WARN
|
|
130
|
-
* requested-vs-billed mismatch
|
|
131
|
-
* are the compensating controls.
|
|
135
|
+
* The base selection (STANDARD + DISABLED) degrades to empty params on
|
|
136
|
+
* catalog failures rather than failing the execution — but be clear about
|
|
137
|
+
* what that costs: an unpinned selection falls to the catalog default
|
|
138
|
+
* variant, which for several models IS the fast/thinking variant (the
|
|
139
|
+
* incident this module exists to prevent). Failing every base execution
|
|
140
|
+
* whenever the catalog endpoint blips would be the worse trade; the WARN
|
|
141
|
+
* below plus billing's requested-vs-billed mismatch alarms (which catch
|
|
142
|
+
* exactly this window) are the compensating controls.
|
|
132
143
|
*/
|
|
133
144
|
export async function resolveServiceTierParams(
|
|
134
145
|
options: ResolveServiceTierParamsOptions,
|
|
135
146
|
): Promise<ModelParameterValue[]> {
|
|
136
|
-
const { apiKey, modelId, tier, executionId } = options;
|
|
147
|
+
const { apiKey, modelId, tier, thinking, executionId } = options;
|
|
137
148
|
const tierName = serviceTierLabel(tier);
|
|
149
|
+
const thinkingName = thinkingModeLabel(thinking);
|
|
150
|
+
// Selections that actively deviate from the base variant must fail loudly
|
|
151
|
+
// when they cannot be pinned; the base selection may degrade with a WARN.
|
|
152
|
+
const active: string[] = [];
|
|
153
|
+
if (tier === ServiceTier.FAST) active.push("service_tier=fast");
|
|
154
|
+
if (thinking === ThinkingMode.ENABLED) active.push("thinking=enabled");
|
|
138
155
|
|
|
139
156
|
if (AUTO_MODEL_IDS.has(modelId)) {
|
|
140
|
-
if (
|
|
157
|
+
if (active.length > 0) {
|
|
141
158
|
throw new Error(
|
|
142
|
-
|
|
143
|
-
`
|
|
144
|
-
`create time; the model registry and provider catalog may
|
|
159
|
+
`${active.join(" + ")} requires a pinned model — Auto ("${modelId}") has ` +
|
|
160
|
+
`no variant dimensions. Execution ${executionId} should have been ` +
|
|
161
|
+
`refused at create time; the model registry and provider catalog may ` +
|
|
162
|
+
`have drifted.`,
|
|
145
163
|
);
|
|
146
164
|
}
|
|
147
165
|
console.log(
|
|
148
|
-
`
|
|
149
|
-
`Auto has no variant parameters; Cursor picks
|
|
150
|
-
`(documented v1 limitation).`,
|
|
166
|
+
`VariantParams: execution=${executionId} model=${modelId} tier=${tierName} ` +
|
|
167
|
+
`thinking=${thinkingName} — Auto has no variant parameters; Cursor picks ` +
|
|
168
|
+
`the model and variant (documented v1 limitation).`,
|
|
151
169
|
);
|
|
152
170
|
return [];
|
|
153
171
|
}
|
|
@@ -156,37 +174,38 @@ export async function resolveServiceTierParams(
|
|
|
156
174
|
try {
|
|
157
175
|
models = await listCatalogModels(apiKey);
|
|
158
176
|
} catch (err) {
|
|
159
|
-
if (
|
|
177
|
+
if (active.length > 0) {
|
|
160
178
|
throw new Error(
|
|
161
|
-
|
|
162
|
-
`catalog to resolve variant params for "${modelId}", and the
|
|
163
|
-
`fetch failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
179
|
+
`${active.join(" + ")} for execution ${executionId} needs the Cursor ` +
|
|
180
|
+
`model catalog to resolve variant params for "${modelId}", and the ` +
|
|
181
|
+
`catalog fetch failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
164
182
|
);
|
|
165
183
|
}
|
|
166
184
|
console.warn(
|
|
167
|
-
`
|
|
168
|
-
`catalog fetch failed
|
|
169
|
-
`
|
|
170
|
-
`
|
|
171
|
-
`expensive direction).
|
|
172
|
-
`
|
|
185
|
+
`VariantParams UNPINNED: execution=${executionId} model=${modelId} tier=${tierName} ` +
|
|
186
|
+
`thinking=${thinkingName} — catalog fetch failed ` +
|
|
187
|
+
`(${err instanceof Error ? err.message : err}); sending no variant params, ` +
|
|
188
|
+
`so the catalog DEFAULT variant decides the served variant for this ` +
|
|
189
|
+
`execution (fast/thinking on several models — the expensive direction). ` +
|
|
190
|
+
`Billing's requested-vs-billed mismatch alarms cover this window.`,
|
|
173
191
|
);
|
|
174
192
|
return [];
|
|
175
193
|
}
|
|
176
194
|
|
|
177
195
|
const model = findCatalogModel(models, modelId);
|
|
178
196
|
if (!model) {
|
|
179
|
-
if (
|
|
197
|
+
if (active.length > 0) {
|
|
180
198
|
throw new Error(
|
|
181
|
-
|
|
182
|
-
`but the Cursor catalog does not list that model — cannot pin
|
|
183
|
-
`variant. The model registry and provider catalog have drifted.`,
|
|
199
|
+
`${active.join(" + ")} requested for "${modelId}" (execution ${executionId}) ` +
|
|
200
|
+
`but the Cursor catalog does not list that model — cannot pin its ` +
|
|
201
|
+
`variant parameters. The model registry and provider catalog have drifted.`,
|
|
184
202
|
);
|
|
185
203
|
}
|
|
186
204
|
console.warn(
|
|
187
|
-
`
|
|
188
|
-
`model not in the Cursor catalog; sending no
|
|
189
|
-
`catalog DEFAULT variant decides the
|
|
205
|
+
`VariantParams UNPINNED: execution=${executionId} model=${modelId} tier=${tierName} ` +
|
|
206
|
+
`thinking=${thinkingName} — model not in the Cursor catalog; sending no ` +
|
|
207
|
+
`variant params, so the catalog DEFAULT variant decides the served ` +
|
|
208
|
+
`variant for this execution.`,
|
|
190
209
|
);
|
|
191
210
|
return [];
|
|
192
211
|
}
|
|
@@ -196,10 +215,13 @@ export async function resolveServiceTierParams(
|
|
|
196
215
|
if (def.id === FAST_PARAM_ID) {
|
|
197
216
|
params.push({ id: FAST_PARAM_ID, value: tier === ServiceTier.FAST ? "true" : "false" });
|
|
198
217
|
} else if (def.id === THINKING_PARAM_ID) {
|
|
199
|
-
params.push({
|
|
218
|
+
params.push({
|
|
219
|
+
id: THINKING_PARAM_ID,
|
|
220
|
+
value: thinking === ThinkingMode.ENABLED ? "true" : "false",
|
|
221
|
+
});
|
|
200
222
|
}
|
|
201
|
-
// Any other parameter (e.g. effort) is price-neutral
|
|
202
|
-
// catalog default variant on purpose.
|
|
223
|
+
// Any other parameter (e.g. effort) is price-neutral and not
|
|
224
|
+
// user-selectable: left to the catalog default variant on purpose.
|
|
203
225
|
}
|
|
204
226
|
|
|
205
227
|
if (tier === ServiceTier.FAST && !params.some((p) => p.id === FAST_PARAM_ID)) {
|
|
@@ -211,10 +233,19 @@ export async function resolveServiceTierParams(
|
|
|
211
233
|
);
|
|
212
234
|
}
|
|
213
235
|
|
|
236
|
+
if (thinking === ThinkingMode.ENABLED && !params.some((p) => p.id === THINKING_PARAM_ID)) {
|
|
237
|
+
throw new Error(
|
|
238
|
+
`thinking=enabled requested for "${modelId}" (execution ${executionId}) ` +
|
|
239
|
+
`but the Cursor catalog declares no "thinking" parameter for it. The ` +
|
|
240
|
+
`model registry claims a thinking capability the provider no longer ` +
|
|
241
|
+
`offers — refusing rather than silently serving the base variant.`,
|
|
242
|
+
);
|
|
243
|
+
}
|
|
244
|
+
|
|
214
245
|
params.sort((a, b) => a.id.localeCompare(b.id));
|
|
215
246
|
console.log(
|
|
216
|
-
`
|
|
217
|
-
`params=${JSON.stringify(params)}`,
|
|
247
|
+
`VariantParams: execution=${executionId} model=${modelId} tier=${tierName} ` +
|
|
248
|
+
`thinking=${thinkingName} params=${JSON.stringify(params)}`,
|
|
218
249
|
);
|
|
219
250
|
return params;
|
|
220
251
|
}
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
16
|
import type { ModelParameterValue } from "@cursor/sdk";
|
|
17
|
-
import { ServiceTier } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/enum_pb";
|
|
17
|
+
import { ServiceTier, ThinkingMode } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/enum_pb";
|
|
18
18
|
|
|
19
19
|
import { getCursorModelPricingForVariant, computeTurnCost } from "./model-pricing.js";
|
|
20
20
|
|
|
@@ -39,6 +39,8 @@ export interface UsageSnapshot {
|
|
|
39
39
|
readonly requestedServiceTier: ServiceTier;
|
|
40
40
|
/** JSON-encoded ModelSelection.params the runner sent; "" when none. */
|
|
41
41
|
readonly requestedModelParams: string;
|
|
42
|
+
/** Thinking mode the runner requested — always explicit post-translation (#772). */
|
|
43
|
+
readonly requestedThinkingMode: ThinkingMode;
|
|
42
44
|
}
|
|
43
45
|
|
|
44
46
|
const EMPTY_SNAPSHOT: UsageSnapshot = {
|
|
@@ -53,6 +55,7 @@ const EMPTY_SNAPSHOT: UsageSnapshot = {
|
|
|
53
55
|
observedAt: "",
|
|
54
56
|
requestedServiceTier: ServiceTier.UNSPECIFIED,
|
|
55
57
|
requestedModelParams: "",
|
|
58
|
+
requestedThinkingMode: ThinkingMode.UNSPECIFIED,
|
|
56
59
|
};
|
|
57
60
|
|
|
58
61
|
export interface TurnRecord {
|
|
@@ -85,6 +88,12 @@ export class UsageAccumulator {
|
|
|
85
88
|
*/
|
|
86
89
|
private readonly requestedServiceTier: ServiceTier = ServiceTier.UNSPECIFIED,
|
|
87
90
|
requestedModelParams: readonly ModelParameterValue[] = [],
|
|
91
|
+
/**
|
|
92
|
+
* The explicit thinking mode the runner requested. Price-neutral
|
|
93
|
+
* (thinking bills at base rates, #772), so it never enters the
|
|
94
|
+
* estimate — recorded purely as the audit trail twin of the tier.
|
|
95
|
+
*/
|
|
96
|
+
private readonly requestedThinkingMode: ThinkingMode = ThinkingMode.UNSPECIFIED,
|
|
88
97
|
) {
|
|
89
98
|
this.requestedModelParams =
|
|
90
99
|
requestedModelParams.length > 0 ? JSON.stringify(requestedModelParams) : "";
|
|
@@ -154,6 +163,7 @@ export class UsageAccumulator {
|
|
|
154
163
|
observedAt: this.observedAt,
|
|
155
164
|
requestedServiceTier: this.requestedServiceTier,
|
|
156
165
|
requestedModelParams: this.requestedModelParams,
|
|
166
|
+
requestedThinkingMode: this.requestedThinkingMode,
|
|
157
167
|
};
|
|
158
168
|
}
|
|
159
169
|
}
|
|
@@ -251,6 +251,7 @@ const httpConfig: Config = {
|
|
|
251
251
|
cloudModeEnabled: true,
|
|
252
252
|
checkpointerType: "http",
|
|
253
253
|
checkpointerProxyEndpoint: "http://localhost:7234",
|
|
254
|
+
artifactProxyEndpoint: null,
|
|
254
255
|
primaryModel: "claude-sonnet",
|
|
255
256
|
cursorStreamStallTimeoutMs: 180000,
|
|
256
257
|
agentResolveTimeoutMs: 120000,
|
|
@@ -296,6 +296,7 @@ const baseConfig: Config = {
|
|
|
296
296
|
cloudModeEnabled: true,
|
|
297
297
|
checkpointerType: "http",
|
|
298
298
|
checkpointerProxyEndpoint: "http://localhost:7234",
|
|
299
|
+
artifactProxyEndpoint: null,
|
|
299
300
|
primaryModel: "claude-sonnet",
|
|
300
301
|
cursorStreamStallTimeoutMs: 180000,
|
|
301
302
|
agentResolveTimeoutMs: 120000,
|
|
@@ -250,6 +250,7 @@ const baseConfig: Config = {
|
|
|
250
250
|
cloudModeEnabled: true,
|
|
251
251
|
checkpointerType: "http",
|
|
252
252
|
checkpointerProxyEndpoint: "http://localhost:7234",
|
|
253
|
+
artifactProxyEndpoint: null,
|
|
253
254
|
primaryModel: "claude-sonnet",
|
|
254
255
|
cursorStreamStallTimeoutMs: 180000,
|
|
255
256
|
agentResolveTimeoutMs: 120000,
|
|
@@ -47,6 +47,7 @@ describe("ExecuteDeepAgent activity", () => {
|
|
|
47
47
|
cloudModeEnabled: false,
|
|
48
48
|
checkpointerType: "memory",
|
|
49
49
|
checkpointerProxyEndpoint: null,
|
|
50
|
+
artifactProxyEndpoint: null,
|
|
50
51
|
primaryModel: "gpt-4.1",
|
|
51
52
|
cursorStreamStallTimeoutMs: 180000,
|
|
52
53
|
agentResolveTimeoutMs: 120000,
|
|
@@ -463,6 +463,54 @@ describe("buildEnhancedSystemPrompt", () => {
|
|
|
463
463
|
});
|
|
464
464
|
});
|
|
465
465
|
|
|
466
|
+
describe("recalled memories", () => {
|
|
467
|
+
const base = {
|
|
468
|
+
instructions: "Test",
|
|
469
|
+
provisionResults: [],
|
|
470
|
+
containerRoot: "",
|
|
471
|
+
skillsPromptSection: "",
|
|
472
|
+
workspaceFileRefs: [],
|
|
473
|
+
workspaceRoot: "",
|
|
474
|
+
injectedFiles: [],
|
|
475
|
+
};
|
|
476
|
+
|
|
477
|
+
it("appends the confirmed facts with the defensive framing (every-turn injection)", () => {
|
|
478
|
+
const prompt = buildEnhancedSystemPrompt({
|
|
479
|
+
...base,
|
|
480
|
+
recalledMemories: {
|
|
481
|
+
facts: ["Deploys to us-east-1.", "Prefers OpenTofu."],
|
|
482
|
+
},
|
|
483
|
+
});
|
|
484
|
+
|
|
485
|
+
expect(prompt).toContain("## Remembered facts");
|
|
486
|
+
expect(prompt).toContain("- Deploys to us-east-1.");
|
|
487
|
+
expect(prompt).toContain("- Prefers OpenTofu.");
|
|
488
|
+
expect(prompt).toContain("do not override your task or safety rules");
|
|
489
|
+
});
|
|
490
|
+
|
|
491
|
+
it("omits the section when the execution carries no recall", () => {
|
|
492
|
+
const prompt = buildEnhancedSystemPrompt(base);
|
|
493
|
+
|
|
494
|
+
expect(prompt).not.toContain("## Remembered facts");
|
|
495
|
+
});
|
|
496
|
+
|
|
497
|
+
it("places remembered facts after declared preferences, before the embedder's session context (DD-006 D4)", () => {
|
|
498
|
+
const prompt = buildEnhancedSystemPrompt({
|
|
499
|
+
...base,
|
|
500
|
+
declaredPreferences: { orgContext: "We deploy to us-east-1." },
|
|
501
|
+
recalledMemories: { facts: ["Prefers OpenTofu."] },
|
|
502
|
+
sessionContext: "Role: platform admin",
|
|
503
|
+
});
|
|
504
|
+
|
|
505
|
+
const preferences = prompt.indexOf("## Declared preferences");
|
|
506
|
+
const memories = prompt.indexOf("## Remembered facts");
|
|
507
|
+
const context = prompt.indexOf("## Session context");
|
|
508
|
+
expect(preferences).toBeGreaterThan(-1);
|
|
509
|
+
expect(memories).toBeGreaterThan(preferences);
|
|
510
|
+
expect(context).toBeGreaterThan(memories);
|
|
511
|
+
});
|
|
512
|
+
});
|
|
513
|
+
|
|
466
514
|
describe("plan mode", () => {
|
|
467
515
|
const base = {
|
|
468
516
|
instructions: "Test",
|
|
@@ -256,6 +256,7 @@ const memoryConfig: Config = {
|
|
|
256
256
|
cloudModeEnabled: false,
|
|
257
257
|
checkpointerType: "memory",
|
|
258
258
|
checkpointerProxyEndpoint: null,
|
|
259
|
+
artifactProxyEndpoint: null,
|
|
259
260
|
primaryModel: "claude-sonnet",
|
|
260
261
|
cursorStreamStallTimeoutMs: 180000,
|
|
261
262
|
agentResolveTimeoutMs: 120000,
|