@stigmer/runner 3.12.4 → 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 (146) hide show
  1. package/dist/.build-fingerprint +1 -1
  2. package/dist/activities/execute-cursor/cursor-mcp-config.d.ts +45 -0
  3. package/dist/activities/execute-cursor/cursor-mcp-config.js +76 -0
  4. package/dist/activities/execute-cursor/cursor-mcp-config.js.map +1 -0
  5. package/dist/activities/execute-cursor/index.d.ts +10 -1
  6. package/dist/activities/execute-cursor/index.js +47 -29
  7. package/dist/activities/execute-cursor/index.js.map +1 -1
  8. package/dist/activities/execute-cursor/prompt-builder.d.ts +13 -2
  9. package/dist/activities/execute-cursor/prompt-builder.js +11 -0
  10. package/dist/activities/execute-cursor/prompt-builder.js.map +1 -1
  11. package/dist/activities/execute-cursor/session-lifecycle.d.ts +1 -1
  12. package/dist/activities/execute-cursor/skill-resolver.d.ts +2 -21
  13. package/dist/activities/execute-cursor/skill-resolver.js +5 -108
  14. package/dist/activities/execute-cursor/skill-resolver.js.map +1 -1
  15. package/dist/activities/execute-deep-agent/approval-file-change.d.ts +9 -0
  16. package/dist/activities/execute-deep-agent/approval-file-change.js +2 -1
  17. package/dist/activities/execute-deep-agent/approval-file-change.js.map +1 -1
  18. package/dist/activities/execute-deep-agent/cas-capture-backend.d.ts +18 -1
  19. package/dist/activities/execute-deep-agent/cas-capture-backend.js +20 -3
  20. package/dist/activities/execute-deep-agent/cas-capture-backend.js.map +1 -1
  21. package/dist/activities/execute-deep-agent/index.js +57 -5
  22. package/dist/activities/execute-deep-agent/index.js.map +1 -1
  23. package/dist/activities/execute-deep-agent/prompt-builder.d.ts +9 -0
  24. package/dist/activities/execute-deep-agent/prompt-builder.js +10 -0
  25. package/dist/activities/execute-deep-agent/prompt-builder.js.map +1 -1
  26. package/dist/activities/execute-deep-agent/setup.js +17 -16
  27. package/dist/activities/execute-deep-agent/setup.js.map +1 -1
  28. package/dist/activities/execute-deep-agent/subagent-transformer.js +17 -11
  29. package/dist/activities/execute-deep-agent/subagent-transformer.js.map +1 -1
  30. package/dist/activities/execute-deep-agent/subagent-wiring.d.ts +3 -3
  31. package/dist/index.d.ts +1 -1
  32. package/dist/index.js +1 -1
  33. package/dist/middleware/path-normalization.d.ts +49 -49
  34. package/dist/middleware/path-normalization.js +73 -84
  35. package/dist/middleware/path-normalization.js.map +1 -1
  36. package/dist/middleware/types.d.ts +3 -3
  37. package/dist/runner-manager.d.ts +1 -1
  38. package/dist/runner-manager.js +22 -15
  39. package/dist/runner-manager.js.map +1 -1
  40. package/dist/runner.js +11 -0
  41. package/dist/runner.js.map +1 -1
  42. package/dist/shared/args-preview.d.ts +8 -0
  43. package/dist/shared/args-preview.js +14 -3
  44. package/dist/shared/args-preview.js.map +1 -1
  45. package/dist/shared/artifact-storage.d.ts +11 -1
  46. package/dist/shared/artifact-storage.js +10 -1
  47. package/dist/shared/artifact-storage.js.map +1 -1
  48. package/dist/shared/declared-preferences.d.ts +47 -0
  49. package/dist/shared/declared-preferences.js +64 -0
  50. package/dist/shared/declared-preferences.js.map +1 -0
  51. package/dist/shared/mcp-enabled-tools.d.ts +2 -3
  52. package/dist/shared/mcp-enabled-tools.js +2 -3
  53. package/dist/shared/mcp-enabled-tools.js.map +1 -1
  54. package/dist/shared/mcp-resolver.d.ts +20 -15
  55. package/dist/shared/mcp-resolver.js +11 -12
  56. package/dist/shared/mcp-resolver.js.map +1 -1
  57. package/dist/shared/placeholder-resolver.d.ts +9 -2
  58. package/dist/shared/placeholder-resolver.js +9 -2
  59. package/dist/shared/placeholder-resolver.js.map +1 -1
  60. package/dist/shared/plan-mode-permissions.d.ts +26 -46
  61. package/dist/shared/plan-mode-permissions.js +27 -55
  62. package/dist/shared/plan-mode-permissions.js.map +1 -1
  63. package/dist/shared/skill-mount.d.ts +89 -0
  64. package/dist/shared/skill-mount.js +142 -0
  65. package/dist/shared/skill-mount.js.map +1 -0
  66. package/dist/shared/skill-writer.d.ts +28 -26
  67. package/dist/shared/skill-writer.js +79 -102
  68. package/dist/shared/skill-writer.js.map +1 -1
  69. package/dist/shared/worker-shutdown.d.ts +67 -0
  70. package/dist/shared/worker-shutdown.js +79 -0
  71. package/dist/shared/worker-shutdown.js.map +1 -0
  72. package/dist/shared/workspace/types.d.ts +3 -2
  73. package/dist/workflow-engine/loader.js +38 -1
  74. package/dist/workflow-engine/loader.js.map +1 -1
  75. package/dist/workflow-engine/tasks/human-input.js +23 -2
  76. package/dist/workflow-engine/tasks/human-input.js.map +1 -1
  77. package/package.json +15 -11
  78. package/src/activities/execute-cursor/__tests__/build-prompt.test.ts +50 -0
  79. package/src/activities/execute-cursor/__tests__/cursor-mcp-config.test.ts +111 -0
  80. package/src/activities/execute-cursor/__tests__/skill-resolver.test.ts +3 -94
  81. package/src/activities/execute-cursor/cursor-mcp-config.ts +107 -0
  82. package/src/activities/execute-cursor/index.ts +63 -35
  83. package/src/activities/execute-cursor/prompt-builder.ts +29 -2
  84. package/src/activities/execute-cursor/session-lifecycle.ts +1 -1
  85. package/src/activities/execute-cursor/skill-resolver.ts +10 -137
  86. package/src/activities/execute-deep-agent/__tests__/approval-file-change.test.ts +15 -0
  87. package/src/activities/execute-deep-agent/__tests__/cas-capture-backend.test.ts +75 -0
  88. package/src/activities/execute-deep-agent/__tests__/hitl-reject.test.ts +1 -0
  89. package/src/activities/execute-deep-agent/__tests__/hitl-resume-approve-all.test.ts +1 -0
  90. package/src/activities/execute-deep-agent/__tests__/hitl-resume-history.test.ts +1 -0
  91. package/src/activities/execute-deep-agent/__tests__/index.test.ts +1 -0
  92. package/src/activities/execute-deep-agent/__tests__/plan-mode-path-normalization.test.ts +42 -29
  93. package/src/activities/execute-deep-agent/__tests__/prompt-builder.test.ts +52 -0
  94. package/src/activities/execute-deep-agent/__tests__/sequential-gate-resume.test.ts +1 -0
  95. package/src/activities/execute-deep-agent/__tests__/subagent-plan-mode-permissions.test.ts +11 -8
  96. package/src/activities/execute-deep-agent/approval-file-change.ts +11 -1
  97. package/src/activities/execute-deep-agent/cas-capture-backend.ts +20 -3
  98. package/src/activities/execute-deep-agent/index.ts +57 -5
  99. package/src/activities/execute-deep-agent/prompt-builder.ts +22 -0
  100. package/src/activities/execute-deep-agent/setup.ts +21 -19
  101. package/src/activities/execute-deep-agent/subagent-transformer.ts +19 -12
  102. package/src/activities/execute-deep-agent/subagent-wiring.ts +3 -3
  103. package/src/index.ts +1 -1
  104. package/src/middleware/__tests__/path-normalization.test.ts +32 -32
  105. package/src/middleware/path-normalization.ts +78 -90
  106. package/src/middleware/types.ts +3 -3
  107. package/src/runner-manager.ts +26 -16
  108. package/src/runner.ts +14 -0
  109. package/src/shared/__tests__/artifact-storage.test.ts +28 -0
  110. package/src/shared/__tests__/bedrock-seam.test.ts +5 -5
  111. package/src/shared/__tests__/declared-preferences.test.ts +109 -0
  112. package/src/shared/__tests__/foundry-seam.test.ts +5 -5
  113. package/src/shared/__tests__/plan-mode-permissions.test.ts +15 -44
  114. package/src/shared/__tests__/skill-mount.test.ts +238 -0
  115. package/src/shared/__tests__/skill-writer.test.ts +151 -128
  116. package/src/shared/__tests__/vertex-adapter.test.ts +30 -3
  117. package/src/shared/__tests__/vertex-seam.test.ts +5 -5
  118. package/src/shared/__tests__/worker-shutdown.test.ts +101 -0
  119. package/src/shared/args-preview.ts +17 -3
  120. package/src/shared/artifact-storage.ts +20 -1
  121. package/src/shared/declared-preferences.ts +84 -0
  122. package/src/shared/mcp-enabled-tools.ts +2 -3
  123. package/src/shared/mcp-resolver.ts +20 -20
  124. package/src/shared/placeholder-resolver.ts +9 -2
  125. package/src/shared/plan-mode-permissions.ts +27 -58
  126. package/src/shared/skill-mount.ts +179 -0
  127. package/src/shared/skill-writer.ts +96 -130
  128. package/src/shared/worker-shutdown.ts +99 -0
  129. package/src/shared/workspace/types.ts +3 -2
  130. package/src/workflow-engine/__tests__/loader.test.ts +51 -0
  131. package/src/workflow-engine/__tests__/tasks/human-input.test.ts +117 -0
  132. package/src/workflow-engine/loader.ts +46 -1
  133. package/src/workflow-engine/tasks/human-input.ts +33 -5
  134. package/dist/activities/execute-cursor/connect-backfill.d.ts +0 -19
  135. package/dist/activities/execute-cursor/connect-backfill.js +0 -27
  136. package/dist/activities/execute-cursor/connect-backfill.js.map +0 -1
  137. package/dist/activities/execute-cursor/mcp-resolver.d.ts +0 -120
  138. package/dist/activities/execute-cursor/mcp-resolver.js +0 -194
  139. package/dist/activities/execute-cursor/mcp-resolver.js.map +0 -1
  140. package/dist/activities/execute-cursor/placeholder-resolver.d.ts +0 -34
  141. package/dist/activities/execute-cursor/placeholder-resolver.js +0 -82
  142. package/dist/activities/execute-cursor/placeholder-resolver.js.map +0 -1
  143. package/src/activities/execute-cursor/__tests__/mcp-resolver.test.ts +0 -125
  144. package/src/activities/execute-cursor/connect-backfill.ts +0 -52
  145. package/src/activities/execute-cursor/mcp-resolver.ts +0 -324
  146. package/src/activities/execute-cursor/placeholder-resolver.ts +0 -109
@@ -1 +1 @@
1
- {"hash":"30fa0c9a11a788e3","builtAt":"2026-08-14T07:34:03.879Z","fileCount":297}
1
+ {"hash":"e77d58b2bc3277e6","builtAt":"2026-08-15T12:57:42.305Z","fileCount":298}
@@ -0,0 +1,45 @@
1
+ /**
2
+ * The Cursor harness's MCP output adapter: maps the shared resolver's
3
+ * {@link ResolvedMcpServer} servers into the Cursor SDK's mcpServers config
4
+ * for Agent.create(), plus the harness's env pre-flight check.
5
+ *
6
+ * Resolution itself lives in shared/mcp-resolver.ts (oss#387 consolidated the
7
+ * near-duplicate resolver this file used to carry) — this module is the
8
+ * symmetric twin of the deep-agent harness's toMcpClientConfig in
9
+ * shared/mcp-manager.ts: both consume the identical intermediate, only the
10
+ * final SDK serialization differs.
11
+ */
12
+ import type { McpServerUsage } from "@stigmer/protos/ai/stigmer/agentic/agent/v1/spec_pb";
13
+ import type { ResolvedMcpServer } from "../../shared/mcp-resolver.js";
14
+ /**
15
+ * Cursor SDK MCP server config shape (matches @cursor/sdk McpServerConfig).
16
+ * Defined here to avoid tight coupling to the SDK's internal types.
17
+ */
18
+ export type CursorMcpServerConfig = {
19
+ type?: "stdio";
20
+ command: string;
21
+ args?: string[];
22
+ env?: Record<string, string>;
23
+ cwd?: string;
24
+ } | {
25
+ type?: "http" | "sse";
26
+ url: string;
27
+ headers?: Record<string, string>;
28
+ };
29
+ /**
30
+ * Transform resolved MCP servers into the Cursor SDK's mcpServers config
31
+ * for Agent.create().
32
+ *
33
+ * Call it ONCE, after the last mutation of the resolved-server list (backfill,
34
+ * synthesized-attachment injection): the config is a projection of that list,
35
+ * and building it early just creates a stale copy someone must remember to
36
+ * rebuild. The Cursor SDK config cannot hide tools, so enabledTools is NOT
37
+ * expressed here — the HITL hook enforces it (see ResolvedMcpServer.enabledTools).
38
+ */
39
+ export declare function toCursorMcpConfig(servers: ResolvedMcpServer[]): Record<string, CursorMcpServerConfig>;
40
+ /**
41
+ * Validate that resolved MCP servers have their required env vars populated.
42
+ * Returns a list of warnings for servers with empty/missing env.
43
+ * Used as a pre-flight check before agent.send() to surface config issues early.
44
+ */
45
+ export declare function validateMcpServerEnv(servers: ResolvedMcpServer[], usages: McpServerUsage[]): string[];
@@ -0,0 +1,76 @@
1
+ /**
2
+ * The Cursor harness's MCP output adapter: maps the shared resolver's
3
+ * {@link ResolvedMcpServer} servers into the Cursor SDK's mcpServers config
4
+ * for Agent.create(), plus the harness's env pre-flight check.
5
+ *
6
+ * Resolution itself lives in shared/mcp-resolver.ts (oss#387 consolidated the
7
+ * near-duplicate resolver this file used to carry) — this module is the
8
+ * symmetric twin of the deep-agent harness's toMcpClientConfig in
9
+ * shared/mcp-manager.ts: both consume the identical intermediate, only the
10
+ * final SDK serialization differs.
11
+ */
12
+ /**
13
+ * Transform resolved MCP servers into the Cursor SDK's mcpServers config
14
+ * for Agent.create().
15
+ *
16
+ * Call it ONCE, after the last mutation of the resolved-server list (backfill,
17
+ * synthesized-attachment injection): the config is a projection of that list,
18
+ * and building it early just creates a stale copy someone must remember to
19
+ * rebuild. The Cursor SDK config cannot hide tools, so enabledTools is NOT
20
+ * expressed here — the HITL hook enforces it (see ResolvedMcpServer.enabledTools).
21
+ */
22
+ export function toCursorMcpConfig(servers) {
23
+ const result = {};
24
+ for (const server of servers) {
25
+ if (server.connectionType === "stdio") {
26
+ if (!server.command)
27
+ continue;
28
+ result[server.slug] = {
29
+ type: "stdio",
30
+ command: server.command,
31
+ args: server.args,
32
+ env: server.env,
33
+ cwd: server.cwd,
34
+ };
35
+ }
36
+ else {
37
+ if (!server.url)
38
+ continue;
39
+ result[server.slug] = {
40
+ type: server.connectionType,
41
+ url: server.url,
42
+ headers: server.headers,
43
+ };
44
+ }
45
+ }
46
+ return result;
47
+ }
48
+ /**
49
+ * Validate that resolved MCP servers have their required env vars populated.
50
+ * Returns a list of warnings for servers with empty/missing env.
51
+ * Used as a pre-flight check before agent.send() to surface config issues early.
52
+ */
53
+ export function validateMcpServerEnv(servers, usages) {
54
+ const warnings = [];
55
+ for (const usage of usages) {
56
+ const slug = usage.mcpServerRef?.slug;
57
+ if (!slug)
58
+ continue;
59
+ const resolved = servers.find((s) => s.slug === slug);
60
+ if (!resolved) {
61
+ warnings.push(`MCP server '${slug}': failed to resolve (server may not exist or is inaccessible)`);
62
+ continue;
63
+ }
64
+ if (resolved.connectionType === "stdio" && resolved.env) {
65
+ const emptyKeys = Object.entries(resolved.env)
66
+ .filter(([, v]) => !v)
67
+ .map(([k]) => k);
68
+ if (emptyKeys.length > 0) {
69
+ warnings.push(`MCP server '${slug}': env vars [${emptyKeys.join(", ")}] are empty — ` +
70
+ `server subprocess will likely fail to connect`);
71
+ }
72
+ }
73
+ }
74
+ return warnings;
75
+ }
76
+ //# sourceMappingURL=cursor-mcp-config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cursor-mcp-config.js","sourceRoot":"","sources":["../../../src/activities/execute-cursor/cursor-mcp-config.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAuBH;;;;;;;;;GASG;AACH,MAAM,UAAU,iBAAiB,CAC/B,OAA4B;IAE5B,MAAM,MAAM,GAA0C,EAAE,CAAC;IAEzD,KAAK,MAAM,MAAM,IAAI,OAAO,EAAE,CAAC;QAC7B,IAAI,MAAM,CAAC,cAAc,KAAK,OAAO,EAAE,CAAC;YACtC,IAAI,CAAC,MAAM,CAAC,OAAO;gBAAE,SAAS;YAC9B,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG;gBACpB,IAAI,EAAE,OAAO;gBACb,OAAO,EAAE,MAAM,CAAC,OAAO;gBACvB,IAAI,EAAE,MAAM,CAAC,IAAI;gBACjB,GAAG,EAAE,MAAM,CAAC,GAAG;gBACf,GAAG,EAAE,MAAM,CAAC,GAAG;aAChB,CAAC;QACJ,CAAC;aAAM,CAAC;YACN,IAAI,CAAC,MAAM,CAAC,GAAG;gBAAE,SAAS;YAC1B,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG;gBACpB,IAAI,EAAE,MAAM,CAAC,cAAc;gBAC3B,GAAG,EAAE,MAAM,CAAC,GAAG;gBACf,OAAO,EAAE,MAAM,CAAC,OAAO;aACxB,CAAC;QACJ,CAAC;IACH,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,oBAAoB,CAClC,OAA4B,EAC5B,MAAwB;IAExB,MAAM,QAAQ,GAAa,EAAE,CAAC;IAE9B,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;QAC3B,MAAM,IAAI,GAAG,KAAK,CAAC,YAAY,EAAE,IAAI,CAAC;QACtC,IAAI,CAAC,IAAI;YAAE,SAAS;QAEpB,MAAM,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,IAAI,CAAC,CAAC;QACtD,IAAI,CAAC,QAAQ,EAAE,CAAC;YACd,QAAQ,CAAC,IAAI,CAAC,eAAe,IAAI,gEAAgE,CAAC,CAAC;YACnG,SAAS;QACX,CAAC;QAED,IAAI,QAAQ,CAAC,cAAc,KAAK,OAAO,IAAI,QAAQ,CAAC,GAAG,EAAE,CAAC;YACxD,MAAM,SAAS,GAAG,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAC;iBAC3C,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC;iBACrB,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC;YACnB,IAAI,SAAS,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;gBACzB,QAAQ,CAAC,IAAI,CACX,eAAe,IAAI,gBAAgB,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,gBAAgB;oBACvE,+CAA+C,CAChD,CAAC;YACJ,CAAC;QACH,CAAC;IACH,CAAC;IAED,OAAO,QAAQ,CAAC;AAClB,CAAC"}
@@ -99,10 +99,19 @@ export interface BuildPromptInput {
99
99
  * turn.
100
100
  */
101
101
  sessionContext?: string;
102
+ /**
103
+ * Platform-declared standing preferences from the execution spec's
104
+ * `declared_preferences` (stigmer/stigmer#293). Like the bridge, only
105
+ * the enhanced-prompt path consumes it — deliberately frozen per Cursor
106
+ * session (DD-002 D3): the first turn delivers it into the agent's own
107
+ * conversation store, and repeating it on resumed turns would bloat the
108
+ * store with identical content.
109
+ */
110
+ declaredPreferences?: import("../../shared/declared-preferences.js").DeclaredPreferencesContent;
102
111
  /**
103
112
  * Conversation catchup from the execution spec's `conversation_catchup`
104
113
  * (cloud DD-006): what happened on the channel conversation that the
105
- * agent has not seen. PER-TURN, so unlike the three standing values
114
+ * agent has not seen. PER-TURN, so unlike the standing values
106
115
  * above it rides BOTH prompt paths — the enhanced prompt and a resumed
107
116
  * turn's prefix (the `interaction_mode` shape). Handback lands
108
117
  * mid-session on a resumed agent: the resumed path is the one that
@@ -45,6 +45,7 @@ import { readConversationCatchup } from "../../shared/conversation-catchup.js";
45
45
  import { readSenderIdentity } from "../../shared/sender-identity.js";
46
46
  import { injectCallerIdentityEnv, resolveCallerIdentity, } from "../../shared/caller-identity.js";
47
47
  import { readSessionContext } from "../../shared/session-context.js";
48
+ import { readDeclaredPreferences } from "../../shared/declared-preferences.js";
48
49
  import { withholdSecretContentFromMessages } from "../../shared/tool-row.js";
49
50
  import { StallTimeoutError, formatStallFailure } from "../../shared/stall-watchdog.js";
50
51
  import { resolveUsableArtifactStorage, loadArtifactStorageConfig } from "../../shared/artifact-storage.js";
@@ -55,7 +56,8 @@ import { DeltaEnricher } from "./delta-enricher.js";
55
56
  import { TodoTracker } from "./todo-tracker.js";
56
57
  import { StreamingUpdateScheduler, loadStreamingConfig } from "../../shared/streaming-scheduler.js";
57
58
  import { createCursorEventRecorder } from "./cursor-event-recorder.js";
58
- import { resolveMcpServers, toCursorMcpConfig, validateMcpServerEnv } from "./mcp-resolver.js";
59
+ import { resolveMcpServers } from "../../shared/mcp-resolver.js";
60
+ import { toCursorMcpConfig, validateMcpServerEnv } from "./cursor-mcp-config.js";
59
61
  import { resolveMcpTransportPosture } from "../../shared/mcp-transport-guard.js";
60
62
  import { discoverChannelMessaging, synthesizeChannelAttachment, } from "../../shared/channel-attachment.js";
61
63
  import { readChannelConversationId, synthesizeConversationAttachment, } from "../../shared/conversation-attachment.js";
@@ -63,7 +65,7 @@ import { injectSynthesizedAttachment } from "../../shared/synthesized-attachment
63
65
  import { mergeApprovalPolicies } from "./approval-policy.js";
64
66
  import { deriveActiveLeases, isUnattendedApprovalMode } from "../../shared/approval-policy.js";
65
67
  import { enabledToolsBySlug } from "../../shared/mcp-enabled-tools.js";
66
- import { backfillMcpServersIfNeeded } from "./connect-backfill.js";
68
+ import { backfillMcpServersIfNeeded } from "../../shared/connect-backfill.js";
67
69
  import { resolveExecutionEnv } from "./env-resolver.js";
68
70
  import { resolveBlueprint } from "./blueprint-resolver.js";
69
71
  import { buildCursorSubAgentDefinitions } from "./subagent-config.js";
@@ -103,7 +105,7 @@ import { synthesizeError, formatClassifiedError, shouldRetryWithFreshAgent, extr
103
105
  import { createAgent, createCloudAgent } from "./session-lifecycle.js";
104
106
  import { setMaxListeners } from "node:events";
105
107
  import { startHeartbeat } from "../../shared/heartbeat.js";
106
- import { getShutdownSignalForQueue } from "../../runner-manager.js";
108
+ import { classifyTurnInterruption, getShutdownSignalForQueue } from "../../shared/worker-shutdown.js";
107
109
  /**
108
110
  * Creates the activity functions bound to the runner config.
109
111
  * Returned object is passed to Temporal Worker.create({ activities }).
@@ -526,12 +528,15 @@ turnSeq) {
526
528
  await reportSetupProgress(client, executionId, "Resolving MCP servers");
527
529
  const transportPosture = resolveMcpTransportPosture(config.mode);
528
530
  const mcpEnvVars = injectCallerIdentityEnv(envVars, resolveCallerIdentity(blueprint.sessionSpec.metadata, session.status?.audit?.specAudit?.createdBy), sessionId);
529
- let mcpResolution = await resolveMcpServers(client, blueprint.mergedMcpServerUsages, mcpEnvVars, transportPosture);
531
+ // The resolved-server list mutates through backfill and attachment
532
+ // injection below; the Cursor SDK config is projected from it exactly
533
+ // once, after the last mutation (see toCursorMcpConfig).
534
+ let resolvedMcpServers = (await resolveMcpServers(client, blueprint.mergedMcpServerUsages, mcpEnvVars, transportPosture)).resolvedServers;
530
535
  setupTiming.mark("resolve_mcp_servers");
531
536
  // Phase 4a: Connect backfill for undiscovered MCP servers
532
537
  heartbeatPhase = "resolving_mcp_servers";
533
538
  const sessionOrg = session.metadata?.org ?? "";
534
- mcpResolution = await backfillMcpServersIfNeeded(client, mcpResolution, blueprint.mergedMcpServerUsages, mcpEnvVars, sessionOrg, transportPosture, heartbeat, secretKeys);
539
+ resolvedMcpServers = await backfillMcpServersIfNeeded(client, resolvedMcpServers, blueprint.mergedMcpServerUsages, mcpEnvVars, sessionOrg, transportPosture, heartbeat, secretKeys);
535
540
  setupTiming.mark("backfill_mcp");
536
541
  // The synthesized attachments' credential story (DD-006 D4): the
537
542
  // exchanged token authenticates the discovery reads per-call (a
@@ -574,11 +579,7 @@ turnSeq) {
574
579
  backendEndpoint: config.stigmerBackendEndpoint,
575
580
  });
576
581
  if (attachment) {
577
- const resolvedServers = injectSynthesizedAttachment(mcpResolution.resolvedServers, attachment, "channel messaging");
578
- mcpResolution = {
579
- resolvedServers,
580
- cursorConfig: toCursorMcpConfig(resolvedServers),
581
- };
582
+ resolvedMcpServers = injectSynthesizedAttachment(resolvedMcpServers, attachment, "channel messaging");
582
583
  }
583
584
  }
584
585
  // Phase 4a4: Synthesize the conversation participation attachment
@@ -594,13 +595,11 @@ turnSeq) {
594
595
  backendEndpoint: config.stigmerBackendEndpoint,
595
596
  });
596
597
  if (conversationAttachment) {
597
- const resolvedServers = injectSynthesizedAttachment(mcpResolution.resolvedServers, conversationAttachment, "conversation participation");
598
- mcpResolution = {
599
- resolvedServers,
600
- cursorConfig: toCursorMcpConfig(resolvedServers),
601
- };
598
+ resolvedMcpServers = injectSynthesizedAttachment(resolvedMcpServers, conversationAttachment, "conversation participation");
602
599
  }
603
- const mcpConfig = mcpResolution.cursorConfig;
600
+ // The one projection point: every mutation above is now visible in the
601
+ // Cursor SDK config by construction (no per-mutation rebuild to forget).
602
+ const mcpConfig = toCursorMcpConfig(resolvedMcpServers);
604
603
  // Phase 4b: Merge approval policies from all layers.
605
604
  //
606
605
  // Two bypasses (see ActiveLeases, shared with the native harness): the
@@ -614,10 +613,10 @@ turnSeq) {
614
613
  // Layer-3 overrides ride each resolved server from its merged usage —
615
614
  // see ResolvedMcpServer.toolApprovalOverrides (issue #349) — so there
616
615
  // is no separate override input to pass here.
617
- const mergedPolicies = mergeApprovalPolicies(mcpResolution.resolvedServers, leases);
616
+ const mergedPolicies = mergeApprovalPolicies(resolvedMcpServers, leases);
618
617
  heartbeat();
619
618
  // Phase 4c: Validate MCP server env health (diagnostic, non-blocking)
620
- const mcpWarnings = validateMcpServerEnv(mcpResolution.resolvedServers, blueprint.mergedMcpServerUsages, envVars);
619
+ const mcpWarnings = validateMcpServerEnv(resolvedMcpServers, blueprint.mergedMcpServerUsages);
621
620
  if (mcpWarnings.length > 0) {
622
621
  console.warn(`ExecuteCursor MCP pre-flight warnings: execution=${executionId}\n` +
623
622
  mcpWarnings.map((w) => ` - ${w}`).join("\n"));
@@ -760,7 +759,7 @@ turnSeq) {
760
759
  // servers' allow-lists, enforced by the hook's "disabled" arm ahead of
761
760
  // every approval bypass. The Cursor SDK config cannot hide a server's
762
761
  // tools, so this deny-at-call is the harness's enforcement.
763
- enabledToolsBySlug(mcpResolution.resolvedServers));
762
+ enabledToolsBySlug(resolvedMcpServers));
764
763
  const hitlGate = await installHitlGate({
765
764
  workspaceRoot: primaryWorkspaceDir,
766
765
  hitlDir,
@@ -989,6 +988,7 @@ turnSeq) {
989
988
  contextBridge: readContextBridge(blueprint.sessionSpec.metadata),
990
989
  senderIdentity: readSenderIdentity(blueprint.sessionSpec.metadata),
991
990
  sessionContext: readSessionContext(blueprint.sessionSpec.metadata),
991
+ declaredPreferences: readDeclaredPreferences(spec.declaredPreferences),
992
992
  conversationCatchup: readConversationCatchup(spec.conversationCatchup),
993
993
  // The turn's recorded transcript, seeded from the persisted execution
994
994
  // on a reinvocation (Phase 3). Consumed only by the HITL-recovery
@@ -1188,14 +1188,22 @@ turnSeq) {
1188
1188
  // classifies a shutdown from the shutdown signal directly (in
1189
1189
  // resolvePreBoundaryTerminal). The heartbeat timer may set `cancelled` before
1190
1190
  // the AbortSignal microtask propagates; the direct signal check catches that.
1191
- const isShutdown = periodicHeartbeat.workerShutdown || (shutdownSignal?.aborted ?? false);
1192
- if (isShutdown) {
1191
+ // The decision table (including #776's grace-window guard: an aborted
1192
+ // shutdown signal with NO interruption evidence stays "none") lives in
1193
+ // classifyTurnInterruption — shared/worker-shutdown.ts.
1194
+ const interruption = classifyTurnInterruption({
1195
+ heartbeatCancelled: periodicHeartbeat.cancelled,
1196
+ heartbeatWorkerShutdown: periodicHeartbeat.workerShutdown,
1197
+ cancellationSignalAborted: Context.current().cancellationSignal.aborted,
1198
+ shutdownSignalAborted: shutdownSignal?.aborted ?? false,
1199
+ });
1200
+ if (interruption === "worker-shutdown") {
1193
1201
  turnState.pauseDetected = false;
1194
1202
  }
1195
- else if (periodicHeartbeat.cancelled) {
1203
+ else if (interruption === "pause") {
1196
1204
  turnState.pauseDetected = true;
1197
1205
  }
1198
- workerShutdownDetected = isShutdown;
1206
+ workerShutdownDetected = interruption === "worker-shutdown";
1199
1207
  // Post-stream finalize, shared by the primary turn and both recovery retries:
1200
1208
  // finalize the transcript + streaming flags, mark any in-flight sub-agent
1201
1209
  // CANCELLED on an aborted turn, snapshot usage, flush the recorder, and
@@ -1270,10 +1278,14 @@ turnSeq) {
1270
1278
  `estimatedCostUsd=${estimated.toFixed(4)}, maxCostUsd=${maxCostUsd.toFixed(2)}`);
1271
1279
  return { kind: "return" };
1272
1280
  }
1273
- // Worker shutdown: the runner-manager aborted the shutdown signal. NOT a
1281
+ // Worker shutdown: the runner/manager aborted the shutdown signal. NOT a
1274
1282
  // user pause. Checked via the shutdown signal directly so a retry (whose
1275
- // periodic heartbeat is already stopped) still classifies it correctly.
1276
- if (workerShutdownDetected || (shutdownSignal?.aborted ?? false)) {
1283
+ // periodic heartbeat is already stopped) still classifies it correctly
1284
+ // but only alongside a delivered cancellation: a retry that completed
1285
+ // normally inside the drain grace window must stay a completion (#776's
1286
+ // grace-window guard, mirroring the primary's `interrupted` gate).
1287
+ if (workerShutdownDetected ||
1288
+ ((shutdownSignal?.aborted ?? false) && Context.current().cancellationSignal.aborted)) {
1277
1289
  status.phase = ExecutionPhase.EXECUTION_FAILED;
1278
1290
  status.error = "Execution interrupted: runner worker was shut down. Retry or resume.";
1279
1291
  status.completedAt = utcTimestamp();
@@ -1574,6 +1586,7 @@ turnSeq) {
1574
1586
  contextBridge: readContextBridge(blueprint.sessionSpec.metadata),
1575
1587
  senderIdentity: readSenderIdentity(blueprint.sessionSpec.metadata),
1576
1588
  sessionContext: readSessionContext(blueprint.sessionSpec.metadata),
1589
+ declaredPreferences: readDeclaredPreferences(spec.declaredPreferences),
1577
1590
  conversationCatchup: readConversationCatchup(spec.conversationCatchup),
1578
1591
  // Composed fresh (not reused from Phase 10): the failed primary
1579
1592
  // stream may have appended partial work onto status.messages,
@@ -1805,9 +1818,12 @@ turnSeq) {
1805
1818
  catch (err) {
1806
1819
  periodicHeartbeat?.stop();
1807
1820
  if (err instanceof CancelledFailure) {
1808
- // workerShutdownDetected means the runner-manager signaled shutdown
1809
- // before the worker drained. This is infrastructure failure, not pause.
1810
- if (workerShutdownDetected) {
1821
+ // Worker shutdown is infrastructure failure, not pause. The direct
1822
+ // signal check covers a CancelledFailure thrown BEFORE the post-stream
1823
+ // classification ran (workerShutdownDetected still false); no extra
1824
+ // interruption-evidence gate is needed here — the caught
1825
+ // CancelledFailure IS the evidence (#776).
1826
+ if (workerShutdownDetected || (shutdownSignal?.aborted ?? false)) {
1811
1827
  console.log(`ExecuteCursor cancelled (worker shutdown) for execution ${executionId}`);
1812
1828
  status.phase = ExecutionPhase.EXECUTION_FAILED;
1813
1829
  status.error = "Execution interrupted: runner worker was shut down. Retry or resume.";
@@ -2109,6 +2125,7 @@ export function buildPrompt(input) {
2109
2125
  contextBridge: input.contextBridge,
2110
2126
  senderIdentity: input.senderIdentity,
2111
2127
  sessionContext: input.sessionContext,
2128
+ declaredPreferences: input.declaredPreferences,
2112
2129
  conversationCatchup,
2113
2130
  }, {
2114
2131
  turnDigest: input.turnRecoveryDigest,
@@ -2167,6 +2184,7 @@ export function buildPrompt(input) {
2167
2184
  contextBridge: input.contextBridge,
2168
2185
  senderIdentity: input.senderIdentity,
2169
2186
  sessionContext: input.sessionContext,
2187
+ declaredPreferences: input.declaredPreferences,
2170
2188
  conversationCatchup,
2171
2189
  });
2172
2190
  }