@adhdev/daemon-core 0.9.82-rc.20 → 0.9.82-rc.201

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 (241) hide show
  1. package/dist/boot/daemon-lifecycle.d.ts +1 -0
  2. package/dist/boot/process-hardening.d.ts +50 -0
  3. package/dist/chat/source-machine.d.ts +166 -0
  4. package/dist/chat/source-resolver.d.ts +104 -0
  5. package/dist/chat/subscription-updates.d.ts +1 -0
  6. package/dist/cli-adapter-types.d.ts +23 -2
  7. package/dist/cli-adapters/cli-script-runner.d.ts +117 -0
  8. package/dist/cli-adapters/cli-state-engine.d.ts +178 -0
  9. package/dist/cli-adapters/provider-cli-adapter.d.ts +106 -63
  10. package/dist/cli-adapters/provider-cli-parse.d.ts +4 -0
  11. package/dist/cli-adapters/provider-cli-shared.d.ts +29 -0
  12. package/dist/cli-adapters/raw-terminal-io.d.ts +37 -0
  13. package/dist/commands/cli-manager.d.ts +2 -1
  14. package/dist/commands/handler.d.ts +110 -0
  15. package/dist/commands/mesh-coordinator.d.ts +85 -1
  16. package/dist/commands/router.d.ts +26 -1
  17. package/dist/config/chat-history.d.ts +9 -0
  18. package/dist/config/config.d.ts +5 -0
  19. package/dist/config/mesh-config.d.ts +72 -1
  20. package/dist/git/git-commands.d.ts +7 -1
  21. package/dist/git/git-types.d.ts +2 -0
  22. package/dist/index.d.ts +39 -10
  23. package/dist/index.js +25488 -8333
  24. package/dist/index.js.map +1 -1
  25. package/dist/index.mjs +29124 -12045
  26. package/dist/index.mjs.map +1 -1
  27. package/dist/installer.d.ts +1 -4
  28. package/dist/ipc/local-ipc-server.d.ts +91 -0
  29. package/dist/launch.d.ts +1 -1
  30. package/dist/logging/async-batch-writer.d.ts +10 -0
  31. package/dist/mesh/contracts.d.ts +164 -0
  32. package/dist/mesh/coordinator-prompt.d.ts +30 -0
  33. package/dist/mesh/coordinator-registry.d.ts +59 -0
  34. package/dist/mesh/mesh-active-work.d.ts +90 -0
  35. package/dist/mesh/mesh-delivery-policy.d.ts +126 -0
  36. package/dist/mesh/mesh-events.d.ts +164 -6
  37. package/dist/mesh/mesh-fast-forward.d.ts +41 -0
  38. package/dist/mesh/mesh-host-ownership.d.ts +9 -0
  39. package/dist/mesh/mesh-ledger.d.ts +58 -1
  40. package/dist/mesh/mesh-refine-status.d.ts +26 -0
  41. package/dist/mesh/mesh-runtime-store.d.ts +149 -0
  42. package/dist/mesh/mesh-work-queue.d.ts +55 -6
  43. package/dist/mesh/preview-freshness.d.ts +18 -0
  44. package/dist/mesh/refine-config.d.ts +193 -0
  45. package/dist/mesh/worktree-bootstrap-config.d.ts +113 -0
  46. package/dist/providers/approval-utils.d.ts +13 -0
  47. package/dist/providers/cli-provider-instance.d.ts +39 -3
  48. package/dist/providers/contracts.d.ts +130 -6
  49. package/dist/providers/external-sources.d.ts +71 -0
  50. package/dist/providers/native-history/antigravity-cli-transcript.d.ts +100 -0
  51. package/dist/providers/native-history/claude-cli-transcript.d.ts +70 -0
  52. package/dist/providers/native-history/codex-cli-transcript.d.ts +73 -0
  53. package/dist/providers/native-history/dispatcher.d.ts +27 -0
  54. package/dist/providers/native-history/hermes-cli-transcript.d.ts +30 -0
  55. package/dist/providers/native-history/index.d.ts +13 -0
  56. package/dist/providers/provider-instance-manager.d.ts +12 -0
  57. package/dist/providers/provider-instance.d.ts +13 -1
  58. package/dist/providers/provider-loader.d.ts +26 -4
  59. package/dist/providers/provider-trust.d.ts +31 -0
  60. package/dist/providers/read-chat-contract.d.ts +29 -0
  61. package/dist/providers/sdk/v1/builders/acp/detect-status.d.ts +68 -0
  62. package/dist/providers/sdk/v1/builders/cli/detect-status.d.ts +85 -0
  63. package/dist/providers/sdk/v1/builders/cli/parse-approval-squash.d.ts +59 -0
  64. package/dist/providers/sdk/v1/builders/cli/parse-approval.d.ts +65 -0
  65. package/dist/providers/sdk/v1/builders/cli/parse-session.d.ts +91 -0
  66. package/dist/providers/sdk/v1/builders/cli/visible-region.d.ts +42 -0
  67. package/dist/providers/sdk/v1/fixture-tooling/format.d.ts +126 -0
  68. package/dist/providers/sdk/v1/fixture-tooling/index.d.ts +8 -0
  69. package/dist/providers/sdk/v1/fixture-tooling/replay.d.ts +38 -0
  70. package/dist/providers/sdk/v1/index.d.ts +30 -0
  71. package/dist/providers/sdk/v1/sandbox/README-design.d.ts +193 -0
  72. package/dist/providers/sdk/v1/sandbox/require-whitelist.d.ts +74 -0
  73. package/dist/providers/sdk/v1/sandbox/script-runner.d.ts +98 -0
  74. package/dist/providers/sdk/v1/types/cli/index.d.ts +277 -0
  75. package/dist/providers/sdk/v1/types/common/index.d.ts +203 -0
  76. package/dist/providers/sdk/v1/validators/index.d.ts +5 -0
  77. package/dist/providers/sdk/v1/validators/manifest.d.ts +40 -0
  78. package/dist/providers/sdk/v1/validators/taint.d.ts +52 -0
  79. package/dist/providers/spec/adapter.d.ts +60 -0
  80. package/dist/providers/spec/cli-adapter.d.ts +101 -0
  81. package/dist/providers/spec/driver.d.ts +195 -0
  82. package/dist/providers/spec/evaluator.d.ts +55 -0
  83. package/dist/providers/spec/loader.d.ts +14 -0
  84. package/dist/providers/spec/native-history-executor.d.ts +41 -0
  85. package/dist/providers/spec/route.d.ts +4 -0
  86. package/dist/providers/spec/schema.gen.d.ts +599 -0
  87. package/dist/providers/spec/types.d.ts +282 -0
  88. package/dist/providers/transcript-v2.d.ts +176 -0
  89. package/dist/providers/types/interactive-prompt.d.ts +48 -0
  90. package/dist/repo-mesh-types.d.ts +108 -1
  91. package/dist/sessions/registry.d.ts +3 -0
  92. package/dist/shared-types-extra.d.ts +1 -1
  93. package/dist/shared-types.d.ts +42 -1
  94. package/dist/status/normalize.d.ts +1 -1
  95. package/dist/status/normalize.js +1 -0
  96. package/dist/status/normalize.js.map +1 -1
  97. package/dist/status/normalize.mjs +1 -0
  98. package/dist/status/normalize.mjs.map +1 -1
  99. package/dist/status/reporter.d.ts +2 -0
  100. package/dist/status/snapshot.d.ts +1 -0
  101. package/dist/types.d.ts +5 -0
  102. package/package.json +7 -2
  103. package/src/agent-stream/poller.ts +2 -3
  104. package/src/boot/daemon-lifecycle.ts +19 -10
  105. package/src/boot/process-hardening.ts +89 -0
  106. package/src/chat/source-machine.ts +534 -0
  107. package/src/chat/source-resolver.ts +0 -0
  108. package/src/chat/subscription-updates.ts +20 -1
  109. package/src/cli-adapter-types.d.ts +1 -0
  110. package/src/cli-adapter-types.ts +22 -2
  111. package/src/cli-adapters/cli-script-runner.ts +421 -0
  112. package/src/cli-adapters/cli-state-engine.ts +1138 -0
  113. package/src/cli-adapters/provider-cli-adapter.d.ts +1 -1
  114. package/src/cli-adapters/provider-cli-adapter.ts +670 -1137
  115. package/src/cli-adapters/provider-cli-parse.d.ts +1 -0
  116. package/src/cli-adapters/provider-cli-parse.ts +13 -0
  117. package/src/cli-adapters/provider-cli-runtime.ts +3 -1
  118. package/src/cli-adapters/provider-cli-shared.d.ts +2 -0
  119. package/src/cli-adapters/provider-cli-shared.ts +59 -11
  120. package/src/cli-adapters/raw-terminal-io.ts +252 -0
  121. package/src/cli-adapters/terminal-backends/ghostty-vt-backend.ts +17 -1
  122. package/src/cli-adapters/terminal-backends/xterm-backend.ts +8 -1
  123. package/src/commands/chat-commands.ts +1787 -60
  124. package/src/commands/cli-manager.ts +283 -14
  125. package/src/commands/handler.ts +809 -2
  126. package/src/commands/mesh-coordinator.ts +331 -122
  127. package/src/commands/router.ts +3628 -515
  128. package/src/config/chat-history.ts +95 -24
  129. package/src/config/config.ts +12 -0
  130. package/src/config/mesh-config.ts +280 -2
  131. package/src/config/recent-activity.ts +8 -2
  132. package/src/daemon/dev-cli-debug.ts +10 -1
  133. package/src/detection/ide-detector.ts +26 -16
  134. package/src/git/git-commands.ts +37 -7
  135. package/src/git/git-status.ts +35 -6
  136. package/src/git/git-types.ts +2 -0
  137. package/src/git/git-worktree.ts +8 -1
  138. package/src/index.ts +119 -9
  139. package/src/installer.d.ts +1 -1
  140. package/src/installer.ts +8 -6
  141. package/src/ipc/local-ipc-server.ts +278 -0
  142. package/src/launch.d.ts +1 -1
  143. package/src/launch.ts +37 -28
  144. package/src/logging/async-batch-writer.ts +55 -0
  145. package/src/logging/logger.ts +2 -1
  146. package/src/mesh/contracts.ts +329 -0
  147. package/src/mesh/coordinator-prompt.ts +204 -30
  148. package/src/mesh/coordinator-registry.ts +121 -0
  149. package/src/mesh/mesh-active-work.ts +437 -0
  150. package/src/mesh/mesh-delivery-policy.ts +298 -0
  151. package/src/mesh/mesh-events.ts +1341 -109
  152. package/src/mesh/mesh-fast-forward.ts +438 -0
  153. package/src/mesh/mesh-host-ownership.ts +73 -0
  154. package/src/mesh/mesh-ledger.ts +457 -104
  155. package/src/mesh/mesh-refine-status.ts +144 -0
  156. package/src/mesh/mesh-runtime-store.ts +769 -0
  157. package/src/mesh/mesh-work-queue.ts +267 -159
  158. package/src/mesh/preview-freshness.ts +118 -0
  159. package/src/mesh/refine-config.ts +366 -0
  160. package/src/mesh/worktree-bootstrap-config.ts +247 -0
  161. package/src/providers/approval-utils.d.ts +4 -0
  162. package/src/providers/approval-utils.ts +47 -5
  163. package/src/providers/chat-message-normalization.ts +4 -11
  164. package/src/providers/cli-provider-instance.ts +806 -64
  165. package/src/providers/contracts.d.ts +55 -0
  166. package/src/providers/contracts.ts +141 -6
  167. package/src/providers/external-sources.ts +218 -0
  168. package/src/providers/ide-provider-instance.ts +19 -5
  169. package/src/providers/native-history/antigravity-cli-transcript.ts +643 -0
  170. package/src/providers/native-history/claude-cli-transcript.ts +396 -0
  171. package/src/providers/native-history/codex-cli-transcript.ts +419 -0
  172. package/src/providers/native-history/dispatcher.ts +340 -0
  173. package/src/providers/native-history/hermes-cli-transcript.ts +230 -0
  174. package/src/providers/native-history/index.ts +30 -0
  175. package/src/providers/provider-instance-manager.ts +30 -0
  176. package/src/providers/provider-instance.ts +10 -1
  177. package/src/providers/provider-loader.ts +582 -50
  178. package/src/providers/provider-schema.ts +87 -14
  179. package/src/providers/provider-trust.ts +114 -0
  180. package/src/providers/read-chat-contract.ts +76 -16
  181. package/src/providers/sdk/README.md +49 -0
  182. package/src/providers/sdk/v1/builders/acp/detect-status.ts +144 -0
  183. package/src/providers/sdk/v1/builders/cli/detect-status.ts +262 -0
  184. package/src/providers/sdk/v1/builders/cli/parse-approval-squash.ts +158 -0
  185. package/src/providers/sdk/v1/builders/cli/parse-approval.ts +250 -0
  186. package/src/providers/sdk/v1/builders/cli/parse-session.ts +276 -0
  187. package/src/providers/sdk/v1/builders/cli/visible-region.ts +143 -0
  188. package/src/providers/sdk/v1/fixture-tooling/format.ts +130 -0
  189. package/src/providers/sdk/v1/fixture-tooling/index.ts +22 -0
  190. package/src/providers/sdk/v1/fixture-tooling/replay.ts +352 -0
  191. package/src/providers/sdk/v1/index.ts +152 -0
  192. package/src/providers/sdk/v1/sandbox/README-design.ts +195 -0
  193. package/src/providers/sdk/v1/sandbox/require-whitelist.ts +472 -0
  194. package/src/providers/sdk/v1/sandbox/script-runner.ts +150 -0
  195. package/src/providers/sdk/v1/schemas/cli/provider.schema.json +504 -0
  196. package/src/providers/sdk/v1/schemas/primitives/acp-session-protocol-v1.json +131 -0
  197. package/src/providers/sdk/v1/schemas/primitives/native-history-codex-rollout-v1.json +66 -0
  198. package/src/providers/sdk/v1/schemas/primitives/tui-approval-squash-v1.json +91 -0
  199. package/src/providers/sdk/v1/schemas/primitives/tui-assistant-block-v1.json +91 -0
  200. package/src/providers/sdk/v1/schemas/primitives/tui-cue-ordering-v1.json +47 -0
  201. package/src/providers/sdk/v1/schemas/primitives/tui-dispatch-order-v1.json +32 -0
  202. package/src/providers/sdk/v1/schemas/primitives/tui-footer-chrome-v1.json +42 -0
  203. package/src/providers/sdk/v1/schemas/primitives/tui-index-finder-v1.json +27 -0
  204. package/src/providers/sdk/v1/schemas/primitives/tui-modal-v1.json +125 -0
  205. package/src/providers/sdk/v1/schemas/primitives/tui-prompt-marker-v1.json +45 -0
  206. package/src/providers/sdk/v1/schemas/primitives/tui-session-id-extraction-v1.json +46 -0
  207. package/src/providers/sdk/v1/schemas/primitives/tui-settled-prompt-v1.json +71 -0
  208. package/src/providers/sdk/v1/schemas/primitives/tui-spinner-v1.json +83 -0
  209. package/src/providers/sdk/v1/schemas/primitives/tui-transcript-pty-v1.json +83 -0
  210. package/src/providers/sdk/v1/schemas/primitives/tui-visible-region-v1.json +57 -0
  211. package/src/providers/sdk/v1/schemas/primitives/tui-welcome-screen-v1.json +35 -0
  212. package/src/providers/sdk/v1/types/cli/index.ts +365 -0
  213. package/src/providers/sdk/v1/types/common/index.ts +229 -0
  214. package/src/providers/sdk/v1/validators/index.ts +19 -0
  215. package/src/providers/sdk/v1/validators/manifest.ts +110 -0
  216. package/src/providers/sdk/v1/validators/taint.ts +309 -0
  217. package/src/providers/spec/adapter.ts +176 -0
  218. package/src/providers/spec/cli-adapter.ts +573 -0
  219. package/src/providers/spec/driver.ts +733 -0
  220. package/src/providers/spec/evaluator.ts +373 -0
  221. package/src/providers/spec/loader.ts +130 -0
  222. package/src/providers/spec/native-history-executor.ts +939 -0
  223. package/src/providers/spec/route.ts +51 -0
  224. package/src/providers/spec/schema.gen.ts +559 -0
  225. package/src/providers/spec/schema.json +237 -0
  226. package/src/providers/spec/types.ts +301 -0
  227. package/src/providers/transcript-v2.ts +567 -0
  228. package/src/providers/types/interactive-prompt.ts +425 -0
  229. package/src/providers/version-archive.ts +38 -20
  230. package/src/repo-mesh-types.ts +118 -1
  231. package/src/sessions/registry.ts +3 -0
  232. package/src/shared-types-extra.ts +1 -1
  233. package/src/shared-types.ts +45 -1
  234. package/src/status/builders.ts +24 -6
  235. package/src/status/normalize.ts +2 -0
  236. package/src/status/reporter.ts +15 -0
  237. package/src/status/snapshot.ts +84 -25
  238. package/src/system/host-memory.ts +29 -12
  239. package/src/types.ts +5 -0
  240. package/dist/mesh/mesh-sync.d.ts +0 -53
  241. package/src/mesh/mesh-sync.ts +0 -111
@@ -1,24 +1,76 @@
1
- import { existsSync, writeFileSync, readFileSync } from 'fs';
2
- import { join } from 'path';
3
1
  import { randomUUID } from 'crypto';
4
- import { getLedgerDir } from './mesh-ledger.js';
2
+ import { requireMeshHostQueueOwner } from './mesh-host-ownership.js';
3
+ import type { RepoMeshDaemonRole } from '../repo-mesh-types.js';
4
+ import { MeshRuntimeStore } from './mesh-runtime-store.js';
5
5
 
6
6
  export type MeshTaskStatus = 'pending' | 'assigned' | 'completed' | 'failed' | 'cancelled';
7
7
  export type MeshActiveTaskStatus = Extract<MeshTaskStatus, 'pending' | 'assigned'>;
8
8
  export type MeshHistoricalTaskStatus = Extract<MeshTaskStatus, 'completed' | 'failed' | 'cancelled'>;
9
+ export type MeshTaskMode = 'code_change' | 'validation' | 'live_debug_readonly' | 'launch_app' | 'convergence';
9
10
 
10
11
  export const ACTIVE_MESH_QUEUE_STATUSES: MeshActiveTaskStatus[] = ['pending', 'assigned'];
11
12
  export const HISTORICAL_MESH_QUEUE_STATUSES: MeshHistoricalTaskStatus[] = ['completed', 'failed', 'cancelled'];
13
+ export const MESH_TASK_MODES: MeshTaskMode[] = ['code_change', 'validation', 'live_debug_readonly', 'launch_app', 'convergence'];
14
+
15
+ export interface MeshTaskModeValidationResult {
16
+ valid: boolean;
17
+ taskMode?: MeshTaskMode;
18
+ violations: string[];
19
+ allowedOperations?: string[];
20
+ }
21
+
22
+ const LIVE_DEBUG_READONLY_FORBIDDEN: Array<{ label: string; pattern: RegExp }> = [
23
+ { label: 'source_edit', pattern: /\b(edit|modify|patch|apply\s+patch|write\s+(?:to\s+)?(?:file|source)|overwrite|delete\s+file|remove\s+file|create\s+file|touch\s+file)\b/i },
24
+ { label: 'git_mutation', pattern: /\b(?:git\s+(?:add|commit|push|reset|rebase|clean|checkout|switch|merge|tag|restore|rm|mv|stash|worktree\s+(?:add|remove|move))|push\b)/i },
25
+ { label: 'checkpoint', pattern: /\b(checkpoint|mesh_checkpoint)\b/i },
26
+ { label: 'deploy_or_version_bump', pattern: /\b(deploy|wrangler\s+deploy|version[-\s]?bump|npm\s+version|release|npm\s+publish|yarn\s+publish|pnpm\s+publish)\b/i },
27
+ { label: 'destructive_shell', pattern: /\b(rm\s+-rf|mv\s+\S+\s+\S+|truncate\s|tee\s+\S+|sed\s+-i|shred\b)\b/i },
28
+ { label: 'package_install', pattern: /\b(npm\s+(?:install|i|add|link|uninstall|remove)|yarn\s+(?:add|remove|link)|pnpm\s+(?:add|remove|link)|pip\s+install|brew\s+install|apt\s+install|cargo\s+install)\b/i },
29
+ { label: 'container_mutation', pattern: /\b(docker\s+(?:build|run|exec|push|tag|rmi|rm|create|start|stop|kill)|kubectl\s+(?:apply|delete|patch|replace|create|scale))\b/i },
30
+ ];
31
+
32
+ export function normalizeMeshTaskMode(value: unknown): MeshTaskMode | undefined {
33
+ if (typeof value !== 'string') return undefined;
34
+ const normalized = value.trim() as MeshTaskMode;
35
+ return (MESH_TASK_MODES as string[]).includes(normalized) ? normalized : undefined;
36
+ }
37
+
38
+ export function validateMeshTaskModeRequest(mode: unknown, message: string): MeshTaskModeValidationResult {
39
+ const taskMode = normalizeMeshTaskMode(mode);
40
+ if (!taskMode) {
41
+ return { valid: true, violations: [] };
42
+ }
43
+ if (taskMode !== 'live_debug_readonly') {
44
+ return { valid: true, taskMode, violations: [] };
45
+ }
46
+ const violations = LIVE_DEBUG_READONLY_FORBIDDEN
47
+ .filter(rule => rule.pattern.test(message || ''))
48
+ .map(rule => rule.label);
49
+ return {
50
+ valid: violations.length === 0,
51
+ taskMode,
52
+ violations,
53
+ allowedOperations: [
54
+ 'process/log/window/port/session inspection',
55
+ 'read-only filesystem listing/reading',
56
+ 'status probes and keep-running handle reporting',
57
+ 'diagnostic summaries without source edits, commits, checkpoints, pushes, deploys, resets, rebases, or destructive cleanups',
58
+ ],
59
+ };
60
+ }
12
61
 
13
62
  export interface MeshWorkQueueEntry {
14
63
  id: string;
15
64
  meshId: string;
16
65
  message: string;
17
66
  status: MeshTaskStatus;
67
+ taskMode?: MeshTaskMode;
18
68
  /** If specified, only this node can claim the task (used by legacy mesh_send_task) */
19
69
  targetNodeId?: string;
20
70
  /** If specified, only this runtime session can claim the task */
21
71
  targetSessionId?: string;
72
+ /** If specified, a node must expose all tags before it can claim the task. */
73
+ requiredTags?: string[];
22
74
  /** The node that actually claimed and is executing the task */
23
75
  assignedNodeId?: string;
24
76
  /** The session currently executing the task */
@@ -45,25 +97,63 @@ export interface MeshWorkQueueEntry {
45
97
  updatedAt: string;
46
98
  }
47
99
 
48
- function getQueuePath(meshId: string): string {
49
- const safe = meshId.replace(/[^a-zA-Z0-9_-]/g, '_');
50
- return join(getLedgerDir(), `${safe}.queue.json`);
100
+ export interface MeshQueueMutationOptions {
101
+ ownerRole?: RepoMeshDaemonRole;
102
+ }
103
+
104
+ export function normalizeMeshCapabilityTags(value: unknown): string[] {
105
+ if (!Array.isArray(value)) return [];
106
+ const seen = new Set<string>();
107
+ return value
108
+ .map(tag => typeof tag === 'string' ? tag.trim() : '')
109
+ .filter(Boolean)
110
+ .filter(tag => {
111
+ if (seen.has(tag)) return false;
112
+ seen.add(tag);
113
+ return true;
114
+ });
115
+ }
116
+
117
+ function firstProviderPriority(policy: unknown): string | undefined {
118
+ const raw = policy && typeof policy === 'object' && !Array.isArray(policy)
119
+ ? (policy as Record<string, unknown>).providerPriority
120
+ : undefined;
121
+ if (!Array.isArray(raw)) return undefined;
122
+ return raw.find(type => typeof type === 'string' && type.trim())?.trim();
123
+ }
124
+
125
+ export function buildMeshNodeCapabilityTags(
126
+ node: { capabilities?: unknown; policy?: unknown } | undefined,
127
+ providerType?: string,
128
+ ): string[] {
129
+ const provider = typeof providerType === 'string' && providerType.trim()
130
+ ? providerType.trim()
131
+ : firstProviderPriority(node?.policy);
132
+ return normalizeMeshCapabilityTags([
133
+ ...(Array.isArray(node?.capabilities) ? node.capabilities : []),
134
+ `os=${process.platform}`,
135
+ `arch=${process.arch}`,
136
+ ...(provider ? [`provider=${provider}`] : []),
137
+ ]);
138
+ }
139
+
140
+ export function nodeSatisfiesRequiredTags(requiredTags: unknown, capabilityTags: unknown): boolean {
141
+ const required = normalizeMeshCapabilityTags(requiredTags);
142
+ if (required.length === 0) return true;
143
+ const available = new Set(normalizeMeshCapabilityTags(capabilityTags));
144
+ return required.every(tag => available.has(tag));
145
+ }
146
+
147
+ function withQueueLock<T>(_meshId: string, fn: () => T): T {
148
+ return MeshRuntimeStore.getInstance().transaction(fn);
51
149
  }
52
150
 
53
151
  function readQueue(meshId: string): MeshWorkQueueEntry[] {
54
- const path = getQueuePath(meshId);
55
- if (!existsSync(path)) return [];
56
- try {
57
- const content = readFileSync(path, 'utf-8');
58
- return JSON.parse(content) as MeshWorkQueueEntry[];
59
- } catch {
60
- return [];
61
- }
152
+ return MeshRuntimeStore.getInstance().getQueueEntries(meshId);
62
153
  }
63
154
 
64
155
  function writeQueue(meshId: string, queue: MeshWorkQueueEntry[]): void {
65
- const path = getQueuePath(meshId);
66
- writeFileSync(path, JSON.stringify(queue, null, 2), 'utf-8');
156
+ MeshRuntimeStore.getInstance().replaceQueue(meshId, queue);
67
157
  }
68
158
 
69
159
  /**
@@ -72,21 +162,26 @@ function writeQueue(meshId: string, queue: MeshWorkQueueEntry[]): void {
72
162
  export function enqueueTask(
73
163
  meshId: string,
74
164
  message: string,
75
- opts?: { targetNodeId?: string; targetSessionId?: string }
165
+ opts?: { targetNodeId?: string; targetSessionId?: string; taskMode?: MeshTaskMode | string; requiredTags?: string[] } & MeshQueueMutationOptions,
76
166
  ): MeshWorkQueueEntry {
77
- const queue = readQueue(meshId);
167
+ requireMeshHostQueueOwner(opts);
168
+ const modeValidation = validateMeshTaskModeRequest(opts?.taskMode, message);
169
+ if (!modeValidation.valid) {
170
+ throw new Error(`live_debug_readonly_guardrail_violation: forbidden operations (${modeValidation.violations.join(', ')})`);
171
+ }
78
172
  const entry: MeshWorkQueueEntry = {
79
173
  id: randomUUID(),
80
174
  meshId,
81
175
  message,
82
176
  status: 'pending',
177
+ taskMode: modeValidation.taskMode,
83
178
  targetNodeId: opts?.targetNodeId,
84
179
  targetSessionId: opts?.targetSessionId,
180
+ requiredTags: normalizeMeshCapabilityTags(opts?.requiredTags),
85
181
  createdAt: new Date().toISOString(),
86
182
  updatedAt: new Date().toISOString(),
87
183
  };
88
- queue.push(entry);
89
- writeQueue(meshId, queue);
184
+ MeshRuntimeStore.getInstance().insertQueueEntry(entry);
90
185
  return entry;
91
186
  }
92
187
 
@@ -94,51 +189,18 @@ export function enqueueTask(
94
189
  * Get all tasks in the queue, optionally filtered by status.
95
190
  */
96
191
  export function getQueue(meshId: string, opts?: { status?: MeshTaskStatus[] }): MeshWorkQueueEntry[] {
97
- let queue = readQueue(meshId);
98
- if (opts?.status?.length) {
99
- const statuses = new Set(opts.status);
100
- queue = queue.filter(q => statuses.has(q.status));
101
- }
102
- return queue;
192
+ return MeshRuntimeStore.getInstance().getQueueEntries(meshId, opts?.status?.length ? opts.status : undefined);
193
+ }
194
+
195
+ export function getMeshQueueRevision(meshId: string): string {
196
+ return MeshRuntimeStore.getInstance().getQueueRevision(meshId);
103
197
  }
104
198
 
105
199
  /**
106
200
  * Find the next pending task that this node is allowed to claim, and mark it as assigned.
107
201
  */
108
- export function claimNextTask(meshId: string, nodeId: string, sessionId: string): MeshWorkQueueEntry | null {
109
- const queue = readQueue(meshId);
110
-
111
- // A worker must finish or fail its current queued assignment before it can
112
- // claim another one. maxParallelTasks limits total mesh concurrency; it is
113
- // not permission for one node/session to accumulate multiple assigned items.
114
- const hasActiveAssignment = queue.some(q => q.status === 'assigned' && (
115
- q.assignedSessionId === sessionId || q.assignedNodeId === nodeId
116
- ));
117
- if (hasActiveAssignment) return null;
118
-
119
- // Find highest priority task:
120
- // 1. Pending tasks explicitly targeted at this runtime session
121
- // 2. Pending tasks explicitly targeted at this node (but not another session)
122
- // 3. Pending tasks with no target node/session
123
- let targetIdx = queue.findIndex(q => q.status === 'pending' && q.targetSessionId === sessionId);
124
- if (targetIdx === -1) {
125
- targetIdx = queue.findIndex(q => q.status === 'pending' && q.targetNodeId === nodeId && !q.targetSessionId);
126
- }
127
- if (targetIdx === -1) {
128
- targetIdx = queue.findIndex(q => q.status === 'pending' && !q.targetNodeId && !q.targetSessionId);
129
- }
130
-
131
- if (targetIdx === -1) return null;
132
-
133
- const entry = queue[targetIdx];
134
- entry.status = 'assigned';
135
- entry.assignedNodeId = nodeId;
136
- entry.assignedSessionId = sessionId;
137
- entry.dispatchTimestamp = new Date().toISOString();
138
- entry.updatedAt = new Date().toISOString();
139
-
140
- writeQueue(meshId, queue);
141
- return entry;
202
+ export function claimNextTask(meshId: string, nodeId: string, sessionId: string, capabilityTags?: string[]): MeshWorkQueueEntry | null {
203
+ return MeshRuntimeStore.getInstance().claimNextQueueTask(meshId, nodeId, sessionId, capabilityTags);
142
204
  }
143
205
 
144
206
  /**
@@ -149,15 +211,16 @@ export function updateTaskStatus(
149
211
  meshId: string,
150
212
  taskId: string,
151
213
  status: MeshTaskStatus,
214
+ opts?: MeshQueueMutationOptions,
152
215
  ): MeshWorkQueueEntry | null {
153
- const queue = readQueue(meshId);
154
- const idx = queue.findIndex(q => q.id === taskId);
155
- if (idx === -1) return null;
156
-
157
- queue[idx].status = status;
158
- queue[idx].updatedAt = new Date().toISOString();
159
- writeQueue(meshId, queue);
160
- return queue[idx];
216
+ requireMeshHostQueueOwner(opts);
217
+ return withQueueLock(meshId, () => {
218
+ const entry = MeshRuntimeStore.getInstance().findQueueEntryById(meshId, taskId);
219
+ if (!entry) return null;
220
+ entry.status = status;
221
+ MeshRuntimeStore.getInstance().updateQueueEntry(entry);
222
+ return entry;
223
+ });
161
224
  }
162
225
 
163
226
  export function recordTaskAutoLaunch(
@@ -165,17 +228,14 @@ export function recordTaskAutoLaunch(
165
228
  taskId: string,
166
229
  autoLaunch: Omit<NonNullable<MeshWorkQueueEntry['autoLaunch']>, 'updatedAt'>,
167
230
  ): MeshWorkQueueEntry | null {
168
- const queue = readQueue(meshId);
169
- const idx = queue.findIndex(q => q.id === taskId);
170
- if (idx === -1) return null;
171
- const now = new Date().toISOString();
172
- queue[idx].autoLaunch = {
173
- ...autoLaunch,
174
- updatedAt: now,
175
- };
176
- queue[idx].updatedAt = now;
177
- writeQueue(meshId, queue);
178
- return queue[idx];
231
+ return withQueueLock(meshId, () => {
232
+ const entry = MeshRuntimeStore.getInstance().findQueueEntryById(meshId, taskId);
233
+ if (!entry) return null;
234
+ const now = new Date().toISOString();
235
+ entry.autoLaunch = { ...autoLaunch, updatedAt: now };
236
+ MeshRuntimeStore.getInstance().updateQueueEntry(entry);
237
+ return entry;
238
+ });
179
239
  }
180
240
 
181
241
  /**
@@ -184,19 +244,19 @@ export function recordTaskAutoLaunch(
184
244
  export function cancelTask(
185
245
  meshId: string,
186
246
  taskId: string,
187
- opts?: { reason?: string },
247
+ opts?: { reason?: string } & MeshQueueMutationOptions,
188
248
  ): MeshWorkQueueEntry | null {
189
- const queue = readQueue(meshId);
190
- const idx = queue.findIndex(q => q.id === taskId);
191
- if (idx === -1) return null;
192
-
193
- const now = new Date().toISOString();
194
- queue[idx].status = 'cancelled';
195
- queue[idx].updatedAt = now;
196
- queue[idx].cancelledAt = now;
197
- if (opts?.reason) queue[idx].cancelReason = opts.reason;
198
- writeQueue(meshId, queue);
199
- return queue[idx];
249
+ requireMeshHostQueueOwner(opts);
250
+ return withQueueLock(meshId, () => {
251
+ const entry = MeshRuntimeStore.getInstance().findQueueEntryById(meshId, taskId);
252
+ if (!entry) return null;
253
+ const now = new Date().toISOString();
254
+ entry.status = 'cancelled';
255
+ entry.cancelledAt = now;
256
+ if (opts?.reason) entry.cancelReason = opts.reason;
257
+ MeshRuntimeStore.getInstance().updateQueueEntry(entry);
258
+ return entry;
259
+ });
200
260
  }
201
261
 
202
262
  /**
@@ -212,29 +272,27 @@ export function requeueTask(
212
272
  targetSessionId?: string;
213
273
  clearTargetNode?: boolean;
214
274
  clearTargetSession?: boolean;
215
- },
275
+ } & MeshQueueMutationOptions,
216
276
  ): MeshWorkQueueEntry | null {
217
- const queue = readQueue(meshId);
218
- const idx = queue.findIndex(q => q.id === taskId);
219
- if (idx === -1) return null;
220
-
221
- const entry = queue[idx];
222
- const now = new Date().toISOString();
223
- entry.status = 'pending';
224
- delete entry.assignedNodeId;
225
- delete entry.assignedSessionId;
226
- delete entry.cancelledAt;
227
- delete entry.cancelReason;
228
- if (opts?.clearTargetNode) delete entry.targetNodeId;
229
- if (typeof opts?.targetNodeId === 'string') entry.targetNodeId = opts.targetNodeId;
230
- if (opts?.clearTargetSession !== false) delete entry.targetSessionId;
231
- if (typeof opts?.targetSessionId === 'string') entry.targetSessionId = opts.targetSessionId;
232
- entry.updatedAt = now;
233
- entry.requeuedAt = now;
234
- entry.requeueCount = (entry.requeueCount || 0) + 1;
235
- if (opts?.reason) entry.requeueReason = opts.reason;
236
- writeQueue(meshId, queue);
237
- return entry;
277
+ requireMeshHostQueueOwner(opts);
278
+ return withQueueLock(meshId, () => {
279
+ const entry = MeshRuntimeStore.getInstance().findQueueEntryById(meshId, taskId);
280
+ if (!entry) return null;
281
+ entry.status = 'pending';
282
+ delete entry.assignedNodeId;
283
+ delete entry.assignedSessionId;
284
+ delete entry.cancelledAt;
285
+ delete entry.cancelReason;
286
+ if (opts?.clearTargetNode) delete entry.targetNodeId;
287
+ if (typeof opts?.targetNodeId === 'string') entry.targetNodeId = opts.targetNodeId;
288
+ if (opts?.clearTargetSession !== false) delete entry.targetSessionId;
289
+ if (typeof opts?.targetSessionId === 'string') entry.targetSessionId = opts.targetSessionId;
290
+ entry.requeuedAt = new Date().toISOString();
291
+ entry.requeueCount = (entry.requeueCount || 0) + 1;
292
+ if (opts?.reason) entry.requeueReason = opts.reason;
293
+ MeshRuntimeStore.getInstance().updateQueueEntry(entry);
294
+ return entry;
295
+ });
238
296
  }
239
297
 
240
298
  /**
@@ -244,29 +302,16 @@ export function updateSessionTaskStatus(
244
302
  meshId: string,
245
303
  sessionId: string,
246
304
  status: MeshTaskStatus,
305
+ opts?: { occurredAt?: string },
247
306
  ): MeshWorkQueueEntry | null {
248
- const queue = readQueue(meshId);
249
- // Collect all assigned tasks for this session, then pick the one with the
250
- // most recent dispatchTimestamp (or updatedAt fallback for legacy entries).
251
- // This prevents completing the wrong task when multiple tasks were assigned
252
- // to the same session in rapid succession.
253
- let bestIdx = -1;
254
- let bestTime = 0;
255
- for (let i = queue.length - 1; i >= 0; i--) {
256
- if (queue[i].assignedSessionId === sessionId && queue[i].status === 'assigned') {
257
- const time = new Date(queue[i].dispatchTimestamp || queue[i].updatedAt).getTime();
258
- if (time > bestTime) {
259
- bestTime = time;
260
- bestIdx = i;
261
- }
262
- }
263
- }
264
- if (bestIdx === -1) return null;
265
-
266
- queue[bestIdx].status = status;
267
- queue[bestIdx].updatedAt = new Date().toISOString();
268
- writeQueue(meshId, queue);
269
- return queue[bestIdx];
307
+ return withQueueLock(meshId, () => {
308
+ const occurredAtIso = opts?.occurredAt ? new Date(opts.occurredAt).toISOString() : undefined;
309
+ const entry = MeshRuntimeStore.getInstance().findAssignedBySession(meshId, sessionId, occurredAtIso);
310
+ if (!entry) return null;
311
+ entry.status = status;
312
+ MeshRuntimeStore.getInstance().updateQueueEntry(entry);
313
+ return entry;
314
+ });
270
315
  }
271
316
 
272
317
  export interface MeshWorkQueueStats {
@@ -294,14 +339,16 @@ export interface MeshWorkQueueStats {
294
339
  * Return aggregate queue statistics for the given mesh.
295
340
  */
296
341
  export function getMeshQueueStats(meshId: string): MeshWorkQueueStats {
297
- const queue = readQueue(meshId);
298
- const pending = queue.filter(q => q.status === 'pending').length;
299
- const assigned = queue.filter(q => q.status === 'assigned').length;
300
- const completed = queue.filter(q => q.status === 'completed').length;
301
- const failed = queue.filter(q => q.status === 'failed').length;
302
- const cancelled = queue.filter(q => q.status === 'cancelled').length;
342
+ const rows = MeshRuntimeStore.getInstance().getQueueStatsByStatus(meshId);
343
+ const counts: Record<string, number> = {};
344
+ for (const r of rows) counts[r.status] = r.count;
345
+ const pending = counts['pending'] ?? 0;
346
+ const assigned = counts['assigned'] ?? 0;
347
+ const completed = counts['completed'] ?? 0;
348
+ const failed = counts['failed'] ?? 0;
349
+ const cancelled = counts['cancelled'] ?? 0;
303
350
  return {
304
- total: queue.length,
351
+ total: pending + assigned + completed + failed + cancelled,
305
352
  active: pending + assigned,
306
353
  historical: completed + failed + cancelled,
307
354
  pending,
@@ -309,22 +356,83 @@ export function getMeshQueueStats(meshId: string): MeshWorkQueueStats {
309
356
  completed,
310
357
  failed,
311
358
  cancelled,
312
- activeCounts: {
313
- pending,
314
- assigned,
315
- },
316
- historicalCounts: {
317
- completed,
318
- failed,
319
- cancelled,
320
- },
321
- activeAssignments: queue
322
- .filter(q => q.status === 'assigned')
323
- .map(q => ({
324
- id: q.id,
325
- nodeId: q.assignedNodeId,
326
- sessionId: q.assignedSessionId,
327
- message: q.message,
328
- })),
359
+ activeCounts: { pending, assigned },
360
+ historicalCounts: { completed, failed, cancelled },
361
+ activeAssignments: MeshRuntimeStore.getInstance().getActiveAssignmentDetails(meshId),
329
362
  };
330
363
  }
364
+
365
+ export function __replaceMeshQueueForTests(meshId: string, queue: MeshWorkQueueEntry[]): void {
366
+ MeshRuntimeStore.getInstance().transaction(() => {
367
+ MeshRuntimeStore.getInstance().replaceQueue(meshId, queue);
368
+ });
369
+ }
370
+
371
+ export function __clearMeshQueueForTests(meshId: string): void {
372
+ MeshRuntimeStore.getInstance().deleteQueue(meshId);
373
+ }
374
+
375
+ export function __clearDirectDispatchesForTests(meshId: string): void {
376
+ MeshRuntimeStore.getInstance().deleteDirectDispatches(meshId);
377
+ }
378
+
379
+ export function __resetMeshRuntimeStoreForTests(): void {
380
+ MeshRuntimeStore.resetForTests();
381
+ }
382
+
383
+ // ── Direct Dispatch Tracking ─────────────────────────────────────────────────
384
+ // Persists direct (non-queue) task dispatches so buildMeshActiveWork can read
385
+ // active work from MeshRuntimeStore instead of scanning ledger JSONL entries.
386
+
387
+ export type DirectDispatchRecord = ReturnType<MeshRuntimeStore['getActiveDirectDispatches']>[number];
388
+
389
+ export function insertDirectDispatch(
390
+ meshId: string,
391
+ data: {
392
+ taskId: string;
393
+ nodeId?: string;
394
+ sessionId?: string;
395
+ providerType?: string;
396
+ message: string;
397
+ taskMode?: string;
398
+ via: string;
399
+ dispatchedToIdleSession?: boolean;
400
+ dispatchedAt: string;
401
+ },
402
+ ): void {
403
+ try {
404
+ MeshRuntimeStore.getInstance().insertDirectDispatch({ ...data, meshId });
405
+ } catch (e: any) {
406
+ process.stderr.write(`[adhdev-mesh] insertDirectDispatch failed for task ${data.taskId}: ${e?.message || e}\n`);
407
+ }
408
+ }
409
+
410
+ export function getActiveDirectDispatches(meshId: string): DirectDispatchRecord[] {
411
+ try {
412
+ return MeshRuntimeStore.getInstance().getActiveDirectDispatches(meshId);
413
+ } catch {
414
+ return [];
415
+ }
416
+ }
417
+
418
+ export function updateDirectDispatchStatus(
419
+ meshId: string,
420
+ sessionId: string,
421
+ status: 'acked' | 'completed' | 'failed' | 'stale',
422
+ ): void {
423
+ try {
424
+ MeshRuntimeStore.getInstance().updateDirectDispatchStatus(meshId, sessionId, status);
425
+ } catch { /* best-effort */ }
426
+ }
427
+
428
+ export function cleanupTerminalDirectDispatches(olderThanMs = 7 * 24 * 60 * 60_000): void {
429
+ try {
430
+ MeshRuntimeStore.getInstance().cleanupTerminalDirectDispatches(olderThanMs);
431
+ } catch { /* best-effort */ }
432
+ }
433
+
434
+ export function markStaleDirectDispatches(meshId: string, olderThanMs = 60 * 60_000): void {
435
+ try {
436
+ MeshRuntimeStore.getInstance().markStaleDirectDispatches(meshId, olderThanMs);
437
+ } catch { /* best-effort */ }
438
+ }
@@ -0,0 +1,118 @@
1
+ import { execFileSync } from 'node:child_process';
2
+ import { existsSync, readFileSync } from 'node:fs';
3
+ import { resolve } from 'node:path';
4
+
5
+ export type PreviewFreshnessStatus = 'fresh' | 'stale' | 'unknown' | 'not_configured';
6
+
7
+ export interface PreviewFreshness {
8
+ status: PreviewFreshnessStatus;
9
+ lastPreviewCommit: string | null;
10
+ currentMainCommit: string | null;
11
+ currentMainCommitSource: 'origin/main' | 'HEAD' | 'unknown';
12
+ recordPath: string;
13
+ lastDeployedAt?: string;
14
+ lastTarget?: string;
15
+ previewVersion?: string;
16
+ targets: Record<'npm' | 'server' | 'web', {
17
+ commit: string | null;
18
+ deployedAt?: string;
19
+ status: PreviewFreshnessStatus;
20
+ }>;
21
+ nextAction: string;
22
+ }
23
+
24
+ const PREVIEW_DEPLOY_RECORD = '.adhdev/preview-deploy.json';
25
+
26
+ function runGit(repoRoot: string, args: readonly string[]): string {
27
+ try {
28
+ return execFileSync('git', args, {
29
+ cwd: repoRoot,
30
+ encoding: 'utf8',
31
+ stdio: ['ignore', 'pipe', 'ignore'],
32
+ timeout: 5000,
33
+ }).trim();
34
+ } catch {
35
+ return '';
36
+ }
37
+ }
38
+
39
+ function readRecord(repoRoot: string): Record<string, unknown> | null {
40
+ const path = resolve(repoRoot, PREVIEW_DEPLOY_RECORD);
41
+ if (!existsSync(path)) return null;
42
+ try {
43
+ const parsed = JSON.parse(readFileSync(path, 'utf8'));
44
+ return parsed && typeof parsed === 'object' && !Array.isArray(parsed)
45
+ ? parsed as Record<string, unknown>
46
+ : null;
47
+ } catch {
48
+ return null;
49
+ }
50
+ }
51
+
52
+ function normalizeCommit(value: unknown): string | null {
53
+ return typeof value === 'string' && /^[0-9a-f]{7,40}$/i.test(value.trim())
54
+ ? value.trim()
55
+ : null;
56
+ }
57
+
58
+ function readTargetFreshness(record: Record<string, unknown> | null, currentCommit: string | null): PreviewFreshness['targets'] {
59
+ const targets = record?.targets && typeof record.targets === 'object' && !Array.isArray(record.targets)
60
+ ? record.targets as Record<string, unknown>
61
+ : {};
62
+ const result = {} as PreviewFreshness['targets'];
63
+ for (const targetName of ['npm', 'server', 'web'] as const) {
64
+ const targetRecord = targets[targetName] && typeof targets[targetName] === 'object' && !Array.isArray(targets[targetName])
65
+ ? targets[targetName] as Record<string, unknown>
66
+ : {};
67
+ const commit = normalizeCommit(targetRecord.commit);
68
+ result[targetName] = {
69
+ commit,
70
+ deployedAt: typeof targetRecord.deployedAt === 'string' ? targetRecord.deployedAt : undefined,
71
+ status: commit && currentCommit ? (commit === currentCommit ? 'fresh' : 'stale') : 'unknown',
72
+ };
73
+ }
74
+ return result;
75
+ }
76
+
77
+ function readCurrentMainCommit(repoRoot: string): Pick<PreviewFreshness, 'currentMainCommit' | 'currentMainCommitSource'> {
78
+ const originMain = runGit(repoRoot, ['rev-parse', '--verify', 'origin/main^{commit}']);
79
+ if (originMain) {
80
+ return { currentMainCommit: originMain, currentMainCommitSource: 'origin/main' };
81
+ }
82
+ const head = runGit(repoRoot, ['rev-parse', '--verify', 'HEAD']);
83
+ if (head) {
84
+ return { currentMainCommit: head, currentMainCommitSource: 'HEAD' };
85
+ }
86
+ return { currentMainCommit: null, currentMainCommitSource: 'unknown' };
87
+ }
88
+
89
+ export function buildPreviewFreshness(repoRoot: string): PreviewFreshness {
90
+ const current = readCurrentMainCommit(repoRoot);
91
+ const record = readRecord(repoRoot);
92
+ const lastPreviewCommit = normalizeCommit(record?.lastPreviewCommit);
93
+ const targets = readTargetFreshness(record, current.currentMainCommit);
94
+ let status: PreviewFreshnessStatus = 'unknown';
95
+ let nextAction = 'Run npm run deploy:preview from the current main commit, then smoke preview.';
96
+
97
+ if (lastPreviewCommit && current.currentMainCommit) {
98
+ status = lastPreviewCommit === current.currentMainCommit ? 'fresh' : 'stale';
99
+ nextAction = status === 'fresh'
100
+ ? 'No preview deploy action needed.'
101
+ : 'Run npm run deploy:preview from origin/main, then smoke preview.';
102
+ } else if (!current.currentMainCommit) {
103
+ nextAction = 'Resolve the current main commit before judging preview freshness.';
104
+ }
105
+
106
+ return {
107
+ status,
108
+ lastPreviewCommit,
109
+ currentMainCommit: current.currentMainCommit,
110
+ currentMainCommitSource: current.currentMainCommitSource,
111
+ recordPath: PREVIEW_DEPLOY_RECORD,
112
+ lastDeployedAt: typeof record?.updatedAt === 'string' ? record.updatedAt : undefined,
113
+ lastTarget: typeof record?.target === 'string' ? record.target : undefined,
114
+ previewVersion: typeof record?.previewVersion === 'string' ? record.previewVersion : undefined,
115
+ targets,
116
+ nextAction,
117
+ };
118
+ }