@stigmer/runner 3.22.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.
Files changed (135) hide show
  1. package/README.md +1 -1
  2. package/dist/.build-fingerprint +1 -1
  3. package/dist/activities/attach-session.js +0 -1
  4. package/dist/activities/attach-session.js.map +1 -1
  5. package/dist/activities/call-agent-status.d.ts +18 -11
  6. package/dist/activities/call-agent-status.js +21 -24
  7. package/dist/activities/call-agent-status.js.map +1 -1
  8. package/dist/activities/call-agent.d.ts +2 -2
  9. package/dist/activities/call-agent.js +4 -6
  10. package/dist/activities/call-agent.js.map +1 -1
  11. package/dist/activities/call-function.d.ts +4 -2
  12. package/dist/activities/call-function.js +13 -4
  13. package/dist/activities/call-function.js.map +1 -1
  14. package/dist/activities/classify-tool-approvals.js +1 -1
  15. package/dist/activities/classify-tool-approvals.js.map +1 -1
  16. package/dist/activities/discover-mcp-server.js +0 -1
  17. package/dist/activities/discover-mcp-server.js.map +1 -1
  18. package/dist/activities/emit-event.d.ts +6 -1
  19. package/dist/activities/emit-event.js +5 -14
  20. package/dist/activities/emit-event.js.map +1 -1
  21. package/dist/activities/execute-cursor/__test-utils__/hermetic-cursor.js +1 -1
  22. package/dist/activities/execute-cursor/__test-utils__/hermetic-cursor.js.map +1 -1
  23. package/dist/activities/execute-cursor/adapter.js +0 -1
  24. package/dist/activities/execute-cursor/adapter.js.map +1 -1
  25. package/dist/activities/execute-cursor/turn-setup.d.ts +1 -1
  26. package/dist/activities/execute-cursor/turn-setup.js +5 -6
  27. package/dist/activities/execute-cursor/turn-setup.js.map +1 -1
  28. package/dist/activities/execute-deep-agent/__test-utils__/hermetic-deep-agent.js +1 -1
  29. package/dist/activities/execute-deep-agent/__test-utils__/hermetic-deep-agent.js.map +1 -1
  30. package/dist/activities/execute-deep-agent/adapter.js +1 -1
  31. package/dist/activities/execute-deep-agent/adapter.js.map +1 -1
  32. package/dist/activities/execute-deep-agent/turn-setup.d.ts +1 -1
  33. package/dist/activities/execute-deep-agent/turn-setup.js +5 -3
  34. package/dist/activities/execute-deep-agent/turn-setup.js.map +1 -1
  35. package/dist/activities/generate-session-subject.js +1 -2
  36. package/dist/activities/generate-session-subject.js.map +1 -1
  37. package/dist/activities/hydrate-workflow-execution.js +0 -1
  38. package/dist/activities/hydrate-workflow-execution.js.map +1 -1
  39. package/dist/activities/promote-task-output.d.ts +2 -1
  40. package/dist/activities/promote-task-output.js +10 -14
  41. package/dist/activities/promote-task-output.js.map +1 -1
  42. package/dist/activities/workflow-event-activities.d.ts +9 -7
  43. package/dist/activities/workflow-event-activities.js +14 -18
  44. package/dist/activities/workflow-event-activities.js.map +1 -1
  45. package/dist/client/stigmer-client.d.ts +13 -13
  46. package/dist/client/stigmer-client.js +4 -7
  47. package/dist/client/stigmer-client.js.map +1 -1
  48. package/dist/config.d.ts +40 -20
  49. package/dist/config.js +25 -9
  50. package/dist/config.js.map +1 -1
  51. package/dist/harness/registry.js +0 -1
  52. package/dist/harness/registry.js.map +1 -1
  53. package/dist/harness/turn-context.js +2 -4
  54. package/dist/harness/turn-context.js.map +1 -1
  55. package/dist/main.js +13 -6
  56. package/dist/main.js.map +1 -1
  57. package/dist/runner-manager.d.ts +8 -6
  58. package/dist/runner-manager.js +20 -10
  59. package/dist/runner-manager.js.map +1 -1
  60. package/dist/runner-token-coordinator.d.ts +8 -3
  61. package/dist/runner-token-coordinator.js +8 -3
  62. package/dist/runner-token-coordinator.js.map +1 -1
  63. package/dist/runner.d.ts +10 -2
  64. package/dist/runner.js +31 -25
  65. package/dist/runner.js.map +1 -1
  66. package/dist/shared/artifact-storage.d.ts +6 -6
  67. package/dist/shared/artifact-storage.js +5 -12
  68. package/dist/shared/artifact-storage.js.map +1 -1
  69. package/dist/shared/checkpointer/factory.js +5 -1
  70. package/dist/shared/checkpointer/factory.js.map +1 -1
  71. package/dist/shared/checkpointer/http-saver.d.ts +13 -2
  72. package/dist/shared/checkpointer/http-saver.js +17 -5
  73. package/dist/shared/checkpointer/http-saver.js.map +1 -1
  74. package/dist/shared/checkpointer/types.d.ts +10 -1
  75. package/dist/shared/checkpointer/types.js +16 -0
  76. package/dist/shared/checkpointer/types.js.map +1 -1
  77. package/dist/shared/extract-structured-output.js +1 -1
  78. package/dist/shared/extract-structured-output.js.map +1 -1
  79. package/package.json +5 -5
  80. package/src/__test-utils__/config-fixture.ts +2 -2
  81. package/src/__test-utils__/module-specifiers.ts +45 -3
  82. package/src/__tests__/config-boundary.test.ts +135 -0
  83. package/src/__tests__/config.test.ts +7 -16
  84. package/src/__tests__/runner-manager.test.ts +28 -6
  85. package/src/__tests__/runner.test.ts +28 -4
  86. package/src/activities/__tests__/attach-session.test.ts +1 -1
  87. package/src/activities/__tests__/call-agent-contracts.test.ts +4 -7
  88. package/src/activities/__tests__/call-agent-status.test.ts +17 -16
  89. package/src/activities/__tests__/call-agent.test.ts +28 -7
  90. package/src/activities/__tests__/call-function.test.ts +5 -4
  91. package/src/activities/__tests__/classify-tool-approvals.test.ts +1 -1
  92. package/src/activities/__tests__/discover-mcp-server.test.ts +1 -1
  93. package/src/activities/__tests__/generate-session-subject-wire.test.ts +1 -1
  94. package/src/activities/__tests__/workflow-event-activities.test.ts +21 -23
  95. package/src/activities/attach-session.ts +0 -1
  96. package/src/activities/call-agent-status.ts +28 -20
  97. package/src/activities/call-agent.ts +5 -6
  98. package/src/activities/call-function.ts +15 -3
  99. package/src/activities/classify-tool-approvals.ts +1 -1
  100. package/src/activities/discover-mcp-server.ts +0 -1
  101. package/src/activities/emit-event.ts +7 -16
  102. package/src/activities/execute-cursor/__test-utils__/hermetic-cursor.ts +1 -1
  103. package/src/activities/execute-cursor/adapter.ts +0 -1
  104. package/src/activities/execute-cursor/turn-setup.ts +6 -7
  105. package/src/activities/execute-deep-agent/__test-utils__/hermetic-deep-agent.ts +1 -1
  106. package/src/activities/execute-deep-agent/__tests__/adapter.test.ts +2 -2
  107. package/src/activities/execute-deep-agent/__tests__/summarization-verification.test.ts +1 -1
  108. package/src/activities/execute-deep-agent/adapter.ts +1 -1
  109. package/src/activities/execute-deep-agent/turn-setup.ts +6 -4
  110. package/src/activities/generate-session-subject.ts +1 -2
  111. package/src/activities/hydrate-workflow-execution.ts +0 -1
  112. package/src/activities/promote-task-output.ts +11 -15
  113. package/src/activities/workflow-event-activities.ts +17 -18
  114. package/src/client/__tests__/stigmer-client.test.ts +8 -19
  115. package/src/client/stigmer-client.ts +18 -20
  116. package/src/config.ts +51 -23
  117. package/src/harness/registry.ts +0 -1
  118. package/src/harness/turn-context.ts +2 -4
  119. package/src/main.ts +13 -6
  120. package/src/runner-manager.ts +25 -15
  121. package/src/runner-token-coordinator.ts +8 -3
  122. package/src/runner.ts +39 -30
  123. package/src/shared/__tests__/artifact-storage-extended.test.ts +9 -9
  124. package/src/shared/__tests__/artifact-storage.test.ts +33 -30
  125. package/src/shared/__tests__/extract-structured-output.test.ts +2 -2
  126. package/src/shared/artifact-storage.ts +12 -17
  127. package/src/shared/checkpointer/__tests__/factory.test.ts +23 -3
  128. package/src/shared/checkpointer/__tests__/http-saver.test.ts +25 -2
  129. package/src/shared/checkpointer/factory.ts +5 -1
  130. package/src/shared/checkpointer/http-saver.ts +20 -6
  131. package/src/shared/checkpointer/types.ts +12 -1
  132. package/src/shared/extract-structured-output.ts +1 -1
  133. package/src/shared/filereview/__tests__/cas-substrate.test.ts +1 -1
  134. package/src/shared/filereview/__tests__/proxy-reconcile.test.ts +1 -1
  135. package/src/workflow-engine/__tests__/tasks/emit-event.test.ts +32 -54
@@ -1,5 +1,6 @@
1
1
  import { describe, it, expect, beforeEach, vi } from "vitest";
2
2
  import { toProtoEvent, initSequenceFromEventLog, emitWorkflowEvents, loadRecoveryContext } from "../workflow-event-activities.js";
3
+ import { StigmerClient } from "../../client/stigmer-client.js";
3
4
  import { WorkflowEventType } from "@stigmer/protos/ai/stigmer/agentic/workflowexecution/v1/event_pb";
4
5
  import { WorkflowTaskKind } from "@stigmer/protos/ai/stigmer/agentic/workflow/v1/enum_pb";
5
6
  import { WorkflowTaskStatus } from "@stigmer/protos/ai/stigmer/agentic/workflowexecution/v1/enum_pb";
@@ -19,12 +20,9 @@ vi.mock("../../client/stigmer-client.js", () => ({
19
20
  })),
20
21
  }));
21
22
 
22
- vi.mock("../../config.js", () => ({
23
- loadConfig: () => ({
24
- stigmerBackendEndpoint: "http://localhost:7234",
25
- stigmerToken: "test-token",
26
- }),
27
- }));
23
+ // The client the factory would build from the runner's Config; the mocked
24
+ // constructor above is what it resolves to.
25
+ const client = new StigmerClient({ endpoint: "http://localhost:7234" });
28
26
 
29
27
  const NOW = "2026-05-22T00:00:00.000Z";
30
28
 
@@ -34,7 +32,7 @@ describe("initSequenceFromEventLog", () => {
34
32
  });
35
33
 
36
34
  it("returns 0 and resets the legacy counter when executionId is empty", async () => {
37
- const highWaterMark = await initSequenceFromEventLog("");
35
+ const highWaterMark = await initSequenceFromEventLog(client, "");
38
36
  expect(highWaterMark).toBe(0);
39
37
 
40
38
  const evt = toProtoEvent({
@@ -51,7 +49,7 @@ describe("initSequenceFromEventLog", () => {
51
49
  it("returns 0 when server has no events", async () => {
52
50
  mockGetEventLogHighWaterMark.mockResolvedValue(BigInt(0));
53
51
 
54
- const highWaterMark = await initSequenceFromEventLog("wfx_test-1");
52
+ const highWaterMark = await initSequenceFromEventLog(client, "wfx_test-1");
55
53
  expect(highWaterMark).toBe(0);
56
54
 
57
55
  const evt = toProtoEvent({
@@ -67,7 +65,7 @@ describe("initSequenceFromEventLog", () => {
67
65
  it("returns the high-water mark as a plain number (Temporal payload converter cannot carry BigInt)", async () => {
68
66
  mockGetEventLogHighWaterMark.mockResolvedValue(BigInt(42));
69
67
 
70
- const highWaterMark = await initSequenceFromEventLog("wfx_recovery-1");
68
+ const highWaterMark = await initSequenceFromEventLog(client, "wfx_recovery-1");
71
69
  expect(highWaterMark).toBe(42);
72
70
  expect(typeof highWaterMark).toBe("number");
73
71
  });
@@ -75,7 +73,7 @@ describe("initSequenceFromEventLog", () => {
75
73
  it("seeds the legacy counter so pre-patch replays continue from N+1", async () => {
76
74
  mockGetEventLogHighWaterMark.mockResolvedValue(BigInt(42));
77
75
 
78
- await initSequenceFromEventLog("wfx_recovery-1");
76
+ await initSequenceFromEventLog(client, "wfx_recovery-1");
79
77
 
80
78
  const e1 = toProtoEvent({
81
79
  type: "task_started",
@@ -101,7 +99,7 @@ describe("initSequenceFromEventLog", () => {
101
99
  it("handles large sequence numbers", async () => {
102
100
  mockGetEventLogHighWaterMark.mockResolvedValue(BigInt(999999));
103
101
 
104
- const highWaterMark = await initSequenceFromEventLog("wfx_large-seq");
102
+ const highWaterMark = await initSequenceFromEventLog(client, "wfx_large-seq");
105
103
  expect(highWaterMark).toBe(999999);
106
104
 
107
105
  const evt = toProtoEvent({
@@ -117,13 +115,13 @@ describe("initSequenceFromEventLog", () => {
117
115
  it("propagates server errors", async () => {
118
116
  mockGetEventLogHighWaterMark.mockRejectedValue(new Error("server unavailable"));
119
117
 
120
- await expect(initSequenceFromEventLog("wfx_error-1")).rejects.toThrow("server unavailable");
118
+ await expect(initSequenceFromEventLog(client, "wfx_error-1")).rejects.toThrow("server unavailable");
121
119
  });
122
120
  });
123
121
 
124
122
  describe("toProtoEvent", () => {
125
123
  beforeEach(async () => {
126
- await initSequenceFromEventLog("");
124
+ await initSequenceFromEventLog(client, "");
127
125
  });
128
126
 
129
127
  describe("sequence numbering", () => {
@@ -199,7 +197,7 @@ describe("toProtoEvent", () => {
199
197
  attemptNumber: 1,
200
198
  });
201
199
 
202
- await initSequenceFromEventLog("");
200
+ await initSequenceFromEventLog(client, "");
203
201
 
204
202
  const evt = toProtoEvent({
205
203
  type: "task_started",
@@ -719,7 +717,7 @@ describe("emitWorkflowEvents", () => {
719
717
  });
720
718
 
721
719
  it("returns silently for empty events array", async () => {
722
- await expect(emitWorkflowEvents("exec-1", [])).resolves.toBeUndefined();
720
+ await expect(emitWorkflowEvents(client, "exec-1", [])).resolves.toBeUndefined();
723
721
  expect(mockUpdateStatus).not.toHaveBeenCalled();
724
722
  });
725
723
 
@@ -731,14 +729,14 @@ describe("emitWorkflowEvents", () => {
731
729
  taskKind: "set",
732
730
  attemptNumber: 1,
733
731
  }];
734
- await expect(emitWorkflowEvents("", events)).resolves.toBeUndefined();
732
+ await expect(emitWorkflowEvents(client, "", events)).resolves.toBeUndefined();
735
733
  expect(mockUpdateStatus).not.toHaveBeenCalled();
736
734
  });
737
735
 
738
736
  it("sends workflow-assigned sequence numbers through to the RPC", async () => {
739
737
  mockUpdateStatus.mockResolvedValue({});
740
738
 
741
- await emitWorkflowEvents("exec-1", [{
739
+ await emitWorkflowEvents(client, "exec-1", [{
742
740
  type: "task_started",
743
741
  taskName: "t",
744
742
  occurredAt: NOW,
@@ -765,7 +763,7 @@ describe("emitWorkflowEvents", () => {
765
763
  sequenceNumber: 1,
766
764
  }];
767
765
 
768
- await expect(emitWorkflowEvents("exec-1", events)).rejects.toThrow("server unavailable");
766
+ await expect(emitWorkflowEvents(client, "exec-1", events)).rejects.toThrow("server unavailable");
769
767
  });
770
768
  });
771
769
 
@@ -792,7 +790,7 @@ describe("loadRecoveryContext", () => {
792
790
  },
793
791
  });
794
792
 
795
- const result = await loadRecoveryContext("wfx_test-1");
793
+ const result = await loadRecoveryContext(client, "wfx_test-1");
796
794
 
797
795
  expect(result).toHaveLength(2);
798
796
  expect(result[0]).toEqual({
@@ -810,7 +808,7 @@ describe("loadRecoveryContext", () => {
810
808
  it("returns empty array when execution has no status", async () => {
811
809
  mockGetWorkflowExecution.mockResolvedValue({});
812
810
 
813
- const result = await loadRecoveryContext("wfx_empty-1");
811
+ const result = await loadRecoveryContext(client, "wfx_empty-1");
814
812
  expect(result).toEqual([]);
815
813
  });
816
814
 
@@ -819,7 +817,7 @@ describe("loadRecoveryContext", () => {
819
817
  status: { tasks: [] },
820
818
  });
821
819
 
822
- const result = await loadRecoveryContext("wfx_no-tasks-1");
820
+ const result = await loadRecoveryContext(client, "wfx_no-tasks-1");
823
821
  expect(result).toEqual([]);
824
822
  });
825
823
 
@@ -836,7 +834,7 @@ describe("loadRecoveryContext", () => {
836
834
  },
837
835
  });
838
836
 
839
- const result = await loadRecoveryContext("wfx_statuses-1");
837
+ const result = await loadRecoveryContext(client, "wfx_statuses-1");
840
838
 
841
839
  expect(result[0].status).toBe("started");
842
840
  expect(result[1].status).toBe("completed");
@@ -848,6 +846,6 @@ describe("loadRecoveryContext", () => {
848
846
  it("propagates server errors", async () => {
849
847
  mockGetWorkflowExecution.mockRejectedValue(new Error("server unavailable"));
850
848
 
851
- await expect(loadRecoveryContext("wfx_error-1")).rejects.toThrow("server unavailable");
849
+ await expect(loadRecoveryContext(client, "wfx_error-1")).rejects.toThrow("server unavailable");
852
850
  });
853
851
  });
@@ -37,7 +37,6 @@ import { TimingRecorder, emitTimingLog } from "../shared/cold-start-timing.js";
37
37
  export function createAttachSessionActivities(config: Config) {
38
38
  const client = new StigmerClient({
39
39
  endpoint: config.stigmerBackendEndpoint,
40
- token: config.stigmerToken,
41
40
  tokenRef: config.stigmerTokenRef,
42
41
  runnerTokenRef: config.stigmerRunnerTokenRef,
43
42
  });
@@ -10,22 +10,19 @@
10
10
  * WorkflowExecution when a child agent enters a tool-approval gate.
11
11
  * - ClearWorkflowApprovalStatus: clears pending_approvals when the
12
12
  * child agent activity completes (approvals resolved).
13
+ *
14
+ * The client is the factory's, built once from the runner's injected
15
+ * `Config` so it reads the live credential ref per request (config.ts
16
+ * header); the functions take it as an argument, which is also what a test
17
+ * hands them.
13
18
  */
14
19
 
15
20
  import { StigmerClient } from "../client/stigmer-client.js";
16
- import { loadConfig } from "../config.js";
21
+ import type { Config } from "../config.js";
17
22
  import { create } from "@bufbuild/protobuf";
18
23
  import { WorkflowExecutionStatusSchema, WorkflowPendingApprovalSchema, WorkflowPendingFileReviewSchema } from "@stigmer/protos/ai/stigmer/agentic/workflowexecution/v1/api_pb";
19
24
  import { ToolCallStatus, FileChangeSetStatus } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/enum_pb";
20
25
 
21
- function buildClient(): StigmerClient {
22
- const config = loadConfig();
23
- return new StigmerClient({
24
- endpoint: config.stigmerBackendEndpoint,
25
- token: config.stigmerToken,
26
- });
27
- }
28
-
29
26
  /**
30
27
  * Derives the child's approval gate onto the parent (DD-012: identity-only
31
28
  * signal, derive-from-child). The signal carries only the child execution id;
@@ -40,13 +37,13 @@ function buildClient(): StigmerClient {
40
37
  * retry: surfacing a resolved gate would show users a stale approval card.
41
38
  */
42
39
  export async function updateWorkflowTaskApprovalStatus(
40
+ client: StigmerClient,
43
41
  executionId: string,
44
42
  _taskName: string,
45
43
  childExecutionId: string,
46
44
  ): Promise<boolean> {
47
45
  if (!executionId || !childExecutionId) return false;
48
46
 
49
- const client = buildClient();
50
47
  const child = await client.getExecution(childExecutionId);
51
48
  const pendingApprovals = (child?.status?.pendingApprovals ?? []).map(
52
49
  (approval) =>
@@ -74,12 +71,12 @@ export async function updateWorkflowTaskApprovalStatus(
74
71
  }
75
72
 
76
73
  export async function clearWorkflowApprovalStatus(
74
+ client: StigmerClient,
77
75
  executionId: string,
78
76
  childExecutionId: string,
79
77
  ): Promise<void> {
80
78
  if (!executionId || !childExecutionId) return;
81
79
 
82
- const client = buildClient();
83
80
  const status = create(WorkflowExecutionStatusSchema, {
84
81
  pendingApprovals: [],
85
82
  });
@@ -101,13 +98,13 @@ export async function clearWorkflowApprovalStatus(
101
98
  * child's entry (per-child merge, so parallel siblings are never disturbed).
102
99
  */
103
100
  export async function updateWorkflowFileReviewStatus(
101
+ client: StigmerClient,
104
102
  executionId: string,
105
103
  childExecutionId: string,
106
104
  changeSetIds: string[],
107
105
  ): Promise<void> {
108
106
  if (!executionId || !childExecutionId) return;
109
107
 
110
- const client = buildClient();
111
108
  const pendingFileReviews =
112
109
  changeSetIds.length > 0
113
110
  ? [
@@ -135,12 +132,12 @@ export async function updateWorkflowFileReviewStatus(
135
132
  * (the same reason getAgentExecutionProgress reads status.messages).
136
133
  */
137
134
  export async function getAwaitingFileReviewChangeSetIds(
135
+ client: StigmerClient,
138
136
  childExecutionId: string,
139
137
  ): Promise<string[]> {
140
138
  if (!childExecutionId) return [];
141
139
 
142
140
  try {
143
- const client = buildClient();
144
141
  const execution = await client.getExecution(childExecutionId);
145
142
  const changeSets = execution?.status?.fileChangeSets ?? [];
146
143
  return changeSets
@@ -169,12 +166,12 @@ export interface AgentProgressSummary {
169
166
  }
170
167
 
171
168
  export async function getAgentExecutionProgress(
169
+ client: StigmerClient,
172
170
  childExecutionId: string,
173
171
  ): Promise<AgentProgressSummary | null> {
174
172
  if (!childExecutionId) return null;
175
173
 
176
174
  try {
177
- const client = buildClient();
178
175
  const execution = await client.getExecution(childExecutionId);
179
176
  const status = execution?.status;
180
177
  if (!status) return null;
@@ -206,12 +203,23 @@ export async function getAgentExecutionProgress(
206
203
  }
207
204
  }
208
205
 
209
- export function createCallAgentStatusActivities() {
206
+ export function createCallAgentStatusActivities(config: Config) {
207
+ const client = new StigmerClient({
208
+ endpoint: config.stigmerBackendEndpoint,
209
+ tokenRef: config.stigmerTokenRef,
210
+ runnerTokenRef: config.stigmerRunnerTokenRef,
211
+ });
212
+
210
213
  return {
211
- UpdateWorkflowTaskApprovalStatus: updateWorkflowTaskApprovalStatus,
212
- ClearWorkflowApprovalStatus: clearWorkflowApprovalStatus,
213
- UpdateWorkflowFileReviewStatus: updateWorkflowFileReviewStatus,
214
- GetAwaitingFileReviewChangeSetIds: getAwaitingFileReviewChangeSetIds,
215
- GetAgentExecutionProgress: getAgentExecutionProgress,
214
+ UpdateWorkflowTaskApprovalStatus: (executionId: string, taskName: string, childExecutionId: string) =>
215
+ updateWorkflowTaskApprovalStatus(client, executionId, taskName, childExecutionId),
216
+ ClearWorkflowApprovalStatus: (executionId: string, childExecutionId: string) =>
217
+ clearWorkflowApprovalStatus(client, executionId, childExecutionId),
218
+ UpdateWorkflowFileReviewStatus: (executionId: string, childExecutionId: string, changeSetIds: string[]) =>
219
+ updateWorkflowFileReviewStatus(client, executionId, childExecutionId, changeSetIds),
220
+ GetAwaitingFileReviewChangeSetIds: (childExecutionId: string) =>
221
+ getAwaitingFileReviewChangeSetIds(client, childExecutionId),
222
+ GetAgentExecutionProgress: (childExecutionId: string) =>
223
+ getAgentExecutionProgress(client, childExecutionId),
216
224
  };
217
225
  }
@@ -22,7 +22,7 @@
22
22
  import { randomUUID } from "node:crypto";
23
23
  import { Context, CompleteAsyncError } from "@temporalio/activity";
24
24
  import { StigmerClient } from "../client/stigmer-client.js";
25
- import { loadConfig, type Config } from "../config.js";
25
+ import type { Config } from "../config.js";
26
26
  import { resolveObjectPlaceholders } from "../workflow-engine/resolve.js";
27
27
  import type { AgentCallConfig } from "../workflow-engine/types.js";
28
28
  import { startHeartbeat } from "../shared/heartbeat.js";
@@ -49,7 +49,7 @@ export async function callAgentAction(
49
49
  config: AgentCallConfig,
50
50
  runtimeEnv: Record<string, unknown>,
51
51
  parentWorkflowId: string,
52
- appConfig: Config = loadConfig(),
52
+ appConfig: Config,
53
53
  ): Promise<void> {
54
54
  const taskToken = Context.current().info.taskToken;
55
55
 
@@ -84,16 +84,15 @@ export async function callAgentAction(
84
84
 
85
85
  const client = new StigmerClient({
86
86
  endpoint: appConfig.stigmerBackendEndpoint,
87
- token: appConfig.stigmerToken,
88
87
  tokenRef: appConfig.stigmerTokenRef,
89
88
  // The child-execution create must authenticate as the RUNNER, not the
90
89
  // user: it stamps the workflow lineage labels, which cloud's
91
90
  // reserved-label guard and environment composer accept only from
92
91
  // runner-class callers. The client's credential-selection table routes
93
92
  // exactly that create to this credential; null (OSS/local, no mint)
94
- // falls through to the control-plane token unchanged. The refs live only
95
- // on the runner's INJECTED Config (loadConfig() rebuilds from env and
96
- // never carries them), which is why the factory passes it in.
93
+ // falls through to the control-plane token unchanged. The refs are the
94
+ // runner's injected Config's (config.ts header), which is why the factory
95
+ // passes it in.
97
96
  runnerTokenRef: appConfig.stigmerRunnerTokenRef,
98
97
  });
99
98
 
@@ -17,6 +17,8 @@
17
17
  */
18
18
 
19
19
  import { ApplicationFailure } from "@temporalio/activity";
20
+ import { StigmerClient } from "../client/stigmer-client.js";
21
+ import type { Config } from "../config.js";
20
22
  import { callLlmAction, type LlmCallConfig } from "./call-llm.js";
21
23
  import { callEvalAction, type EvalConfig } from "./call-eval.js";
22
24
  import { emitEventAction, type EmitEventConfig } from "./emit-event.js";
@@ -30,6 +32,7 @@ export async function callFunctionAction(
30
32
  config: Record<string, unknown>,
31
33
  runtimeEnv: Record<string, unknown>,
32
34
  executionId: string,
35
+ client: StigmerClient,
33
36
  ): Promise<unknown> {
34
37
  const resolved = resolveObjectPlaceholders(config, runtimeEnv) as Record<string, unknown>;
35
38
 
@@ -39,7 +42,7 @@ export async function callFunctionAction(
39
42
  case "eval":
40
43
  return callEvalAction(resolved as unknown as EvalConfig, runtimeEnv, executionId);
41
44
  case "emit_event":
42
- return emitEventAction(resolved as unknown as EmitEventConfig, executionId, runtimeEnv);
45
+ return emitEventAction(resolved as unknown as EmitEventConfig, executionId, runtimeEnv, client);
43
46
  case "notification":
44
47
  return notificationAction(resolved as unknown as NotificationConfig, runtimeEnv);
45
48
  case "transform":
@@ -59,7 +62,16 @@ export async function callFunctionAction(
59
62
  }
60
63
  }
61
64
 
62
- export function createCallFunctionActivities() {
65
+ export function createCallFunctionActivities(runnerConfig: Config) {
66
+ // The signal-delivery client for emit_event, one per factory from the
67
+ // runner's injected Config: it reads the live credential ref per request
68
+ // (config.ts header), so a rotation reaches every signal it sends.
69
+ const client = new StigmerClient({
70
+ endpoint: runnerConfig.stigmerBackendEndpoint,
71
+ tokenRef: runnerConfig.stigmerTokenRef,
72
+ runnerTokenRef: runnerConfig.stigmerRunnerTokenRef,
73
+ });
74
+
63
75
  return {
64
76
  CallFunction: async (
65
77
  call: string,
@@ -67,7 +79,7 @@ export function createCallFunctionActivities() {
67
79
  runtimeEnv: Record<string, unknown>,
68
80
  executionId: string,
69
81
  ): Promise<unknown> => {
70
- return callFunctionAction(call, config, runtimeEnv, executionId);
82
+ return callFunctionAction(call, config, runtimeEnv, executionId, client);
71
83
  },
72
84
  };
73
85
  }
@@ -420,7 +420,7 @@ export function createClassifyToolApprovalsActivities(config: Config) {
420
420
  // serve /v1/proxy/model-registry; that fallback does not port here.)
421
421
  return await classifyTools(input, {
422
422
  proxyEndpoint: config.proxyEndpoint,
423
- stigmerToken: config.stigmerToken,
423
+ stigmerToken: config.stigmerTokenRef.current,
424
424
  primaryModel: config.primaryModel,
425
425
  });
426
426
  } finally {
@@ -613,7 +613,6 @@ async function classifyHttpOAuthFailure(
613
613
  export function createDiscoverMcpServerActivities(config: Config) {
614
614
  const stigmerClient = new StigmerClient({
615
615
  endpoint: config.stigmerBackendEndpoint,
616
- token: config.stigmerToken,
617
616
  tokenRef: config.stigmerTokenRef,
618
617
  runnerTokenRef: config.stigmerRunnerTokenRef,
619
618
  });
@@ -21,13 +21,16 @@
21
21
  * holding a different key. The server re-produces the payload, so each
22
22
  * side's codec passes it through.
23
23
  *
24
+ * The signal client is the caller's (`call-function.ts` builds one from the
25
+ * runner's injected `Config`, so it reads the live credential ref per
26
+ * request; config.ts header). This module constructs no client of its own.
27
+ *
24
28
  * CloudEvents spec: https://github.com/cloudevents/spec/blob/v1.0.2/cloudevents/spec.md
25
29
  */
26
30
 
27
31
  import { randomUUID } from "node:crypto";
28
32
  import type { JsonObject } from "@bufbuild/protobuf";
29
- import { StigmerClient } from "../client/stigmer-client.js";
30
- import { loadConfig } from "../config.js";
33
+ import type { StigmerClient } from "../client/stigmer-client.js";
31
34
  import { resolveRuntimePlaceholders } from "../workflow-engine/resolve.js";
32
35
 
33
36
  export interface WebhookDeliveryTarget {
@@ -139,14 +142,6 @@ async function deliverWebhook(
139
142
  }
140
143
  }
141
144
 
142
- function buildClient(): StigmerClient {
143
- const config = loadConfig();
144
- return new StigmerClient({
145
- endpoint: config.stigmerBackendEndpoint,
146
- token: config.stigmerToken,
147
- });
148
- }
149
-
150
145
  async function deliverSignal(
151
146
  envelope: Record<string, unknown>,
152
147
  target: SignalDeliveryTarget,
@@ -194,7 +189,8 @@ async function deliverSignal(
194
189
  export async function emitEventAction(
195
190
  config: EmitEventConfig,
196
191
  executionId: string,
197
- runtimeEnv?: Record<string, unknown>,
192
+ runtimeEnv: Record<string, unknown> | undefined,
193
+ client: StigmerClient,
198
194
  ): Promise<Record<string, unknown>> {
199
195
  if (!config.event) {
200
196
  throw new Error("emit_event: 'event' field is required");
@@ -212,17 +208,12 @@ export async function emitEventAction(
212
208
  const errors: DeliveryError[] = [];
213
209
  const env = runtimeEnv ?? {};
214
210
 
215
- // One client serves all signal targets of this emit; constructed lazily so
216
- // webhook-only emits never pay for a gRPC transport.
217
- let client: StigmerClient | undefined;
218
-
219
211
  for (const target of config.delivery) {
220
212
  let err: DeliveryError | null = null;
221
213
 
222
214
  if ("webhook" in target) {
223
215
  err = await deliverWebhook(envelope, target.webhook, env);
224
216
  } else if ("signal" in target) {
225
- client ??= buildClient();
226
217
  err = await deliverSignal(envelope, target.signal, client);
227
218
  }
228
219
 
@@ -147,7 +147,7 @@ export function hermeticCursorConfig(env: HermeticEnvironment): Config {
147
147
  temporalNamespace: "default",
148
148
  stigmerBackendEndpoint: "http://localhost:7234",
149
149
  mcpBridgeEndpoint: null,
150
- stigmerToken: null,
150
+ stigmerTokenRef: { current: null },
151
151
  cursorApiKey: FIXTURE.cursorApiKey,
152
152
  workspaceRootDir: env.workspaceRootDir,
153
153
  mode: "local",
@@ -66,7 +66,6 @@ export function resolveCursorConfig(config: Config): CursorAdapterConfig {
66
66
  return {
67
67
  proxyEndpoint: config.proxyEndpoint,
68
68
  cursorApiKey: config.cursorApiKey,
69
- stigmerToken: config.stigmerToken,
70
69
  stigmerTokenRef: config.stigmerTokenRef,
71
70
  workspaceRootDir: config.workspaceRootDir,
72
71
  cloudModeEnabled: config.cloudModeEnabled,
@@ -81,7 +81,7 @@ import { installHitlGate, removeHitlGate, type HitlGateHandle } from "./workspac
81
81
  */
82
82
  export type CursorAdapterConfig = Pick<
83
83
  Config,
84
- "proxyEndpoint" | "cursorApiKey" | "stigmerToken" | "stigmerTokenRef" | "workspaceRootDir" | "cloudModeEnabled" | "agentResolveTimeoutMs"
84
+ "proxyEndpoint" | "cursorApiKey" | "stigmerTokenRef" | "workspaceRootDir" | "cloudModeEnabled" | "agentResolveTimeoutMs"
85
85
  >;
86
86
 
87
87
  export type CursorAgentMode = "cloud" | "local";
@@ -309,19 +309,18 @@ export async function resolveEngine(input: TurnInput, sink: TurnSink, config: Cu
309
309
 
310
310
  await sink.reportProgress("Initializing Cursor agent");
311
311
 
312
- // In proxy mode, use the stigmer token as the API key — the proxy
313
- // validates it and injects the real Cursor API key server-side.
314
- // In direct mode, use the user's own CURSOR_API_KEY.
312
+ // In proxy mode, the SDK's API key is the control-plane credential, read
313
+ // from the ref now (per turn) — the proxy validates it and injects the real
314
+ // Cursor API key server-side. In direct mode, the user's own CURSOR_API_KEY.
315
315
  const effectiveApiKey = config.proxyEndpoint
316
- ? (config.stigmerTokenRef?.current ?? config.stigmerToken ?? config.cursorApiKey)
316
+ ? config.stigmerTokenRef.current
317
317
  : config.cursorApiKey;
318
318
  if (!effectiveApiKey || effectiveApiKey === "proxy-managed") {
319
319
  const source = config.proxyEndpoint ? "proxy (STIGMER_TOKEN)" : "direct (CURSOR_API_KEY)";
320
320
  throw new Error(
321
321
  `No Cursor API credential available. Mode=${source}, ` +
322
322
  `proxyEndpoint=${config.proxyEndpoint ?? "unset"}, ` +
323
- `hasStigmerToken=${!!config.stigmerToken}, ` +
324
- `hasTokenRef=${!!config.stigmerTokenRef?.current}`,
323
+ `hasStigmerToken=${!!config.stigmerTokenRef.current}`,
325
324
  );
326
325
  }
327
326
 
@@ -145,7 +145,7 @@ export function hermeticDeepAgentConfig(env: HermeticEnvironment, checkpointerTy
145
145
  temporalNamespace: "default",
146
146
  stigmerBackendEndpoint: "http://localhost:7234",
147
147
  mcpBridgeEndpoint: null,
148
- stigmerToken: null,
148
+ stigmerTokenRef: { current: null },
149
149
  cursorApiKey: "",
150
150
  workspaceRootDir: env.workspaceRootDir,
151
151
  mode: "local",
@@ -25,7 +25,7 @@ const CONFIG: Config = {
25
25
  temporalNamespace: "default",
26
26
  stigmerBackendEndpoint: "http://localhost:7234",
27
27
  mcpBridgeEndpoint: null,
28
- stigmerToken: "tok",
28
+ stigmerTokenRef: { current: "tok" },
29
29
  cursorApiKey: "",
30
30
  workspaceRootDir: "/tmp/adapter-test",
31
31
  mode: "local",
@@ -72,7 +72,7 @@ describe("createDeepAgentAdapter", () => {
72
72
  expect(resolveDeepAgentConfig(CONFIG)).toEqual({
73
73
  checkpointerType: "memory",
74
74
  checkpointerProxyEndpoint: null,
75
- stigmerToken: "tok",
75
+ stigmerTokenRef: { current: "tok" },
76
76
  proxyEndpoint: null,
77
77
  mode: "local",
78
78
  });
@@ -112,7 +112,7 @@ describe("computeSummarizationDefaults", () => {
112
112
  // ── 2. Checkpoint Serialization Roundtrip ───────────────────────────
113
113
 
114
114
  describe("summarization state checkpoint serialization", () => {
115
- const saver = new HttpCheckpointSaver("https://proxy.test", "test-token");
115
+ const saver = new HttpCheckpointSaver("https://proxy.test", { current: "test-token" });
116
116
 
117
117
  it("serializes and deserializes _summarizationEvent through JsonPlusSerializer", async () => {
118
118
  const summarizationEvent = {
@@ -44,7 +44,7 @@ export function resolveDeepAgentConfig(config: Config): DeepAgentAdapterConfig {
44
44
  return {
45
45
  checkpointerType: config.checkpointerType,
46
46
  checkpointerProxyEndpoint: config.checkpointerProxyEndpoint,
47
- stigmerToken: config.stigmerToken,
47
+ stigmerTokenRef: config.stigmerTokenRef,
48
48
  proxyEndpoint: config.proxyEndpoint,
49
49
  mode: config.mode,
50
50
  };
@@ -85,7 +85,7 @@ import { modelHasNativeThinking, transformAndCompileSubagents } from "./subagent
85
85
  */
86
86
  export type DeepAgentAdapterConfig = Pick<
87
87
  Config,
88
- "checkpointerType" | "checkpointerProxyEndpoint" | "stigmerToken" | "proxyEndpoint" | "mode"
88
+ "checkpointerType" | "checkpointerProxyEndpoint" | "stigmerTokenRef" | "proxyEndpoint" | "mode"
89
89
  >;
90
90
 
91
91
  /** The deepagents graph as this module holds it; deepagents exports no stable type for the compiled agent. */
@@ -164,7 +164,7 @@ export async function openCheckpointer(input: TurnInput, sink: TurnSink, config:
164
164
  const checkpointer = await createCheckpointer({
165
165
  type: config.checkpointerType,
166
166
  proxyEndpoint: config.checkpointerProxyEndpoint ?? undefined,
167
- authToken: config.stigmerToken ?? undefined,
167
+ authToken: config.stigmerTokenRef,
168
168
  sqlitePath: config.checkpointerType === "sqlite" ? await ensureCheckpointDbPath(input.sessionId) : undefined,
169
169
  });
170
170
  sink.setupTiming.mark("create_checkpointer");
@@ -340,12 +340,14 @@ export async function buildEngine(
340
340
 
341
341
  // The model. Resolution to the provider API id happens inside
342
342
  // buildChatModel; modelName stays the registry id for pricing, the
343
- // native-thinking heuristic, and sub-agent inheritance.
343
+ // native-thinking heuristic, and sub-agent inheritance. The credential is
344
+ // read from the ref at build, once per turn: a LangChain client carries
345
+ // its headers for its life, so this is the freshest a turn's model can be.
344
346
  const buildModelFor = async (name: string) =>
345
347
  (await buildChatModel({
346
348
  modelName: name,
347
349
  proxyEndpoint: config.proxyEndpoint ?? undefined,
348
- stigmerToken: config.stigmerToken ?? undefined,
350
+ stigmerToken: config.stigmerTokenRef.current ?? undefined,
349
351
  headerScope: { executionId },
350
352
  serviceTier: input.model.serviceTier,
351
353
  })).model;
@@ -376,7 +376,6 @@ export interface GenerateSessionSubjectInput {
376
376
  export function createGenerateSessionSubjectActivities(config: Config) {
377
377
  const client = new StigmerClient({
378
378
  endpoint: config.stigmerBackendEndpoint,
379
- token: config.stigmerToken,
380
379
  tokenRef: config.stigmerTokenRef,
381
380
  runnerTokenRef: config.stigmerRunnerTokenRef,
382
381
  });
@@ -391,7 +390,7 @@ export function createGenerateSessionSubjectActivities(config: Config) {
391
390
  log(`started: execution=${executionId}`);
392
391
  await generateSessionSubject(executionId, client, {
393
392
  proxyEndpoint: config.proxyEndpoint,
394
- stigmerToken: config.stigmerToken,
393
+ stigmerToken: config.stigmerTokenRef.current,
395
394
  primaryModel: config.primaryModel,
396
395
  });
397
396
  } finally {
@@ -31,7 +31,6 @@ export interface HydrateInput {
31
31
  export function createHydrateWorkflowActivities(config: Config) {
32
32
  const client = new StigmerClient({
33
33
  endpoint: config.stigmerBackendEndpoint,
34
- token: config.stigmerToken,
35
34
  tokenRef: config.stigmerTokenRef,
36
35
  runnerTokenRef: config.stigmerRunnerTokenRef,
37
36
  });