@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.
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
@@ -28,7 +28,7 @@ describe("loadConfig", () => {
28
28
  expect(config.temporalAddress).toBe("");
29
29
  expect(config.temporalNamespace).toBe("default");
30
30
  expect(config.stigmerBackendEndpoint).toBe("http://localhost:7234");
31
- expect(config.stigmerToken).toBeNull();
31
+ expect(config.stigmerTokenRef.current).toBeNull();
32
32
  expect(config.cursorApiKey).toBe("test-key");
33
33
  expect(config.proxyEndpoint).toBeNull();
34
34
  expect(config.maxConcurrentActivities).toBe(5);
@@ -85,7 +85,7 @@ describe("loadConfig", () => {
85
85
 
86
86
  const config = loadConfig();
87
87
  expect(config.temporalAddress).toBe("");
88
- expect(config.stigmerToken).toBe("token");
88
+ expect(config.stigmerTokenRef.current).toBe("token");
89
89
  });
90
90
 
91
91
  it("requires STIGMER_TOKEN in cloud mode", () => {
@@ -102,18 +102,19 @@ describe("loadConfig", () => {
102
102
  const config = loadConfig();
103
103
 
104
104
  expect(config.proxyEndpoint).toBe("https://proxy.example.com");
105
- expect(config.stigmerToken).toBe("token");
105
+ expect(config.stigmerTokenRef.current).toBe("token");
106
106
  });
107
107
 
108
- it("uses STIGMER_TOKEN as cursorApiKey in proxy mode for BiDi proxy auth", () => {
108
+ it("never copies STIGMER_TOKEN into cursorApiKey in proxy mode: the placeholder the option mappers use, the credential only in the ref", () => {
109
109
  process.env.STIGMER_PROXY_ENDPOINT = "https://api.stigmer.ai";
110
110
  process.env.STIGMER_TOKEN = "eyJhbGciOiJSUzI1NiJ9.test-jwt-token";
111
111
  const config = loadConfig();
112
112
 
113
- expect(config.cursorApiKey).toBe("eyJhbGciOiJSUzI1NiJ9.test-jwt-token");
113
+ expect(config.cursorApiKey).toBe("proxy-managed");
114
+ expect(config.stigmerTokenRef.current).toBe("eyJhbGciOiJSUzI1NiJ9.test-jwt-token");
114
115
  });
115
116
 
116
- it("prefers explicit CURSOR_API_KEY over STIGMER_TOKEN in proxy mode", () => {
117
+ it("keeps an explicit CURSOR_API_KEY in proxy mode", () => {
117
118
  process.env.STIGMER_PROXY_ENDPOINT = "https://api.stigmer.ai";
118
119
  process.env.STIGMER_TOKEN = "stigmer-jwt";
119
120
  process.env.CURSOR_API_KEY = "explicit-cursor-key";
@@ -122,16 +123,6 @@ describe("loadConfig", () => {
122
123
  expect(config.cursorApiKey).toBe("explicit-cursor-key");
123
124
  });
124
125
 
125
- it("falls back to proxy-managed when no token in proxy mode", () => {
126
- process.env.MODE = "local";
127
- process.env.STIGMER_PROXY_ENDPOINT = "http://localhost:9090";
128
- process.env.STIGMER_TOKEN = "token";
129
- const config = loadConfig();
130
-
131
- // With STIGMER_TOKEN present, cursorApiKey should be the token
132
- expect(config.cursorApiKey).toBe("token");
133
- });
134
-
135
126
  it("normalizes bare host:port to http://", () => {
136
127
  process.env.STIGMER_BACKEND_ENDPOINT = "backend:7234";
137
128
  process.env.CURSOR_API_KEY = "test-key";
@@ -1,22 +1,26 @@
1
1
  import { describe, it, expect } from "vitest";
2
2
  import { mapManagerOptionsToConfig } from "../runner-manager.js";
3
3
  import type { RunnerManagerOptions } from "../runner-manager.js";
4
+ import type { TokenRef } from "../config.js";
4
5
 
5
6
  /**
6
7
  * The manager maps its options into a runner {@link Config}. The critical
7
8
  * invariant these tests lock is that execution location (`mode`) is decoupled
8
9
  * from credential transport (`proxyEndpoint`): the desktop runner executes
9
10
  * LOCALLY while still routing Cursor traffic through the proxy. Re-coupling the
10
- * two previously broke local-path workspace sessions.
11
+ * two previously broke local-path workspace sessions. The second invariant:
12
+ * the config carries the root's credential REFS and no credential by value,
13
+ * so a rotation the root writes is what every reader sees (config.ts header).
11
14
  */
12
15
  describe("mapManagerOptionsToConfig", () => {
13
16
  const base: RunnerManagerOptions = {
14
17
  temporalAddress: "localhost:7233",
15
18
  stigmerEndpoint: "http://localhost:7234",
16
19
  };
20
+ const refs = (): [TokenRef, TokenRef] => [{ current: null }, { current: null }];
17
21
 
18
22
  it("defaults mode to local when executionMode is unset", () => {
19
- const config = mapManagerOptionsToConfig(base);
23
+ const config = mapManagerOptionsToConfig(base, ...refs());
20
24
  expect(config.mode).toBe("local");
21
25
  });
22
26
 
@@ -25,7 +29,7 @@ describe("mapManagerOptionsToConfig", () => {
25
29
  ...base,
26
30
  proxyEndpoint: "https://localhost:9090",
27
31
  stigmerToken: "tok",
28
- });
32
+ }, ...refs());
29
33
  expect(config.mode).toBe("local");
30
34
  expect(config.proxyEndpoint).toBe("https://localhost:9090");
31
35
  // Proxy transport still engages independently of execution location.
@@ -48,7 +52,25 @@ describe("mapManagerOptionsToConfig", () => {
48
52
  // After the mint the two refs diverge; the proxy credential follows the mint.
49
53
  runnerTokenRef.current = "minted-runner-token";
50
54
  expect(config.proxyTokenRef?.current).toBe("minted-runner-token");
51
- expect(config.stigmerTokenRef?.current).toBe("control-plane-token");
55
+ expect(config.stigmerTokenRef.current).toBe("control-plane-token");
56
+ });
57
+
58
+ it("carries the control-plane credential as the root's ref and nowhere by value: a rotation the root writes is what a reader sees", () => {
59
+ const tokenRef: TokenRef = { current: "boot-credential" };
60
+ const runnerTokenRef: TokenRef = { current: "boot-credential" };
61
+ const config = mapManagerOptionsToConfig(
62
+ { ...base, proxyEndpoint: "https://localhost:9090", stigmerToken: "boot-credential" },
63
+ tokenRef,
64
+ runnerTokenRef,
65
+ );
66
+ expect(config.stigmerTokenRef).toBe(tokenRef);
67
+ expect(config.cursorApiKey).toBe("proxy-managed");
68
+
69
+ // The pre-mint lockstep the coordinator keeps: one rotation writes both refs.
70
+ tokenRef.current = "session-credential";
71
+ runnerTokenRef.current = "session-credential";
72
+ expect(config.stigmerTokenRef.current).toBe("session-credential");
73
+ expect(JSON.stringify(config)).not.toContain("boot-credential");
52
74
  });
53
75
 
54
76
  it("uses cloud only when executionMode is explicitly cloud", () => {
@@ -57,7 +79,7 @@ describe("mapManagerOptionsToConfig", () => {
57
79
  executionMode: "cloud",
58
80
  proxyEndpoint: "https://proxy.example.com",
59
81
  stigmerToken: "tok",
60
- });
82
+ }, ...refs());
61
83
  expect(config.mode).toBe("cloud");
62
84
  });
63
85
 
@@ -67,7 +89,7 @@ describe("mapManagerOptionsToConfig", () => {
67
89
  executionMode: "local",
68
90
  proxyEndpoint: "https://proxy.example.com",
69
91
  stigmerToken: "tok",
70
- });
92
+ }, ...refs());
71
93
  expect(config.mode).toBe("local");
72
94
  });
73
95
  });
@@ -1,6 +1,7 @@
1
1
  import { describe, it, expect } from "vitest";
2
2
  import { mapOptionsToConfig } from "../runner.js";
3
3
  import type { StigmerRunnerOptions, StigmerRunner } from "../runner.js";
4
+ import type { TokenRef } from "../config.js";
4
5
 
5
6
  /**
6
7
  * Unit tests for the createStigmerRunner factory.
@@ -107,6 +108,9 @@ describe("StigmerRunnerOptions type contract", () => {
107
108
  * credential transport (`proxyEndpoint`): an explicit `executionMode` always
108
109
  * wins, and only when it is unset does `mode` fall back to the proxy-derived
109
110
  * default for backward compatibility. This mirrors `mapManagerOptionsToConfig`.
111
+ * The static root binds ONE ref as both the control-plane and the proxy
112
+ * credential (it mints no separate proxy token), and the config carries no
113
+ * credential by value (config.ts header).
110
114
  */
111
115
  describe("mapOptionsToConfig — execution mode", () => {
112
116
  const base: StigmerRunnerOptions = {
@@ -114,9 +118,10 @@ describe("mapOptionsToConfig — execution mode", () => {
114
118
  temporalAddress: "localhost:7233",
115
119
  stigmerEndpoint: "http://localhost:7234",
116
120
  };
121
+ const refs = (): [TokenRef, TokenRef] => [{ current: null }, { current: null }];
117
122
 
118
123
  it("defaults mode to local with no proxy and no executionMode", () => {
119
- const config = mapOptionsToConfig(base);
124
+ const config = mapOptionsToConfig(base, ...refs());
120
125
  expect(config.mode).toBe("local");
121
126
  });
122
127
 
@@ -125,7 +130,7 @@ describe("mapOptionsToConfig — execution mode", () => {
125
130
  ...base,
126
131
  proxyEndpoint: "https://proxy.example.com",
127
132
  stigmerToken: "tok",
128
- });
133
+ }, ...refs());
129
134
  expect(config.mode).toBe("cloud");
130
135
  // Proxy transport still engages independently of execution location.
131
136
  expect(config.proxyEndpoint).toBe("https://proxy.example.com");
@@ -139,7 +144,7 @@ describe("mapOptionsToConfig — execution mode", () => {
139
144
  executionMode: "local",
140
145
  proxyEndpoint: "https://proxy.example.com",
141
146
  stigmerToken: "tok",
142
- });
147
+ }, ...refs());
143
148
  expect(config.mode).toBe("local");
144
149
  // Transport still routes through the proxy despite local execution.
145
150
  expect(config.proxyEndpoint).toBe("https://proxy.example.com");
@@ -147,9 +152,28 @@ describe("mapOptionsToConfig — execution mode", () => {
147
152
  });
148
153
 
149
154
  it("honors explicit cloud executionMode without a proxy", () => {
150
- const config = mapOptionsToConfig({ ...base, executionMode: "cloud" });
155
+ const config = mapOptionsToConfig({ ...base, executionMode: "cloud" }, ...refs());
151
156
  expect(config.mode).toBe("cloud");
152
157
  });
158
+
159
+ it("binds the one ref as both the control-plane and the proxy credential, and carries no credential by value", () => {
160
+ const tokenRef: TokenRef = { current: "boot-credential" };
161
+ const runnerTokenRef: TokenRef = { current: null };
162
+ const config = mapOptionsToConfig(
163
+ { ...base, proxyEndpoint: "https://proxy.example.com", stigmerToken: "boot-credential" },
164
+ tokenRef,
165
+ runnerTokenRef,
166
+ );
167
+ expect(config.stigmerTokenRef).toBe(tokenRef);
168
+ expect(config.proxyTokenRef).toBe(tokenRef);
169
+ expect(config.stigmerRunnerTokenRef).toBe(runnerTokenRef);
170
+ expect(config.cursorApiKey).toBe("proxy-managed");
171
+
172
+ tokenRef.current = "renewed-credential";
173
+ expect(config.stigmerTokenRef.current).toBe("renewed-credential");
174
+ expect(config.proxyTokenRef?.current).toBe("renewed-credential");
175
+ expect(JSON.stringify(config)).not.toContain("boot-credential");
176
+ });
153
177
  });
154
178
 
155
179
  describe("StigmerRunner type contract", () => {
@@ -21,7 +21,7 @@ import {
21
21
 
22
22
  const config = {
23
23
  stigmerBackendEndpoint: "http://localhost:7234",
24
- stigmerToken: "pool-tok",
24
+ stigmerTokenRef: { current: "pool-tok" },
25
25
  } as unknown as Config;
26
26
 
27
27
  function fakeManager(): PoolAttachTarget & {
@@ -61,18 +61,14 @@ vi.mock("../../client/stigmer-client.js", () => ({
61
61
  })),
62
62
  }));
63
63
 
64
- vi.mock("../../config.js", () => ({
65
- loadConfig: () => ({
66
- stigmerBackendEndpoint: "http://localhost:7234",
67
- stigmerToken: "test-token",
68
- }),
69
- }));
70
-
71
64
  vi.mock("../../shared/heartbeat.js", () => ({
72
65
  startHeartbeat: () => ({ stop: vi.fn() }),
73
66
  }));
74
67
 
75
68
  import { callAgentAction } from "../call-agent.js";
69
+ import { testConfig } from "../../__test-utils__/config-fixture.js";
70
+
71
+ const appConfig = testConfig({ stigmerTokenRef: { current: "test-token" } });
76
72
 
77
73
  describe("CallAgent server contract compliance", () => {
78
74
  beforeEach(() => {
@@ -93,6 +89,7 @@ describe("CallAgent server contract compliance", () => {
93
89
  { agent: "notification-analyst", message: "Analyze cohort data", ...config },
94
90
  { __stigmer_org_id: "tt-demo", ...env },
95
91
  "wfl_parent_123",
92
+ appConfig,
96
93
  );
97
94
  } catch (e: any) {
98
95
  if (e.name !== "CompleteAsyncError") throw e;
@@ -27,10 +27,7 @@ vi.mock("../../client/stigmer-client.js", () => ({
27
27
  })),
28
28
  }));
29
29
 
30
- vi.mock("../../config.js", () => ({
31
- loadConfig: () => ({ stigmerBackendEndpoint: "http://localhost:7234", stigmerToken: "t" }),
32
- }));
33
-
30
+ import { StigmerClient } from "../../client/stigmer-client.js";
34
31
  import {
35
32
  updateWorkflowFileReviewStatus,
36
33
  getAwaitingFileReviewChangeSetIds,
@@ -38,6 +35,10 @@ import {
38
35
  clearWorkflowApprovalStatus,
39
36
  } from "../call-agent-status.js";
40
37
 
38
+ // The client the factory would build from the runner's Config; the mocked
39
+ // constructor above is what it resolves to.
40
+ const client = new StigmerClient({ endpoint: "http://localhost:7234" });
41
+
41
42
  describe("call-agent-status file-review activities", () => {
42
43
  beforeEach(() => {
43
44
  capturedUpdates = [];
@@ -46,7 +47,7 @@ describe("call-agent-status file-review activities", () => {
46
47
 
47
48
  describe("updateWorkflowFileReviewStatus", () => {
48
49
  it("writes a single per-child reference scoped to the child", async () => {
49
- await updateWorkflowFileReviewStatus("wfx_1", "aex_child", ["fcs_1", "fcs_2"]);
50
+ await updateWorkflowFileReviewStatus(client, "wfx_1", "aex_child", ["fcs_1", "fcs_2"]);
50
51
 
51
52
  expect(capturedUpdates).toHaveLength(1);
52
53
  const { executionId, status, options } = capturedUpdates[0];
@@ -59,7 +60,7 @@ describe("call-agent-status file-review activities", () => {
59
60
  });
60
61
 
61
62
  it("empty changeSetIds writes an empty list (scoped clear for the child)", async () => {
62
- await updateWorkflowFileReviewStatus("wfx_1", "aex_child", []);
63
+ await updateWorkflowFileReviewStatus(client, "wfx_1", "aex_child", []);
63
64
 
64
65
  expect(capturedUpdates).toHaveLength(1);
65
66
  const { status, options } = capturedUpdates[0];
@@ -69,8 +70,8 @@ describe("call-agent-status file-review activities", () => {
69
70
  });
70
71
 
71
72
  it("is a no-op when executionId or childExecutionId is missing", async () => {
72
- await updateWorkflowFileReviewStatus("", "aex_child", ["fcs_1"]);
73
- await updateWorkflowFileReviewStatus("wfx_1", "", ["fcs_1"]);
73
+ await updateWorkflowFileReviewStatus(client, "", "aex_child", ["fcs_1"]);
74
+ await updateWorkflowFileReviewStatus(client, "wfx_1", "", ["fcs_1"]);
74
75
  expect(capturedUpdates).toHaveLength(0);
75
76
  });
76
77
  });
@@ -88,17 +89,17 @@ describe("call-agent-status file-review activities", () => {
88
89
  },
89
90
  };
90
91
 
91
- const ids = await getAwaitingFileReviewChangeSetIds("aex_child");
92
+ const ids = await getAwaitingFileReviewChangeSetIds(client, "aex_child");
92
93
  expect(ids).toEqual(["fcs_await1", "fcs_await2"]);
93
94
  });
94
95
 
95
96
  it("returns empty when the child has no file change sets", async () => {
96
97
  mockGetExecutionResult = { status: {} };
97
- expect(await getAwaitingFileReviewChangeSetIds("aex_child")).toEqual([]);
98
+ expect(await getAwaitingFileReviewChangeSetIds(client, "aex_child")).toEqual([]);
98
99
  });
99
100
 
100
101
  it("returns empty (non-fatal) on a missing child id", async () => {
101
- expect(await getAwaitingFileReviewChangeSetIds("")).toEqual([]);
102
+ expect(await getAwaitingFileReviewChangeSetIds(client, "")).toEqual([]);
102
103
  expect(capturedUpdates).toHaveLength(0);
103
104
  });
104
105
  });
@@ -114,7 +115,7 @@ describe("call-agent-status file-review activities", () => {
114
115
  },
115
116
  };
116
117
 
117
- const surfaced = await updateWorkflowTaskApprovalStatus("wfx_1", "task_a", "aex_child");
118
+ const surfaced = await updateWorkflowTaskApprovalStatus(client, "wfx_1", "task_a", "aex_child");
118
119
 
119
120
  expect(surfaced).toBe(true);
120
121
  expect(capturedUpdates).toHaveLength(1);
@@ -131,19 +132,19 @@ describe("call-agent-status file-review activities", () => {
131
132
  // approval card, and the orchestrator deliberately does not retry.
132
133
  mockGetExecutionResult = { status: { pendingApprovals: [] } };
133
134
 
134
- const surfaced = await updateWorkflowTaskApprovalStatus("wfx_1", "task_a", "aex_child");
135
+ const surfaced = await updateWorkflowTaskApprovalStatus(client, "wfx_1", "task_a", "aex_child");
135
136
 
136
137
  expect(surfaced).toBe(false);
137
138
  expect(capturedUpdates).toHaveLength(0);
138
139
  });
139
140
 
140
141
  it("updateWorkflowTaskApprovalStatus is a no-op without a child id", async () => {
141
- expect(await updateWorkflowTaskApprovalStatus("wfx_1", "task_a", "")).toBe(false);
142
+ expect(await updateWorkflowTaskApprovalStatus(client, "wfx_1", "task_a", "")).toBe(false);
142
143
  expect(capturedUpdates).toHaveLength(0);
143
144
  });
144
145
 
145
146
  it("clearWorkflowApprovalStatus scopes the clear to the given child", async () => {
146
- await clearWorkflowApprovalStatus("wfx_1", "aex_child");
147
+ await clearWorkflowApprovalStatus(client, "wfx_1", "aex_child");
147
148
 
148
149
  expect(capturedUpdates).toHaveLength(1);
149
150
  const { status, options } = capturedUpdates[0];
@@ -153,7 +154,7 @@ describe("call-agent-status file-review activities", () => {
153
154
  });
154
155
 
155
156
  it("clearWorkflowApprovalStatus is a no-op without a child id", async () => {
156
- await clearWorkflowApprovalStatus("wfx_1", "");
157
+ await clearWorkflowApprovalStatus(client, "wfx_1", "");
157
158
  expect(capturedUpdates).toHaveLength(0);
158
159
  });
159
160
  });
@@ -30,18 +30,16 @@ vi.mock("../../client/stigmer-client.js", () => ({
30
30
  })),
31
31
  }));
32
32
 
33
- vi.mock("../../config.js", () => ({
34
- loadConfig: () => ({
35
- stigmerBackendEndpoint: "http://localhost:7234",
36
- stigmerToken: "test-token",
37
- }),
38
- }));
39
-
40
33
  vi.mock("../../shared/heartbeat.js", () => ({
41
34
  startHeartbeat: () => ({ stop: vi.fn() }),
42
35
  }));
43
36
 
44
37
  import { callAgentAction } from "../call-agent.js";
38
+ import { testConfig } from "../../__test-utils__/config-fixture.js";
39
+
40
+ // The runner's injected Config, as the factory passes it (the mocked client
41
+ // constructor above is what its refs resolve to).
42
+ const appConfig = testConfig({ stigmerTokenRef: { current: "test-token" } });
45
43
 
46
44
  describe("callAgentAction", () => {
47
45
  beforeEach(() => {
@@ -68,6 +66,7 @@ describe("callAgentAction", () => {
68
66
  { agent: "notification-analyst", message: "Analyze data" },
69
67
  { __stigmer_org_id: "tt-demo" },
70
68
  "wfl_parent123",
69
+ appConfig,
71
70
  ),
72
71
  ).rejects.toThrow("CompleteAsyncError");
73
72
 
@@ -84,6 +83,7 @@ describe("callAgentAction", () => {
84
83
  { agent: "acme/my-agent", message: "Do something" },
85
84
  { __stigmer_org_id: "default-org" },
86
85
  "wfl_parent456",
86
+ appConfig,
87
87
  ),
88
88
  ).rejects.toThrow("CompleteAsyncError");
89
89
 
@@ -100,6 +100,7 @@ describe("callAgentAction", () => {
100
100
  { agent: "explicit-org/my-agent", message: "Hello" },
101
101
  { __stigmer_org_id: "workflow-org" },
102
102
  "wfl_parent789",
103
+ appConfig,
103
104
  ),
104
105
  ).rejects.toThrow("CompleteAsyncError");
105
106
 
@@ -122,6 +123,7 @@ describe("callAgentAction", () => {
122
123
  { agent: "my-agent", message: "Hello" },
123
124
  {},
124
125
  "wfl_parent",
126
+ appConfig,
125
127
  ),
126
128
  ).rejects.toThrow("call:agent requires an organization context");
127
129
  });
@@ -132,6 +134,7 @@ describe("callAgentAction", () => {
132
134
  { agent: "my-agent", message: "Hello" },
133
135
  { __stigmer_org_id: "env-org" },
134
136
  "wfl_parent",
137
+ appConfig,
135
138
  ),
136
139
  ).rejects.toThrow("CompleteAsyncError");
137
140
 
@@ -152,6 +155,7 @@ describe("callAgentAction", () => {
152
155
  { agent: "ghost-agent", message: "Hello" },
153
156
  { __stigmer_org_id: "test-org" },
154
157
  "wfl_parent",
158
+ appConfig,
155
159
  ),
156
160
  ).rejects.toThrow("resolved but has no metadata.id");
157
161
  });
@@ -164,6 +168,7 @@ describe("callAgentAction", () => {
164
168
  { agent: "", message: "Hello" },
165
169
  { __stigmer_org_id: "test-org" },
166
170
  "wfl_parent",
171
+ appConfig,
167
172
  ),
168
173
  ).rejects.toThrow("'agent' resolved to empty");
169
174
  });
@@ -174,6 +179,7 @@ describe("callAgentAction", () => {
174
179
  { agent: "my-agent", message: "" },
175
180
  { __stigmer_org_id: "test-org" },
176
181
  "wfl_parent",
182
+ appConfig,
177
183
  ),
178
184
  ).rejects.toThrow("'message' resolved to empty");
179
185
  });
@@ -186,6 +192,7 @@ describe("callAgentAction", () => {
186
192
  { agent: "my-agent", message: "Hello", harness: "CURSOR" },
187
193
  { __stigmer_org_id: "test-org" },
188
194
  "wfl_parent",
195
+ appConfig,
189
196
  ),
190
197
  ).rejects.toThrow("CompleteAsyncError");
191
198
 
@@ -204,6 +211,7 @@ describe("callAgentAction", () => {
204
211
  { agent: "my-agent", message: "Hello" },
205
212
  { __stigmer_org_id: "test-org" },
206
213
  "wfl_parent",
214
+ appConfig,
207
215
  ),
208
216
  ).rejects.toThrow("CompleteAsyncError");
209
217
 
@@ -218,6 +226,7 @@ describe("callAgentAction", () => {
218
226
  { agent: "my-agent", message: "Review this" },
219
227
  { __stigmer_org_id: "test-org" },
220
228
  "wfl_parent_id",
229
+ appConfig,
221
230
  ),
222
231
  ).rejects.toThrow("CompleteAsyncError");
223
232
 
@@ -243,6 +252,7 @@ describe("callAgentAction", () => {
243
252
  __stigmer_activity_task_queue: "wfexec:wex_abc123",
244
253
  },
245
254
  "wfl_parent",
255
+ appConfig,
246
256
  ),
247
257
  ).rejects.toThrow("CompleteAsyncError");
248
258
 
@@ -259,6 +269,7 @@ describe("callAgentAction", () => {
259
269
  __stigmer_activity_task_queue: "stigmer_runner",
260
270
  },
261
271
  "wfl_parent",
272
+ appConfig,
262
273
  ),
263
274
  ).rejects.toThrow("CompleteAsyncError");
264
275
 
@@ -282,6 +293,7 @@ describe("callAgentAction", () => {
282
293
  },
283
294
  { __stigmer_org_id: "test-org" },
284
295
  "wfl_parent",
296
+ appConfig,
285
297
  ),
286
298
  ).rejects.toThrow("CompleteAsyncError");
287
299
 
@@ -302,6 +314,7 @@ describe("callAgentAction", () => {
302
314
  },
303
315
  { __stigmer_org_id: "test-org" },
304
316
  "wfl_parent",
317
+ appConfig,
305
318
  ),
306
319
  ).rejects.toThrow("CompleteAsyncError");
307
320
 
@@ -323,6 +336,7 @@ describe("callAgentAction", () => {
323
336
  },
324
337
  { __stigmer_org_id: "test-org" },
325
338
  "wfl_parent",
339
+ appConfig,
326
340
  ),
327
341
  ).rejects.toThrow(
328
342
  "call:agent run_config.service_tier 'SERVICE_TIER_TURBO' has no proto mapping",
@@ -335,6 +349,7 @@ describe("callAgentAction", () => {
335
349
  { agent: "my-agent", message: "Hello" },
336
350
  { __stigmer_org_id: "test-org" },
337
351
  "wfl_parent",
352
+ appConfig,
338
353
  ),
339
354
  ).rejects.toThrow("CompleteAsyncError");
340
355
 
@@ -352,6 +367,7 @@ describe("callAgentAction", () => {
352
367
  },
353
368
  { __stigmer_org_id: "test-org" },
354
369
  "wfl_parent",
370
+ appConfig,
355
371
  ),
356
372
  ).rejects.toThrow("CompleteAsyncError");
357
373
 
@@ -379,6 +395,7 @@ describe("callAgentAction", () => {
379
395
  },
380
396
  { __stigmer_org_id: "test-org" },
381
397
  "wfl_parent",
398
+ appConfig,
382
399
  ),
383
400
  ).rejects.toThrow("CompleteAsyncError");
384
401
 
@@ -398,6 +415,7 @@ describe("callAgentAction", () => {
398
415
  { agent: "my-agent", message: "Hello" },
399
416
  { __stigmer_org_id: "test-org" },
400
417
  "wfl_parent",
418
+ appConfig,
401
419
  ),
402
420
  ).rejects.toThrow("CompleteAsyncError");
403
421
 
@@ -416,6 +434,7 @@ describe("callAgentAction", () => {
416
434
  } as any,
417
435
  { __stigmer_org_id: "test-org" },
418
436
  "wfl_parent",
437
+ appConfig,
419
438
  ),
420
439
  ).rejects.toThrow("CompleteAsyncError");
421
440
 
@@ -430,6 +449,7 @@ describe("callAgentAction", () => {
430
449
  { agent: "my-agent", message: "Hello" },
431
450
  { __stigmer_org_id: "test-org" },
432
451
  "wfl_parent",
452
+ appConfig,
433
453
  ),
434
454
  ).rejects.toThrow("CompleteAsyncError");
435
455
 
@@ -463,6 +483,7 @@ describe("callAgentAction", () => {
463
483
  },
464
484
  { __stigmer_org_id: "test-org" },
465
485
  "wfl_parent",
486
+ appConfig,
466
487
  ),
467
488
  ).rejects.toThrow("CompleteAsyncError");
468
489
 
@@ -1,14 +1,15 @@
1
1
  import { describe, it, expect } from "vitest";
2
2
  import { createCallFunctionActivities } from "../call-function.js";
3
+ import { testConfig } from "../../__test-utils__/config-fixture.js";
3
4
 
4
5
  describe("createCallFunctionActivities", () => {
5
6
  it("creates activities object with CallFunction method", () => {
6
- const activities = createCallFunctionActivities();
7
+ const activities = createCallFunctionActivities(testConfig());
7
8
  expect(typeof activities.CallFunction).toBe("function");
8
9
  });
9
10
 
10
11
  it("throws non-retryable error for unknown call type", async () => {
11
- const activities = createCallFunctionActivities();
12
+ const activities = createCallFunctionActivities(testConfig());
12
13
 
13
14
  try {
14
15
  await activities.CallFunction("unknown_function", {}, {}, "exec-1");
@@ -20,7 +21,7 @@ describe("createCallFunctionActivities", () => {
20
21
  });
21
22
 
22
23
  it("throws non-retryable error for call:agent (not yet implemented)", async () => {
23
- const activities = createCallFunctionActivities();
24
+ const activities = createCallFunctionActivities(testConfig());
24
25
 
25
26
  try {
26
27
  await activities.CallFunction("agent", { agent: "code-reviewer" }, {}, "exec-1");
@@ -31,7 +32,7 @@ describe("createCallFunctionActivities", () => {
31
32
  });
32
33
 
33
34
  it("resolves runtime placeholders in config before dispatching", async () => {
34
- const activities = createCallFunctionActivities();
35
+ const activities = createCallFunctionActivities(testConfig());
35
36
 
36
37
  try {
37
38
  await activities.CallFunction(
@@ -726,7 +726,7 @@ function makeConfig() {
726
726
  temporalNamespace: "default",
727
727
  stigmerBackendEndpoint: "http://localhost:7234",
728
728
  mcpBridgeEndpoint: null,
729
- stigmerToken: "test-token",
729
+ stigmerTokenRef: { current: "test-token" },
730
730
  cursorApiKey: "",
731
731
  workspaceRootDir: "/tmp/test",
732
732
  mode: "local" as const,
@@ -902,7 +902,7 @@ function makeConfig() {
902
902
  temporalNamespace: "default",
903
903
  stigmerBackendEndpoint: "http://localhost:7234",
904
904
  mcpBridgeEndpoint: null,
905
- stigmerToken: "test-token",
905
+ stigmerTokenRef: { current: "test-token" },
906
906
  cursorApiKey: "",
907
907
  workspaceRootDir: "/tmp/test",
908
908
  mode: "local" as const,
@@ -84,7 +84,7 @@ function activityUnderTest() {
84
84
  bindHermeticClient(client);
85
85
  const config = {
86
86
  stigmerBackendEndpoint: "http://localhost:0",
87
- stigmerToken: null,
87
+ stigmerTokenRef: { current: null },
88
88
  proxyEndpoint: null,
89
89
  primaryModel: "claude-sonnet-4.5",
90
90
  } as unknown as Config;