@stigmer/runner 3.5.3 → 3.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/.build-fingerprint +1 -1
- package/dist/activities/call-agent.js +85 -10
- package/dist/activities/call-agent.js.map +1 -1
- package/dist/activities/discover-mcp-server.js +9 -1
- package/dist/activities/discover-mcp-server.js.map +1 -1
- package/dist/activities/execute-cursor/index.d.ts +5 -0
- package/dist/activities/execute-cursor/index.js +88 -14
- package/dist/activities/execute-cursor/index.js.map +1 -1
- package/dist/activities/execute-cursor/model-pricing.d.ts +9 -0
- package/dist/activities/execute-cursor/model-pricing.js +19 -0
- package/dist/activities/execute-cursor/model-pricing.js.map +1 -1
- package/dist/activities/execute-cursor/prompt-builder.d.ts +7 -0
- package/dist/activities/execute-cursor/prompt-builder.js +9 -0
- package/dist/activities/execute-cursor/prompt-builder.js.map +1 -1
- package/dist/activities/execute-cursor/service-tier.d.ts +68 -0
- package/dist/activities/execute-cursor/service-tier.js +187 -0
- package/dist/activities/execute-cursor/service-tier.js.map +1 -0
- package/dist/activities/execute-cursor/session-lifecycle.d.ts +16 -1
- package/dist/activities/execute-cursor/session-lifecycle.js +12 -4
- package/dist/activities/execute-cursor/session-lifecycle.js.map +1 -1
- package/dist/activities/execute-cursor/usage-accumulator.d.ts +21 -1
- package/dist/activities/execute-cursor/usage-accumulator.js +23 -3
- package/dist/activities/execute-cursor/usage-accumulator.js.map +1 -1
- package/dist/activities/execute-deep-agent/prompt-builder.d.ts +6 -0
- package/dist/activities/execute-deep-agent/prompt-builder.js +3 -0
- package/dist/activities/execute-deep-agent/prompt-builder.js.map +1 -1
- package/dist/activities/execute-deep-agent/setup.js +45 -9
- package/dist/activities/execute-deep-agent/setup.js.map +1 -1
- package/dist/client/stigmer-client.d.ts +32 -1
- package/dist/client/stigmer-client.js +42 -2
- package/dist/client/stigmer-client.js.map +1 -1
- package/dist/main.js +18 -0
- package/dist/main.js.map +1 -1
- package/dist/runner.js +48 -0
- package/dist/runner.js.map +1 -1
- package/dist/sandbox-token-renewal.d.ts +65 -0
- package/dist/sandbox-token-renewal.js +169 -0
- package/dist/sandbox-token-renewal.js.map +1 -0
- package/dist/shared/artifact-storage.d.ts +17 -3
- package/dist/shared/artifact-storage.js +22 -4
- package/dist/shared/artifact-storage.js.map +1 -1
- package/dist/shared/caller-identity.d.ts +89 -0
- package/dist/shared/caller-identity.js +124 -0
- package/dist/shared/caller-identity.js.map +1 -0
- package/dist/shared/channel-attachment.d.ts +85 -0
- package/dist/shared/channel-attachment.js +203 -0
- package/dist/shared/channel-attachment.js.map +1 -0
- package/dist/shared/datastore-attachment.d.ts +2 -25
- package/dist/shared/datastore-attachment.js +1 -28
- package/dist/shared/datastore-attachment.js.map +1 -1
- package/dist/shared/synthesized-attachment.d.ts +51 -0
- package/dist/shared/synthesized-attachment.js +45 -0
- package/dist/shared/synthesized-attachment.js.map +1 -0
- package/dist/workflow-engine/loader.js +99 -2
- package/dist/workflow-engine/loader.js.map +1 -1
- package/dist/workflow-engine/tasks/call-agent.d.ts +0 -2
- package/dist/workflow-engine/tasks/call-agent.js +0 -2
- package/dist/workflow-engine/tasks/call-agent.js.map +1 -1
- package/dist/workflow-engine/types.d.ts +39 -7
- package/dist/workflow-engine/types.js.map +1 -1
- package/dist/workflows/call-agent-orchestrator.d.ts +3 -2
- package/dist/workflows/call-agent-orchestrator.js +8 -2
- package/dist/workflows/call-agent-orchestrator.js.map +1 -1
- package/package.json +2 -2
- package/src/__test-utils__/mock-client.ts +4 -0
- package/src/__tests__/sandbox-token-renewal.test.ts +174 -0
- package/src/activities/__tests__/call-agent-contracts.test.ts +4 -4
- package/src/activities/__tests__/call-agent.test.ts +219 -4
- package/src/activities/__tests__/discover-mcp-server.test.ts +49 -0
- package/src/activities/call-agent.ts +94 -10
- package/src/activities/discover-mcp-server.ts +13 -1
- package/src/activities/execute-cursor/__tests__/model-pricing.test.ts +20 -0
- package/src/activities/execute-cursor/__tests__/service-tier.test.ts +170 -0
- package/src/activities/execute-cursor/__tests__/usage-accumulator.test.ts +87 -1
- package/src/activities/execute-cursor/index.ts +121 -20
- package/src/activities/execute-cursor/model-pricing.ts +23 -0
- package/src/activities/execute-cursor/prompt-builder.ts +19 -0
- package/src/activities/execute-cursor/service-tier.ts +244 -0
- package/src/activities/execute-cursor/session-lifecycle.ts +33 -5
- package/src/activities/execute-cursor/usage-accumulator.ts +35 -3
- package/src/activities/execute-deep-agent/prompt-builder.ts +10 -0
- package/src/activities/execute-deep-agent/setup.ts +66 -10
- package/src/client/stigmer-client.ts +57 -4
- package/src/main.ts +20 -0
- package/src/runner.ts +62 -0
- package/src/sandbox-token-renewal.ts +212 -0
- package/src/shared/__tests__/caller-identity.test.ts +159 -0
- package/src/shared/__tests__/channel-attachment.test.ts +276 -0
- package/src/shared/__tests__/datastore-attachment.test.ts +4 -4
- package/src/shared/artifact-storage.ts +32 -7
- package/src/shared/caller-identity.ts +161 -0
- package/src/shared/channel-attachment.ts +237 -0
- package/src/shared/datastore-attachment.ts +2 -54
- package/src/shared/synthesized-attachment.ts +77 -0
- package/src/workflow-engine/__tests__/golden-execution.test.ts +8 -8
- package/src/workflow-engine/__tests__/loader.test.ts +192 -7
- package/src/workflow-engine/__tests__/tasks/call-agent.test.ts +9 -9
- package/src/workflow-engine/loader.ts +113 -2
- package/src/workflow-engine/tasks/call-agent.ts +0 -2
- package/src/workflow-engine/types.ts +40 -7
- package/src/workflows/call-agent-orchestrator.ts +8 -2
|
@@ -40,7 +40,12 @@ import { mkdirSync } from "node:fs";
|
|
|
40
40
|
import { join } from "node:path";
|
|
41
41
|
|
|
42
42
|
import { Agent } from "@cursor/sdk";
|
|
43
|
-
import type {
|
|
43
|
+
import type {
|
|
44
|
+
SDKAgent,
|
|
45
|
+
CursorAgentPlatformOptions,
|
|
46
|
+
AgentDefinition,
|
|
47
|
+
ModelParameterValue,
|
|
48
|
+
} from "@cursor/sdk";
|
|
44
49
|
import { withTimeout, TimeoutError } from "../../shared/with-timeout.js";
|
|
45
50
|
import type { CursorMcpServerConfig } from "./mcp-resolver.js";
|
|
46
51
|
|
|
@@ -83,6 +88,15 @@ const LOCAL_SETTING_SOURCES = ["project"] as const;
|
|
|
83
88
|
export interface CreateAgentOptions {
|
|
84
89
|
apiKey: string;
|
|
85
90
|
model: string;
|
|
91
|
+
/**
|
|
92
|
+
* Explicit variant parameters sent with the model selection on every
|
|
93
|
+
* create AND resume (stigmer/stigmer#357). A bare `{ id }` lets the
|
|
94
|
+
* Cursor catalog's default variant pick the price, so the caller always
|
|
95
|
+
* supplies the pinned params from resolveServiceTierParams — possibly
|
|
96
|
+
* empty (Auto, or a model with no price-bearing parameters), but never
|
|
97
|
+
* absent by accident.
|
|
98
|
+
*/
|
|
99
|
+
modelParams?: ModelParameterValue[];
|
|
86
100
|
workspaceDirs: string[];
|
|
87
101
|
sessionId: string;
|
|
88
102
|
/** Durable workspace volume root; the SDK state store lives under it. */
|
|
@@ -112,6 +126,8 @@ export interface ResumeAgentOptions {
|
|
|
112
126
|
/** Durable workspace volume root; the SDK state store lives under it. */
|
|
113
127
|
workspaceRootDir: string;
|
|
114
128
|
model?: string;
|
|
129
|
+
/** Explicit variant parameters — see {@link CreateAgentOptions.modelParams}. */
|
|
130
|
+
modelParams?: ModelParameterValue[];
|
|
115
131
|
mcpServers?: Record<string, CursorMcpServerConfig>;
|
|
116
132
|
/** Custom sub-agents — see {@link CreateAgentOptions.agents}. */
|
|
117
133
|
agents?: Record<string, AgentDefinition>;
|
|
@@ -129,6 +145,8 @@ export interface CloudRepo {
|
|
|
129
145
|
export interface CreateCloudAgentOptions {
|
|
130
146
|
apiKey: string;
|
|
131
147
|
model?: string;
|
|
148
|
+
/** Explicit variant parameters — see {@link CreateAgentOptions.modelParams}. */
|
|
149
|
+
modelParams?: ModelParameterValue[];
|
|
132
150
|
repos: CloudRepo[];
|
|
133
151
|
sessionId: string;
|
|
134
152
|
mcpServers?: Record<string, CursorMcpServerConfig>;
|
|
@@ -140,6 +158,8 @@ export interface ResumeCloudAgentOptions {
|
|
|
140
158
|
apiKey: string;
|
|
141
159
|
agentId: string;
|
|
142
160
|
model?: string;
|
|
161
|
+
/** Explicit variant parameters — see {@link CreateAgentOptions.modelParams}. */
|
|
162
|
+
modelParams?: ModelParameterValue[];
|
|
143
163
|
mcpServers?: Record<string, CursorMcpServerConfig>;
|
|
144
164
|
/** Custom sub-agents — see {@link CreateAgentOptions.agents}. */
|
|
145
165
|
agents?: Record<string, AgentDefinition>;
|
|
@@ -245,7 +265,9 @@ export async function createAgent(options: CreateAgentOptions): Promise<SDKAgent
|
|
|
245
265
|
|
|
246
266
|
return Agent.create({
|
|
247
267
|
apiKey: options.apiKey,
|
|
248
|
-
|
|
268
|
+
// Always a full selection — id AND params. A bare { id } would let the
|
|
269
|
+
// catalog's default variant (account-influenced) pick the price (#357).
|
|
270
|
+
model: { id: options.model, params: options.modelParams },
|
|
249
271
|
local: { cwd, settingSources: [...LOCAL_SETTING_SOURCES] },
|
|
250
272
|
mcpServers: options.mcpServers as Record<string, any>,
|
|
251
273
|
agents: options.agents,
|
|
@@ -273,7 +295,11 @@ export async function resumeAgent(options: ResumeAgentOptions): Promise<SDKAgent
|
|
|
273
295
|
|
|
274
296
|
return Agent.resume(options.agentId, {
|
|
275
297
|
apiKey: options.apiKey,
|
|
276
|
-
|
|
298
|
+
// Variant params must be re-supplied on resume exactly like mcpServers:
|
|
299
|
+
// explicit params hold across resume (verified against the billing
|
|
300
|
+
// ledger, #357), but an id-only resume would fall back to the catalog
|
|
301
|
+
// default variant for the new turns.
|
|
302
|
+
model: options.model ? { id: options.model, params: options.modelParams } : undefined,
|
|
277
303
|
// Neither cwd nor settingSources survive Agent.resume(); both must be
|
|
278
304
|
// re-supplied every turn. Omitting cwd makes the SDK fall back to
|
|
279
305
|
// process.cwd(), which re-roots the agent in the runner's own working
|
|
@@ -307,7 +333,7 @@ export async function createCloudAgent(options: CreateCloudAgentOptions): Promis
|
|
|
307
333
|
|
|
308
334
|
return Agent.create({
|
|
309
335
|
apiKey: options.apiKey,
|
|
310
|
-
model: options.model ? { id: options.model } : undefined,
|
|
336
|
+
model: options.model ? { id: options.model, params: options.modelParams } : undefined,
|
|
311
337
|
cloud: { repos: options.repos },
|
|
312
338
|
mcpServers: options.mcpServers as Record<string, any>,
|
|
313
339
|
agents: options.agents,
|
|
@@ -328,7 +354,7 @@ export async function resumeCloudAgent(options: ResumeCloudAgentOptions): Promis
|
|
|
328
354
|
|
|
329
355
|
return Agent.resume(options.agentId, {
|
|
330
356
|
apiKey: options.apiKey,
|
|
331
|
-
model: options.model ? { id: options.model } : undefined,
|
|
357
|
+
model: options.model ? { id: options.model, params: options.modelParams } : undefined,
|
|
332
358
|
mcpServers: options.mcpServers as Record<string, any>,
|
|
333
359
|
agents: options.agents,
|
|
334
360
|
});
|
|
@@ -372,6 +398,7 @@ export async function resolveAgent(
|
|
|
372
398
|
apiKey: options.apiKey,
|
|
373
399
|
agentId: harnessStateId,
|
|
374
400
|
model: options.model,
|
|
401
|
+
modelParams: options.modelParams,
|
|
375
402
|
mcpServers: options.mcpServers,
|
|
376
403
|
agents: options.agents,
|
|
377
404
|
})
|
|
@@ -382,6 +409,7 @@ export async function resolveAgent(
|
|
|
382
409
|
workspaceDirs: (options as CreateAgentOptions).workspaceDirs,
|
|
383
410
|
workspaceRootDir: (options as CreateAgentOptions).workspaceRootDir,
|
|
384
411
|
model: options.model,
|
|
412
|
+
modelParams: options.modelParams,
|
|
385
413
|
mcpServers: options.mcpServers,
|
|
386
414
|
agents: options.agents,
|
|
387
415
|
});
|
|
@@ -13,7 +13,10 @@
|
|
|
13
13
|
* wire via ProxyUsageReporter once traffic routing (Task 5B) is complete.
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
|
-
import {
|
|
16
|
+
import type { ModelParameterValue } from "@cursor/sdk";
|
|
17
|
+
import { ServiceTier } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/enum_pb";
|
|
18
|
+
|
|
19
|
+
import { getCursorModelPricingForVariant, computeTurnCost } from "./model-pricing.js";
|
|
17
20
|
|
|
18
21
|
export interface TurnUsage {
|
|
19
22
|
readonly inputTokens?: number;
|
|
@@ -32,6 +35,10 @@ export interface UsageSnapshot {
|
|
|
32
35
|
readonly estimatedCostUsd: number;
|
|
33
36
|
readonly model: string;
|
|
34
37
|
readonly observedAt: string;
|
|
38
|
+
/** Tier the runner requested — always explicit post-translation (#357). */
|
|
39
|
+
readonly requestedServiceTier: ServiceTier;
|
|
40
|
+
/** JSON-encoded ModelSelection.params the runner sent; "" when none. */
|
|
41
|
+
readonly requestedModelParams: string;
|
|
35
42
|
}
|
|
36
43
|
|
|
37
44
|
const EMPTY_SNAPSHOT: UsageSnapshot = {
|
|
@@ -44,6 +51,8 @@ const EMPTY_SNAPSHOT: UsageSnapshot = {
|
|
|
44
51
|
estimatedCostUsd: 0,
|
|
45
52
|
model: "",
|
|
46
53
|
observedAt: "",
|
|
54
|
+
requestedServiceTier: ServiceTier.UNSPECIFIED,
|
|
55
|
+
requestedModelParams: "",
|
|
47
56
|
};
|
|
48
57
|
|
|
49
58
|
export interface TurnRecord {
|
|
@@ -64,7 +73,22 @@ export class UsageAccumulator {
|
|
|
64
73
|
private observedAt = "";
|
|
65
74
|
private readonly turnRecords: TurnRecord[] = [];
|
|
66
75
|
|
|
67
|
-
|
|
76
|
+
/** JSON-encoded params the runner sent with the model selection (#357). */
|
|
77
|
+
private readonly requestedModelParams: string;
|
|
78
|
+
|
|
79
|
+
constructor(
|
|
80
|
+
private readonly model: string,
|
|
81
|
+
/**
|
|
82
|
+
* The explicit tier the runner requested from the provider. Recorded
|
|
83
|
+
* verbatim into status as the audit trail that the account default was
|
|
84
|
+
* never left in control (#357).
|
|
85
|
+
*/
|
|
86
|
+
private readonly requestedServiceTier: ServiceTier = ServiceTier.UNSPECIFIED,
|
|
87
|
+
requestedModelParams: readonly ModelParameterValue[] = [],
|
|
88
|
+
) {
|
|
89
|
+
this.requestedModelParams =
|
|
90
|
+
requestedModelParams.length > 0 ? JSON.stringify(requestedModelParams) : "";
|
|
91
|
+
}
|
|
68
92
|
|
|
69
93
|
addTurn(usage: TurnUsage): void {
|
|
70
94
|
const input = usage.inputTokens ?? 0;
|
|
@@ -86,7 +110,13 @@ export class UsageAccumulator {
|
|
|
86
110
|
cacheWriteTokens: cacheWrite,
|
|
87
111
|
});
|
|
88
112
|
|
|
89
|
-
|
|
113
|
+
// Estimate at the rates of the variant we explicitly requested — a
|
|
114
|
+
// FAST run priced at base rates would understate the display estimate
|
|
115
|
+
// ~4x relative to the authoritative bill (#357).
|
|
116
|
+
const pricing = getCursorModelPricingForVariant(
|
|
117
|
+
this.model,
|
|
118
|
+
this.requestedServiceTier === ServiceTier.FAST ? "fast" : null,
|
|
119
|
+
);
|
|
90
120
|
this.estimatedCostUsd += computeTurnCost(
|
|
91
121
|
pricing, input, output, cacheWrite, cacheRead,
|
|
92
122
|
);
|
|
@@ -122,6 +152,8 @@ export class UsageAccumulator {
|
|
|
122
152
|
estimatedCostUsd: this.estimatedCostUsd,
|
|
123
153
|
model: this.model,
|
|
124
154
|
observedAt: this.observedAt,
|
|
155
|
+
requestedServiceTier: this.requestedServiceTier,
|
|
156
|
+
requestedModelParams: this.requestedModelParams,
|
|
125
157
|
};
|
|
126
158
|
}
|
|
127
159
|
}
|
|
@@ -98,6 +98,12 @@ export interface PromptBuilderInput {
|
|
|
98
98
|
* formatDatastoresSection); empty when the agent uses no datastores.
|
|
99
99
|
*/
|
|
100
100
|
datastoresPromptSection?: string;
|
|
101
|
+
/**
|
|
102
|
+
* The `<available_channel_templates>` section
|
|
103
|
+
* (shared/channel-attachment.ts formatChannelTemplatesSection); absent
|
|
104
|
+
* when the agent serves no proactive channel or nothing is sendable.
|
|
105
|
+
*/
|
|
106
|
+
channelTemplatesPromptSection?: string;
|
|
101
107
|
workspaceFileRefs: string[];
|
|
102
108
|
workspaceRoot: string;
|
|
103
109
|
injectedFiles: InjectedFile[];
|
|
@@ -174,6 +180,10 @@ export function buildEnhancedSystemPrompt(input: PromptBuilderInput): string {
|
|
|
174
180
|
prompt += "\n\n" + input.datastoresPromptSection;
|
|
175
181
|
}
|
|
176
182
|
|
|
183
|
+
if (input.channelTemplatesPromptSection) {
|
|
184
|
+
prompt += "\n\n" + input.channelTemplatesPromptSection;
|
|
185
|
+
}
|
|
186
|
+
|
|
177
187
|
if (input.workspaceFileRefs.length > 0) {
|
|
178
188
|
const refSection = buildReferencedFilesSection(
|
|
179
189
|
input.workspaceFileRefs,
|
|
@@ -26,6 +26,10 @@ import { TimingRecorder, emitTimingLog } from "../../shared/cold-start-timing.js
|
|
|
26
26
|
import { createCheckpointer } from "../../shared/checkpointer/factory.js";
|
|
27
27
|
import { readContextBridge } from "../../shared/context-bridge.js";
|
|
28
28
|
import { readSenderIdentity } from "../../shared/sender-identity.js";
|
|
29
|
+
import {
|
|
30
|
+
injectCallerIdentityEnv,
|
|
31
|
+
resolveCallerIdentity,
|
|
32
|
+
} from "../../shared/caller-identity.js";
|
|
29
33
|
import { readSessionContext } from "../../shared/session-context.js";
|
|
30
34
|
import { connectMcpServers, type McpConnectionResult } from "../../shared/mcp-manager.js";
|
|
31
35
|
import { resolveMcpServers } from "../../shared/mcp-resolver.js";
|
|
@@ -33,9 +37,14 @@ import { resolveMcpTransportPosture } from "../../shared/mcp-transport-guard.js"
|
|
|
33
37
|
import { backfillMcpServersIfNeeded } from "../../shared/connect-backfill.js";
|
|
34
38
|
import {
|
|
35
39
|
formatDatastoresSection,
|
|
36
|
-
injectDatastoreAttachment,
|
|
37
40
|
synthesizeDatastoreAttachment,
|
|
38
41
|
} from "../../shared/datastore-attachment.js";
|
|
42
|
+
import {
|
|
43
|
+
discoverChannelMessaging,
|
|
44
|
+
formatChannelTemplatesSection,
|
|
45
|
+
synthesizeChannelAttachment,
|
|
46
|
+
} from "../../shared/channel-attachment.js";
|
|
47
|
+
import { injectSynthesizedAttachment } from "../../shared/synthesized-attachment.js";
|
|
39
48
|
import { WorkspaceProvisioner } from "../../shared/workspace/provisioner.js";
|
|
40
49
|
import { LocalWorkspaceBackend } from "../../shared/workspace/local-backend.js";
|
|
41
50
|
import type { WorkspaceBackend, ProvisionResult } from "../../shared/workspace/types.js";
|
|
@@ -334,12 +343,41 @@ export async function performSetup(deps: SetupDependencies): Promise<SetupResult
|
|
|
334
343
|
];
|
|
335
344
|
const datastoreUsages = agent.spec!.datastoreUsages || [];
|
|
336
345
|
|
|
346
|
+
// The synthesized attachments' credential story (DD-006 D4): the
|
|
347
|
+
// exchanged token authenticates the discovery reads per-call (the
|
|
348
|
+
// messaging reach refuses a desktop runner's ambient embedded_runner
|
|
349
|
+
// credential; undefined lets the ambient credential apply). The
|
|
350
|
+
// attachment header falls back to the ambient credential.
|
|
351
|
+
const exchangedRunnerToken =
|
|
352
|
+
await client.acquireScopedRunnerToken({ agentExecutionId: executionId });
|
|
353
|
+
const attachmentCredential = exchangedRunnerToken
|
|
354
|
+
?? config.stigmerTokenRef?.current
|
|
355
|
+
?? config.stigmerToken;
|
|
356
|
+
|
|
357
|
+
// The channel discovery read runs BEFORE the MCP gate below: an
|
|
358
|
+
// agent whose ONLY tool source is a proactive channel would
|
|
359
|
+
// otherwise never enter MCP resolution and never connect the
|
|
360
|
+
// attachment (DD-006 D7). Every failure mode degrades to an empty
|
|
361
|
+
// answer — no tool, no section, execution unharmed.
|
|
362
|
+
const channelMessaging = await discoverChannelMessaging(client, exchangedRunnerToken);
|
|
363
|
+
|
|
337
364
|
let resolvedMcpServers: Awaited<ReturnType<typeof resolveMcpServers>> | null = null;
|
|
338
|
-
if (mcpServerUsages.length > 0 || datastoreUsages.length > 0) {
|
|
365
|
+
if (mcpServerUsages.length > 0 || datastoreUsages.length > 0 || channelMessaging.length > 0) {
|
|
339
366
|
await reportSetupProgress(client, executionId, "Connecting tools…");
|
|
340
367
|
const transportPosture = resolveMcpTransportPosture(config.mode);
|
|
368
|
+
// The MCP-bound env map (and ONLY it) carries the reserved
|
|
369
|
+
// caller-identity keys — mirror of the Cursor harness's Phase 4
|
|
370
|
+
// injection; filterEnvToDeclaredKeys keeps undeclared servers blind.
|
|
371
|
+
const mcpEnvVars = injectCallerIdentityEnv(
|
|
372
|
+
envResult.mergedEnvVars,
|
|
373
|
+
resolveCallerIdentity(
|
|
374
|
+
session.spec!.metadata,
|
|
375
|
+
session.status?.audit?.specAudit?.createdBy,
|
|
376
|
+
),
|
|
377
|
+
sessionId,
|
|
378
|
+
);
|
|
341
379
|
resolvedMcpServers = await resolveMcpServers(
|
|
342
|
-
client, mcpServerUsages,
|
|
380
|
+
client, mcpServerUsages, mcpEnvVars, transportPosture,
|
|
343
381
|
);
|
|
344
382
|
timing.mark("resolve_mcp_servers");
|
|
345
383
|
|
|
@@ -348,7 +386,7 @@ export async function performSetup(deps: SetupDependencies): Promise<SetupResult
|
|
|
348
386
|
client,
|
|
349
387
|
resolvedMcpServers.resolvedServers,
|
|
350
388
|
mcpServerUsages,
|
|
351
|
-
|
|
389
|
+
mcpEnvVars,
|
|
352
390
|
sessionOrg,
|
|
353
391
|
transportPosture,
|
|
354
392
|
undefined,
|
|
@@ -360,17 +398,30 @@ export async function performSetup(deps: SetupDependencies): Promise<SetupResult
|
|
|
360
398
|
// approval maps keep it approval-free by construction (see
|
|
361
399
|
// shared/datastore-attachment.ts).
|
|
362
400
|
if (datastoreUsages.length > 0) {
|
|
363
|
-
const scopedCredential =
|
|
364
|
-
(await client.acquireScopedRunnerToken({ agentExecutionId: executionId }))
|
|
365
|
-
?? config.stigmerTokenRef?.current
|
|
366
|
-
?? config.stigmerToken;
|
|
367
401
|
const attachment = synthesizeDatastoreAttachment(datastoreUsages, {
|
|
368
402
|
bridgeEndpoint: config.mcpBridgeEndpoint,
|
|
369
|
-
credential:
|
|
403
|
+
credential: attachmentCredential,
|
|
404
|
+
backendEndpoint: config.stigmerBackendEndpoint,
|
|
405
|
+
});
|
|
406
|
+
if (attachment) {
|
|
407
|
+
backfilledServers = injectSynthesizedAttachment(
|
|
408
|
+
backfilledServers, attachment, "datastore records",
|
|
409
|
+
);
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
// The channel messaging attachment (DD-006 D7/D8) — the records
|
|
414
|
+
// attachment's twin, injected under the same after-backfill rule.
|
|
415
|
+
if (channelMessaging.length > 0) {
|
|
416
|
+
const attachment = synthesizeChannelAttachment(channelMessaging, {
|
|
417
|
+
bridgeEndpoint: config.mcpBridgeEndpoint,
|
|
418
|
+
credential: attachmentCredential,
|
|
370
419
|
backendEndpoint: config.stigmerBackendEndpoint,
|
|
371
420
|
});
|
|
372
421
|
if (attachment) {
|
|
373
|
-
backfilledServers =
|
|
422
|
+
backfilledServers = injectSynthesizedAttachment(
|
|
423
|
+
backfilledServers, attachment, "channel messaging",
|
|
424
|
+
);
|
|
374
425
|
}
|
|
375
426
|
}
|
|
376
427
|
resolvedMcpServers = { resolvedServers: backfilledServers };
|
|
@@ -445,6 +496,11 @@ export async function performSetup(deps: SetupDependencies): Promise<SetupResult
|
|
|
445
496
|
datastoresPromptSection: datastoreUsages.length > 0
|
|
446
497
|
? formatDatastoresSection(datastoreUsages)
|
|
447
498
|
: undefined,
|
|
499
|
+
// "" (nothing sendable) threads as undefined: the tool alone still
|
|
500
|
+
// serves text sends inside a 24-hour window (DD-006 D6).
|
|
501
|
+
channelTemplatesPromptSection: channelMessaging.length > 0
|
|
502
|
+
? formatChannelTemplatesSection(channelMessaging) || undefined
|
|
503
|
+
: undefined,
|
|
448
504
|
workspaceFileRefs: execution.spec!.workspaceFileRefs || [],
|
|
449
505
|
workspaceRoot: workspaceBackend.rootDir,
|
|
450
506
|
injectedFiles,
|
|
@@ -49,7 +49,9 @@ import { WorkflowQueryController } from "@stigmer/protos/ai/stigmer/agentic/work
|
|
|
49
49
|
import type { Workflow } from "@stigmer/protos/ai/stigmer/agentic/workflow/v1/api_pb";
|
|
50
50
|
import { WorkflowInstanceQueryController } from "@stigmer/protos/ai/stigmer/agentic/workflowinstance/v1/query_pb";
|
|
51
51
|
import type { WorkflowInstance } from "@stigmer/protos/ai/stigmer/agentic/workflowinstance/v1/api_pb";
|
|
52
|
-
import { PlatformQueryController, GetRunnerScopedTokenInputSchema } from "@stigmer/protos/ai/stigmer/platform/v1/server_info_pb";
|
|
52
|
+
import { PlatformQueryController, GetRunnerScopedTokenInputSchema, TokenRenewalSchema } from "@stigmer/protos/ai/stigmer/platform/v1/server_info_pb";
|
|
53
|
+
import { ChannelMessageQueryController } from "@stigmer/protos/ai/stigmer/agentic/agentchannel/v1/message_query_pb";
|
|
54
|
+
import type { ChannelTemplate, MessagingChannel } from "@stigmer/protos/ai/stigmer/agentic/agentchannel/v1/message_io_pb";
|
|
53
55
|
import { isEmbeddedRunnerToken } from "./token-claims.js";
|
|
54
56
|
import { assertCreateRequirements, assertReferenceRequirements } from "./server-contracts.js";
|
|
55
57
|
|
|
@@ -92,12 +94,16 @@ export interface RunnerScopedToken {
|
|
|
92
94
|
* `poolClaimSessionId` is the warm-pool attach exchange: a pool sandbox
|
|
93
95
|
* presenting its pool_sandbox credential for the session it was claimed for
|
|
94
96
|
* (the server authorizes against the claim record, not the caller's FGA
|
|
95
|
-
* relations).
|
|
97
|
+
* relations). `renewal` is a live sandbox extending its own credential
|
|
98
|
+
* before expiry: no id is named because every mint parameter comes from the
|
|
99
|
+
* presented credential's verified claims (see sandbox-token-renewal.ts).
|
|
100
|
+
* The execution arms remain embedded_runner-only.
|
|
96
101
|
*/
|
|
97
102
|
export type RunnerScopedTokenScope =
|
|
98
103
|
| { agentExecutionId: string }
|
|
99
104
|
| { workflowExecutionId: string }
|
|
100
|
-
| { poolClaimSessionId: string }
|
|
105
|
+
| { poolClaimSessionId: string }
|
|
106
|
+
| { renewal: true };
|
|
101
107
|
|
|
102
108
|
/**
|
|
103
109
|
* Map the scope union onto the proto oneof init shape. The narrowing chain is
|
|
@@ -111,7 +117,10 @@ function toRunnerScopedTokenOneof(scope: RunnerScopedTokenScope) {
|
|
|
111
117
|
if ("workflowExecutionId" in scope) {
|
|
112
118
|
return { case: "workflowExecutionId", value: scope.workflowExecutionId } as const;
|
|
113
119
|
}
|
|
114
|
-
|
|
120
|
+
if ("poolClaimSessionId" in scope) {
|
|
121
|
+
return { case: "poolClaim", value: { sessionId: scope.poolClaimSessionId } } as const;
|
|
122
|
+
}
|
|
123
|
+
return { case: "renewal", value: create(TokenRenewalSchema) } as const;
|
|
115
124
|
}
|
|
116
125
|
|
|
117
126
|
/**
|
|
@@ -156,6 +165,7 @@ export class StigmerClient {
|
|
|
156
165
|
private readonly workflowQuery: Client<typeof WorkflowQueryController>;
|
|
157
166
|
private readonly workflowInstanceQuery: Client<typeof WorkflowInstanceQueryController>;
|
|
158
167
|
private readonly platformQuery: Client<typeof PlatformQueryController>;
|
|
168
|
+
private readonly channelMessageQuery: Client<typeof ChannelMessageQueryController>;
|
|
159
169
|
|
|
160
170
|
private readonly tokenRef: TokenRef | null;
|
|
161
171
|
private readonly runnerTokenRef: TokenRef | null;
|
|
@@ -239,6 +249,7 @@ export class StigmerClient {
|
|
|
239
249
|
this.workflowQuery = createClient(WorkflowQueryController, this.transport);
|
|
240
250
|
this.workflowInstanceQuery = createClient(WorkflowInstanceQueryController, this.transport);
|
|
241
251
|
this.platformQuery = createClient(PlatformQueryController, this.transport);
|
|
252
|
+
this.channelMessageQuery = createClient(ChannelMessageQueryController, this.transport);
|
|
242
253
|
}
|
|
243
254
|
|
|
244
255
|
/**
|
|
@@ -380,6 +391,48 @@ export class StigmerClient {
|
|
|
380
391
|
}
|
|
381
392
|
}
|
|
382
393
|
|
|
394
|
+
/**
|
|
395
|
+
* The agent's serving proactive-messaging channels, as data
|
|
396
|
+
* (proactive-messaging DD-006 D2) — the runner's tool-attachment
|
|
397
|
+
* decision. An empty list is the everyday answer (most agents have no
|
|
398
|
+
* proactive channel).
|
|
399
|
+
*
|
|
400
|
+
* When a scoped runner token is supplied, the read authenticates with
|
|
401
|
+
* it per-call (the {@link getExecutionContextByExecutionId} precedent):
|
|
402
|
+
* the messaging reach refuses the desktop runner's ambient
|
|
403
|
+
* embedded_runner credential outright, and one desktop runner process
|
|
404
|
+
* serves many sessions concurrently, so the credential cannot live in
|
|
405
|
+
* a shared ref. A cloud sandbox runner's ambient credential is already
|
|
406
|
+
* the session-scoped token; OSS sends nothing and answers empty.
|
|
407
|
+
*/
|
|
408
|
+
async listMessagingChannels(scopedToken?: string): Promise<MessagingChannel[]> {
|
|
409
|
+
const res = await this.channelMessageQuery.listMessagingChannels(
|
|
410
|
+
{},
|
|
411
|
+
scopedToken
|
|
412
|
+
? { headers: { authorization: `Bearer ${scopedToken}` } }
|
|
413
|
+
: undefined,
|
|
414
|
+
);
|
|
415
|
+
return res.entries;
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
/**
|
|
419
|
+
* The channel's provider template registry, approved entries only —
|
|
420
|
+
* the `<available_channel_templates>` prompt section's source
|
|
421
|
+
* (proactive-messaging DD-003 D5). Entries carry the DD-006 D1
|
|
422
|
+
* sendability verdict (`unsupportedReason`, empty means sendable);
|
|
423
|
+
* the section formatter filters on it. Credential rules as
|
|
424
|
+
* {@link listMessagingChannels}.
|
|
425
|
+
*/
|
|
426
|
+
async listChannelTemplates(channel: string, scopedToken?: string): Promise<ChannelTemplate[]> {
|
|
427
|
+
const res = await this.channelMessageQuery.listTemplates(
|
|
428
|
+
{ channel, approvedOnly: true },
|
|
429
|
+
scopedToken
|
|
430
|
+
? { headers: { authorization: `Bearer ${scopedToken}` } }
|
|
431
|
+
: undefined,
|
|
432
|
+
);
|
|
433
|
+
return res.entries;
|
|
434
|
+
}
|
|
435
|
+
|
|
383
436
|
async getSession(sessionId: string): Promise<Session> {
|
|
384
437
|
return this.sessionQuery.get({ value: sessionId });
|
|
385
438
|
}
|
package/src/main.ts
CHANGED
|
@@ -213,6 +213,25 @@ async function runPoolMode(
|
|
|
213
213
|
executionMode: config.mode,
|
|
214
214
|
});
|
|
215
215
|
|
|
216
|
+
// Sandbox credential self-renewal (see sandbox-token-renewal.ts). Watches
|
|
217
|
+
// the env var because manager.updateToken keeps it in lockstep with the
|
|
218
|
+
// manager's internal ref: a blank member's pool_sandbox token parks the
|
|
219
|
+
// loop, the claim's updateToken swaps in a renewable session token, and
|
|
220
|
+
// from then on renewal applies fresh tokens back through the same
|
|
221
|
+
// updateToken (which also cascades to the proxy-credential coordinator).
|
|
222
|
+
const { startSandboxTokenRenewal } = await import("./sandbox-token-renewal.js");
|
|
223
|
+
const { StigmerClient } = await import("./client/stigmer-client.js");
|
|
224
|
+
const renewalClient = new StigmerClient({
|
|
225
|
+
endpoint: config.stigmerBackendEndpoint,
|
|
226
|
+
token: null,
|
|
227
|
+
});
|
|
228
|
+
const tokenRenewal = startSandboxTokenRenewal({
|
|
229
|
+
getToken: () => process.env.STIGMER_TOKEN ?? null,
|
|
230
|
+
renew: (currentToken) =>
|
|
231
|
+
renewalClient.getRunnerScopedToken({ renewal: true }, currentToken),
|
|
232
|
+
applyToken: (token) => manager.updateToken(token),
|
|
233
|
+
});
|
|
234
|
+
|
|
216
235
|
let taskQueue: string;
|
|
217
236
|
if (intent.kind === "pool-control") {
|
|
218
237
|
registerPoolMemberContext({
|
|
@@ -277,6 +296,7 @@ async function runPoolMode(
|
|
|
277
296
|
}
|
|
278
297
|
shutdownRequested = true;
|
|
279
298
|
console.warn(`[pool-member] Received ${signal}, shutting down gracefully...`);
|
|
299
|
+
tokenRenewal.stop();
|
|
280
300
|
void manager.shutdown().then(resolve, (err) => {
|
|
281
301
|
console.error("[pool-member] Shutdown failed:", err);
|
|
282
302
|
resolve();
|
package/src/runner.ts
CHANGED
|
@@ -114,6 +114,56 @@ export interface StigmerRunner {
|
|
|
114
114
|
shutdown(): void;
|
|
115
115
|
}
|
|
116
116
|
|
|
117
|
+
/**
|
|
118
|
+
* Wire up self-renewal for a static cloud sandbox's control-plane credential
|
|
119
|
+
* (see sandbox-token-renewal.ts for the model). The applied token reaches
|
|
120
|
+
* every consumer: activity gRPC clients read {@code tokenRef} per request,
|
|
121
|
+
* env-reading call sites (call-llm, registry-endpoint) read
|
|
122
|
+
* {@code process.env.STIGMER_TOKEN} per call, artifact storage resolves the
|
|
123
|
+
* ref per call, and the two Cursor SDK interceptors are updated directly —
|
|
124
|
+
* a static runner has no {@code RunnerTokenCoordinator} minting a separate
|
|
125
|
+
* proxy credential, so its x-stigmer-auth IS this token.
|
|
126
|
+
*/
|
|
127
|
+
async function startStaticSandboxTokenRenewal(
|
|
128
|
+
config: Config,
|
|
129
|
+
tokenRef: { current: string | null },
|
|
130
|
+
): Promise<{ stop(): void } | null> {
|
|
131
|
+
const { isRenewableSandboxToken, startSandboxTokenRenewal } = await import(
|
|
132
|
+
"./sandbox-token-renewal.js"
|
|
133
|
+
);
|
|
134
|
+
// Static mode's credential class never changes (it is baked into the pod's
|
|
135
|
+
// env), so a non-renewable credential — desktop/OSS/local — never starts
|
|
136
|
+
// the loop at all.
|
|
137
|
+
if (!isRenewableSandboxToken(tokenRef.current)) {
|
|
138
|
+
return null;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const { StigmerClient } = await import("./client/stigmer-client.js");
|
|
142
|
+
const { updateInterceptorToken } = await import(
|
|
143
|
+
"./activities/execute-cursor/fetch-interceptor.js"
|
|
144
|
+
);
|
|
145
|
+
const { updateHttp2InterceptorToken } = await import(
|
|
146
|
+
"./activities/execute-cursor/http2-interceptor.js"
|
|
147
|
+
);
|
|
148
|
+
const client = new StigmerClient({
|
|
149
|
+
endpoint: config.stigmerBackendEndpoint,
|
|
150
|
+
token: null,
|
|
151
|
+
tokenRef,
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
return startSandboxTokenRenewal({
|
|
155
|
+
getToken: () => tokenRef.current,
|
|
156
|
+
renew: (currentToken) =>
|
|
157
|
+
client.getRunnerScopedToken({ renewal: true }, currentToken),
|
|
158
|
+
applyToken: (token) => {
|
|
159
|
+
tokenRef.current = token;
|
|
160
|
+
process.env.STIGMER_TOKEN = token;
|
|
161
|
+
updateInterceptorToken(token);
|
|
162
|
+
updateHttp2InterceptorToken(token);
|
|
163
|
+
},
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
|
|
117
167
|
/**
|
|
118
168
|
* Create a Stigmer runner ready to poll a Temporal task queue.
|
|
119
169
|
*
|
|
@@ -198,13 +248,24 @@ export async function createStigmerRunner(
|
|
|
198
248
|
token: options.stigmerToken,
|
|
199
249
|
stigmerEndpoint: baseConfig.stigmerBackendEndpoint,
|
|
200
250
|
});
|
|
251
|
+
// The control-plane credential lives in a shared mutable ref (as in manager
|
|
252
|
+
// mode) so the sandbox-token renewal below can rotate it in-process:
|
|
253
|
+
// activity clients read the ref per request instead of pinning the boot
|
|
254
|
+
// token for the pod's whole life.
|
|
255
|
+
const tokenRef = { current: baseConfig.stigmerToken };
|
|
201
256
|
const config: Config = {
|
|
202
257
|
...baseConfig,
|
|
203
258
|
temporalAddress: coordinates.temporalAddress,
|
|
204
259
|
temporalNamespace: coordinates.temporalNamespace,
|
|
260
|
+
stigmerTokenRef: tokenRef,
|
|
205
261
|
};
|
|
206
262
|
markBoot("bootstrap_resolved");
|
|
207
263
|
|
|
264
|
+
// Cloud sandbox credential self-renewal (no-op for desktop/OSS/local
|
|
265
|
+
// credentials — see the helper). Started before the worker so the first
|
|
266
|
+
// renewal point is scheduled even if the pod boots with a part-used token.
|
|
267
|
+
const tokenRenewal = await startStaticSandboxTokenRenewal(config, tokenRef);
|
|
268
|
+
|
|
208
269
|
const { setExecutionContextRef } = await import(
|
|
209
270
|
"./activities/execute-cursor/rejection-capture.js"
|
|
210
271
|
);
|
|
@@ -242,6 +303,7 @@ export async function createStigmerRunner(
|
|
|
242
303
|
console.log("Worker stopped");
|
|
243
304
|
},
|
|
244
305
|
shutdown() {
|
|
306
|
+
tokenRenewal?.stop();
|
|
245
307
|
worker.shutdown();
|
|
246
308
|
},
|
|
247
309
|
};
|