@adhdev/daemon-core 0.9.82-rc.22 → 0.9.82-rc.221

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 (270) 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/cli-adapters/terminal-backends/ghostty-vt-backend.d.ts +3 -3
  14. package/dist/cli-adapters/terminal-backends/types.d.ts +1 -2
  15. package/dist/cli-adapters/terminal-screen.d.ts +2 -11
  16. package/dist/commands/cli-manager.d.ts +2 -1
  17. package/dist/commands/handler.d.ts +110 -0
  18. package/dist/commands/mesh-coordinator.d.ts +85 -1
  19. package/dist/commands/router.d.ts +32 -1
  20. package/dist/config/chat-history.d.ts +9 -0
  21. package/dist/config/config.d.ts +5 -0
  22. package/dist/config/mesh-config.d.ts +72 -1
  23. package/dist/git/git-commands.d.ts +9 -1
  24. package/dist/git/git-diff.d.ts +6 -0
  25. package/dist/git/git-types.d.ts +2 -0
  26. package/dist/index.d.ts +47 -11
  27. package/dist/index.js +30225 -11330
  28. package/dist/index.js.map +1 -1
  29. package/dist/index.mjs +30047 -11244
  30. package/dist/index.mjs.map +1 -1
  31. package/dist/installer.d.ts +1 -4
  32. package/dist/ipc/local-ipc-server.d.ts +91 -0
  33. package/dist/launch.d.ts +1 -1
  34. package/dist/logging/async-batch-writer.d.ts +10 -0
  35. package/dist/mesh/contracts.d.ts +164 -0
  36. package/dist/mesh/coordinator-prompt.d.ts +36 -0
  37. package/dist/mesh/coordinator-registry.d.ts +59 -0
  38. package/dist/mesh/mesh-active-work.d.ts +90 -0
  39. package/dist/mesh/mesh-delivery-policy.d.ts +131 -0
  40. package/dist/mesh/mesh-events-coordinator.d.ts +151 -0
  41. package/dist/mesh/mesh-events-pending.d.ts +33 -0
  42. package/dist/mesh/mesh-events-stale.d.ts +40 -0
  43. package/dist/mesh/mesh-events-utils.d.ts +14 -0
  44. package/dist/mesh/mesh-events.d.ts +5 -40
  45. package/dist/mesh/mesh-fast-forward.d.ts +41 -0
  46. package/dist/mesh/mesh-host-ownership.d.ts +9 -0
  47. package/dist/mesh/mesh-ledger-reconciliation.d.ts +23 -3
  48. package/dist/mesh/mesh-ledger.d.ts +77 -1
  49. package/dist/mesh/mesh-missions.d.ts +58 -0
  50. package/dist/mesh/mesh-refine-status.d.ts +26 -0
  51. package/dist/mesh/mesh-review-inbox.d.ts +90 -0
  52. package/dist/mesh/mesh-runtime-store.d.ts +324 -0
  53. package/dist/mesh/mesh-task-stats.d.ts +49 -0
  54. package/dist/mesh/mesh-work-queue.d.ts +137 -6
  55. package/dist/mesh/preview-freshness.d.ts +18 -0
  56. package/dist/mesh/refine-config.d.ts +215 -0
  57. package/dist/mesh/worktree-bootstrap-config.d.ts +135 -0
  58. package/dist/providers/acp-provider-instance.d.ts +2 -0
  59. package/dist/providers/approval-utils.d.ts +13 -0
  60. package/dist/providers/cli-provider-instance.d.ts +39 -3
  61. package/dist/providers/contracts.d.ts +130 -6
  62. package/dist/providers/external-sources.d.ts +71 -0
  63. package/dist/providers/native-history/antigravity-cli-transcript.d.ts +100 -0
  64. package/dist/providers/native-history/claude-cli-transcript.d.ts +70 -0
  65. package/dist/providers/native-history/codex-cli-transcript.d.ts +73 -0
  66. package/dist/providers/native-history/dispatcher.d.ts +27 -0
  67. package/dist/providers/native-history/hermes-cli-transcript.d.ts +30 -0
  68. package/dist/providers/native-history/index.d.ts +13 -0
  69. package/dist/providers/provider-instance-manager.d.ts +12 -0
  70. package/dist/providers/provider-instance.d.ts +13 -1
  71. package/dist/providers/provider-loader.d.ts +26 -4
  72. package/dist/providers/provider-trust.d.ts +31 -0
  73. package/dist/providers/read-chat-contract.d.ts +29 -0
  74. package/dist/providers/sdk/v1/builders/acp/detect-status.d.ts +68 -0
  75. package/dist/providers/sdk/v1/builders/cli/detect-status.d.ts +85 -0
  76. package/dist/providers/sdk/v1/builders/cli/parse-approval-squash.d.ts +59 -0
  77. package/dist/providers/sdk/v1/builders/cli/parse-approval.d.ts +65 -0
  78. package/dist/providers/sdk/v1/builders/cli/parse-session.d.ts +91 -0
  79. package/dist/providers/sdk/v1/builders/cli/visible-region.d.ts +42 -0
  80. package/dist/providers/sdk/v1/fixture-tooling/format.d.ts +126 -0
  81. package/dist/providers/sdk/v1/fixture-tooling/index.d.ts +8 -0
  82. package/dist/providers/sdk/v1/fixture-tooling/replay.d.ts +38 -0
  83. package/dist/providers/sdk/v1/index.d.ts +30 -0
  84. package/dist/providers/sdk/v1/sandbox/README-design.d.ts +193 -0
  85. package/dist/providers/sdk/v1/sandbox/require-whitelist.d.ts +74 -0
  86. package/dist/providers/sdk/v1/sandbox/script-runner.d.ts +98 -0
  87. package/dist/providers/sdk/v1/types/cli/index.d.ts +277 -0
  88. package/dist/providers/sdk/v1/types/common/index.d.ts +203 -0
  89. package/dist/providers/sdk/v1/validators/index.d.ts +5 -0
  90. package/dist/providers/sdk/v1/validators/manifest.d.ts +40 -0
  91. package/dist/providers/sdk/v1/validators/taint.d.ts +52 -0
  92. package/dist/providers/spec/adapter.d.ts +52 -0
  93. package/dist/providers/spec/cli-adapter.d.ts +102 -0
  94. package/dist/providers/spec/evaluator.d.ts +23 -0
  95. package/dist/providers/spec/fsm-driver.d.ts +231 -0
  96. package/dist/providers/spec/fsm-evaluator.d.ts +72 -0
  97. package/dist/providers/spec/fsm-loader.d.ts +14 -0
  98. package/dist/providers/spec/fsm-types.d.ts +91 -0
  99. package/dist/providers/spec/native-history-executor.d.ts +41 -0
  100. package/dist/providers/spec/route.d.ts +4 -0
  101. package/dist/providers/spec/types.d.ts +129 -0
  102. package/dist/providers/transcript-v2.d.ts +176 -0
  103. package/dist/providers/types/interactive-prompt.d.ts +48 -0
  104. package/dist/repo-mesh-types.d.ts +126 -1
  105. package/dist/sessions/registry.d.ts +3 -0
  106. package/dist/shared-types-extra.d.ts +2 -4
  107. package/dist/shared-types.d.ts +42 -1
  108. package/dist/status/normalize.d.ts +1 -1
  109. package/dist/status/normalize.js +1 -0
  110. package/dist/status/normalize.js.map +1 -1
  111. package/dist/status/normalize.mjs +1 -0
  112. package/dist/status/normalize.mjs.map +1 -1
  113. package/dist/status/reporter.d.ts +2 -0
  114. package/dist/status/snapshot.d.ts +1 -0
  115. package/dist/types.d.ts +5 -0
  116. package/package.json +6 -3
  117. package/src/agent-stream/poller.ts +2 -3
  118. package/src/boot/daemon-lifecycle.ts +22 -10
  119. package/src/boot/process-hardening.ts +89 -0
  120. package/src/chat/source-machine.ts +534 -0
  121. package/src/chat/source-resolver.ts +0 -0
  122. package/src/chat/subscription-updates.ts +20 -1
  123. package/src/cli-adapter-types.d.ts +1 -0
  124. package/src/cli-adapter-types.ts +22 -2
  125. package/src/cli-adapters/cli-script-runner.ts +421 -0
  126. package/src/cli-adapters/cli-state-engine.ts +1162 -0
  127. package/src/cli-adapters/provider-cli-adapter.d.ts +1 -1
  128. package/src/cli-adapters/provider-cli-adapter.ts +673 -1139
  129. package/src/cli-adapters/provider-cli-parse.d.ts +1 -0
  130. package/src/cli-adapters/provider-cli-parse.ts +13 -0
  131. package/src/cli-adapters/provider-cli-runtime.ts +3 -1
  132. package/src/cli-adapters/provider-cli-shared.d.ts +2 -0
  133. package/src/cli-adapters/provider-cli-shared.ts +59 -11
  134. package/src/cli-adapters/raw-terminal-io.ts +252 -0
  135. package/src/cli-adapters/terminal-backends/ghostty-vt-backend.ts +35 -29
  136. package/src/cli-adapters/terminal-backends/types.ts +1 -3
  137. package/src/cli-adapters/terminal-screen.ts +11 -81
  138. package/src/commands/chat-commands.ts +1787 -60
  139. package/src/commands/cli-manager.ts +283 -14
  140. package/src/commands/handler.ts +809 -2
  141. package/src/commands/mesh-coordinator.ts +332 -122
  142. package/src/commands/router.ts +4369 -558
  143. package/src/config/chat-history.ts +95 -24
  144. package/src/config/config.ts +12 -0
  145. package/src/config/mesh-config.ts +303 -3
  146. package/src/config/recent-activity.ts +8 -2
  147. package/src/daemon/dev-auto-implement.ts +3 -2
  148. package/src/daemon/dev-cli-debug.ts +10 -1
  149. package/src/detection/ide-detector.ts +26 -16
  150. package/src/git/git-commands.ts +42 -12
  151. package/src/git/git-diff.ts +53 -0
  152. package/src/git/git-status.ts +35 -6
  153. package/src/git/git-types.ts +2 -0
  154. package/src/git/git-worktree.ts +8 -1
  155. package/src/index.ts +127 -10
  156. package/src/installer.d.ts +1 -1
  157. package/src/installer.ts +8 -6
  158. package/src/ipc/local-ipc-server.ts +278 -0
  159. package/src/launch.d.ts +1 -1
  160. package/src/launch.ts +37 -28
  161. package/src/logging/async-batch-writer.ts +55 -0
  162. package/src/logging/logger.ts +2 -1
  163. package/src/mesh/contracts.ts +329 -0
  164. package/src/mesh/coordinator-prompt.ts +218 -31
  165. package/src/mesh/coordinator-registry.ts +121 -0
  166. package/src/mesh/mesh-active-work.ts +437 -0
  167. package/src/mesh/mesh-delivery-policy.ts +315 -0
  168. package/src/mesh/mesh-events-coordinator.ts +1447 -0
  169. package/src/mesh/mesh-events-pending.ts +371 -0
  170. package/src/mesh/mesh-events-stale.ts +283 -0
  171. package/src/mesh/mesh-events-utils.ts +166 -0
  172. package/src/mesh/mesh-events.ts +24 -908
  173. package/src/mesh/mesh-fast-forward.ts +438 -0
  174. package/src/mesh/mesh-host-ownership.ts +73 -0
  175. package/src/mesh/mesh-ledger-reconciliation.ts +12 -5
  176. package/src/mesh/mesh-ledger.ts +587 -102
  177. package/src/mesh/mesh-missions.ts +151 -0
  178. package/src/mesh/mesh-refine-status.ts +144 -0
  179. package/src/mesh/mesh-review-inbox.ts +307 -0
  180. package/src/mesh/mesh-runtime-store.ts +1305 -0
  181. package/src/mesh/mesh-task-stats.ts +154 -0
  182. package/src/mesh/mesh-work-queue.ts +490 -166
  183. package/src/mesh/preview-freshness.ts +118 -0
  184. package/src/mesh/refine-config.ts +403 -0
  185. package/src/mesh/worktree-bootstrap-config.ts +326 -0
  186. package/src/providers/acp-provider-instance.ts +15 -1
  187. package/src/providers/approval-utils.d.ts +4 -0
  188. package/src/providers/approval-utils.ts +47 -5
  189. package/src/providers/chat-message-normalization.ts +4 -11
  190. package/src/providers/cli-provider-instance.ts +834 -64
  191. package/src/providers/contracts.d.ts +55 -0
  192. package/src/providers/contracts.ts +141 -6
  193. package/src/providers/external-sources.ts +218 -0
  194. package/src/providers/ide-provider-instance.ts +19 -5
  195. package/src/providers/native-history/antigravity-cli-transcript.ts +643 -0
  196. package/src/providers/native-history/claude-cli-transcript.ts +396 -0
  197. package/src/providers/native-history/codex-cli-transcript.ts +479 -0
  198. package/src/providers/native-history/dispatcher.ts +340 -0
  199. package/src/providers/native-history/hermes-cli-transcript.ts +230 -0
  200. package/src/providers/native-history/index.ts +30 -0
  201. package/src/providers/provider-instance-manager.ts +30 -0
  202. package/src/providers/provider-instance.ts +10 -1
  203. package/src/providers/provider-loader.ts +584 -50
  204. package/src/providers/provider-schema.ts +87 -14
  205. package/src/providers/provider-trust.ts +114 -0
  206. package/src/providers/read-chat-contract.ts +76 -16
  207. package/src/providers/sdk/README.md +49 -0
  208. package/src/providers/sdk/v1/builders/acp/detect-status.ts +144 -0
  209. package/src/providers/sdk/v1/builders/cli/detect-status.ts +262 -0
  210. package/src/providers/sdk/v1/builders/cli/parse-approval-squash.ts +158 -0
  211. package/src/providers/sdk/v1/builders/cli/parse-approval.ts +250 -0
  212. package/src/providers/sdk/v1/builders/cli/parse-session.ts +276 -0
  213. package/src/providers/sdk/v1/builders/cli/visible-region.ts +143 -0
  214. package/src/providers/sdk/v1/fixture-tooling/format.ts +130 -0
  215. package/src/providers/sdk/v1/fixture-tooling/index.ts +22 -0
  216. package/src/providers/sdk/v1/fixture-tooling/replay.ts +352 -0
  217. package/src/providers/sdk/v1/index.ts +152 -0
  218. package/src/providers/sdk/v1/sandbox/README-design.ts +195 -0
  219. package/src/providers/sdk/v1/sandbox/require-whitelist.ts +472 -0
  220. package/src/providers/sdk/v1/sandbox/script-runner.ts +150 -0
  221. package/src/providers/sdk/v1/schemas/cli/provider.schema.json +504 -0
  222. package/src/providers/sdk/v1/schemas/primitives/acp-session-protocol-v1.json +131 -0
  223. package/src/providers/sdk/v1/schemas/primitives/native-history-codex-rollout-v1.json +66 -0
  224. package/src/providers/sdk/v1/schemas/primitives/tui-approval-squash-v1.json +91 -0
  225. package/src/providers/sdk/v1/schemas/primitives/tui-assistant-block-v1.json +91 -0
  226. package/src/providers/sdk/v1/schemas/primitives/tui-cue-ordering-v1.json +47 -0
  227. package/src/providers/sdk/v1/schemas/primitives/tui-dispatch-order-v1.json +32 -0
  228. package/src/providers/sdk/v1/schemas/primitives/tui-footer-chrome-v1.json +42 -0
  229. package/src/providers/sdk/v1/schemas/primitives/tui-index-finder-v1.json +27 -0
  230. package/src/providers/sdk/v1/schemas/primitives/tui-modal-v1.json +125 -0
  231. package/src/providers/sdk/v1/schemas/primitives/tui-prompt-marker-v1.json +45 -0
  232. package/src/providers/sdk/v1/schemas/primitives/tui-session-id-extraction-v1.json +46 -0
  233. package/src/providers/sdk/v1/schemas/primitives/tui-settled-prompt-v1.json +71 -0
  234. package/src/providers/sdk/v1/schemas/primitives/tui-spinner-v1.json +83 -0
  235. package/src/providers/sdk/v1/schemas/primitives/tui-transcript-pty-v1.json +83 -0
  236. package/src/providers/sdk/v1/schemas/primitives/tui-visible-region-v1.json +57 -0
  237. package/src/providers/sdk/v1/schemas/primitives/tui-welcome-screen-v1.json +35 -0
  238. package/src/providers/sdk/v1/types/cli/index.ts +365 -0
  239. package/src/providers/sdk/v1/types/common/index.ts +229 -0
  240. package/src/providers/sdk/v1/validators/index.ts +19 -0
  241. package/src/providers/sdk/v1/validators/manifest.ts +110 -0
  242. package/src/providers/sdk/v1/validators/taint.ts +309 -0
  243. package/src/providers/spec/adapter.ts +138 -0
  244. package/src/providers/spec/cli-adapter.ts +646 -0
  245. package/src/providers/spec/evaluator.ts +321 -0
  246. package/src/providers/spec/fsm-driver.ts +749 -0
  247. package/src/providers/spec/fsm-evaluator.ts +255 -0
  248. package/src/providers/spec/fsm-loader.ts +102 -0
  249. package/src/providers/spec/fsm-types.ts +184 -0
  250. package/src/providers/spec/native-history-executor.ts +939 -0
  251. package/src/providers/spec/route.ts +51 -0
  252. package/src/providers/spec/types.ts +173 -0
  253. package/src/providers/transcript-v2.ts +567 -0
  254. package/src/providers/types/interactive-prompt.ts +425 -0
  255. package/src/providers/version-archive.ts +38 -20
  256. package/src/repo-mesh-types.ts +138 -1
  257. package/src/sessions/registry.ts +3 -0
  258. package/src/shared-types-extra.ts +2 -4
  259. package/src/shared-types.ts +45 -1
  260. package/src/status/builders.ts +24 -6
  261. package/src/status/normalize.ts +2 -0
  262. package/src/status/reporter.ts +15 -0
  263. package/src/status/snapshot.ts +84 -25
  264. package/src/system/host-memory.ts +29 -12
  265. package/src/types.ts +5 -0
  266. package/dist/cli-adapters/terminal-backends/xterm-backend.d.ts +0 -17
  267. package/dist/mesh/mesh-sync.d.ts +0 -53
  268. package/src/cli-adapters/terminal-backends/ghostty-vt-backend.d.ts +0 -16
  269. package/src/cli-adapters/terminal-backends/xterm-backend.ts +0 -97
  270. package/src/mesh/mesh-sync.ts +0 -111
@@ -1345,7 +1345,16 @@ function rewriteCanonicalSavedHistory(agentType: string, historySessionId: strin
1345
1345
 
1346
1346
  export type ProviderNativeHistoryScripts = Record<string, ((input: any) => any) | undefined>;
1347
1347
 
1348
- type ProviderNativeHistoryReadResult = { records: HistoryMessage[]; sourcePath: string; sourceMtimeMs: number };
1348
+ type ProviderNativeHistoryReadResult = {
1349
+ records: HistoryMessage[];
1350
+ sourcePath: string;
1351
+ sourceMtimeMs: number;
1352
+ providerSessionId?: string;
1353
+ workspace?: string;
1354
+ nativeHistoryCoverage?: string;
1355
+ partialReason?: string;
1356
+ unavailableReason?: string;
1357
+ };
1349
1358
 
1350
1359
  function getNativeHistoryScriptName(canonicalHistory: ProviderCanonicalHistoryConfig | undefined, key: 'readSession' | 'listSessions'): string {
1351
1360
  const configured = canonicalHistory?.scripts?.[key];
@@ -1367,19 +1376,42 @@ function normalizeProviderNativeHistoryRecords(agentType: string, historySession
1367
1376
  if (!Array.isArray(records)) return [];
1368
1377
  const normalizedSessionId = normalizeSavedHistorySessionId(historySessionId);
1369
1378
  return records
1370
- .map((record: any) => sanitizeHistoryMessage(agentType, {
1371
- ts: typeof record?.ts === 'string' ? record.ts : new Date(Number(record?.receivedAt) || Date.now()).toISOString(),
1372
- receivedAt: Number(record?.receivedAt) || Date.parse(record?.ts || '') || Date.now(),
1373
- role: record?.role,
1374
- content: String(record?.content || ''),
1375
- kind: record?.kind || (record?.role === 'system' ? 'session_start' : 'standard'),
1376
- senderName: record?.senderName,
1377
- agent: agentType,
1378
- instanceId: record?.instanceId,
1379
- historySessionId: normalizeSavedHistorySessionId(record?.historySessionId || normalizedSessionId),
1380
- sessionTitle: record?.sessionTitle,
1381
- workspace: record?.workspace,
1382
- } as HistoryMessage))
1379
+ .map((record: any) => {
1380
+ const base: HistoryMessage = {
1381
+ ts: typeof record?.ts === 'string' ? record.ts : new Date(Number(record?.receivedAt) || Date.now()).toISOString(),
1382
+ receivedAt: Number(record?.receivedAt) || Date.parse(record?.ts || '') || Date.now(),
1383
+ role: record?.role,
1384
+ content: String(record?.content || ''),
1385
+ kind: record?.kind || (record?.role === 'system' ? 'session_start' : 'standard'),
1386
+ senderName: record?.senderName,
1387
+ agent: agentType,
1388
+ instanceId: record?.instanceId,
1389
+ historySessionId: normalizeSavedHistorySessionId(record?.historySessionId || normalizedSessionId),
1390
+ sessionTitle: record?.sessionTitle,
1391
+ workspace: record?.workspace,
1392
+ } as HistoryMessage;
1393
+ // (A2.3 v2 identity passthrough) — if the producer (native_history.js)
1394
+ // emitted v2 stable identity, keep it across the sanitize layer so
1395
+ // downstream (chat-commands.ts normalizeNativeHistoryMessages) sees
1396
+ // the producer's contract output instead of recomputing from index
1397
+ // and content hash. v1 producers without these fields are unaffected.
1398
+ if (typeof record?.providerUnitKey === 'string' && record.providerUnitKey) {
1399
+ (base as any).providerUnitKey = record.providerUnitKey;
1400
+ }
1401
+ if (typeof record?.bubbleId === 'string' && record.bubbleId) {
1402
+ (base as any).bubbleId = record.bubbleId;
1403
+ }
1404
+ if (typeof record?.sequence === 'number' && Number.isFinite(record.sequence)) {
1405
+ (base as any).sequence = record.sequence;
1406
+ }
1407
+ if (typeof record?._turnKey === 'string' && record._turnKey) {
1408
+ (base as any)._turnKey = record._turnKey;
1409
+ }
1410
+ if (typeof record?.bubbleState === 'string' && record.bubbleState) {
1411
+ (base as any).bubbleState = record.bubbleState;
1412
+ }
1413
+ return sanitizeHistoryMessage(agentType, base);
1414
+ })
1383
1415
  .filter(Boolean) as HistoryMessage[];
1384
1416
  }
1385
1417
 
@@ -1387,27 +1419,41 @@ function callProviderNativeHistoryRead(
1387
1419
  agentType: string,
1388
1420
  canonicalHistory: ProviderCanonicalHistoryConfig | undefined,
1389
1421
  scripts: ProviderNativeHistoryScripts | undefined,
1390
- historySessionId: string,
1422
+ historySessionId: string | undefined,
1391
1423
  workspace?: string,
1424
+ excludeInProgressTurn?: boolean,
1425
+ sessionStartedAtMs?: number,
1426
+ envOverrides?: Record<string, string>,
1427
+ forceRefresh?: boolean,
1392
1428
  ): ProviderNativeHistoryReadResult | null {
1393
1429
  const fn = getProviderNativeHistoryScript(scripts, canonicalHistory, 'readSession');
1394
1430
  if (!fn) return null;
1431
+ const normalizedSessionId = normalizeSavedHistorySessionId(historySessionId || '');
1395
1432
  const result = fn({
1396
1433
  agentType,
1397
- sessionId: historySessionId,
1398
- historySessionId,
1434
+ sessionId: normalizedSessionId,
1435
+ historySessionId: normalizedSessionId,
1399
1436
  workspace,
1400
1437
  format: canonicalHistory?.format,
1401
1438
  watchPath: canonicalHistory?.watchPath,
1402
- args: { sessionId: historySessionId, historySessionId, workspace },
1439
+ excludeInProgressTurn: excludeInProgressTurn === true,
1440
+ sessionStartedAtMs,
1441
+ envOverrides,
1442
+ forceRefresh: forceRefresh === true,
1443
+ args: { sessionId: normalizedSessionId, historySessionId: normalizedSessionId, workspace, excludeInProgressTurn: excludeInProgressTurn === true, sessionStartedAtMs, envOverrides, forceRefresh: forceRefresh === true },
1403
1444
  });
1404
1445
  if (!result || typeof result !== 'object') return null;
1405
- const records = normalizeProviderNativeHistoryRecords(agentType, historySessionId, (result as any).messages || (result as any).records);
1446
+ const records = normalizeProviderNativeHistoryRecords(agentType, normalizedSessionId, (result as any).messages || (result as any).records);
1406
1447
  if (records.length === 0) return null;
1407
1448
  return {
1408
1449
  records,
1409
1450
  sourcePath: typeof (result as any).sourcePath === 'string' ? (result as any).sourcePath : '',
1410
1451
  sourceMtimeMs: Number((result as any).sourceMtimeMs) || 0,
1452
+ providerSessionId: typeof (result as any).providerSessionId === 'string' ? (result as any).providerSessionId.trim() : undefined,
1453
+ workspace: typeof (result as any).workspace === 'string' ? (result as any).workspace.trim() : undefined,
1454
+ nativeHistoryCoverage: typeof (result as any).nativeHistoryCoverage === 'string' ? (result as any).nativeHistoryCoverage.trim() : undefined,
1455
+ partialReason: typeof (result as any).partialReason === 'string' ? (result as any).partialReason.trim() : undefined,
1456
+ unavailableReason: typeof (result as any).unavailableReason === 'string' ? (result as any).unavailableReason.trim() : undefined,
1411
1457
  };
1412
1458
  }
1413
1459
 
@@ -1417,10 +1463,15 @@ function buildNativeHistoryReadResult(
1417
1463
  scripts: ProviderNativeHistoryScripts | undefined,
1418
1464
  historySessionId: string | undefined,
1419
1465
  workspace?: string,
1466
+ excludeInProgressTurn?: boolean,
1467
+ sessionStartedAtMs?: number,
1468
+ envOverrides?: Record<string, string>,
1469
+ forceRefresh?: boolean,
1420
1470
  ): ProviderNativeHistoryReadResult | null {
1421
1471
  const normalizedSessionId = normalizeSavedHistorySessionId(historySessionId || '');
1422
- if (!canonicalHistory || !normalizedSessionId || !isNativeSourceCanonicalHistory(canonicalHistory)) return null;
1423
- return callProviderNativeHistoryRead(agentType, canonicalHistory, scripts, normalizedSessionId, workspace);
1472
+ const normalizedWorkspace = typeof workspace === 'string' ? workspace.trim() : '';
1473
+ if (!canonicalHistory || (!normalizedSessionId && !normalizedWorkspace) || !isNativeSourceCanonicalHistory(canonicalHistory)) return null;
1474
+ return callProviderNativeHistoryRead(agentType, canonicalHistory, scripts, normalizedSessionId, workspace, excludeInProgressTurn, sessionStartedAtMs, envOverrides, forceRefresh);
1424
1475
  }
1425
1476
 
1426
1477
  function materializeNativeHistoryToMirror(
@@ -1476,16 +1527,36 @@ export function readProviderChatHistory(
1476
1527
  excludeRecentCount?: number;
1477
1528
  historyBehavior?: ProviderHistoryBehavior;
1478
1529
  scripts?: ProviderNativeHistoryScripts;
1530
+ excludeInProgressTurn?: boolean;
1531
+ sessionStartedAtMs?: number;
1532
+ envOverrides?: Record<string, string>;
1533
+ forceRefresh?: boolean;
1479
1534
  } = {},
1480
- ): { messages: HistoryMessage[]; hasMore: boolean; source: 'provider-native' | 'adhdev-mirror' | 'native-unavailable'; sourcePath?: string; sourceMtimeMs?: number } {
1481
- if (isNativeSourceCanonicalHistory(options.canonicalHistory) && options.historySessionId) {
1482
- const nativeResult = buildNativeHistoryReadResult(agentType, options.canonicalHistory, options.scripts, options.historySessionId, options.workspace);
1535
+ ): {
1536
+ messages: HistoryMessage[];
1537
+ hasMore: boolean;
1538
+ source: 'provider-native' | 'adhdev-mirror' | 'native-unavailable';
1539
+ sourcePath?: string;
1540
+ sourceMtimeMs?: number;
1541
+ providerSessionId?: string;
1542
+ workspace?: string;
1543
+ nativeHistoryCoverage?: string;
1544
+ partialReason?: string;
1545
+ unavailableReason?: string;
1546
+ } {
1547
+ if (isNativeSourceCanonicalHistory(options.canonicalHistory) && (options.historySessionId || options.workspace)) {
1548
+ const nativeResult = buildNativeHistoryReadResult(agentType, options.canonicalHistory, options.scripts, options.historySessionId, options.workspace, options.excludeInProgressTurn, options.sessionStartedAtMs, options.envOverrides, options.forceRefresh);
1483
1549
  if (!nativeResult) return { messages: [], hasMore: false, source: 'native-unavailable' };
1484
1550
  return {
1485
1551
  ...pageHistoryRecords(agentType, nativeResult.records, options.offset || 0, options.limit || 30, options.excludeRecentCount || 0, options.historyBehavior),
1486
1552
  source: 'provider-native',
1487
1553
  sourcePath: nativeResult.sourcePath,
1488
1554
  sourceMtimeMs: nativeResult.sourceMtimeMs,
1555
+ providerSessionId: nativeResult.providerSessionId,
1556
+ workspace: nativeResult.workspace,
1557
+ nativeHistoryCoverage: nativeResult.nativeHistoryCoverage,
1558
+ partialReason: nativeResult.partialReason,
1559
+ unavailableReason: nativeResult.unavailableReason,
1489
1560
  };
1490
1561
  }
1491
1562
  return {
@@ -272,6 +272,18 @@ export function getConfigDir(): string {
272
272
  return dir;
273
273
  }
274
274
 
275
+ /**
276
+ * Get the daemon runtime data directory (~/.adhdev/daemon/).
277
+ * Distinct from the user-config dir so runtime state can be cleared independently.
278
+ */
279
+ export function getDaemonDataDir(): string {
280
+ const dir = join(getConfigDir(), 'daemon');
281
+ if (!existsSync(dir)) {
282
+ mkdirSync(dir, { recursive: true });
283
+ }
284
+ return dir;
285
+ }
286
+
275
287
  /**
276
288
  * Get the config file path
277
289
  */
@@ -8,7 +8,7 @@
8
8
 
9
9
  import { existsSync, readFileSync, writeFileSync } from 'fs';
10
10
  import { join } from 'path';
11
- import { randomUUID } from 'crypto';
11
+ import { createHash, randomBytes, randomUUID } from 'crypto';
12
12
  import { getConfigDir } from './config.js';
13
13
  import type {
14
14
  LocalMeshConfig,
@@ -18,8 +18,11 @@ import type {
18
18
  RepoMeshNodePolicy,
19
19
  RepoMeshNodeCapabilities,
20
20
  RepoMeshCoordinatorConfig,
21
+ RepoMeshHostMetadata,
22
+ RepoMeshDaemonRole,
21
23
  } from '../repo-mesh-types.js';
22
24
  import { DEFAULT_MESH_POLICY } from '../repo-mesh-types.js';
25
+ import { createDefaultMeshHostMetadata } from '../mesh/mesh-host-ownership.js';
23
26
 
24
27
  // ─── Persistence ────────────────────────────────
25
28
 
@@ -39,6 +42,20 @@ function loadMeshConfig(): LocalMeshConfig {
39
42
  }
40
43
  }
41
44
 
45
+ function normalizeCapabilityTags(value: unknown): string[] | undefined {
46
+ if (!Array.isArray(value)) return undefined;
47
+ const seen = new Set<string>();
48
+ const tags = value
49
+ .map(tag => typeof tag === 'string' ? tag.trim() : '')
50
+ .filter(Boolean)
51
+ .filter(tag => {
52
+ if (seen.has(tag)) return false;
53
+ seen.add(tag);
54
+ return true;
55
+ });
56
+ return tags.length ? tags : undefined;
57
+ }
58
+
42
59
  function saveMeshConfig(config: LocalMeshConfig): void {
43
60
  const path = getMeshConfigPath();
44
61
  writeFileSync(path, JSON.stringify(config, null, 2), { encoding: 'utf-8', mode: 0o600 });
@@ -78,12 +95,23 @@ const SESSION_CLEANUP_MODES = new Set(['preserve', 'stop', 'delete_stopped', 'st
78
95
  const SPAWNED_SESSION_VISIBILITY_MODES = new Set(['visible', 'hidden']);
79
96
 
80
97
  function mergeMeshPolicy(base: RepoMeshPolicy | undefined, patch: Partial<RepoMeshPolicy> | undefined): RepoMeshPolicy {
81
- const policy: RepoMeshPolicy = { ...DEFAULT_MESH_POLICY, ...(base || {}), ...(patch || {}) };
98
+ const autoFastForward = normalizeAutoFastForwardPolicy({
99
+ ...DEFAULT_MESH_POLICY.autoFastForward,
100
+ ...((base?.autoFastForward && typeof base.autoFastForward === 'object') ? base.autoFastForward : {}),
101
+ ...((patch?.autoFastForward && typeof patch.autoFastForward === 'object') ? patch.autoFastForward : {}),
102
+ });
103
+ const policy: RepoMeshPolicy = {
104
+ ...DEFAULT_MESH_POLICY,
105
+ ...(base || {}),
106
+ ...(patch || {}),
107
+ autoFastForward,
108
+ };
82
109
  if (!['block', 'warn', 'checkpoint_then_continue'].includes(policy.dirtyWorkspaceBehavior)) {
83
110
  policy.dirtyWorkspaceBehavior = 'warn';
84
111
  }
85
112
  const maxParallelTasks = Number(policy.maxParallelTasks);
86
113
  policy.maxParallelTasks = Number.isFinite(maxParallelTasks) ? Math.max(1, Math.min(8, Math.floor(maxParallelTasks))) : 2;
114
+ policy.allowAutoPublishSubmoduleMainCommits = policy.allowAutoPublishSubmoduleMainCommits === true;
87
115
  if (!SESSION_CLEANUP_MODES.has(String(policy.sessionCleanupOnNodeRemove))) {
88
116
  policy.sessionCleanupOnNodeRemove = 'preserve';
89
117
  }
@@ -93,6 +121,18 @@ function mergeMeshPolicy(base: RepoMeshPolicy | undefined, patch: Partial<RepoMe
93
121
  return policy;
94
122
  }
95
123
 
124
+ function normalizeAutoFastForwardPolicy(value: unknown): NonNullable<RepoMeshPolicy['autoFastForward']> {
125
+ const record = value && typeof value === 'object' && !Array.isArray(value)
126
+ ? value as Record<string, unknown>
127
+ : {};
128
+ const maxBehind = Number(record.maxBehind);
129
+ return {
130
+ enabled: record.enabled !== false,
131
+ ...(Number.isFinite(maxBehind) && maxBehind >= 0 ? { maxBehind: Math.floor(maxBehind) } : {}),
132
+ requireCleanSubmodules: record.requireCleanSubmodules !== false,
133
+ };
134
+ }
135
+
96
136
  export function listMeshes(): LocalMeshEntry[] {
97
137
  return loadMeshConfig().meshes;
98
138
  }
@@ -112,6 +152,7 @@ export interface CreateMeshOptions {
112
152
  defaultBranch?: string;
113
153
  policy?: Partial<RepoMeshPolicy>;
114
154
  coordinator?: RepoMeshCoordinatorConfig;
155
+ meshHost?: RepoMeshHostMetadata;
115
156
  }
116
157
 
117
158
  export function createMesh(opts: CreateMeshOptions): LocalMeshEntry {
@@ -133,6 +174,7 @@ export function createMesh(opts: CreateMeshOptions): LocalMeshEntry {
133
174
  defaultBranch: opts.defaultBranch,
134
175
  policy: mergeMeshPolicy(undefined, opts.policy),
135
176
  coordinator: opts.coordinator || {},
177
+ meshHost: opts.meshHost || createDefaultMeshHostMetadata(),
136
178
  nodes: [],
137
179
  createdAt: now,
138
180
  updatedAt: now,
@@ -148,6 +190,7 @@ export interface UpdateMeshOptions {
148
190
  defaultBranch?: string;
149
191
  policy?: Partial<RepoMeshPolicy>;
150
192
  coordinator?: RepoMeshCoordinatorConfig;
193
+ meshHost?: RepoMeshHostMetadata;
151
194
  }
152
195
 
153
196
  export function updateMesh(meshId: string, opts: UpdateMeshOptions): LocalMeshEntry | undefined {
@@ -159,6 +202,7 @@ export function updateMesh(meshId: string, opts: UpdateMeshOptions): LocalMeshEn
159
202
  if (opts.defaultBranch !== undefined) mesh.defaultBranch = opts.defaultBranch;
160
203
  if (opts.policy) mesh.policy = mergeMeshPolicy(mesh.policy, opts.policy);
161
204
  if (opts.coordinator) mesh.coordinator = opts.coordinator;
205
+ if (opts.meshHost) mesh.meshHost = opts.meshHost;
162
206
  mesh.updatedAt = new Date().toISOString();
163
207
 
164
208
  saveMeshConfig(config);
@@ -174,6 +218,240 @@ export function deleteMesh(meshId: string): boolean {
174
218
  return true;
175
219
  }
176
220
 
221
+ function normalizeManualHostAddress(hostAddress: string): string {
222
+ const normalized = hostAddress.trim().replace(/\/+$/, '');
223
+ if (!normalized) throw new Error('hostAddress required');
224
+ let parsed: URL;
225
+ try {
226
+ parsed = new URL(normalized);
227
+ } catch {
228
+ throw new Error('hostAddress must be a valid http(s) or ws(s) URL');
229
+ }
230
+ if (!['http:', 'https:', 'ws:', 'wss:'].includes(parsed.protocol)) {
231
+ throw new Error('hostAddress must use http, https, ws, or wss');
232
+ }
233
+ return normalized;
234
+ }
235
+
236
+ export function tokenIdForManualPairing(token: string): string {
237
+ return `tok_${createHash('sha256').update(token).digest('hex').slice(0, 16)}`;
238
+ }
239
+
240
+ function normalizeTokenExpiry(value: unknown): string | undefined {
241
+ if (typeof value !== 'string' || !value.trim()) return undefined;
242
+ const date = new Date(value);
243
+ if (Number.isNaN(date.getTime())) throw new Error('expiresAt must be a valid ISO date');
244
+ return date.toISOString();
245
+ }
246
+
247
+ function assertPairingTokenValid(pairing: RepoMeshHostMetadata['pairing'], rawToken: string, nowIso: string): { ok: true; tokenId: string } | { ok: false; reason: string; expectedTokenId?: string; presentedTokenId?: string } {
248
+ const token = rawToken.trim();
249
+ if (!token) return { ok: false, reason: 'token required' };
250
+ const presentedTokenId = tokenIdForManualPairing(token);
251
+ const expectedTokenId = pairing?.tokenId;
252
+ if (!expectedTokenId || pairing?.status === 'not_configured' || pairing?.status === 'revoked') {
253
+ return { ok: false, reason: 'host pairing token is not configured', presentedTokenId };
254
+ }
255
+ if (pairing.expiresAt && new Date(pairing.expiresAt).getTime() <= new Date(nowIso).getTime()) {
256
+ return { ok: false, reason: 'host pairing token expired', expectedTokenId, presentedTokenId };
257
+ }
258
+ if (presentedTokenId !== expectedTokenId) {
259
+ return { ok: false, reason: 'invalid pairing token', expectedTokenId, presentedTokenId };
260
+ }
261
+ return { ok: true, tokenId: presentedTokenId };
262
+ }
263
+
264
+ export interface ConfigureMeshHostPairingOptions {
265
+ hostAddress: string;
266
+ token: string;
267
+ now?: string;
268
+ }
269
+
270
+ export function configureMeshHostPairing(
271
+ meshId: string,
272
+ opts: ConfigureMeshHostPairingOptions,
273
+ ): { mesh: LocalMeshEntry; meshHost: RepoMeshHostMetadata; hostAddress: string } | undefined {
274
+ const hostAddress = normalizeManualHostAddress(opts.hostAddress);
275
+ const token = opts.token.trim();
276
+ if (!token) throw new Error('token required');
277
+
278
+ const config = loadMeshConfig();
279
+ const mesh = config.meshes.find(m => m.id === meshId);
280
+ if (!mesh) return undefined;
281
+
282
+ const now = opts.now || new Date().toISOString();
283
+ const previous = mesh.meshHost || createDefaultMeshHostMetadata();
284
+ const meshHost: RepoMeshHostMetadata = {
285
+ ...previous,
286
+ role: 'member',
287
+ hostAddress,
288
+ pairing: {
289
+ status: 'pairing',
290
+ tokenId: tokenIdForManualPairing(token),
291
+ lastPairedAt: now,
292
+ },
293
+ };
294
+
295
+ mesh.meshHost = meshHost;
296
+ mesh.updatedAt = now;
297
+ saveMeshConfig(config);
298
+ return { mesh, meshHost, hostAddress };
299
+ }
300
+
301
+ export interface CreateMeshHostPairingTokenOptions {
302
+ token?: string;
303
+ expiresAt?: string;
304
+ now?: string;
305
+ }
306
+
307
+ export function createMeshHostPairingToken(
308
+ meshId: string,
309
+ opts: CreateMeshHostPairingTokenOptions = {},
310
+ ): { mesh: LocalMeshEntry; meshHost: RepoMeshHostMetadata; token: string; tokenId: string; expiresAt?: string } | undefined {
311
+ const config = loadMeshConfig();
312
+ const mesh = config.meshes.find(m => m.id === meshId);
313
+ if (!mesh) return undefined;
314
+ const now = opts.now || new Date().toISOString();
315
+ const token = (opts.token || `mhj_${randomBytes(24).toString('base64url')}`).trim();
316
+ if (!token) throw new Error('token required');
317
+ const tokenId = tokenIdForManualPairing(token);
318
+ const expiresAt = normalizeTokenExpiry(opts.expiresAt);
319
+ const previous = mesh.meshHost || createDefaultMeshHostMetadata();
320
+ if (previous.role === 'member') {
321
+ throw new Error('Mesh Host daemon required to create host pairing tokens; member daemons cannot mint host join tokens.');
322
+ }
323
+ const meshHost: RepoMeshHostMetadata = {
324
+ ...previous,
325
+ role: 'host',
326
+ pairing: {
327
+ status: 'pairing',
328
+ tokenId,
329
+ lastPairedAt: now,
330
+ ...(expiresAt ? { expiresAt } : {}),
331
+ },
332
+ };
333
+ mesh.meshHost = meshHost;
334
+ mesh.updatedAt = now;
335
+ saveMeshConfig(config);
336
+ return { mesh, meshHost, token, tokenId, ...(expiresAt ? { expiresAt } : {}) };
337
+ }
338
+
339
+ export interface MeshHostJoinMemberNodeInput {
340
+ id?: string;
341
+ workspace: string;
342
+ repoRoot?: string;
343
+ daemonId?: string;
344
+ machineId?: string;
345
+ userOverrides?: Partial<RepoMeshNodeCapabilities>;
346
+ policy?: RepoMeshNodePolicy;
347
+ role?: RepoMeshDaemonRole;
348
+ }
349
+
350
+ export interface ApplyMeshHostJoinOptions {
351
+ token: string;
352
+ memberNode: MeshHostJoinMemberNodeInput;
353
+ memberMeshId?: string;
354
+ now?: string;
355
+ }
356
+
357
+ export function applyMeshHostJoinRequest(
358
+ meshId: string,
359
+ opts: ApplyMeshHostJoinOptions,
360
+ ): { accepted: true; mesh: LocalMeshEntry; meshHost: RepoMeshHostMetadata; node: LocalMeshNodeEntry; tokenId: string } | { accepted: false; mesh?: LocalMeshEntry; meshHost?: RepoMeshHostMetadata; tokenId?: string; reason: string } | undefined {
361
+ const config = loadMeshConfig();
362
+ const mesh = config.meshes.find(m => m.id === meshId);
363
+ if (!mesh) return undefined;
364
+ const now = opts.now || new Date().toISOString();
365
+ const previous = mesh.meshHost || createDefaultMeshHostMetadata();
366
+ if (previous.role === 'member') {
367
+ return { accepted: false, mesh, meshHost: previous, reason: 'Mesh Host daemon required to accept join requests' };
368
+ }
369
+ const meshHost: RepoMeshHostMetadata = { ...previous, role: 'host' };
370
+ const validation = assertPairingTokenValid(meshHost.pairing, opts.token, now);
371
+ if (!validation.ok) {
372
+ mesh.meshHost = {
373
+ ...meshHost,
374
+ pairing: {
375
+ ...(meshHost.pairing || { status: 'not_configured' as const }),
376
+ status: 'rejected',
377
+ lastRejectedAt: now,
378
+ },
379
+ };
380
+ mesh.updatedAt = now;
381
+ saveMeshConfig(config);
382
+ return { accepted: false, mesh, meshHost: mesh.meshHost, tokenId: validation.presentedTokenId, reason: validation.reason };
383
+ }
384
+
385
+ const workspace = opts.memberNode.workspace.trim();
386
+ if (!workspace) throw new Error('memberNode.workspace required');
387
+ const memberId = opts.memberNode.id?.trim();
388
+ let node = mesh.nodes.find(n => (memberId && n.id === memberId) || n.workspace === workspace);
389
+ if (node) {
390
+ node.workspace = workspace;
391
+ node.repoRoot = opts.memberNode.repoRoot;
392
+ node.daemonId = opts.memberNode.daemonId;
393
+ node.machineId = opts.memberNode.machineId;
394
+ node.userOverrides = opts.memberNode.userOverrides || node.userOverrides || {};
395
+ node.policy = { ...(node.policy || {}), ...(opts.memberNode.policy || {}) };
396
+ node.role = 'member';
397
+ } else {
398
+ if (mesh.nodes.length >= 10) throw new Error('Maximum 10 nodes per mesh');
399
+ node = {
400
+ id: memberId || `node_${randomUUID().replace(/-/g, '')}`,
401
+ workspace,
402
+ repoRoot: opts.memberNode.repoRoot,
403
+ daemonId: opts.memberNode.daemonId,
404
+ machineId: opts.memberNode.machineId,
405
+ userOverrides: opts.memberNode.userOverrides || {},
406
+ policy: opts.memberNode.policy || {},
407
+ role: 'member',
408
+ };
409
+ mesh.nodes.push(node);
410
+ }
411
+ mesh.meshHost = {
412
+ ...meshHost,
413
+ pairing: {
414
+ ...(meshHost.pairing || {}),
415
+ status: 'paired',
416
+ tokenId: validation.tokenId,
417
+ joinedAt: now,
418
+ lastPairedAt: meshHost.pairing?.lastPairedAt || now,
419
+ ...(meshHost.pairing?.expiresAt ? { expiresAt: meshHost.pairing.expiresAt } : {}),
420
+ },
421
+ };
422
+ mesh.updatedAt = now;
423
+ saveMeshConfig(config);
424
+ return { accepted: true, mesh, meshHost: mesh.meshHost, node, tokenId: validation.tokenId };
425
+ }
426
+
427
+ export function markMeshHostPairingJoined(
428
+ meshId: string,
429
+ opts: { hostDaemonId?: string; hostNodeId?: string; joinedAt?: string; token?: string; tokenId?: string },
430
+ ): { mesh: LocalMeshEntry; meshHost: RepoMeshHostMetadata } | undefined {
431
+ const config = loadMeshConfig();
432
+ const mesh = config.meshes.find(m => m.id === meshId);
433
+ if (!mesh) return undefined;
434
+ const now = opts.joinedAt || new Date().toISOString();
435
+ const previous = mesh.meshHost || createDefaultMeshHostMetadata();
436
+ const tokenId = opts.tokenId || (opts.token ? tokenIdForManualPairing(opts.token) : previous.pairing?.tokenId);
437
+ mesh.meshHost = {
438
+ ...previous,
439
+ role: 'member',
440
+ ...(opts.hostDaemonId ? { hostDaemonId: opts.hostDaemonId } : {}),
441
+ ...(opts.hostNodeId ? { hostNodeId: opts.hostNodeId } : {}),
442
+ pairing: {
443
+ ...(previous.pairing || {}),
444
+ status: 'paired',
445
+ ...(tokenId ? { tokenId } : {}),
446
+ joinedAt: now,
447
+ lastPairedAt: previous.pairing?.lastPairedAt || now,
448
+ },
449
+ };
450
+ mesh.updatedAt = now;
451
+ saveMeshConfig(config);
452
+ return { mesh, meshHost: mesh.meshHost };
453
+ }
454
+
177
455
  // ─── Node Operations ────────────────────────────
178
456
 
179
457
  export interface AddNodeOptions {
@@ -181,11 +459,14 @@ export interface AddNodeOptions {
181
459
  repoRoot?: string;
182
460
  daemonId?: string;
183
461
  machineId?: string;
462
+ capabilities?: string[];
184
463
  userOverrides?: Partial<RepoMeshNodeCapabilities>;
185
464
  policy?: RepoMeshNodePolicy;
186
465
  isLocalWorktree?: boolean;
187
466
  worktreeBranch?: string;
188
467
  clonedFromNodeId?: string;
468
+ worktreeBootstrap?: LocalMeshNodeEntry['worktreeBootstrap'];
469
+ role?: RepoMeshDaemonRole;
189
470
  }
190
471
 
191
472
  export function addNode(meshId: string, opts: AddNodeOptions): LocalMeshNodeEntry | undefined {
@@ -208,11 +489,14 @@ export function addNode(meshId: string, opts: AddNodeOptions): LocalMeshNodeEntr
208
489
  repoRoot: opts.repoRoot,
209
490
  daemonId: opts.daemonId,
210
491
  machineId: opts.machineId,
492
+ capabilities: normalizeCapabilityTags(opts.capabilities),
211
493
  userOverrides: opts.userOverrides || {},
212
494
  policy: opts.policy || {},
213
495
  isLocalWorktree: opts.isLocalWorktree,
214
496
  worktreeBranch: opts.worktreeBranch,
215
497
  clonedFromNodeId: opts.clonedFromNodeId,
498
+ worktreeBootstrap: opts.worktreeBootstrap,
499
+ role: opts.role,
216
500
  };
217
501
 
218
502
  mesh.nodes.push(node);
@@ -238,7 +522,14 @@ export function removeNode(meshId: string, nodeId: string): boolean {
238
522
  export function updateNode(
239
523
  meshId: string,
240
524
  nodeId: string,
241
- opts: { userOverrides?: Partial<RepoMeshNodeCapabilities>; policy?: RepoMeshNodePolicy },
525
+ opts: {
526
+ userOverrides?: Partial<RepoMeshNodeCapabilities>;
527
+ policy?: RepoMeshNodePolicy;
528
+ worktreeBootstrap?: LocalMeshNodeEntry['worktreeBootstrap'];
529
+ /** Per-node instruction surfaced in the coordinator prompt. Pass an
530
+ * empty string or undefined to clear it. */
531
+ systemPrompt?: string;
532
+ },
242
533
  ): LocalMeshNodeEntry | undefined {
243
534
  const config = loadMeshConfig();
244
535
  const mesh = config.meshes.find(m => m.id === meshId);
@@ -249,6 +540,15 @@ export function updateNode(
249
540
 
250
541
  if (opts.userOverrides) node.userOverrides = { ...node.userOverrides, ...opts.userOverrides };
251
542
  if (opts.policy) node.policy = { ...node.policy, ...opts.policy };
543
+ if (opts.worktreeBootstrap) node.worktreeBootstrap = opts.worktreeBootstrap;
544
+ if (Object.prototype.hasOwnProperty.call(opts, 'systemPrompt')) {
545
+ // Honor explicit clears: { systemPrompt: undefined } drops the field.
546
+ if (opts.systemPrompt && opts.systemPrompt.trim()) {
547
+ node.systemPrompt = opts.systemPrompt;
548
+ } else {
549
+ delete node.systemPrompt;
550
+ }
551
+ }
252
552
  mesh.updatedAt = new Date().toISOString();
253
553
  saveMeshConfig(config);
254
554
  return node;
@@ -92,12 +92,18 @@ export function buildSessionReadStateKey(sessionId: string, providerSessionId?:
92
92
 
93
93
  export function getSessionSeenAt(state: DaemonState, sessionId: string, providerSessionId?: string | null): number {
94
94
  const providerKey = buildSessionReadStateKey(sessionId, providerSessionId);
95
- return state.sessionReads?.[providerKey] || state.sessionReads?.[sessionId] || 0;
95
+ return Math.max(state.sessionReads?.[providerKey] || 0, state.sessionReads?.[sessionId] || 0);
96
96
  }
97
97
 
98
98
  export function getSessionSeenMarker(state: DaemonState, sessionId: string, providerSessionId?: string | null): string {
99
99
  const providerKey = buildSessionReadStateKey(sessionId, providerSessionId);
100
- return state.sessionReadMarkers?.[providerKey] || state.sessionReadMarkers?.[sessionId] || '';
100
+ const providerSeenAt = state.sessionReads?.[providerKey] || 0;
101
+ const sessionSeenAt = state.sessionReads?.[sessionId] || 0;
102
+ const providerMarker = state.sessionReadMarkers?.[providerKey] || '';
103
+ const sessionMarker = state.sessionReadMarkers?.[sessionId] || '';
104
+ if (sessionSeenAt > providerSeenAt && sessionMarker) return sessionMarker;
105
+ if (providerSeenAt > sessionSeenAt && providerMarker) return providerMarker;
106
+ return providerMarker || sessionMarker;
101
107
  }
102
108
 
103
109
  export function getSessionNotificationDismissal(state: DaemonState, sessionId: string, providerSessionId?: string | null): string {
@@ -9,6 +9,7 @@
9
9
  import * as fs from 'fs';
10
10
  import * as path from 'path';
11
11
  import * as os from 'os';
12
+ import { DEFAULT_SESSION_HOST_COLS, DEFAULT_SESSION_HOST_ROWS } from '@adhdev/session-host-core';
12
13
  import type * as http from 'http';
13
14
  import type { ChildProcess } from 'child_process';
14
15
  import type { DevServerContext, ProviderCategory } from './dev-server-types.js';
@@ -482,8 +483,8 @@ export async function handleAutoImplement(ctx: DevServerContext, type: string, r
482
483
  const isWin = os.platform() === 'win32';
483
484
  child = pty.spawn(isWin ? 'cmd.exe' : (process.env.SHELL || '/bin/zsh'), [isWin ? '/c' : '-c', shellCmd], {
484
485
  name: 'xterm-256color',
485
- cols: 120,
486
- rows: 40,
486
+ cols: DEFAULT_SESSION_HOST_COLS,
487
+ rows: DEFAULT_SESSION_HOST_ROWS,
487
488
  cwd: providerDir,
488
489
  env: { ...process.env, ...(spawn.env || {}) },
489
490
  });
@@ -802,7 +802,16 @@ export async function handleCliSend(ctx: DevServerContext, req: http.IncomingMes
802
802
  }
803
803
 
804
804
  try {
805
- ctx.instanceManager!.sendEvent(target.instanceId, 'send_message', { text });
805
+ if (target.category === 'cli') {
806
+ const bundle = getCliTargetBundle(ctx, type, instanceId);
807
+ if (!bundle) {
808
+ ctx.json(res, 404, { error: `No running CLI adapter found for: ${type || instanceId}` });
809
+ return;
810
+ }
811
+ await bundle.adapter.sendMessage(text);
812
+ } else {
813
+ ctx.instanceManager!.sendEvent(target.instanceId, 'send_message', { text });
814
+ }
806
815
  ctx.json(res, 200, { sent: true, type: target.type, instanceId: target.instanceId });
807
816
  } catch (e: any) {
808
817
  ctx.json(res, 500, { error: `Send failed: ${e.message}` });