@stigmer/runner 3.21.0 → 3.22.1-dev.20260922095755
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/README.md +1 -1
- package/dist/.build-fingerprint +1 -1
- package/dist/activities/attach-session.js +0 -1
- package/dist/activities/attach-session.js.map +1 -1
- package/dist/activities/call-agent-status.d.ts +18 -11
- package/dist/activities/call-agent-status.js +21 -24
- package/dist/activities/call-agent-status.js.map +1 -1
- package/dist/activities/call-agent.d.ts +2 -2
- package/dist/activities/call-agent.js +4 -6
- package/dist/activities/call-agent.js.map +1 -1
- package/dist/activities/call-function.d.ts +4 -2
- package/dist/activities/call-function.js +13 -4
- package/dist/activities/call-function.js.map +1 -1
- package/dist/activities/classify-tool-approvals.js +1 -1
- package/dist/activities/classify-tool-approvals.js.map +1 -1
- package/dist/activities/discover-mcp-server.js +0 -1
- package/dist/activities/discover-mcp-server.js.map +1 -1
- package/dist/activities/emit-event.d.ts +6 -1
- package/dist/activities/emit-event.js +5 -14
- package/dist/activities/emit-event.js.map +1 -1
- package/dist/activities/execute-cursor/__test-utils__/hermetic-cursor.js +1 -1
- package/dist/activities/execute-cursor/__test-utils__/hermetic-cursor.js.map +1 -1
- package/dist/activities/execute-cursor/adapter.js +0 -1
- package/dist/activities/execute-cursor/adapter.js.map +1 -1
- package/dist/activities/execute-cursor/turn-setup.d.ts +1 -1
- package/dist/activities/execute-cursor/turn-setup.js +5 -6
- package/dist/activities/execute-cursor/turn-setup.js.map +1 -1
- package/dist/activities/execute-deep-agent/__test-utils__/hermetic-deep-agent.js +1 -1
- package/dist/activities/execute-deep-agent/__test-utils__/hermetic-deep-agent.js.map +1 -1
- package/dist/activities/execute-deep-agent/adapter.js +1 -1
- package/dist/activities/execute-deep-agent/adapter.js.map +1 -1
- package/dist/activities/execute-deep-agent/turn-setup.d.ts +1 -1
- package/dist/activities/execute-deep-agent/turn-setup.js +5 -3
- package/dist/activities/execute-deep-agent/turn-setup.js.map +1 -1
- package/dist/activities/generate-session-subject.js +1 -2
- package/dist/activities/generate-session-subject.js.map +1 -1
- package/dist/activities/hydrate-workflow-execution.js +0 -1
- package/dist/activities/hydrate-workflow-execution.js.map +1 -1
- package/dist/activities/promote-task-output.d.ts +2 -1
- package/dist/activities/promote-task-output.js +10 -14
- package/dist/activities/promote-task-output.js.map +1 -1
- package/dist/activities/workflow-event-activities.d.ts +9 -7
- package/dist/activities/workflow-event-activities.js +14 -18
- package/dist/activities/workflow-event-activities.js.map +1 -1
- package/dist/client/stigmer-client.d.ts +13 -13
- package/dist/client/stigmer-client.js +4 -7
- package/dist/client/stigmer-client.js.map +1 -1
- package/dist/config.d.ts +40 -20
- package/dist/config.js +25 -9
- package/dist/config.js.map +1 -1
- package/dist/harness/registry.js +0 -1
- package/dist/harness/registry.js.map +1 -1
- package/dist/harness/turn-context.js +2 -4
- package/dist/harness/turn-context.js.map +1 -1
- package/dist/main.js +13 -6
- package/dist/main.js.map +1 -1
- package/dist/runner-manager.d.ts +8 -6
- package/dist/runner-manager.js +20 -10
- package/dist/runner-manager.js.map +1 -1
- package/dist/runner-token-coordinator.d.ts +8 -3
- package/dist/runner-token-coordinator.js +8 -3
- package/dist/runner-token-coordinator.js.map +1 -1
- package/dist/runner.d.ts +10 -2
- package/dist/runner.js +31 -25
- package/dist/runner.js.map +1 -1
- package/dist/shared/artifact-storage.d.ts +6 -6
- package/dist/shared/artifact-storage.js +5 -12
- package/dist/shared/artifact-storage.js.map +1 -1
- package/dist/shared/checkpointer/factory.js +5 -1
- package/dist/shared/checkpointer/factory.js.map +1 -1
- package/dist/shared/checkpointer/http-saver.d.ts +13 -2
- package/dist/shared/checkpointer/http-saver.js +17 -5
- package/dist/shared/checkpointer/http-saver.js.map +1 -1
- package/dist/shared/checkpointer/types.d.ts +10 -1
- package/dist/shared/checkpointer/types.js +16 -0
- package/dist/shared/checkpointer/types.js.map +1 -1
- package/dist/shared/extract-structured-output.js +1 -1
- package/dist/shared/extract-structured-output.js.map +1 -1
- package/package.json +5 -5
- package/src/__test-utils__/config-fixture.ts +2 -2
- package/src/__test-utils__/module-specifiers.ts +45 -3
- package/src/__tests__/config-boundary.test.ts +135 -0
- package/src/__tests__/config.test.ts +7 -16
- package/src/__tests__/runner-manager.test.ts +28 -6
- package/src/__tests__/runner.test.ts +28 -4
- package/src/activities/__tests__/attach-session.test.ts +1 -1
- package/src/activities/__tests__/call-agent-contracts.test.ts +4 -7
- package/src/activities/__tests__/call-agent-status.test.ts +17 -16
- package/src/activities/__tests__/call-agent.test.ts +28 -7
- package/src/activities/__tests__/call-function.test.ts +5 -4
- package/src/activities/__tests__/classify-tool-approvals.test.ts +1 -1
- package/src/activities/__tests__/discover-mcp-server.test.ts +1 -1
- package/src/activities/__tests__/generate-session-subject-wire.test.ts +1 -1
- package/src/activities/__tests__/workflow-event-activities.test.ts +21 -23
- package/src/activities/attach-session.ts +0 -1
- package/src/activities/call-agent-status.ts +28 -20
- package/src/activities/call-agent.ts +5 -6
- package/src/activities/call-function.ts +15 -3
- package/src/activities/classify-tool-approvals.ts +1 -1
- package/src/activities/discover-mcp-server.ts +0 -1
- package/src/activities/emit-event.ts +7 -16
- package/src/activities/execute-cursor/__test-utils__/hermetic-cursor.ts +1 -1
- package/src/activities/execute-cursor/adapter.ts +0 -1
- package/src/activities/execute-cursor/turn-setup.ts +6 -7
- package/src/activities/execute-deep-agent/__test-utils__/hermetic-deep-agent.ts +1 -1
- package/src/activities/execute-deep-agent/__tests__/adapter.test.ts +2 -2
- package/src/activities/execute-deep-agent/__tests__/summarization-verification.test.ts +1 -1
- package/src/activities/execute-deep-agent/adapter.ts +1 -1
- package/src/activities/execute-deep-agent/turn-setup.ts +6 -4
- package/src/activities/generate-session-subject.ts +1 -2
- package/src/activities/hydrate-workflow-execution.ts +0 -1
- package/src/activities/promote-task-output.ts +11 -15
- package/src/activities/workflow-event-activities.ts +17 -18
- package/src/client/__tests__/stigmer-client.test.ts +8 -19
- package/src/client/stigmer-client.ts +18 -20
- package/src/config.ts +51 -23
- package/src/harness/registry.ts +0 -1
- package/src/harness/turn-context.ts +2 -4
- package/src/main.ts +13 -6
- package/src/runner-manager.ts +25 -15
- package/src/runner-token-coordinator.ts +8 -3
- package/src/runner.ts +39 -30
- package/src/shared/__tests__/artifact-storage-extended.test.ts +9 -9
- package/src/shared/__tests__/artifact-storage.test.ts +33 -30
- package/src/shared/__tests__/extract-structured-output.test.ts +2 -2
- package/src/shared/artifact-storage.ts +12 -17
- package/src/shared/checkpointer/__tests__/factory.test.ts +23 -3
- package/src/shared/checkpointer/__tests__/http-saver.test.ts +25 -2
- package/src/shared/checkpointer/factory.ts +5 -1
- package/src/shared/checkpointer/http-saver.ts +20 -6
- package/src/shared/checkpointer/types.ts +12 -1
- package/src/shared/extract-structured-output.ts +1 -1
- package/src/shared/filereview/__tests__/cas-substrate.test.ts +1 -1
- package/src/shared/filereview/__tests__/proxy-reconcile.test.ts +1 -1
- package/src/workflow-engine/__tests__/tasks/emit-event.test.ts +32 -54
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
16
|
import { StigmerClient } from "../client/stigmer-client.js";
|
|
17
|
-
import {
|
|
17
|
+
import type { Config } from "../config.js";
|
|
18
18
|
import { create } from "@bufbuild/protobuf";
|
|
19
19
|
import {
|
|
20
20
|
CreateArtifactInputSchema,
|
|
@@ -44,20 +44,16 @@ export interface ArtifactCreatedEventDescriptor {
|
|
|
44
44
|
occurredAt: string;
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
-
|
|
47
|
+
export function createPromoteTaskOutputActivities(config: Config) {
|
|
48
|
+
// One client for the factory's life, from the runner's injected Config: it
|
|
49
|
+
// reads the live credential ref per request (config.ts header), so holding
|
|
50
|
+
// it is safe across every rotation.
|
|
51
|
+
const client = new StigmerClient({
|
|
52
|
+
endpoint: config.stigmerBackendEndpoint,
|
|
53
|
+
tokenRef: config.stigmerTokenRef,
|
|
54
|
+
runnerTokenRef: config.stigmerRunnerTokenRef,
|
|
55
|
+
});
|
|
48
56
|
|
|
49
|
-
function getClient(): StigmerClient {
|
|
50
|
-
if (!cachedClient) {
|
|
51
|
-
const config = loadConfig();
|
|
52
|
-
cachedClient = new StigmerClient({
|
|
53
|
-
endpoint: config.stigmerBackendEndpoint,
|
|
54
|
-
token: config.stigmerToken,
|
|
55
|
-
});
|
|
56
|
-
}
|
|
57
|
-
return cachedClient;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
export function createPromoteTaskOutputActivities() {
|
|
61
57
|
return {
|
|
62
58
|
/**
|
|
63
59
|
* Check if a task output should be promoted to an artifact. If the
|
|
@@ -107,7 +103,7 @@ export function createPromoteTaskOutputActivities() {
|
|
|
107
103
|
content: new Uint8Array(contentBytes),
|
|
108
104
|
});
|
|
109
105
|
|
|
110
|
-
const artifact = await
|
|
106
|
+
const artifact = await client.createArtifact(input);
|
|
111
107
|
const artifactId = artifact.metadata?.id ?? "";
|
|
112
108
|
|
|
113
109
|
return {
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
17
|
import { StigmerClient } from "../client/stigmer-client.js";
|
|
18
|
-
import {
|
|
18
|
+
import type { Config } from "../config.js";
|
|
19
19
|
import { create, fromJson } from "@bufbuild/protobuf";
|
|
20
20
|
import { ValueSchema } from "@bufbuild/protobuf/wkt";
|
|
21
21
|
import {
|
|
@@ -136,14 +136,6 @@ function toJsonObject(value: unknown): JsonObject | undefined {
|
|
|
136
136
|
}
|
|
137
137
|
}
|
|
138
138
|
|
|
139
|
-
function buildClient(): StigmerClient {
|
|
140
|
-
const config = loadConfig();
|
|
141
|
-
return new StigmerClient({
|
|
142
|
-
endpoint: config.stigmerBackendEndpoint,
|
|
143
|
-
token: config.stigmerToken,
|
|
144
|
-
});
|
|
145
|
-
}
|
|
146
|
-
|
|
147
139
|
/**
|
|
148
140
|
* LEGACY sequence assignment — only for descriptors that arrive without a
|
|
149
141
|
* workflow-assigned `sequenceNumber`, i.e. replays of histories recorded
|
|
@@ -181,12 +173,11 @@ function nextSequence(): bigint {
|
|
|
181
173
|
* Returns a plain number — the value crosses back into the workflow
|
|
182
174
|
* through Temporal's JSON payload converter, which cannot carry BigInt.
|
|
183
175
|
*/
|
|
184
|
-
export async function initSequenceFromEventLog(executionId: string): Promise<number> {
|
|
176
|
+
export async function initSequenceFromEventLog(client: StigmerClient, executionId: string): Promise<number> {
|
|
185
177
|
if (!executionId) {
|
|
186
178
|
sequenceCounter = 0;
|
|
187
179
|
return 0;
|
|
188
180
|
}
|
|
189
|
-
const client = buildClient();
|
|
190
181
|
const highWaterMark = await client.getEventLogHighWaterMark(executionId);
|
|
191
182
|
sequenceCounter = Number(highWaterMark);
|
|
192
183
|
return sequenceCounter;
|
|
@@ -417,13 +408,13 @@ export function toProtoEvent(desc: WorkflowEventDescriptor): WorkflowExecutionEv
|
|
|
417
408
|
* exhausted, so a broken timeline write never fails the run.
|
|
418
409
|
*/
|
|
419
410
|
export async function emitWorkflowEvents(
|
|
411
|
+
client: StigmerClient,
|
|
420
412
|
executionId: string,
|
|
421
413
|
events: WorkflowEventDescriptor[],
|
|
422
414
|
taskStatuses?: TaskStatusEntry[],
|
|
423
415
|
): Promise<void> {
|
|
424
416
|
if (!executionId || events.length === 0) return;
|
|
425
417
|
|
|
426
|
-
const client = buildClient();
|
|
427
418
|
const protoEvents = events.map(toProtoEvent);
|
|
428
419
|
|
|
429
420
|
const protoTasks = (taskStatuses ?? []).map(ts =>
|
|
@@ -507,8 +498,7 @@ function structToPlain(value: JsonObject | undefined): unknown {
|
|
|
507
498
|
* Unlike emitWorkflowEvents (best-effort), this activity propagates
|
|
508
499
|
* errors — recovery context loading is required for correctness.
|
|
509
500
|
*/
|
|
510
|
-
export async function loadRecoveryContext(executionId: string): Promise<RecoveryTaskData[]> {
|
|
511
|
-
const client = buildClient();
|
|
501
|
+
export async function loadRecoveryContext(client: StigmerClient, executionId: string): Promise<RecoveryTaskData[]> {
|
|
512
502
|
const execution = await client.getWorkflowExecution(executionId);
|
|
513
503
|
const tasks = execution.status?.tasks ?? [];
|
|
514
504
|
|
|
@@ -519,10 +509,19 @@ export async function loadRecoveryContext(executionId: string): Promise<Recovery
|
|
|
519
509
|
}));
|
|
520
510
|
}
|
|
521
511
|
|
|
522
|
-
export function createWorkflowEventActivities() {
|
|
512
|
+
export function createWorkflowEventActivities(config: Config) {
|
|
513
|
+
// One client from the runner's injected Config: it reads the live credential
|
|
514
|
+
// ref per request (config.ts header), so a rotation reaches these writes.
|
|
515
|
+
const client = new StigmerClient({
|
|
516
|
+
endpoint: config.stigmerBackendEndpoint,
|
|
517
|
+
tokenRef: config.stigmerTokenRef,
|
|
518
|
+
runnerTokenRef: config.stigmerRunnerTokenRef,
|
|
519
|
+
});
|
|
520
|
+
|
|
523
521
|
return {
|
|
524
|
-
EmitWorkflowEvents:
|
|
525
|
-
|
|
526
|
-
|
|
522
|
+
EmitWorkflowEvents: (executionId: string, events: WorkflowEventDescriptor[], taskStatuses?: TaskStatusEntry[]) =>
|
|
523
|
+
emitWorkflowEvents(client, executionId, events, taskStatuses),
|
|
524
|
+
ResetEventSequence: (executionId: string) => initSequenceFromEventLog(client, executionId),
|
|
525
|
+
LoadRecoveryContext: (executionId: string) => loadRecoveryContext(client, executionId),
|
|
527
526
|
};
|
|
528
527
|
}
|
|
@@ -93,20 +93,22 @@ describe("StigmerClient", () => {
|
|
|
93
93
|
expect(req.header.has("authorization")).toBe(false);
|
|
94
94
|
});
|
|
95
95
|
|
|
96
|
-
it("
|
|
97
|
-
const
|
|
96
|
+
it("a rotation is a write to the shared ref, never a method on the client: the next request carries the new value", async () => {
|
|
97
|
+
const ref: TokenRef = { current: "old" };
|
|
98
|
+
new StigmerClient({ endpoint: "http://localhost", tokenRef: ref });
|
|
98
99
|
|
|
99
|
-
|
|
100
|
+
ref.current = "new-tok";
|
|
100
101
|
|
|
101
102
|
const req = makeRequest();
|
|
102
103
|
await runInterceptor(req);
|
|
103
104
|
expect(req.header.get("authorization")).toBe("Bearer new-tok");
|
|
104
105
|
});
|
|
105
106
|
|
|
106
|
-
it("
|
|
107
|
-
const
|
|
107
|
+
it("a ref rotated to null removes the Authorization header", async () => {
|
|
108
|
+
const ref: TokenRef = { current: "tok" };
|
|
109
|
+
new StigmerClient({ endpoint: "http://localhost", tokenRef: ref });
|
|
108
110
|
|
|
109
|
-
|
|
111
|
+
ref.current = null;
|
|
110
112
|
|
|
111
113
|
const req = makeRequest();
|
|
112
114
|
await runInterceptor(req);
|
|
@@ -623,19 +625,6 @@ describe("StigmerClient", () => {
|
|
|
623
625
|
});
|
|
624
626
|
});
|
|
625
627
|
|
|
626
|
-
describe("updateToken", () => {
|
|
627
|
-
it("does not affect tokenRef-based resolution", async () => {
|
|
628
|
-
const ref: TokenRef = { current: "from-ref" };
|
|
629
|
-
const client = new StigmerClient({ endpoint: "http://localhost", token: null, tokenRef: ref });
|
|
630
|
-
|
|
631
|
-
client.updateToken("from-update");
|
|
632
|
-
|
|
633
|
-
const req = makeRequest();
|
|
634
|
-
await runInterceptor(req);
|
|
635
|
-
expect(req.header.get("authorization")).toBe("Bearer from-ref");
|
|
636
|
-
});
|
|
637
|
-
});
|
|
638
|
-
|
|
639
628
|
describe("transport", () => {
|
|
640
629
|
it("exposes transport as a readonly property", () => {
|
|
641
630
|
const client = new StigmerClient({ endpoint: "http://localhost", token: null });
|
|
@@ -55,6 +55,7 @@ import { ChannelMessageQueryController } from "@stigmer/protos/ai/stigmer/agenti
|
|
|
55
55
|
import type { ChannelTemplate, MessagingChannel } from "@stigmer/protos/ai/stigmer/agentic/agentchannel/v1/message_io_pb";
|
|
56
56
|
import { TOKEN_TYPE_EMBEDDED_RUNNER, tokenTypeOf } from "./token-claims.js";
|
|
57
57
|
import { assertCreateRequirements, assertReferenceRequirements } from "./server-contracts.js";
|
|
58
|
+
import type { TokenRef } from "../config.js";
|
|
58
59
|
import { currentRunCredential } from "../shared/run-credential-store.js";
|
|
59
60
|
|
|
60
61
|
/**
|
|
@@ -151,31 +152,31 @@ function toRunnerScopedTokenOneof(scope: RunnerScopedTokenScope) {
|
|
|
151
152
|
return { case: "renewal", value: create(TokenRenewalSchema) } as const;
|
|
152
153
|
}
|
|
153
154
|
|
|
154
|
-
/**
|
|
155
|
-
|
|
156
|
-
* reads from this on every request, enabling token updates to
|
|
157
|
-
* propagate to all clients sharing the same ref.
|
|
158
|
-
*/
|
|
159
|
-
export interface TokenRef {
|
|
160
|
-
current: string | null;
|
|
161
|
-
}
|
|
155
|
+
/** The runner's shared credential reference (config.ts); re-exported for the client's callers. */
|
|
156
|
+
export type { TokenRef };
|
|
162
157
|
|
|
163
158
|
export interface StigmerClientOptions {
|
|
164
159
|
endpoint: string;
|
|
165
|
-
|
|
166
|
-
|
|
160
|
+
/**
|
|
161
|
+
* A fixed credential, for a client whose credential never rotates (a
|
|
162
|
+
* renewal client that authenticates each call explicitly passes `null`).
|
|
163
|
+
* Runner activities pass {@link tokenRef} instead: the interceptor reads
|
|
164
|
+
* it on every request, so one rotation reaches every client sharing it.
|
|
165
|
+
*/
|
|
166
|
+
token?: string | null;
|
|
167
|
+
tokenRef?: Readonly<TokenRef>;
|
|
167
168
|
/**
|
|
168
169
|
* Optional runner credential (a server-minted token with a runner-class
|
|
169
170
|
* `token_type` claim). When populated, ExecutionContext reads authenticate
|
|
170
171
|
* with this instead of the control-plane token — see the interceptor in the
|
|
171
172
|
* constructor for why the credential differs per service.
|
|
172
173
|
*/
|
|
173
|
-
runnerTokenRef?: TokenRef
|
|
174
|
+
runnerTokenRef?: Readonly<TokenRef>;
|
|
174
175
|
}
|
|
175
176
|
|
|
176
177
|
export class StigmerClient {
|
|
177
178
|
readonly transport: Transport;
|
|
178
|
-
private
|
|
179
|
+
private readonly fixedToken: string | null;
|
|
179
180
|
private readonly executionQuery: Client<typeof AgentExecutionQueryController>;
|
|
180
181
|
private readonly executionCommand: Client<typeof AgentExecutionCommandController>;
|
|
181
182
|
private readonly executionContextQuery: Client<typeof ExecutionContextQueryController>;
|
|
@@ -195,11 +196,11 @@ export class StigmerClient {
|
|
|
195
196
|
private readonly platformQuery: Client<typeof PlatformQueryController>;
|
|
196
197
|
private readonly channelMessageQuery: Client<typeof ChannelMessageQueryController>;
|
|
197
198
|
|
|
198
|
-
private readonly tokenRef: TokenRef | null;
|
|
199
|
-
private readonly runnerTokenRef: TokenRef | null;
|
|
199
|
+
private readonly tokenRef: Readonly<TokenRef> | null;
|
|
200
|
+
private readonly runnerTokenRef: Readonly<TokenRef> | null;
|
|
200
201
|
|
|
201
202
|
constructor(options: StigmerClientOptions) {
|
|
202
|
-
this.
|
|
203
|
+
this.fixedToken = options.token ?? null;
|
|
203
204
|
this.tokenRef = options.tokenRef ?? null;
|
|
204
205
|
this.runnerTokenRef = options.runnerTokenRef ?? null;
|
|
205
206
|
this.transport = createGrpcTransport({
|
|
@@ -274,7 +275,7 @@ export class StigmerClient {
|
|
|
274
275
|
(isProcessBound ? undefined : currentRunCredential())
|
|
275
276
|
?? (usesRunnerCredential ? this.runnerTokenRef?.current : null)
|
|
276
277
|
?? this.tokenRef?.current
|
|
277
|
-
?? this.
|
|
278
|
+
?? this.fixedToken;
|
|
278
279
|
if (token) {
|
|
279
280
|
req.header.set("authorization", `Bearer ${token}`);
|
|
280
281
|
}
|
|
@@ -467,7 +468,7 @@ export class StigmerClient {
|
|
|
467
468
|
}
|
|
468
469
|
// Inspect the same credential chain the EC read's interceptor would use.
|
|
469
470
|
const ambientTokenType = tokenTypeOf(
|
|
470
|
-
this.runnerTokenRef?.current ?? this.tokenRef?.current ?? this.
|
|
471
|
+
this.runnerTokenRef?.current ?? this.tokenRef?.current ?? this.fixedToken,
|
|
471
472
|
);
|
|
472
473
|
const isEmbeddedRunner = ambientTokenType === TOKEN_TYPE_EMBEDDED_RUNNER;
|
|
473
474
|
if (ambientTokenType !== undefined && !isEmbeddedRunner) {
|
|
@@ -762,7 +763,4 @@ export class StigmerClient {
|
|
|
762
763
|
return this.workflowInstanceQuery.get({ value: instanceId });
|
|
763
764
|
}
|
|
764
765
|
|
|
765
|
-
updateToken(token: string | null): void {
|
|
766
|
-
this.currentToken = token;
|
|
767
|
-
}
|
|
768
766
|
}
|
package/src/config.ts
CHANGED
|
@@ -20,6 +20,21 @@
|
|
|
20
20
|
* - CURSOR_API_KEY is NOT required — the proxy injects it.
|
|
21
21
|
* - The fetch interceptor rewrites outbound Cursor SDK requests.
|
|
22
22
|
*
|
|
23
|
+
* The credential is never held by value:
|
|
24
|
+
*
|
|
25
|
+
* The control-plane credential rotates while the process runs (the manager's
|
|
26
|
+
* `updateToken` on a pool claim or a host refresh, the static root's
|
|
27
|
+
* sandbox-token renewal), so `Config` carries it as a shared {@link TokenRef}
|
|
28
|
+
* and nothing else. Every reader resolves `ref.current` at the moment of use;
|
|
29
|
+
* a client that makes many requests over its life holds the ref and reads it
|
|
30
|
+
* per request (the artifact store, the checkpoint saver). The two roots own
|
|
31
|
+
* the writable ref; readers see it through `Readonly`, so a rotation has one
|
|
32
|
+
* writer and no copy to go stale. {@link loadConfig} is the environment
|
|
33
|
+
* boundary and is read once, by `main.ts`: the ref it returns holds the boot
|
|
34
|
+
* capture that the roots turn into the live ref (a test pins that no other
|
|
35
|
+
* module imports it). The only by-value reads are the pool boot's, where the
|
|
36
|
+
* credential the pod BOOTED with is the fact (`main.ts`).
|
|
37
|
+
*
|
|
23
38
|
* Workspace isolation:
|
|
24
39
|
*
|
|
25
40
|
* WORKSPACE_ROOT_DIR must always resolve to a directory that is NOT the
|
|
@@ -58,12 +73,28 @@ export { DEFAULT_WORKSPACE_LOCK_TIMEOUT_MS } from "./shared/workspace/workspace-
|
|
|
58
73
|
import { DEFAULT_WORKSPACE_LOCK_TIMEOUT_MS } from "./shared/workspace/workspace-lock.js";
|
|
59
74
|
import { getRunnerSecret } from "./shared/runner-credential-store.js";
|
|
60
75
|
|
|
76
|
+
/**
|
|
77
|
+
* A shared mutable credential reference. The root that owns a credential
|
|
78
|
+
* writes `current` on every rotation; every reader resolves it at the moment
|
|
79
|
+
* of use, so one write reaches all of them (the per-request reads in the
|
|
80
|
+
* Cursor interceptors, StigmerClient, the artifact store and the checkpoint
|
|
81
|
+
* saver). `Config` hands readers a `Readonly` view: rotation has one writer.
|
|
82
|
+
*/
|
|
83
|
+
export interface TokenRef {
|
|
84
|
+
current: string | null;
|
|
85
|
+
}
|
|
86
|
+
|
|
61
87
|
export interface Config {
|
|
62
88
|
readonly taskQueue: string;
|
|
63
89
|
readonly temporalAddress: string;
|
|
64
90
|
readonly temporalNamespace: string;
|
|
65
91
|
readonly stigmerBackendEndpoint: string;
|
|
66
|
-
|
|
92
|
+
/**
|
|
93
|
+
* The control-plane credential, read per use (the module header). The
|
|
94
|
+
* static root and the manager both rotate it in place; `main.ts`'s
|
|
95
|
+
* `loadConfig` result holds the boot capture the roots start from.
|
|
96
|
+
*/
|
|
97
|
+
readonly stigmerTokenRef: Readonly<TokenRef>;
|
|
67
98
|
/**
|
|
68
99
|
* The MCP bridge endpoint for the runner-synthesized attachments —
|
|
69
100
|
* channel messaging and conversation participation
|
|
@@ -74,14 +105,12 @@ export interface Config {
|
|
|
74
105
|
*/
|
|
75
106
|
readonly mcpBridgeEndpoint: string | null;
|
|
76
107
|
/**
|
|
77
|
-
*
|
|
78
|
-
*
|
|
79
|
-
*
|
|
80
|
-
*
|
|
81
|
-
*
|
|
82
|
-
*
|
|
83
|
-
* Named after the CURSOR_API_KEY env var the SDK reads, though in proxy mode
|
|
84
|
-
* the credential authenticates with the proxy endpoint, not with Cursor.
|
|
108
|
+
* The Cursor API key for direct mode (authenticates with Cursor directly).
|
|
109
|
+
* In proxy mode this is a placeholder (`"proxy-managed"` unless the operator
|
|
110
|
+
* set CURSOR_API_KEY anyway): the credential the SDK transport presents to
|
|
111
|
+
* the proxy is the control-plane credential, read from
|
|
112
|
+
* {@link stigmerTokenRef} per turn by the Cursor adapter, never copied here.
|
|
113
|
+
* Named after the CURSOR_API_KEY env var the SDK reads.
|
|
85
114
|
*/
|
|
86
115
|
readonly cursorApiKey: string;
|
|
87
116
|
readonly workspaceRootDir: string;
|
|
@@ -136,16 +165,14 @@ export interface Config {
|
|
|
136
165
|
* See shared/workspace/workspace-lock.ts for the serialization model.
|
|
137
166
|
*/
|
|
138
167
|
readonly workspaceLockTimeoutMs: number;
|
|
139
|
-
/** Shared mutable token reference for dynamic token updates (manager mode). */
|
|
140
|
-
readonly stigmerTokenRef?: { current: string | null };
|
|
141
168
|
/**
|
|
142
|
-
* Shared
|
|
169
|
+
* Shared reference to the runner credential (the roots bind it): the
|
|
143
170
|
* server-minted runner token once adopted, tracking the control-plane token
|
|
144
171
|
* in lockstep before that. StigmerClient authenticates ExecutionContext
|
|
145
172
|
* reads with it so the server's runner-class decrypt gate recognizes a
|
|
146
173
|
* desktop runner (see stigmer-client.ts).
|
|
147
174
|
*/
|
|
148
|
-
readonly stigmerRunnerTokenRef?:
|
|
175
|
+
readonly stigmerRunnerTokenRef?: Readonly<TokenRef>;
|
|
149
176
|
/**
|
|
150
177
|
* Shared mutable reference to the credential the Cursor proxy transport
|
|
151
178
|
* sends as `x-stigmer-auth` (the fetch and HTTP/2 interceptors read it per
|
|
@@ -158,7 +185,7 @@ export interface Config {
|
|
|
158
185
|
* Required whenever `proxyEndpoint` is set; the Cursor adapter's
|
|
159
186
|
* boot refuses a proxy without it.
|
|
160
187
|
*/
|
|
161
|
-
readonly proxyTokenRef?:
|
|
188
|
+
readonly proxyTokenRef?: Readonly<TokenRef>;
|
|
162
189
|
}
|
|
163
190
|
|
|
164
191
|
export function loadConfig(): Config {
|
|
@@ -186,22 +213,23 @@ export function loadConfig(): Config {
|
|
|
186
213
|
);
|
|
187
214
|
|
|
188
215
|
// Secrets resolve through the credential store, not process.env — the
|
|
189
|
-
// boot capture has already moved them out of the environment (#508).
|
|
216
|
+
// boot capture has already moved them out of the environment (#508). The
|
|
217
|
+
// ref holds the boot capture: this function is read once, by main.ts, and
|
|
218
|
+
// the root it feeds builds the live ref from the value (the module header).
|
|
190
219
|
const stigmerTokenValue = getRunnerSecret("STIGMER_TOKEN");
|
|
191
220
|
if ((mode === "cloud" || proxyActive) && !stigmerTokenValue) {
|
|
192
221
|
throw new Error("Required environment variable STIGMER_TOKEN is not set");
|
|
193
222
|
}
|
|
194
|
-
const
|
|
223
|
+
const stigmerTokenRef: TokenRef = { current: stigmerTokenValue ?? null };
|
|
195
224
|
|
|
196
225
|
const mcpBridgeEndpoint = process.env.STIGMER_MCP_BRIDGE_ENDPOINT ?? null;
|
|
197
226
|
|
|
198
|
-
// In proxy mode
|
|
199
|
-
//
|
|
200
|
-
//
|
|
201
|
-
//
|
|
202
|
-
// api2.cursor.sh unchanged.
|
|
227
|
+
// In proxy mode the SDK's API key is the control-plane credential, which the
|
|
228
|
+
// Cursor adapter reads from the ref per turn (execute-cursor/turn-setup.ts);
|
|
229
|
+
// the placeholder here is the two option mappers' exact value, so the three
|
|
230
|
+
// construction sites agree and no copy of the credential rides in Config.
|
|
203
231
|
const cursorApiKey = proxyActive
|
|
204
|
-
? (getRunnerSecret("CURSOR_API_KEY") ??
|
|
232
|
+
? (getRunnerSecret("CURSOR_API_KEY") ?? "proxy-managed")
|
|
205
233
|
: (getRunnerSecret("CURSOR_API_KEY") ?? "");
|
|
206
234
|
|
|
207
235
|
const workspaceRootDir = resolveWorkspaceRootDir();
|
|
@@ -245,7 +273,7 @@ export function loadConfig(): Config {
|
|
|
245
273
|
temporalAddress,
|
|
246
274
|
temporalNamespace,
|
|
247
275
|
stigmerBackendEndpoint,
|
|
248
|
-
|
|
276
|
+
stigmerTokenRef,
|
|
249
277
|
mcpBridgeEndpoint,
|
|
250
278
|
cursorApiKey,
|
|
251
279
|
workspaceRootDir,
|
package/src/harness/registry.ts
CHANGED
|
@@ -104,7 +104,6 @@ export async function createHarnessActivities(
|
|
|
104
104
|
const { StigmerClient } = await import("../client/stigmer-client.js");
|
|
105
105
|
const client: StigmerClient = new StigmerClient({
|
|
106
106
|
endpoint: config.stigmerBackendEndpoint,
|
|
107
|
-
token: config.stigmerToken,
|
|
108
107
|
tokenRef: config.stigmerTokenRef,
|
|
109
108
|
runnerTokenRef: config.stigmerRunnerTokenRef,
|
|
110
109
|
});
|
|
@@ -724,9 +724,7 @@ export async function resolveMcpServersAndPolicies(
|
|
|
724
724
|
`reads; degrading to the ambient credential: ${err instanceof Error ? err.message : err}`,
|
|
725
725
|
);
|
|
726
726
|
}
|
|
727
|
-
const attachmentCredential = exchangedRunnerToken
|
|
728
|
-
?? config.stigmerTokenRef?.current
|
|
729
|
-
?? config.stigmerToken;
|
|
727
|
+
const attachmentCredential = exchangedRunnerToken ?? config.stigmerTokenRef.current;
|
|
730
728
|
const attachmentEndpoints = {
|
|
731
729
|
bridgeEndpoint: config.mcpBridgeEndpoint,
|
|
732
730
|
credential: attachmentCredential,
|
|
@@ -1004,7 +1002,7 @@ export function resolveStandingContext(
|
|
|
1004
1002
|
const selectRecalledMemories = (): Promise<RecalledMemoriesContent | undefined> => {
|
|
1005
1003
|
memorySelection ??= selectRecalledFacts(spec.recalledMemories, spec.message, {
|
|
1006
1004
|
proxyEndpoint: deps.config.proxyEndpoint,
|
|
1007
|
-
stigmerToken: deps.config.
|
|
1005
|
+
stigmerToken: deps.config.stigmerTokenRef.current,
|
|
1008
1006
|
executionId: deps.input.executionId,
|
|
1009
1007
|
priorReport: execution.status?.recalledMemoriesReport,
|
|
1010
1008
|
}).then((selection) => {
|
package/src/main.ts
CHANGED
|
@@ -81,7 +81,7 @@ async function runManagerMode(config: import("./config.js").Config): Promise<voi
|
|
|
81
81
|
temporalAddress: config.temporalAddress,
|
|
82
82
|
temporalNamespace: config.temporalNamespace,
|
|
83
83
|
stigmerEndpoint: config.stigmerBackendEndpoint,
|
|
84
|
-
stigmerToken: config.
|
|
84
|
+
stigmerToken: config.stigmerTokenRef.current ?? undefined,
|
|
85
85
|
cursorApiKey: config.cursorApiKey || undefined,
|
|
86
86
|
workspaceRootDir: config.workspaceRootDir,
|
|
87
87
|
maxConcurrentActivitiesPerSession: config.maxConcurrentActivities,
|
|
@@ -197,7 +197,14 @@ async function runPoolMode(
|
|
|
197
197
|
// process serves (the control plane rewrites the Secret at claim time, and
|
|
198
198
|
// secretKeyRef env is read only at pod start — so a restart lands here with
|
|
199
199
|
// the post-claim identity). Decide before any expensive boot work.
|
|
200
|
-
|
|
200
|
+
//
|
|
201
|
+
// Read by value on purpose — the one place the runner may: the credential
|
|
202
|
+
// the pod BOOTED with is the fact here. Its class decides the mode, and the
|
|
203
|
+
// attach exchange must authenticate with the pool credential after the
|
|
204
|
+
// claim has rotated the live ref to the session's (pool-member.ts,
|
|
205
|
+
// PoolMemberContext.poolToken). Everything after this line reads the ref.
|
|
206
|
+
const bootToken = config.stigmerTokenRef.current;
|
|
207
|
+
const intent = decidePoolBoot(bootToken);
|
|
201
208
|
if (intent.kind === "invalid") {
|
|
202
209
|
throw new Error(`Pool member ${memberId} cannot boot: ${intent.reason}`);
|
|
203
210
|
}
|
|
@@ -206,7 +213,7 @@ async function runPoolMode(
|
|
|
206
213
|
temporalAddress: config.temporalAddress,
|
|
207
214
|
temporalNamespace: config.temporalNamespace,
|
|
208
215
|
stigmerEndpoint: config.stigmerBackendEndpoint,
|
|
209
|
-
stigmerToken: config.
|
|
216
|
+
stigmerToken: config.stigmerTokenRef.current ?? undefined,
|
|
210
217
|
cursorApiKey: config.cursorApiKey || undefined,
|
|
211
218
|
workspaceRootDir: config.workspaceRootDir,
|
|
212
219
|
maxConcurrentActivitiesPerSession: config.maxConcurrentActivities,
|
|
@@ -243,7 +250,7 @@ async function runPoolMode(
|
|
|
243
250
|
if (intent.kind === "pool-control") {
|
|
244
251
|
registerPoolMemberContext({
|
|
245
252
|
memberId,
|
|
246
|
-
poolToken:
|
|
253
|
+
poolToken: bootToken!,
|
|
247
254
|
manager,
|
|
248
255
|
});
|
|
249
256
|
await manager.addPoolControl(memberId);
|
|
@@ -332,7 +339,7 @@ async function runStaticMode(config: import("./config.js").Config): Promise<void
|
|
|
332
339
|
temporalAddress: config.temporalAddress,
|
|
333
340
|
temporalNamespace: config.temporalNamespace,
|
|
334
341
|
stigmerEndpoint: config.stigmerBackendEndpoint,
|
|
335
|
-
stigmerToken: config.
|
|
342
|
+
stigmerToken: config.stigmerTokenRef.current ?? undefined,
|
|
336
343
|
cursorApiKey: config.cursorApiKey || undefined,
|
|
337
344
|
workspaceRootDir: config.workspaceRootDir,
|
|
338
345
|
maxConcurrentActivities: config.maxConcurrentActivities,
|
|
@@ -495,7 +502,7 @@ async function main(): Promise<void> {
|
|
|
495
502
|
: "static";
|
|
496
503
|
console.warn(
|
|
497
504
|
`[runner] mode=${runnerMode}, proxy=${config.proxyEndpoint ?? "none"}, ` +
|
|
498
|
-
`hasToken=${!!config.
|
|
505
|
+
`hasToken=${!!config.stigmerTokenRef.current}, workspace=${config.workspaceRootDir}, ` +
|
|
499
506
|
`taskQueue=${config.taskQueue}`,
|
|
500
507
|
);
|
|
501
508
|
|
package/src/runner-manager.ts
CHANGED
|
@@ -22,7 +22,7 @@ import {
|
|
|
22
22
|
type InjectedSinks,
|
|
23
23
|
type WorkflowBundleOption,
|
|
24
24
|
} from "@temporalio/worker";
|
|
25
|
-
import type { Config } from "./config.js";
|
|
25
|
+
import type { Config, TokenRef } from "./config.js";
|
|
26
26
|
import { DEFAULT_CURSOR_AGENT_RESOLVE_TIMEOUT_MS, DEFAULT_CURSOR_STREAM_STALL_TIMEOUT_MS, DEFAULT_WORKSPACE_LOCK_TIMEOUT_MS } from "./config.js";
|
|
27
27
|
// Boot marks mirror the static runner's segment names where the work is the
|
|
28
28
|
// same. Inert unless a mode emits the boot timeline (pool mode does; the
|
|
@@ -237,7 +237,7 @@ export async function createStigmerRunnerManager(
|
|
|
237
237
|
// separately by a RunnerTokenCoordinator (created after the harnesses boot)
|
|
238
238
|
// — see that module for the two-writer rationale and the staleness history
|
|
239
239
|
// it guards.
|
|
240
|
-
const tokenRef = { current: options.stigmerToken ?? null };
|
|
240
|
+
const tokenRef: TokenRef = { current: options.stigmerToken ?? null };
|
|
241
241
|
// `runnerTokenRef` mirrors the coordinator's proxy credential for gRPC use:
|
|
242
242
|
// the minted runner token (token_type=embedded_runner) once adopted, tracking
|
|
243
243
|
// the control-plane token in lockstep before that. StigmerClient authenticates
|
|
@@ -246,7 +246,7 @@ export async function createStigmerRunnerManager(
|
|
|
246
246
|
// is the user's own Auth0 token, which the server treats as a browsing user
|
|
247
247
|
// and answers with redacted secrets. Pre-mint the ref equals the control-plane
|
|
248
248
|
// token, which is exactly what the client would fall back to anyway.
|
|
249
|
-
const runnerTokenRef = { current: options.stigmerToken ?? null };
|
|
249
|
+
const runnerTokenRef: TokenRef = { current: options.stigmerToken ?? null };
|
|
250
250
|
const baseConfig = mapManagerOptionsToConfig(options, tokenRef, runnerTokenRef);
|
|
251
251
|
|
|
252
252
|
assertLlmBackendsPreflight(baseConfig.proxyEndpoint);
|
|
@@ -582,11 +582,16 @@ export async function createStigmerRunnerManager(
|
|
|
582
582
|
},
|
|
583
583
|
|
|
584
584
|
updateToken(token: string | null): void {
|
|
585
|
-
// Writes the control-plane credential.
|
|
586
|
-
//
|
|
587
|
-
//
|
|
588
|
-
//
|
|
589
|
-
//
|
|
585
|
+
// Writes the control-plane credential. Every reader follows this one
|
|
586
|
+
// write: the activity clients, the artifact store and the checkpoint
|
|
587
|
+
// saver read `tokenRef` per request, the per-turn model clients read it
|
|
588
|
+
// at build, and nothing holds the value (Config carries the ref alone;
|
|
589
|
+
// config.ts header). The coordinator decides whether the proxy
|
|
590
|
+
// credential follows it: only when no token has been minted (so the
|
|
591
|
+
// pre-mint lockstep is preserved — the in-cluster sandbox case, where
|
|
592
|
+
// nothing is ever minted), never once the runner owns a minted token.
|
|
593
|
+
// See runner-token-coordinator.ts and the staleness changelogs. The
|
|
594
|
+
// credential store is the second leg of the pair (it replaced the
|
|
590
595
|
// process.env.STIGMER_TOKEN write, #508): per-call readers like
|
|
591
596
|
// call-llm and the registry headers resolve the current token there.
|
|
592
597
|
tokenRef.current = token;
|
|
@@ -677,10 +682,16 @@ function validateManagerOptions(options: RunnerManagerOptions): void {
|
|
|
677
682
|
// explicit address nor a token is present, discovery falls back to localhost.
|
|
678
683
|
}
|
|
679
684
|
|
|
685
|
+
/**
|
|
686
|
+
* The runtime `Config` for the manager root. The refs are the root's: it
|
|
687
|
+
* creates them before mapping and writes them on every rotation
|
|
688
|
+
* (`updateToken`, the token coordinator), so a `Config` never carries a
|
|
689
|
+
* credential by value (config.ts header).
|
|
690
|
+
*/
|
|
680
691
|
export function mapManagerOptionsToConfig(
|
|
681
692
|
options: RunnerManagerOptions,
|
|
682
|
-
tokenRef
|
|
683
|
-
runnerTokenRef
|
|
693
|
+
tokenRef: TokenRef,
|
|
694
|
+
runnerTokenRef: TokenRef,
|
|
684
695
|
): Config {
|
|
685
696
|
const proxyActive = !!options.proxyEndpoint;
|
|
686
697
|
|
|
@@ -697,7 +708,6 @@ export function mapManagerOptionsToConfig(
|
|
|
697
708
|
temporalAddress: options.temporalAddress ?? "",
|
|
698
709
|
temporalNamespace: options.temporalNamespace ?? "default",
|
|
699
710
|
stigmerBackendEndpoint: normalizeEndpoint(options.stigmerEndpoint),
|
|
700
|
-
stigmerToken: options.stigmerToken ?? null,
|
|
701
711
|
// Env-only like the env-loaded path (loadConfig): the bridge endpoint
|
|
702
712
|
// is deployment topology, not per-session state.
|
|
703
713
|
mcpBridgeEndpoint: process.env.STIGMER_MCP_BRIDGE_ENDPOINT ?? null,
|
|
@@ -791,14 +801,14 @@ async function createAllActivities(config: Config): Promise<WorkerActivities> {
|
|
|
791
801
|
...createEvaluateExpressionsActivities(),
|
|
792
802
|
...createCallHttpActivities(),
|
|
793
803
|
...createCallGrpcActivities(),
|
|
794
|
-
...createCallFunctionActivities(),
|
|
804
|
+
...createCallFunctionActivities(config),
|
|
795
805
|
...createCallLlmActivities(),
|
|
796
806
|
...createCallAgentActivities(config),
|
|
797
|
-
...createCallAgentStatusActivities(),
|
|
807
|
+
...createCallAgentStatusActivities(config),
|
|
798
808
|
...createRunCommandActivities(),
|
|
799
809
|
...createHydrateWorkflowActivities(config),
|
|
800
|
-
...createWorkflowEventActivities(),
|
|
801
|
-
...createPromoteTaskOutputActivities(),
|
|
810
|
+
...createWorkflowEventActivities(config),
|
|
811
|
+
...createPromoteTaskOutputActivities(config),
|
|
802
812
|
...createAttachSessionActivities(config),
|
|
803
813
|
};
|
|
804
814
|
}
|
|
@@ -7,9 +7,14 @@
|
|
|
7
7
|
* Auth0 token), used for Authorization on control-plane gRPC; and
|
|
8
8
|
* - the proxy token (x-stigmer-auth), used for Cursor-proxy traffic.
|
|
9
9
|
*
|
|
10
|
-
* On most hosts these are the same token.
|
|
11
|
-
*
|
|
12
|
-
*
|
|
10
|
+
* On most hosts these are the same token. When a manager boots WITHOUT a
|
|
11
|
+
* pinned Temporal address (the desktop runner), bootstrap discovery dials the
|
|
12
|
+
* control plane and it mints a dedicated iss=stigmer proxy token; from that
|
|
13
|
+
* point the proxy token is a *separate* credential with its own TTL, owned and
|
|
14
|
+
* refreshed here. An in-cluster sandbox pod pins its Temporal address, so its
|
|
15
|
+
* bootstrap returns before any mint and nothing is ever minted: its proxy
|
|
16
|
+
* credential stays the control-plane credential, in lockstep, for the pod's
|
|
17
|
+
* whole life — the claim of a pool member rotates both with one write.
|
|
13
18
|
*
|
|
14
19
|
* This coordinator exists because the proxy token's freshness was the subject of
|
|
15
20
|
* two production fixes (stigmer-cloud _changelog 2026-05-26 and 2026-06-01):
|