@stigmer/runner 3.12.3 → 3.12.5

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 (227) hide show
  1. package/dist/.build-fingerprint +1 -1
  2. package/dist/activities/call-http.js +12 -0
  3. package/dist/activities/call-http.js.map +1 -1
  4. package/dist/activities/call-llm.d.ts +18 -0
  5. package/dist/activities/call-llm.js +56 -2
  6. package/dist/activities/call-llm.js.map +1 -1
  7. package/dist/activities/execute-cursor/agent-session-cache.d.ts +72 -0
  8. package/dist/activities/execute-cursor/agent-session-cache.js +186 -0
  9. package/dist/activities/execute-cursor/agent-session-cache.js.map +1 -0
  10. package/dist/activities/execute-cursor/cursor-mcp-config.d.ts +45 -0
  11. package/dist/activities/execute-cursor/cursor-mcp-config.js +76 -0
  12. package/dist/activities/execute-cursor/cursor-mcp-config.js.map +1 -0
  13. package/dist/activities/execute-cursor/index.d.ts +10 -1
  14. package/dist/activities/execute-cursor/index.js +108 -57
  15. package/dist/activities/execute-cursor/index.js.map +1 -1
  16. package/dist/activities/execute-cursor/prompt-builder.d.ts +13 -2
  17. package/dist/activities/execute-cursor/prompt-builder.js +11 -0
  18. package/dist/activities/execute-cursor/prompt-builder.js.map +1 -1
  19. package/dist/activities/execute-cursor/service-tier.d.ts +5 -15
  20. package/dist/activities/execute-cursor/service-tier.js +5 -21
  21. package/dist/activities/execute-cursor/service-tier.js.map +1 -1
  22. package/dist/activities/execute-cursor/session-lifecycle.d.ts +1 -1
  23. package/dist/activities/execute-cursor/skill-resolver.d.ts +2 -21
  24. package/dist/activities/execute-cursor/skill-resolver.js +5 -108
  25. package/dist/activities/execute-cursor/skill-resolver.js.map +1 -1
  26. package/dist/activities/execute-deep-agent/approval-file-change.d.ts +9 -0
  27. package/dist/activities/execute-deep-agent/approval-file-change.js +2 -1
  28. package/dist/activities/execute-deep-agent/approval-file-change.js.map +1 -1
  29. package/dist/activities/execute-deep-agent/cas-capture-backend.d.ts +18 -1
  30. package/dist/activities/execute-deep-agent/cas-capture-backend.js +20 -3
  31. package/dist/activities/execute-deep-agent/cas-capture-backend.js.map +1 -1
  32. package/dist/activities/execute-deep-agent/index.js +57 -5
  33. package/dist/activities/execute-deep-agent/index.js.map +1 -1
  34. package/dist/activities/execute-deep-agent/prompt-builder.d.ts +9 -0
  35. package/dist/activities/execute-deep-agent/prompt-builder.js +10 -0
  36. package/dist/activities/execute-deep-agent/prompt-builder.js.map +1 -1
  37. package/dist/activities/execute-deep-agent/setup.js +31 -16
  38. package/dist/activities/execute-deep-agent/setup.js.map +1 -1
  39. package/dist/activities/execute-deep-agent/shell-env.d.ts +8 -5
  40. package/dist/activities/execute-deep-agent/shell-env.js +10 -7
  41. package/dist/activities/execute-deep-agent/shell-env.js.map +1 -1
  42. package/dist/activities/execute-deep-agent/subagent-transformer.js +17 -11
  43. package/dist/activities/execute-deep-agent/subagent-transformer.js.map +1 -1
  44. package/dist/activities/execute-deep-agent/subagent-wiring.d.ts +3 -3
  45. package/dist/config.js +10 -5
  46. package/dist/config.js.map +1 -1
  47. package/dist/encryption/config.js +7 -2
  48. package/dist/encryption/config.js.map +1 -1
  49. package/dist/index.d.ts +1 -1
  50. package/dist/index.js +1 -1
  51. package/dist/main.js +12 -6
  52. package/dist/main.js.map +1 -1
  53. package/dist/middleware/path-normalization.d.ts +49 -49
  54. package/dist/middleware/path-normalization.js +73 -84
  55. package/dist/middleware/path-normalization.js.map +1 -1
  56. package/dist/middleware/types.d.ts +3 -3
  57. package/dist/payload-codecs.js +2 -1
  58. package/dist/payload-codecs.js.map +1 -1
  59. package/dist/runner-manager.d.ts +1 -1
  60. package/dist/runner-manager.js +42 -22
  61. package/dist/runner-manager.js.map +1 -1
  62. package/dist/runner.js +30 -6
  63. package/dist/runner.js.map +1 -1
  64. package/dist/shared/args-preview.d.ts +8 -0
  65. package/dist/shared/args-preview.js +14 -3
  66. package/dist/shared/args-preview.js.map +1 -1
  67. package/dist/shared/artifact-storage.d.ts +11 -1
  68. package/dist/shared/artifact-storage.js +10 -1
  69. package/dist/shared/artifact-storage.js.map +1 -1
  70. package/dist/shared/declared-preferences.d.ts +47 -0
  71. package/dist/shared/declared-preferences.js +64 -0
  72. package/dist/shared/declared-preferences.js.map +1 -0
  73. package/dist/shared/fingerprint-secret.d.ts +3 -2
  74. package/dist/shared/fingerprint-secret.js +5 -3
  75. package/dist/shared/fingerprint-secret.js.map +1 -1
  76. package/dist/shared/llm-backend.js +8 -1
  77. package/dist/shared/llm-backend.js.map +1 -1
  78. package/dist/shared/mcp-enabled-tools.d.ts +2 -3
  79. package/dist/shared/mcp-enabled-tools.js +2 -3
  80. package/dist/shared/mcp-enabled-tools.js.map +1 -1
  81. package/dist/shared/mcp-resolver.d.ts +20 -15
  82. package/dist/shared/mcp-resolver.js +11 -12
  83. package/dist/shared/mcp-resolver.js.map +1 -1
  84. package/dist/shared/model-client.d.ts +15 -0
  85. package/dist/shared/model-client.js +57 -13
  86. package/dist/shared/model-client.js.map +1 -1
  87. package/dist/shared/placeholder-resolver.d.ts +9 -2
  88. package/dist/shared/placeholder-resolver.js +9 -2
  89. package/dist/shared/placeholder-resolver.js.map +1 -1
  90. package/dist/shared/plan-mode-permissions.d.ts +26 -46
  91. package/dist/shared/plan-mode-permissions.js +27 -55
  92. package/dist/shared/plan-mode-permissions.js.map +1 -1
  93. package/dist/shared/registry-endpoint.d.ts +5 -0
  94. package/dist/shared/registry-endpoint.js +7 -1
  95. package/dist/shared/registry-endpoint.js.map +1 -1
  96. package/dist/shared/runner-credential-keys.d.ts +26 -1
  97. package/dist/shared/runner-credential-keys.js +34 -1
  98. package/dist/shared/runner-credential-keys.js.map +1 -1
  99. package/dist/shared/runner-credential-store.d.ts +77 -0
  100. package/dist/shared/runner-credential-store.js +111 -0
  101. package/dist/shared/runner-credential-store.js.map +1 -0
  102. package/dist/shared/service-tier.d.ts +55 -0
  103. package/dist/shared/service-tier.js +67 -0
  104. package/dist/shared/service-tier.js.map +1 -0
  105. package/dist/shared/skill-mount.d.ts +89 -0
  106. package/dist/shared/skill-mount.js +142 -0
  107. package/dist/shared/skill-mount.js.map +1 -0
  108. package/dist/shared/skill-writer.d.ts +28 -26
  109. package/dist/shared/skill-writer.js +79 -102
  110. package/dist/shared/skill-writer.js.map +1 -1
  111. package/dist/shared/worker-shutdown.d.ts +67 -0
  112. package/dist/shared/worker-shutdown.js +79 -0
  113. package/dist/shared/worker-shutdown.js.map +1 -0
  114. package/dist/shared/workspace/types.d.ts +3 -2
  115. package/dist/shared/zip-extract.d.ts +10 -3
  116. package/dist/shared/zip-extract.js +10 -3
  117. package/dist/shared/zip-extract.js.map +1 -1
  118. package/dist/workflow-engine/loader.js +38 -1
  119. package/dist/workflow-engine/loader.js.map +1 -1
  120. package/dist/workflow-engine/tasks/call-function.d.ts +14 -0
  121. package/dist/workflow-engine/tasks/call-function.js +49 -5
  122. package/dist/workflow-engine/tasks/call-function.js.map +1 -1
  123. package/dist/workflow-engine/tasks/human-input.js +23 -2
  124. package/dist/workflow-engine/tasks/human-input.js.map +1 -1
  125. package/dist/workflow-engine/types.d.ts +6 -0
  126. package/dist/workflow-engine/types.js.map +1 -1
  127. package/dist/workflows/engine-core.js +36 -8
  128. package/dist/workflows/engine-core.js.map +1 -1
  129. package/package.json +15 -11
  130. package/src/activities/__tests__/call-http.test.ts +36 -0
  131. package/src/activities/__tests__/call-llm.test.ts +77 -0
  132. package/src/activities/call-http.ts +17 -0
  133. package/src/activities/call-llm.ts +78 -2
  134. package/src/activities/execute-cursor/__tests__/agent-session-cache.test.ts +220 -0
  135. package/src/activities/execute-cursor/__tests__/build-prompt.test.ts +50 -0
  136. package/src/activities/execute-cursor/__tests__/cursor-mcp-config.test.ts +111 -0
  137. package/src/activities/execute-cursor/__tests__/service-tier.test.ts +1 -1
  138. package/src/activities/execute-cursor/__tests__/skill-resolver.test.ts +3 -94
  139. package/src/activities/execute-cursor/agent-session-cache.ts +229 -0
  140. package/src/activities/execute-cursor/cursor-mcp-config.ts +107 -0
  141. package/src/activities/execute-cursor/index.ts +129 -55
  142. package/src/activities/execute-cursor/prompt-builder.ts +29 -2
  143. package/src/activities/execute-cursor/service-tier.ts +5 -29
  144. package/src/activities/execute-cursor/session-lifecycle.ts +1 -1
  145. package/src/activities/execute-cursor/skill-resolver.ts +10 -137
  146. package/src/activities/execute-deep-agent/__tests__/approval-file-change.test.ts +15 -0
  147. package/src/activities/execute-deep-agent/__tests__/cas-capture-backend.test.ts +75 -0
  148. package/src/activities/execute-deep-agent/__tests__/hitl-reject.test.ts +1 -0
  149. package/src/activities/execute-deep-agent/__tests__/hitl-resume-approve-all.test.ts +1 -0
  150. package/src/activities/execute-deep-agent/__tests__/hitl-resume-history.test.ts +1 -0
  151. package/src/activities/execute-deep-agent/__tests__/index.test.ts +1 -0
  152. package/src/activities/execute-deep-agent/__tests__/plan-mode-path-normalization.test.ts +42 -29
  153. package/src/activities/execute-deep-agent/__tests__/prompt-builder.test.ts +52 -0
  154. package/src/activities/execute-deep-agent/__tests__/sequential-gate-resume.test.ts +1 -0
  155. package/src/activities/execute-deep-agent/__tests__/subagent-plan-mode-permissions.test.ts +11 -8
  156. package/src/activities/execute-deep-agent/approval-file-change.ts +11 -1
  157. package/src/activities/execute-deep-agent/cas-capture-backend.ts +20 -3
  158. package/src/activities/execute-deep-agent/index.ts +57 -5
  159. package/src/activities/execute-deep-agent/prompt-builder.ts +22 -0
  160. package/src/activities/execute-deep-agent/setup.ts +36 -19
  161. package/src/activities/execute-deep-agent/shell-env.ts +10 -7
  162. package/src/activities/execute-deep-agent/subagent-transformer.ts +19 -12
  163. package/src/activities/execute-deep-agent/subagent-wiring.ts +3 -3
  164. package/src/config.ts +10 -5
  165. package/src/encryption/config.ts +8 -2
  166. package/src/index.ts +1 -1
  167. package/src/main.ts +16 -6
  168. package/src/middleware/__tests__/path-normalization.test.ts +32 -32
  169. package/src/middleware/path-normalization.ts +78 -90
  170. package/src/middleware/types.ts +3 -3
  171. package/src/payload-codecs.ts +2 -1
  172. package/src/runner-manager.ts +55 -22
  173. package/src/runner.ts +39 -6
  174. package/src/shared/__tests__/artifact-storage.test.ts +28 -0
  175. package/src/shared/__tests__/bedrock-seam.test.ts +5 -5
  176. package/src/shared/__tests__/declared-preferences.test.ts +109 -0
  177. package/src/shared/__tests__/foundry-seam.test.ts +5 -5
  178. package/src/shared/__tests__/model-client.test.ts +99 -0
  179. package/src/shared/__tests__/plan-mode-permissions.test.ts +15 -44
  180. package/src/shared/__tests__/runner-credential-store.test.ts +155 -0
  181. package/src/shared/__tests__/skill-mount.test.ts +238 -0
  182. package/src/shared/__tests__/skill-writer.test.ts +151 -128
  183. package/src/shared/__tests__/vertex-adapter.test.ts +30 -3
  184. package/src/shared/__tests__/vertex-seam.test.ts +5 -5
  185. package/src/shared/__tests__/worker-shutdown.test.ts +101 -0
  186. package/src/shared/__tests__/zip-extract.test.ts +46 -11
  187. package/src/shared/args-preview.ts +17 -3
  188. package/src/shared/artifact-storage.ts +20 -1
  189. package/src/shared/declared-preferences.ts +84 -0
  190. package/src/shared/fingerprint-secret.ts +5 -3
  191. package/src/shared/llm-backend.ts +7 -1
  192. package/src/shared/mcp-enabled-tools.ts +2 -3
  193. package/src/shared/mcp-resolver.ts +20 -20
  194. package/src/shared/model-client.ts +76 -13
  195. package/src/shared/placeholder-resolver.ts +9 -2
  196. package/src/shared/plan-mode-permissions.ts +27 -58
  197. package/src/shared/registry-endpoint.ts +9 -1
  198. package/src/shared/runner-credential-keys.ts +36 -1
  199. package/src/shared/runner-credential-store.ts +115 -0
  200. package/src/shared/service-tier.ts +78 -0
  201. package/src/shared/skill-mount.ts +179 -0
  202. package/src/shared/skill-writer.ts +96 -130
  203. package/src/shared/worker-shutdown.ts +99 -0
  204. package/src/shared/workspace/types.ts +3 -2
  205. package/src/shared/zip-extract.ts +14 -7
  206. package/src/workflow-engine/__tests__/golden-execution.test.ts +20 -1
  207. package/src/workflow-engine/__tests__/loader.test.ts +51 -0
  208. package/src/workflow-engine/__tests__/tasks/call-function.test.ts +105 -0
  209. package/src/workflow-engine/__tests__/tasks/human-input.test.ts +117 -0
  210. package/src/workflow-engine/loader.ts +46 -1
  211. package/src/workflow-engine/tasks/call-function.ts +74 -13
  212. package/src/workflow-engine/tasks/human-input.ts +33 -5
  213. package/src/workflow-engine/types.ts +6 -0
  214. package/src/workflows/engine-core.ts +39 -8
  215. package/dist/activities/execute-cursor/connect-backfill.d.ts +0 -19
  216. package/dist/activities/execute-cursor/connect-backfill.js +0 -27
  217. package/dist/activities/execute-cursor/connect-backfill.js.map +0 -1
  218. package/dist/activities/execute-cursor/mcp-resolver.d.ts +0 -120
  219. package/dist/activities/execute-cursor/mcp-resolver.js +0 -194
  220. package/dist/activities/execute-cursor/mcp-resolver.js.map +0 -1
  221. package/dist/activities/execute-cursor/placeholder-resolver.d.ts +0 -34
  222. package/dist/activities/execute-cursor/placeholder-resolver.js +0 -82
  223. package/dist/activities/execute-cursor/placeholder-resolver.js.map +0 -1
  224. package/src/activities/execute-cursor/__tests__/mcp-resolver.test.ts +0 -125
  225. package/src/activities/execute-cursor/connect-backfill.ts +0 -52
  226. package/src/activities/execute-cursor/mcp-resolver.ts +0 -324
  227. package/src/activities/execute-cursor/placeholder-resolver.ts +0 -109
@@ -4,13 +4,13 @@
4
4
  *
5
5
  * This is NOT a unit test of our code (there is no production vertex code
6
6
  * yet). It pins the exact cross-package behavior production will rely on:
7
- * the REAL `ChatAnthropic` (@langchain/anthropic, bundling @anthropic-ai/sdk
8
- * 0.95.x) driving the REAL `AnthropicVertex` client (@anthropic-ai/vertex-sdk,
9
- * bundling its own nested @anthropic-ai/sdk >=0.115). If a future bump of
7
+ * the REAL `ChatAnthropic` (@langchain/anthropic) driving the REAL
8
+ * `AnthropicVertex` client (@anthropic-ai/vertex-sdk), both resolving the
9
+ * single override-pinned @anthropic-ai/sdk copy. If a future bump of
10
10
  * either side changes request shaping, streaming event handling, tool-call
11
11
  * assembly, or usage accounting across this seam, this suite fails in CI
12
- * instead of production. See scripts/check-langchain-deps.sh for why two
13
- * @anthropic-ai/sdk copies coexist and when they collapse to one.
12
+ * instead of production. See scripts/check-langchain-deps.sh for the
13
+ * override rationale and the single-copy invariant it guards.
14
14
  *
15
15
  * Determinism: zero credentials, zero network. Google auth is bypassed by
16
16
  * injecting a fake `authClient` (the SDK's supported constructor option —
@@ -0,0 +1,101 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import {
3
+ classifyTurnInterruption,
4
+ getShutdownSignalForQueue,
5
+ registerWorkerShutdownSignal,
6
+ signalWorkerShutdown,
7
+ unregisterWorkerShutdownSignal,
8
+ } from "../worker-shutdown.js";
9
+
10
+ describe("worker-shutdown signal registry", () => {
11
+ it("registers a live signal an activity can resolve by queue name", () => {
12
+ const controller = registerWorkerShutdownSignal("session:reg-1");
13
+ const signal = getShutdownSignalForQueue("session:reg-1");
14
+ expect(signal).toBe(controller.signal);
15
+ expect(signal?.aborted).toBe(false);
16
+ unregisterWorkerShutdownSignal("session:reg-1");
17
+ });
18
+
19
+ it("aborting via signalWorkerShutdown marks the queue's signal (idempotent, unknown queues are no-ops)", () => {
20
+ registerWorkerShutdownSignal("session:abort-1");
21
+ signalWorkerShutdown("session:abort-1");
22
+ signalWorkerShutdown("session:abort-1");
23
+ expect(getShutdownSignalForQueue("session:abort-1")?.aborted).toBe(true);
24
+ expect(() => signalWorkerShutdown("session:never-registered")).not.toThrow();
25
+ unregisterWorkerShutdownSignal("session:abort-1");
26
+ });
27
+
28
+ it("re-registering a reused queue replaces an aborted signal with a fresh one", () => {
29
+ // A re-created worker on a reused queue (desktop re-opens a session) must
30
+ // not observe the previous worker's aborted signal as its own shutdown.
31
+ const first = registerWorkerShutdownSignal("session:reuse-1");
32
+ first.abort();
33
+ const second = registerWorkerShutdownSignal("session:reuse-1");
34
+ expect(getShutdownSignalForQueue("session:reuse-1")).toBe(second.signal);
35
+ expect(getShutdownSignalForQueue("session:reuse-1")?.aborted).toBe(false);
36
+ unregisterWorkerShutdownSignal("session:reuse-1");
37
+ });
38
+
39
+ it("unregistering removes the signal", () => {
40
+ registerWorkerShutdownSignal("session:gone-1");
41
+ unregisterWorkerShutdownSignal("session:gone-1");
42
+ expect(getShutdownSignalForQueue("session:gone-1")).toBeUndefined();
43
+ });
44
+ });
45
+
46
+ describe("classifyTurnInterruption", () => {
47
+ const none = {
48
+ heartbeatCancelled: false,
49
+ heartbeatWorkerShutdown: false,
50
+ cancellationSignalAborted: false,
51
+ shutdownSignalAborted: false,
52
+ };
53
+
54
+ it("an uninterrupted turn is none", () => {
55
+ expect(classifyTurnInterruption(none)).toBe("none");
56
+ });
57
+
58
+ it("GRACE-WINDOW GUARD (#776): an aborted shutdown signal alone stays none — a run that completed inside the drain grace window must not be failed", () => {
59
+ expect(
60
+ classifyTurnInterruption({ ...none, shutdownSignalAborted: true }),
61
+ ).toBe("none");
62
+ });
63
+
64
+ it("heartbeat cancellation without a shutdown signal is the orchestrator's pause", () => {
65
+ expect(
66
+ classifyTurnInterruption({ ...none, heartbeatCancelled: true }),
67
+ ).toBe("pause");
68
+ });
69
+
70
+ it("heartbeat cancellation WITH the shutdown signal aborted is a worker shutdown, not a pause (the #776 misclassification)", () => {
71
+ expect(
72
+ classifyTurnInterruption({
73
+ ...none,
74
+ heartbeatCancelled: true,
75
+ shutdownSignalAborted: true,
76
+ }),
77
+ ).toBe("worker-shutdown");
78
+ });
79
+
80
+ it("the heartbeat's own workerShutdown flag classifies directly", () => {
81
+ expect(
82
+ classifyTurnInterruption({ ...none, heartbeatWorkerShutdown: true }),
83
+ ).toBe("worker-shutdown");
84
+ });
85
+
86
+ it("a delivered cancellation with the shutdown signal aborted is a worker shutdown (signal races the heartbeat tick)", () => {
87
+ expect(
88
+ classifyTurnInterruption({
89
+ ...none,
90
+ cancellationSignalAborted: true,
91
+ shutdownSignalAborted: true,
92
+ }),
93
+ ).toBe("worker-shutdown");
94
+ });
95
+
96
+ it("a delivered cancellation alone (heartbeat timeout / infra cancel) is neither shutdown nor pause", () => {
97
+ expect(
98
+ classifyTurnInterruption({ ...none, cancellationSignalAborted: true }),
99
+ ).toBe("none");
100
+ });
101
+ });
@@ -1,7 +1,16 @@
1
1
  import { describe, it, expect } from "vitest";
2
- import { extractZipFileEntries } from "../zip-extract.js";
2
+ import { extractZipFileEntries, type ZipFileEntry } from "../zip-extract.js";
3
3
  import { buildZip } from "../../__test-utils__/zip-fixtures.js";
4
4
 
5
+ /**
6
+ * Decode entries for text-content assertions. The production contract is
7
+ * bytes (issue #683); decoding belongs to the tests that want to assert on
8
+ * human-readable fixtures, not to the module under test.
9
+ */
10
+ function decoded(entries: ZipFileEntry[]): { path: string; content: string }[] {
11
+ return entries.map((e) => ({ path: e.path, content: new TextDecoder().decode(e.content) }));
12
+ }
13
+
5
14
  // ─── extractZipFileEntries ───────────────────────────────────────────────
6
15
 
7
16
  describe("extractZipFileEntries", () => {
@@ -12,9 +21,10 @@ describe("extractZipFileEntries", () => {
12
21
  ]);
13
22
 
14
23
  const entries = await extractZipFileEntries(zip);
15
- expect(entries).toHaveLength(2);
16
- expect(entries[0]).toEqual({ path: "SKILL.md", content: "# My Skill" });
17
- expect(entries[1]).toEqual({ path: "references/schema.md", content: "# Schema\n\nTable definitions." });
24
+ expect(decoded(entries)).toEqual([
25
+ { path: "SKILL.md", content: "# My Skill" },
26
+ { path: "references/schema.md", content: "# Schema\n\nTable definitions." },
27
+ ]);
18
28
  });
19
29
 
20
30
  it("extracts deflated files", async () => {
@@ -22,8 +32,7 @@ describe("extractZipFileEntries", () => {
22
32
  const zip = buildZip([{ name: "notes.txt", content, method: "deflated" }]);
23
33
 
24
34
  const entries = await extractZipFileEntries(zip);
25
- expect(entries).toHaveLength(1);
26
- expect(entries[0]).toEqual({ path: "notes.txt", content });
35
+ expect(decoded(entries)).toEqual([{ path: "notes.txt", content }]);
27
36
  });
28
37
 
29
38
  it("skips directory entries", async () => {
@@ -123,6 +132,32 @@ describe("extractZipFileEntries", () => {
123
132
  expect(entries).toHaveLength(1);
124
133
  });
125
134
 
135
+ // ── Binary safety (issue #683) ──────────────────────────────────────────
136
+
137
+ it("round-trips a stored binary entry byte-identically", async () => {
138
+ // PNG magic followed by bytes that are not valid UTF-8 (0x89 alone, a
139
+ // lone continuation byte, an unpaired lead byte). A decode/encode
140
+ // round-trip replaces these with U+FFFD — the corruption this pins.
141
+ const binary = new Uint8Array([
142
+ 0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0xff, 0xfe, 0x00, 0x80, 0xc3,
143
+ ]);
144
+ const zip = buildZip([{ name: "references/diagram.png", content: binary }]);
145
+
146
+ const entries = await extractZipFileEntries(zip);
147
+ expect(entries).toHaveLength(1);
148
+ expect(new Uint8Array(entries[0].content)).toEqual(binary);
149
+ });
150
+
151
+ it("round-trips a deflated binary entry byte-identically", async () => {
152
+ const binary = new Uint8Array(256);
153
+ for (let i = 0; i < 256; i++) binary[i] = i; // every byte value once
154
+ const zip = buildZip([{ name: "assets/font.woff2", content: binary, method: "deflated" }]);
155
+
156
+ const entries = await extractZipFileEntries(zip);
157
+ expect(entries).toHaveLength(1);
158
+ expect(new Uint8Array(entries[0].content)).toEqual(binary);
159
+ });
160
+
126
161
  // ── Streaming entries (issue #450) ─────────────────────────────────────
127
162
 
128
163
  it("extracts Go-default streaming archives (deflated, data descriptors)", async () => {
@@ -132,7 +167,7 @@ describe("extractZipFileEntries", () => {
132
167
  ]);
133
168
 
134
169
  const entries = await extractZipFileEntries(zip);
135
- expect(entries).toEqual([
170
+ expect(decoded(entries)).toEqual([
136
171
  { path: "SKILL.md", content: "# Streamed Skill" },
137
172
  { path: "references/notes.md", content: "streamed notes" },
138
173
  ]);
@@ -151,7 +186,7 @@ describe("extractZipFileEntries", () => {
151
186
  ]);
152
187
 
153
188
  const entries = await extractZipFileEntries(zip);
154
- expect(entries).toEqual([
189
+ expect(decoded(entries)).toEqual([
155
190
  { path: "poison.md", content: poisoned },
156
191
  { path: "after.md", content: "the entry after the poisoned one" },
157
192
  ]);
@@ -162,7 +197,7 @@ describe("extractZipFileEntries", () => {
162
197
  const zip = buildZip([{ name: "cd-sizes.txt", content, streaming: true }]);
163
198
 
164
199
  const entries = await extractZipFileEntries(zip);
165
- expect(entries).toEqual([{ path: "cd-sizes.txt", content }]);
200
+ expect(decoded(entries)).toEqual([{ path: "cd-sizes.txt", content }]);
166
201
  });
167
202
 
168
203
  // ── Central directory edge cases ───────────────────────────────────────
@@ -173,7 +208,7 @@ describe("extractZipFileEntries", () => {
173
208
  });
174
209
 
175
210
  const entries = await extractZipFileEntries(zip);
176
- expect(entries).toEqual([{ path: "a.txt", content: "aaa" }]);
211
+ expect(decoded(entries)).toEqual([{ path: "a.txt", content: "aaa" }]);
177
212
  });
178
213
 
179
214
  it("is not fooled by EOCD signature bytes inside the archive comment", async () => {
@@ -185,7 +220,7 @@ describe("extractZipFileEntries", () => {
185
220
  });
186
221
 
187
222
  const entries = await extractZipFileEntries(zip);
188
- expect(entries).toEqual([{ path: "a.txt", content: "aaa" }]);
223
+ expect(decoded(entries)).toEqual([{ path: "a.txt", content: "aaa" }]);
189
224
  });
190
225
 
191
226
  it("returns empty array when the central directory is missing", async () => {
@@ -34,11 +34,25 @@ export const MAX_ARGS_PREVIEW_LENGTH = 500;
34
34
  * Cursor gate path must instead use {@link buildElidedArgsPreview}, which keeps
35
35
  * the JSON valid and preserves the salient identity fields.
36
36
  */
37
- export function sanitizeArgsPreview(args: Record<string, unknown>): string {
38
- const sanitized: Record<string, unknown> = {};
37
+ /**
38
+ * Redact secret-keyed values (see {@link SENSITIVE_ARG_KEYS}), preserving every
39
+ * other entry verbatim. The shared first step of both preview builders, and the
40
+ * shape stamped as `args` on interrupt-placeholder tool calls (issue #754's
41
+ * header fix): full enough for the UI's path/primary-arg extraction, never
42
+ * carrying a secret value.
43
+ */
44
+ export function redactSensitiveArgs(
45
+ args: Record<string, unknown>,
46
+ ): Record<string, unknown> {
47
+ const redacted: Record<string, unknown> = {};
39
48
  for (const [key, value] of Object.entries(args)) {
40
- sanitized[key] = SENSITIVE_ARG_KEYS.has(key.toLowerCase()) ? "[REDACTED]" : value;
49
+ redacted[key] = SENSITIVE_ARG_KEYS.has(key.toLowerCase()) ? "[REDACTED]" : value;
41
50
  }
51
+ return redacted;
52
+ }
53
+
54
+ export function sanitizeArgsPreview(args: Record<string, unknown>): string {
55
+ const sanitized = redactSensitiveArgs(args);
42
56
  try {
43
57
  const json = JSON.stringify(sanitized);
44
58
  return json.length > MAX_ARGS_PREVIEW_LENGTH
@@ -56,7 +56,17 @@ export interface ArtifactStorage {
56
56
  exists(key: string): Promise<boolean>;
57
57
  }
58
58
 
59
- export type ArtifactStorageType = "local" | "proxy";
59
+ /**
60
+ * "none" is the explicit no-store posture: capture degrades to the deny-gate
61
+ * (file writes gated pre-execution, DD-22/DD-26) and offload is disabled —
62
+ * the same first-class absent-store state a proxy misconfig or unwritable
63
+ * local path resolves to, but chosen deliberately (ARTIFACT_STORAGE_TYPE=none)
64
+ * instead of reached by failure. The e2e file-gate stack boots with it, and
65
+ * an operator can use it to force gate-everything behavior. It is only valid
66
+ * where an absent store is: claimcheck (fail-hard by contract) refuses it at
67
+ * construction.
68
+ */
69
+ export type ArtifactStorageType = "local" | "proxy" | "none";
60
70
 
61
71
  // ── Local Backend ────────────────────────────────────────────────────
62
72
 
@@ -371,6 +381,7 @@ export function loadArtifactStorageConfig(config: Config): ArtifactStorageConfig
371
381
  const type: ArtifactStorageType =
372
382
  envType === "proxy" ? "proxy" :
373
383
  envType === "local" ? "local" :
384
+ envType === "none" ? "none" :
374
385
  config.proxyEndpoint ? "proxy" : "local";
375
386
 
376
387
  return {
@@ -387,6 +398,14 @@ export function loadArtifactStorageConfig(config: Config): ArtifactStorageConfig
387
398
  }
388
399
 
389
400
  export function createArtifactStorage(cfg: ArtifactStorageConfig): ArtifactStorage {
401
+ if (cfg.type === "none") {
402
+ // Deliberate no-store posture. Throwing keeps both consumers honest:
403
+ // resolveUsableArtifactStorage catches and degrades to the deny-gate
404
+ // (the intent of "none"), while claimcheck's direct fail-hard call
405
+ // surfaces the config contradiction at boot instead of silently
406
+ // dropping Temporal payloads.
407
+ throw new Error("ARTIFACT_STORAGE_TYPE=none: artifact storage deliberately disabled");
408
+ }
390
409
  if (cfg.type === "proxy") {
391
410
  if (!cfg.proxyEndpoint) {
392
411
  throw new Error("Proxy artifact storage requires STIGMER_PROXY_ENDPOINT");
@@ -0,0 +1,84 @@
1
+ /**
2
+ * Declared preferences (stigmer/stigmer#293, DD-002): standing free-text
3
+ * preferences the organization and the calling user declared once — "keep
4
+ * answers terse", "we deploy to us-east-1" — that every eligible execution
5
+ * receives without the user re-typing them.
6
+ *
7
+ * The server composes the CONTENT at execution create: the create pipeline
8
+ * snapshots `Organization.spec.preferences.standing_context` and (cloud, for
9
+ * first-party human callers only) the caller
10
+ * `IdentityAccount.spec.preferences.standing_context` verbatim onto the
11
+ * execution spec's `declared_preferences` field. This module owns the
12
+ * PRESENTATION — the preamble and the per-scope attribution — so the framing
13
+ * cannot drift between harnesses.
14
+ *
15
+ * Like conversation-catchup (its typed-field sibling) there is no metadata
16
+ * key to mirror-guard: the value rides a TYPED proto field, so codegen
17
+ * enforces the cross-repo contract. Degradation is safe by construction: an
18
+ * absent or blank field renders nothing, and a runner predating this module
19
+ * simply ignores it — the agent runs without preferences, exactly the
20
+ * pre-#293 behavior, never worse.
21
+ */
22
+
23
+ import type { DeclaredPreferences } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/spec_pb";
24
+
25
+ /**
26
+ * How the preferences are introduced to the model, shared by both harnesses
27
+ * so the behavioral contract cannot drift between them. Same posture as the
28
+ * session-context channel: calibration material, never authority — the task
29
+ * always outranks a standing preference.
30
+ */
31
+ const DECLARED_PREFERENCES_PREAMBLE =
32
+ "Standing preferences declared by the organization and/or the user you " +
33
+ "are assisting. Treat them as background you already know: use them to " +
34
+ "calibrate depth, defaults, and tone. Do not repeat them back, quote " +
35
+ "them, or mention that you received them. They are context, not " +
36
+ "instructions that override your task.";
37
+
38
+ /**
39
+ * The non-blank preference scopes of an execution, in presentation order.
40
+ * At least one scope is set (the read function returns undefined otherwise).
41
+ */
42
+ export interface DeclaredPreferencesContent {
43
+ /** The organization's standing context, trimmed; absent when blank. */
44
+ orgContext?: string;
45
+ /** The calling user's standing context, trimmed; absent when blank. */
46
+ userContext?: string;
47
+ }
48
+
49
+ /**
50
+ * Read the declared preferences from an execution spec's
51
+ * `declared_preferences`. Returns undefined when the field is absent or both
52
+ * scopes are blank — the caller renders no section. Blank-is-absent applies
53
+ * PER SCOPE: an org-only snapshot renders only the org subsection.
54
+ */
55
+ export function readDeclaredPreferences(
56
+ preferences: DeclaredPreferences | undefined,
57
+ ): DeclaredPreferencesContent | undefined {
58
+ const orgContext = preferences?.orgContext?.trim() || undefined;
59
+ const userContext = preferences?.userContext?.trim() || undefined;
60
+ if (!orgContext && !userContext) {
61
+ return undefined;
62
+ }
63
+ return { orgContext, userContext };
64
+ }
65
+
66
+ /**
67
+ * The framed preferences body (preamble + labeled per-scope subsections),
68
+ * ready for section wrapping. Attribution is explicit — the model must know
69
+ * WHO declared what. Organization first, user last: the user's refinement
70
+ * reads last and so naturally wins where the two conflict, without this
71
+ * module encoding any override rule.
72
+ */
73
+ export function formatDeclaredPreferencesText(
74
+ content: DeclaredPreferencesContent,
75
+ ): string {
76
+ const sections: string[] = [DECLARED_PREFERENCES_PREAMBLE];
77
+ if (content.orgContext) {
78
+ sections.push(`Declared by the organization:\n${content.orgContext}`);
79
+ }
80
+ if (content.userContext) {
81
+ sections.push(`Declared by the user:\n${content.userContext}`);
82
+ }
83
+ return sections.join("\n\n");
84
+ }
@@ -20,6 +20,7 @@
20
20
  */
21
21
 
22
22
  import { randomBytes, type BinaryLike } from "node:crypto";
23
+ import { getRunnerSecret } from "./runner-credential-store.js";
23
24
 
24
25
  const ENV_VAR = "STIGMER_RUNNER_HITL_SECRET";
25
26
 
@@ -27,13 +28,14 @@ let cached: Buffer | undefined;
27
28
  let warned = false;
28
29
 
29
30
  /**
30
- * Return the runner's HITL master secret (env-configured, else a stable
31
- * per-process random fallback). Memoized.
31
+ * Return the runner's HITL master secret (operator-configured via the
32
+ * {@link ENV_VAR} env var, resolved through the credential store since the
33
+ * #508 boot capture; else a stable per-process random fallback). Memoized.
32
34
  */
33
35
  export function getRunnerHitlMasterSecret(): BinaryLike {
34
36
  if (cached) return cached;
35
37
 
36
- const fromEnv = process.env[ENV_VAR];
38
+ const fromEnv = getRunnerSecret(ENV_VAR);
37
39
  if (fromEnv && fromEnv.length > 0) {
38
40
  cached = Buffer.from(fromEnv, "utf-8");
39
41
  return cached;
@@ -15,6 +15,7 @@
15
15
  */
16
16
 
17
17
  import type { LlmProvider } from "./llm-proxy.js";
18
+ import { runnerSecretsEnvView } from "./runner-credential-store.js";
18
19
 
19
20
  // ─── Backend selection ───────────────────────────────────────────────────────
20
21
 
@@ -345,7 +346,12 @@ export function checkFoundryPrerequisites(
345
346
  */
346
347
  export function checkDirectCredentials(
347
348
  provider: LlmProvider,
348
- env: NodeJS.ProcessEnv = process.env,
349
+ // Credential keys live in the runner credential store after the boot
350
+ // capture (#508), so the default is the store view, not bare process.env.
351
+ // The other checks in this module keep the process.env default: they read
352
+ // deployment CONFIG (backend selection, regions), which is deliberately
353
+ // not captured.
354
+ env: NodeJS.ProcessEnv = runnerSecretsEnvView(),
349
355
  ): string | null {
350
356
  if (provider === "openai") {
351
357
  if (env.OPENAI_API_KEY?.trim()) return null;
@@ -7,9 +7,8 @@
7
7
  * - An empty usage list falls back to the server's default_enabled_tools.
8
8
  * - Both empty means NO restriction — every discovered tool is available.
9
9
  *
10
- * Both harnesses import THIS module so they can never drift on these
11
- * semantics (the mcp-transport-guard precedent for logic shared across the
12
- * duplicated resolvers — see the header of shared/mcp-resolver.ts):
10
+ * Resolution is shared (shared/mcp-resolver.ts threads the effective list
11
+ * onto every ResolvedMcpServer), but ENFORCEMENT is per-harness:
13
12
  * - The deep-agent harness filters the discovered toolset before it reaches
14
13
  * the model (shared/mcp-manager.ts connectMcpServers).
15
14
  * - The Cursor harness cannot hide tools (the Cursor SDK's McpServerConfig
@@ -2,13 +2,17 @@
2
2
  * Resolves Stigmer McpServerUsage references into an intermediate
3
3
  * ResolvedMcpServer format.
4
4
  *
5
- * Consumers: the deep-agent harness (execute-deep-agent/setup.ts), the
6
- * connect backfill (shared/connect-backfill.ts), and the discovery
7
- * activity (activities/discover-mcp-server.ts, via mcpServerToResolved).
8
- * NOTE: the Cursor harness does NOT use this module — it has its own
9
- * near-duplicate resolver at activities/execute-cursor/mcp-resolver.ts.
10
- * A behavioral change here (like the transport guard) must be mirrored
11
- * there until the two are consolidated.
5
+ * THE single resolver for both harnesses (oss#387 retired the Cursor
6
+ * harness's near-duplicate): the deep-agent harness
7
+ * (execute-deep-agent/setup.ts), the Cursor harness
8
+ * (execute-cursor/index.ts), the connect backfill
9
+ * (shared/connect-backfill.ts), and the discovery activity
10
+ * (activities/discover-mcp-server.ts, via mcpServerToResolved) all consume
11
+ * it. Each harness maps the result into its SDK format at the last hop:
12
+ * toMcpClientConfig (shared/mcp-manager.ts) for LangChain,
13
+ * toCursorMcpConfig (execute-cursor/cursor-mcp-config.ts) for the Cursor
14
+ * SDK — so a behavioral change here (like the transport guard) lands in
15
+ * both execution paths by construction.
12
16
  */
13
17
 
14
18
  import type { McpServerUsage, ToolApprovalOverride } from "@stigmer/protos/ai/stigmer/agentic/agent/v1/spec_pb";
@@ -51,8 +55,11 @@ export interface ResolvedMcpServer {
51
55
  * enabled_tools, falling back to the server's default_enabled_tools when
52
56
  * the usage's list is empty (see shared/mcp-enabled-tools.ts). Absent when
53
57
  * unrestricted — which keeps synthesized attachment servers (built without
54
- * a usage) and the discovery path unfiltered by construction. Enforced at
55
- * connect time by connectMcpServers (shared/mcp-manager.ts).
58
+ * a usage) and the discovery path unfiltered by construction. Enforcement
59
+ * is per-harness: the deep-agent path filters at connect time
60
+ * (connectMcpServers, shared/mcp-manager.ts); the Cursor SDK config cannot
61
+ * hide tools, so that harness denies non-enabled calls in the HITL hook
62
+ * (see hook-script.ts).
56
63
  */
57
64
  enabledTools?: string[];
58
65
  /**
@@ -61,11 +68,10 @@ export interface ResolvedMcpServer {
61
68
  * override to its own server — a flat cross-server list is how an
62
69
  * override once leaked onto (or silently un-gated) a same-named tool on
63
70
  * another server. Deliberately REQUIRED, not optional: empty means "no
64
- * overrides", and every construction site — including the near-duplicate
65
- * Cursor resolver and the synthesized attachments, which have no usage
66
- * and therefore no layer 3 must say so explicitly, so a forgotten
67
- * mirror cannot compile. Consumed by mergeApprovalPolicies
68
- * (shared/approval-policy.ts).
71
+ * overrides", and every construction site — including the synthesized
72
+ * attachments, which have no usage and therefore no layer 3 must say so
73
+ * explicitly, so a forgotten site cannot compile. Consumed by
74
+ * mergeApprovalPolicies (shared/approval-policy.ts).
69
75
  */
70
76
  toolApprovalOverrides: ToolApprovalOverride[];
71
77
  }
@@ -202,12 +208,6 @@ export function mcpServerToResolved(
202
208
  }
203
209
  }
204
210
 
205
- export function extractMcpServerSlugs(usages: McpServerUsage[]): string[] {
206
- return usages
207
- .map((u) => u.mcpServerRef?.slug)
208
- .filter((s): s is string => !!s);
209
- }
210
-
211
211
  /**
212
212
  * Merge MCP server usages from agent (base) and session (overlay).
213
213
  *