@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
@@ -78,10 +78,33 @@ import { stampFlowedFileEditRows, stampFlowedSubAgentFileEditRows } from "./stam
78
78
  import { deriveTurnCommandProvenance } from "./command-provenance.js";
79
79
  import { describeExecutionError } from "../../shared/model-error.js";
80
80
  import { inferProvider, type LlmProvider } from "../../shared/llm-proxy.js";
81
+ import { getShutdownSignalForQueue } from "../../shared/worker-shutdown.js";
81
82
 
82
83
  /** The harness id stamped on the deep-agent's file-review ledger events. */
83
84
  const DEEP_AGENT_HARNESS_ID = "deep-agent";
84
85
 
86
+ /**
87
+ * The worker-shutdown terminal status (#776). A shutdown is not a pause: the
88
+ * execution will not resume on this worker, so persisting PAUSED would strand
89
+ * the user with a lie. The copy is byte-identical to the Cursor harness's
90
+ * worker-shutdown branch so every downstream status.error consumer (the Go and
91
+ * Java workflow fallbacks, the cloud channel decision table) keys on ONE shape.
92
+ */
93
+ function buildWorkerShutdownStatus(): AgentExecutionStatus {
94
+ return create(AgentExecutionStatusSchema, {
95
+ phase: ExecutionPhase.EXECUTION_FAILED,
96
+ error: "Execution interrupted: runner worker was shut down. Retry or resume.",
97
+ completedAt: utcTimestamp(),
98
+ messages: [
99
+ create(AgentMessageSchema, {
100
+ type: MessageType.MESSAGE_SYSTEM,
101
+ content: "Execution interrupted: the runner worker was shut down while the agent was still running. You can retry or resume.",
102
+ timestamp: utcTimestamp(),
103
+ }),
104
+ ],
105
+ });
106
+ }
107
+
85
108
  /**
86
109
  * Best-effort provider inference for error-message wording. inferProvider
87
110
  * throws on unrecognized names; an error path must never throw over a label.
@@ -115,6 +138,11 @@ export function createDeepAgentActivities(config: Config) {
115
138
  ): Promise<unknown> => {
116
139
  const { executionId, threadId, turnSeq } = normalizeActivityInput(arg0, arg1);
117
140
  activityStarted();
141
+ // Worker-shutdown classification channel (#776): the pause paths below
142
+ // consult this signal to distinguish "my worker is draining" (SIGTERM,
143
+ // desktop quit) from "the orchestrator cancelled me" (a real user
144
+ // pause). See shared/worker-shutdown.ts for the ownership contract.
145
+ const shutdownSignal = getShutdownSignalForQueue(Context.current().info.taskQueue);
118
146
  let setup: SetupResult | null = null;
119
147
  // Exclusive turn lock on the workspace working tree — held across the
120
148
  // entire tree-mutating window (decision reconcile, agent writes,
@@ -567,6 +595,13 @@ export function createDeepAgentActivities(config: Config) {
567
595
 
568
596
  if (result.terminalStatus) {
569
597
  if (initialStatus.phase === ExecutionPhase.EXECUTION_PAUSED) {
598
+ // The stream loop marks PAUSED for ANY platform cancellation; a
599
+ // worker shutdown is not a pause and must not persist as one (#776).
600
+ if (shutdownSignal?.aborted) {
601
+ console.log(`[ExecuteDeepAgent] Cancelled (worker shutdown) for execution ${executionId}: events=${result.eventsProcessed}`);
602
+ await persistStatus(client, executionId, buildWorkerShutdownStatus(), { offload: statusOffload }).catch(() => {});
603
+ throw new CancelledFailure("Activity cancelled (worker shutdown, not user pause)");
604
+ }
570
605
  await persistStatus(client, executionId, initialStatus, { offload: statusOffload });
571
606
  console.log(`[ExecuteDeepAgent] Paused for execution ${executionId}: events=${result.eventsProcessed}`);
572
607
  throw new CancelledFailure("Activity paused by orchestrator");
@@ -617,15 +652,20 @@ export function createDeepAgentActivities(config: Config) {
617
652
  policyEngineVersion: intr.policySource ? POLICY_ENGINE_VERSION : "",
618
653
  });
619
654
 
620
- // Capture a sanitized args preview while the graph is paused, so the
621
- // approval UI renders the proposed change before the tool runs. Args
622
- // are correlated from the AI-message tool call in graph state (the
623
- // single source of truth).
624
- const { argsPreview } = captureApprovalArtifacts({
655
+ // Capture a sanitized args preview AND the redacted args object
656
+ // while the graph is paused, so the approval UI renders the
657
+ // proposed change before the tool runs and the row header can
658
+ // extract its filename-first path from `args` like every other
659
+ // row (issue #754: a placeholder without args rendered a
660
+ // pathless "Write" header). Args are correlated from the
661
+ // AI-message tool call in graph state (the single source of
662
+ // truth).
663
+ const { argsPreview, args } = captureApprovalArtifacts({
625
664
  toolCallId: intr.toolCallId,
626
665
  messages: aiMessages,
627
666
  });
628
667
  if (argsPreview) toolCall.argsPreview = argsPreview;
668
+ if (args) toolCall.args = args as typeof toolCall.args;
629
669
 
630
670
  aiMsg.toolCalls.push(toolCall);
631
671
  }
@@ -749,6 +789,13 @@ export function createDeepAgentActivities(config: Config) {
749
789
 
750
790
  } catch (err: unknown) {
751
791
  if (err instanceof CancelledFailure) {
792
+ // Worker shutdown is infrastructure failure, not pause — the caught
793
+ // CancelledFailure is itself the interruption evidence (#776).
794
+ if (shutdownSignal?.aborted) {
795
+ console.log(`[ExecuteDeepAgent] Cancelled (worker shutdown) for execution ${executionId}`);
796
+ await persistStatus(client, executionId, buildWorkerShutdownStatus()).catch(() => {});
797
+ throw err;
798
+ }
752
799
  console.log(`[ExecuteDeepAgent] Cancelled (pause) for execution ${executionId}`);
753
800
  const pausedStatus = create(AgentExecutionStatusSchema, {
754
801
  phase: ExecutionPhase.EXECUTION_PAUSED,
@@ -758,6 +805,11 @@ export function createDeepAgentActivities(config: Config) {
758
805
  }
759
806
 
760
807
  if (Context.current().cancellationSignal.aborted) {
808
+ if (shutdownSignal?.aborted) {
809
+ console.log(`[ExecuteDeepAgent] Error during worker-shutdown cancellation for ${executionId}: ${err}`);
810
+ await persistStatus(client, executionId, buildWorkerShutdownStatus()).catch(() => {});
811
+ throw new CancelledFailure("Activity cancelled (worker shutdown, not user pause)");
812
+ }
761
813
  console.log(`[ExecuteDeepAgent] Error during cancellation for ${executionId}, treating as pause: ${err}`);
762
814
  const pausedStatus = create(AgentExecutionStatusSchema, {
763
815
  phase: ExecutionPhase.EXECUTION_PAUSED,
@@ -17,6 +17,10 @@ import {
17
17
  type SenderIdentity,
18
18
  } from "../../shared/sender-identity.js";
19
19
  import { formatSessionContextText } from "../../shared/session-context.js";
20
+ import {
21
+ formatDeclaredPreferencesText,
22
+ type DeclaredPreferencesContent,
23
+ } from "../../shared/declared-preferences.js";
20
24
  import {
21
25
  visionDisclosureLines,
22
26
  type NotViewableEntry,
@@ -166,6 +170,14 @@ export interface PromptBuilderInput {
166
170
  * context is standing session state, like skills.
167
171
  */
168
172
  sessionContext?: string;
173
+ /**
174
+ * Platform-declared standing preferences (stigmer/stigmer#293): the org's
175
+ * and user's standing context, server-snapshotted onto the execution
176
+ * spec's `declared_preferences` at create. Injected on EVERY turn like
177
+ * the bridge — the native system prompt is rebuilt per invocation, so an
178
+ * edited preference reaches the very next turn.
179
+ */
180
+ declaredPreferences?: DeclaredPreferencesContent;
169
181
  }
170
182
 
171
183
  // The prompt renders the injector's own result type — a local structural twin
@@ -229,6 +241,16 @@ export function buildEnhancedSystemPrompt(input: PromptBuilderInput): string {
229
241
  formatSenderIdentityText(input.senderIdentity);
230
242
  }
231
243
 
244
+ // Platform-declared standing facts precede embedder-supplied context
245
+ // (DD-002 D3): both are standing background, but the declared preferences
246
+ // are platform-authored while session context is the embedder's overlay —
247
+ // the more specific overlay reads later and naturally refines.
248
+ if (input.declaredPreferences) {
249
+ prompt +=
250
+ "\n\n## Declared preferences\n\n" +
251
+ formatDeclaredPreferencesText(input.declaredPreferences);
252
+ }
253
+
232
254
  // Standing facts about the user (session context) come before the
233
255
  // carried conversation (bridge): the bridge may refer back to them.
234
256
  if (input.sessionContext) {
@@ -32,6 +32,7 @@ import {
32
32
  resolveCallerIdentity,
33
33
  } from "../../shared/caller-identity.js";
34
34
  import { readSessionContext } from "../../shared/session-context.js";
35
+ import { readDeclaredPreferences } from "../../shared/declared-preferences.js";
35
36
  import { connectMcpServers, type McpConnectionResult } from "../../shared/mcp-manager.js";
36
37
  import { mergeMcpServerUsages, resolveMcpServers } from "../../shared/mcp-resolver.js";
37
38
  import { resolveMcpTransportPosture } from "../../shared/mcp-transport-guard.js";
@@ -72,6 +73,7 @@ import { getRunnerHitlMasterSecret } from "../../shared/fingerprint-secret.js";
72
73
  import { getModelPricing, ensureLoaded as ensurePricingLoaded } from "../../shared/model-pricing.js";
73
74
  import { getDefaultModel, getModelVisionCapability } from "../../shared/model-registry.js";
74
75
  import { buildChatModel } from "../../shared/model-client.js";
76
+ import { resolveEffectiveServiceTier } from "../../shared/service-tier.js";
75
77
  import {
76
78
  loadArtifactStorageConfig,
77
79
  resolveUsableArtifactStorage,
@@ -87,12 +89,9 @@ import type { ToolApprovalCategory } from "../../shared/tool-kind.js";
87
89
  import {
88
90
  mergeSkillRefs,
89
91
  fetchSkillsByRefs,
90
- writeSkills,
91
- computeSkillPaths,
92
- checkSkillIntegrity,
92
+ mountSkills,
93
93
  generatePromptSection,
94
94
  generateAlsoAvailableSection,
95
- fetchSkillArtifacts,
96
95
  } from "../../shared/skill-writer.js";
97
96
  import { filterSkills, SKILL_COUNT_THRESHOLD } from "../../shared/skill-relevance.js";
98
97
  import { injectAttachments } from "./attachment-injector.js";
@@ -477,8 +476,11 @@ export async function performSetup(deps: SetupDependencies): Promise<SetupResult
477
476
  const skills = await fetchSkillsByRefs(client, skillRefs);
478
477
 
479
478
  if (skills.length > 0) {
480
- const artifacts = await fetchSkillArtifacts(client, skills);
481
- const { paths: skillPaths } = await writeSkills(skills, workspaceBackend, artifacts);
479
+ // platformDir is an invariant of this path: provisionWorkspace below
480
+ // threads ensurePlatformDir into every backend it constructs.
481
+ const { paths: skillPaths } = await mountSkills(
482
+ client, skills, workspaceBackend.platformDir!,
483
+ );
482
484
 
483
485
  const userMessage = execution.spec!.message || "";
484
486
  const skillNames = skills.map(s => s.spec?.name || s.metadata?.slug || "unknown");
@@ -572,6 +574,9 @@ export async function performSetup(deps: SetupDependencies): Promise<SetupResult
572
574
  contextBridge: readContextBridge(session.spec!.metadata),
573
575
  senderIdentity: readSenderIdentity(session.spec!.metadata),
574
576
  sessionContext: readSessionContext(session.spec!.metadata),
577
+ declaredPreferences: readDeclaredPreferences(
578
+ execution.spec!.declaredPreferences,
579
+ ),
575
580
  });
576
581
 
577
582
  // Step 9: Construct the LLM model. Resolution to the provider API id
@@ -580,11 +585,22 @@ export async function performSetup(deps: SetupDependencies): Promise<SetupResult
580
585
  // The operator's STIGMER_LLM_REQUEST_TIMEOUT_MS bound is applied inside
581
586
  // buildChatModel (#468) — the sub-agent modelFactory below silently
582
587
  // dropped it when each caller parsed the env itself.
588
+ //
589
+ // The service tier resolves ONCE here (UNSPECIFIED → explicit
590
+ // STANDARD, shared/service-tier.ts) and rides every model this
591
+ // execution constructs — the primary below AND the sub-agent factory —
592
+ // so the provider account's default can never pick the price of any
593
+ // turn (#361, the native half of #357's contract). Sub-agents inherit
594
+ // it because the tier is an attribute of the EXECUTION's bill, and
595
+ // sub-agent calls land on the same ledger.
596
+ const serviceTier = resolveEffectiveServiceTier(
597
+ execution.spec!.executionConfig?.serviceTier);
583
598
  const { model } = await buildChatModel({
584
599
  modelName,
585
600
  proxyEndpoint: config.proxyEndpoint ?? undefined,
586
601
  stigmerToken: config.stigmerToken ?? undefined,
587
602
  headerScope: { executionId },
603
+ serviceTier,
588
604
  });
589
605
  timing.mark("build_model");
590
606
 
@@ -594,16 +610,13 @@ export async function performSetup(deps: SetupDependencies): Promise<SetupResult
594
610
  const execConfig = execution.spec!.executionConfig;
595
611
  const isPlanMode = execConfig?.interactionMode === InteractionMode.PLAN;
596
612
  const shellEnv = isPlanMode ? undefined : buildShellEnv(envResult.mergedEnvVars);
597
- // Plan mode's filesystem permission rules, hoisted once: the parent graph,
598
- // every sub-agent graph, AND the path-normalization middleware (issue #429)
599
- // all derive from this single value, so a rule-bearing graph can never miss
600
- // the shim that keeps prompt-compliant relative paths from dying in rule
601
- // validation (`path must be absolute`). Built from the same rootDir the
602
- // backends and the shim resolve against, so the workspace read boundary
603
- // (issue #528) and the paths it must admit agree by construction.
604
- const planModePermissions = isPlanMode
605
- ? buildPlanModePermissions(workspaceBackend.rootDir)
606
- : undefined;
613
+ // Plan mode's filesystem permission rules, hoisted once: the parent graph
614
+ // and every sub-agent graph carry this single value. The rules are the
615
+ // write-deny half of plan mode only the read boundary is structural
616
+ // (virtual-rooted backends, issue #754), and the path-normalization shim
617
+ // is no longer tied to the rules: every native graph installs it (see
618
+ // buildMiddlewareStack below) so the whole harness speaks one dialect.
619
+ const planModePermissions = isPlanMode ? buildPlanModePermissions() : undefined;
607
620
 
608
621
  const toolServerMap = new Map<string, string>();
609
622
  if (mcpConnection) {
@@ -717,9 +730,10 @@ export async function performSetup(deps: SetupDependencies): Promise<SetupResult
717
730
  } : null,
718
731
  otelSpans: { toolServerMap },
719
732
  approvalGate: approvalGateConfig,
720
- pathNormalization: planModePermissions
721
- ? { rootDir: workspaceBackend.rootDir }
722
- : null,
733
+ // Every graph, every mode (issue #754): the dialect-repair seam that
734
+ // keeps model-supplied paths canonical for the virtual-rooted backend
735
+ // and every downstream consumer (gate, capture, spans).
736
+ pathNormalization: { rootDir: workspaceBackend.rootDir },
723
737
  });
724
738
  timing.mark("build_middleware");
725
739
 
@@ -776,6 +790,9 @@ export async function performSetup(deps: SetupDependencies): Promise<SetupResult
776
790
  proxyEndpoint: config.proxyEndpoint ?? undefined,
777
791
  stigmerToken: config.stigmerToken ?? undefined,
778
792
  headerScope: { executionId },
793
+ // The execution's tier, inherited: sub-agent calls bill to the
794
+ // same execution (#361 — see the Step 9 resolution comment).
795
+ serviceTier,
779
796
  })).model,
780
797
  // Presence of shellEnv is the shell-capability switch for sub-agent
781
798
  // backends too (undefined in plan mode; see buildShellEnv above).
@@ -1,19 +1,22 @@
1
1
  /**
2
2
  * Shell environment for the native harness `execute` tool.
3
3
  *
4
- * Per-execution snapshot: runner-manager rotates `STIGMER_TOKEN` in
5
- * `process.env` at runtime, so this must run inside setup for each execution,
6
- * never once at process start.
4
+ * Since #508's boot capture, runner secrets never LIVE in `process.env`, so
5
+ * the denylist below is defense-in-depth: it keeps this surface safe even if
6
+ * something re-plants a secret in the environment after boot (a test, an
7
+ * embedder, a future regression). Still built per execution — the snapshot
8
+ * must reflect the env as it is now, not at process start.
7
9
  */
8
10
 
9
- import { RUNNER_CREDENTIAL_ENV_KEYS } from "../../shared/runner-credential-keys.js";
11
+ import { RUNNER_SECRET_ENV_KEYS } from "../../shared/runner-credential-keys.js";
10
12
 
11
13
  /**
12
14
  * Runner-internal keys that must never reach agent shell commands: every
13
- * credential the runner holds for its own outbound calls (issue #385). The
14
- * names and the rule for adding onelive in runner-credential-keys.ts.
15
+ * credential the runner holds for its own outbound calls (issue #385) plus
16
+ * the runner's encryption keys (issue #508). The namesand the rules for
17
+ * adding one — live in runner-credential-keys.ts.
15
18
  */
16
- export const SHELL_ENV_DENYLIST: readonly string[] = RUNNER_CREDENTIAL_ENV_KEYS;
19
+ export const SHELL_ENV_DENYLIST: readonly string[] = RUNNER_SECRET_ENV_KEYS;
17
20
 
18
21
  /**
19
22
  * Build the environment map passed to deepagents' LocalShellBackend.
@@ -42,8 +42,7 @@ import { SubAgentGate } from "../../shared/subagent-gate.js";
42
42
  import { isModelRegistered } from "../../shared/model-registry.js";
43
43
  import {
44
44
  fetchSkillsByRefs,
45
- fetchSkillArtifacts,
46
- writeSkills,
45
+ mountSkills,
47
46
  generatePromptSection,
48
47
  } from "../../shared/skill-writer.js";
49
48
 
@@ -493,6 +492,10 @@ export function resolveSubagentSkillPrompt(
493
492
  * (approval-gated by the sub-agent's own middleware). Absent (plan mode),
494
493
  * the backend is filesystem-only — read-only by construction, matching the
495
494
  * parent.
495
+ *
496
+ * Every variant is virtual-rooted (`virtualMode: true`) like the parent's —
497
+ * see the cas-capture-backend.ts header (issue #754): workspace confinement
498
+ * is structural on every graph, sub-agents included.
496
499
  */
497
500
  async function buildSubagentBackend(opts: {
498
501
  readonly workspaceRootDir: string;
@@ -508,11 +511,12 @@ async function buildSubagentBackend(opts: {
508
511
  }
509
512
 
510
513
  if (opts.shellEnv === undefined) {
511
- return new FilesystemBackend({ rootDir: opts.workspaceRootDir });
514
+ return new FilesystemBackend({ rootDir: opts.workspaceRootDir, virtualMode: true });
512
515
  }
513
516
 
514
517
  const shellBackend = new LocalShellBackend({
515
518
  rootDir: opts.workspaceRootDir,
519
+ virtualMode: true,
516
520
  env: opts.shellEnv,
517
521
  });
518
522
  await shellBackend.initialize();
@@ -554,17 +558,14 @@ export async function compileSubagents(
554
558
  // Structural coupling (DD-19): a sub-agent gate flows gitignored writes into
555
559
  // CAS iff a CAS observer backs that sub-agent's filesystem backend. Deriving
556
560
  // both from the same `casObserver` makes "unobserved unreviewable bytes"
557
- // impossible by construction. Same idiom for path normalization
558
- // (issue #429): derived from the same `permissions` baked into the graph
559
- // below, so a rule-bearing graph always carries the shim that keeps
560
- // prompt-compliant relative paths from dying in rule validation.
561
+ // impossible by construction. Path normalization is unconditional
562
+ // (issue #754): every graph speaks the virtual dialect, so every graph
563
+ // carries the repair seam matching the parent's composition.
561
564
  const middleware = buildSubAgentMiddleware({
562
565
  costCap: opts.costCap,
563
566
  approvalGate: opts.approvalGate,
564
567
  captureIgnored: !!opts.casObserver,
565
- ...(opts.permissions?.length
566
- ? { pathNormalization: { rootDir: opts.workspaceRootDir } }
567
- : {}),
568
+ pathNormalization: { rootDir: opts.workspaceRootDir },
568
569
  });
569
570
 
570
571
  const modelName = spec.model ?? opts.parentModelName;
@@ -679,8 +680,14 @@ export async function transformAndCompileSubagents(
679
680
  const skills = await fetchSkillsByRefs(skillClient, refs);
680
681
 
681
682
  if (skills.length > 0) {
682
- const artifacts = await fetchSkillArtifacts(skillClient, skills);
683
- const { paths: skillPaths } = await writeSkills(skills, workspaceBackend, artifacts);
683
+ // Runs after the parent's setup step 7b mounted its skills, so any
684
+ // skill shared by parent and sub-agent is a cache hit here — the
685
+ // hash-keyed marker turns the old double download into a no-op.
686
+ // platformDir is an invariant: provisionWorkspace threads
687
+ // ensurePlatformDir into every backend it constructs.
688
+ const { paths: skillPaths } = await mountSkills(
689
+ skillClient, skills, workspaceBackend.platformDir!,
690
+ );
684
691
 
685
692
  for (const skill of skills) {
686
693
  const slug = (skill as { metadata?: { slug?: string } }).metadata?.slug;
@@ -75,9 +75,9 @@ export interface SubAgentMiddlewareOptions {
75
75
  */
76
76
  readonly captureIgnored?: boolean;
77
77
  /**
78
- * Workspace-relative path normalization (issue #429). Present iff this
79
- * sub-agent's graph carries filesystem permission rules the caller
80
- * derives it from the same `permissions` value it bakes into the graph.
78
+ * Virtual-dialect path normalization (issues #429/#754). Present on every
79
+ * sub-agent graph the backends are virtual-rooted, so every graph
80
+ * carries the repair seam, matching the parent composition.
81
81
  */
82
82
  readonly pathNormalization?: PathNormalizationConfig;
83
83
  }
package/src/config.ts CHANGED
@@ -53,6 +53,7 @@ export const DEFAULT_CURSOR_AGENT_RESOLVE_TIMEOUT_MS = 120_000;
53
53
  // three construction sites — mirrors DEFAULT_CURSOR_STREAM_STALL_TIMEOUT_MS).
54
54
  export { DEFAULT_WORKSPACE_LOCK_TIMEOUT_MS } from "./shared/workspace/workspace-lock.js";
55
55
  import { DEFAULT_WORKSPACE_LOCK_TIMEOUT_MS } from "./shared/workspace/workspace-lock.js";
56
+ import { getRunnerSecret } from "./shared/runner-credential-store.js";
56
57
 
57
58
  export interface Config {
58
59
  readonly taskQueue: string;
@@ -158,9 +159,13 @@ export function loadConfig(): Config {
158
159
  : requireEnv("STIGMER_BACKEND_ENDPOINT"),
159
160
  );
160
161
 
161
- const stigmerToken = (mode === "cloud" || proxyActive)
162
- ? requireEnv("STIGMER_TOKEN")
163
- : (process.env.STIGMER_TOKEN ?? null);
162
+ // Secrets resolve through the credential store, not process.env — the
163
+ // boot capture has already moved them out of the environment (#508).
164
+ const stigmerTokenValue = getRunnerSecret("STIGMER_TOKEN");
165
+ if ((mode === "cloud" || proxyActive) && !stigmerTokenValue) {
166
+ throw new Error("Required environment variable STIGMER_TOKEN is not set");
167
+ }
168
+ const stigmerToken = stigmerTokenValue ?? null;
164
169
 
165
170
  const mcpBridgeEndpoint = process.env.STIGMER_MCP_BRIDGE_ENDPOINT ?? null;
166
171
 
@@ -170,8 +175,8 @@ export function loadConfig(): Config {
170
175
  // the SDK's authorization header (Cursor access token) passes through to
171
176
  // api2.cursor.sh unchanged.
172
177
  const cursorApiKey = proxyActive
173
- ? (process.env.CURSOR_API_KEY ?? stigmerToken ?? "proxy-managed")
174
- : (process.env.CURSOR_API_KEY ?? "");
178
+ ? (getRunnerSecret("CURSOR_API_KEY") ?? stigmerToken ?? "proxy-managed")
179
+ : (getRunnerSecret("CURSOR_API_KEY") ?? "");
175
180
 
176
181
  const workspaceRootDir = resolveWorkspaceRootDir();
177
182
 
@@ -25,6 +25,8 @@
25
25
  * on the next runner boot — there is no live re-key.
26
26
  */
27
27
 
28
+ import { getRunnerSecret } from "../shared/runner-credential-store.js";
29
+
28
30
  export interface EncryptionKey {
29
31
  readonly keyId: string;
30
32
  /** 32-byte AES-256 key. */
@@ -75,14 +77,18 @@ const AES_256_KEY_BYTES = 32;
75
77
  export function loadPayloadEncryptionConfig(
76
78
  bootstrap?: BootstrapKeyMaterial,
77
79
  ): PayloadEncryptionConfig | undefined {
78
- const rawKey = process.env[KEY_ENV];
80
+ // Key VALUES resolve through the credential store (the #508 boot capture
81
+ // moves them out of process.env — agent shells must not read them); the
82
+ // *_KEY_ID companions are rotation bookkeeping, not secrets, and stay
83
+ // plain env reads.
84
+ const rawKey = getRunnerSecret(KEY_ENV);
79
85
  if (rawKey) {
80
86
  const primary: EncryptionKey = {
81
87
  keyId: requireKeyId(KEY_ID_ENV),
82
88
  key: parseKey(rawKey, KEY_ENV),
83
89
  };
84
90
 
85
- const rawSecondary = process.env[SECONDARY_KEY_ENV];
91
+ const rawSecondary = getRunnerSecret(SECONDARY_KEY_ENV);
86
92
  const secondary: EncryptionKey | undefined = rawSecondary
87
93
  ? {
88
94
  keyId: requireKeyId(SECONDARY_KEY_ID_ENV),
package/src/index.ts CHANGED
@@ -14,7 +14,7 @@
14
14
  * import { createStigmerRunner } from '@stigmer/runner';
15
15
  *
16
16
  * const runner = await createStigmerRunner({
17
- * taskQueue: 'agent_execution_runner',
17
+ * taskQueue: 'stigmer_runner',
18
18
  * temporalAddress: 'localhost:7233',
19
19
  * stigmerEndpoint: 'http://localhost:7234',
20
20
  * });
package/src/main.ts CHANGED
@@ -32,6 +32,10 @@ import { createInterface } from "node:readline";
32
32
  import { preflightNodeRuntime } from "./preflight.js";
33
33
  import { markBoot, emitRunnerBootTiming } from "./shared/cold-start-timing.js";
34
34
  import { loadConfig } from "./config.js";
35
+ import {
36
+ captureRunnerSecrets,
37
+ getRunnerSecret,
38
+ } from "./shared/runner-credential-store.js";
35
39
  import { initTracing, initMetrics } from "./otel.js";
36
40
  import { createStigmerRunner } from "./runner.js";
37
41
  import { createStigmerRunnerManager } from "./runner-manager.js";
@@ -214,11 +218,12 @@ async function runPoolMode(
214
218
  });
215
219
 
216
220
  // Sandbox credential self-renewal (see sandbox-token-renewal.ts). Watches
217
- // the env var because manager.updateToken keeps it in lockstep with the
218
- // manager's internal ref: a blank member's pool_sandbox token parks the
219
- // loop, the claim's updateToken swaps in a renewable session token, and
220
- // from then on renewal applies fresh tokens back through the same
221
- // updateToken (which also cascades to the proxy-credential coordinator).
221
+ // the credential store because manager.updateToken keeps it in lockstep
222
+ // with the manager's internal ref: a blank member's pool_sandbox token
223
+ // parks the loop, the claim's updateToken swaps in a renewable session
224
+ // token, and from then on renewal applies fresh tokens back through the
225
+ // same updateToken (which also cascades to the proxy-credential
226
+ // coordinator).
222
227
  const { startSandboxTokenRenewal } = await import("./sandbox-token-renewal.js");
223
228
  const { StigmerClient } = await import("./client/stigmer-client.js");
224
229
  const renewalClient = new StigmerClient({
@@ -226,7 +231,7 @@ async function runPoolMode(
226
231
  token: null,
227
232
  });
228
233
  const tokenRenewal = startSandboxTokenRenewal({
229
- getToken: () => process.env.STIGMER_TOKEN ?? null,
234
+ getToken: () => getRunnerSecret("STIGMER_TOKEN") ?? null,
230
235
  renew: (currentToken) =>
231
236
  renewalClient.getRunnerScopedToken({ renewal: true }, currentToken),
232
237
  applyToken: (token) => manager.updateToken(token),
@@ -449,6 +454,11 @@ async function main(): Promise<void> {
449
454
 
450
455
  checkBuildFreshness();
451
456
 
457
+ // Take custody of runner secrets before the config load reads them (#508).
458
+ // The factories capture too (they are the library boot doors); doing it
459
+ // here as well keeps main.ts's own late readers store-only from the start.
460
+ captureRunnerSecrets();
461
+
452
462
  const config = loadConfig();
453
463
  markBoot("config_loaded");
454
464