@adhdev/daemon-core 0.9.82-rc.48 → 0.9.82-rc.481

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 (473) hide show
  1. package/dist/boot/daemon-lifecycle.d.ts +9 -0
  2. package/dist/boot/process-hardening.d.ts +50 -0
  3. package/dist/build-info.d.ts +37 -0
  4. package/dist/chat/source-machine.d.ts +166 -0
  5. package/dist/chat/source-resolver.d.ts +104 -0
  6. package/dist/chat/subscription-updates.d.ts +1 -0
  7. package/dist/cli-adapter-types.d.ts +68 -2
  8. package/dist/cli-adapters/cli-script-runner.d.ts +117 -0
  9. package/dist/cli-adapters/cli-state-engine.d.ts +220 -0
  10. package/dist/cli-adapters/provider-cli-adapter.d.ts +126 -64
  11. package/dist/cli-adapters/provider-cli-parse.d.ts +5 -0
  12. package/dist/cli-adapters/provider-cli-runtime.d.ts +34 -0
  13. package/dist/cli-adapters/provider-cli-shared.d.ts +74 -0
  14. package/dist/cli-adapters/pty-write-chunking.d.ts +34 -0
  15. package/dist/cli-adapters/raw-terminal-io.d.ts +37 -0
  16. package/dist/cli-adapters/resolve-executable.d.ts +69 -0
  17. package/dist/cli-adapters/terminal-backends/ghostty-vt-backend.d.ts +6 -3
  18. package/dist/cli-adapters/terminal-backends/types.d.ts +10 -2
  19. package/dist/cli-adapters/terminal-screen.d.ts +4 -11
  20. package/dist/commands/chat-commands-debug-bundle.d.ts +14 -0
  21. package/dist/commands/chat-commands-read.d.ts +23 -0
  22. package/dist/commands/chat-commands-scope.d.ts +39 -0
  23. package/dist/commands/chat-commands-shared.d.ts +33 -0
  24. package/dist/commands/chat-commands-write.d.ts +14 -0
  25. package/dist/commands/chat-commands.d.ts +10 -23
  26. package/dist/commands/cli-manager.d.ts +51 -1
  27. package/dist/commands/handler.d.ts +125 -0
  28. package/dist/commands/high-family/index.d.ts +3 -0
  29. package/dist/commands/high-family/mesh-coordinator-launch.d.ts +2 -0
  30. package/dist/commands/high-family/mesh-events.d.ts +2 -0
  31. package/dist/commands/high-family/mesh-status.d.ts +2 -0
  32. package/dist/commands/high-family/types.d.ts +64 -0
  33. package/dist/commands/low-family/coordinator-prompt.d.ts +9 -0
  34. package/dist/commands/low-family/daemon-lifecycle.d.ts +2 -0
  35. package/dist/commands/low-family/diagnostics.d.ts +2 -0
  36. package/dist/commands/low-family/index.d.ts +3 -0
  37. package/dist/commands/low-family/mesh-ledger.d.ts +10 -0
  38. package/dist/commands/low-family/mesh-node-logs.d.ts +2 -0
  39. package/dist/commands/low-family/notification.d.ts +2 -0
  40. package/dist/commands/low-family/refine-config.d.ts +2 -0
  41. package/dist/commands/low-family/session-host.d.ts +2 -0
  42. package/dist/commands/low-family/spec-providerdev.d.ts +11 -0
  43. package/dist/commands/low-family/status-meta.d.ts +2 -0
  44. package/dist/commands/low-family/types.d.ts +33 -0
  45. package/dist/commands/med-family/cli-agent.d.ts +2 -0
  46. package/dist/commands/med-family/fast-forward.d.ts +2 -0
  47. package/dist/commands/med-family/ide.d.ts +10 -0
  48. package/dist/commands/med-family/index.d.ts +3 -0
  49. package/dist/commands/med-family/mesh-crud.d.ts +48 -0
  50. package/dist/commands/med-family/mesh-host-pairing.d.ts +2 -0
  51. package/dist/commands/med-family/mesh-queue.d.ts +2 -0
  52. package/dist/commands/med-family/mesh-restart.d.ts +2 -0
  53. package/dist/commands/med-family/types.d.ts +152 -0
  54. package/dist/commands/mesh-coordinator.d.ts +85 -1
  55. package/dist/commands/router-aggregate-status.d.ts +24 -0
  56. package/dist/commands/router-mesh-session-owner.d.ts +59 -0
  57. package/dist/commands/router-refine.d.ts +137 -0
  58. package/dist/commands/router-worktree-cleanup.d.ts +133 -0
  59. package/dist/commands/router.d.ts +204 -15
  60. package/dist/commands/upgrade-helper.d.ts +41 -1
  61. package/dist/config/chat-history.d.ts +11 -0
  62. package/dist/config/config.d.ts +19 -0
  63. package/dist/config/mesh-config.d.ts +136 -1
  64. package/dist/config/mesh-json-config.d.ts +199 -0
  65. package/dist/config/registry-resolver.d.ts +54 -0
  66. package/dist/config/repo-settings.d.ts +77 -0
  67. package/dist/config/state-store.d.ts +30 -0
  68. package/dist/daemon/dev-server.d.ts +0 -2
  69. package/dist/detection/cli-detector.d.ts +17 -0
  70. package/dist/detection/ide-detector.d.ts +13 -0
  71. package/dist/detection/win32-ide-version.d.ts +37 -0
  72. package/dist/git/change-impact-config.d.ts +159 -0
  73. package/dist/git/git-commands.d.ts +26 -2
  74. package/dist/git/git-diff.d.ts +6 -0
  75. package/dist/git/git-executor.d.ts +11 -0
  76. package/dist/git/git-status.d.ts +57 -0
  77. package/dist/git/git-types.d.ts +2 -50
  78. package/dist/git/git-worktree.d.ts +71 -1
  79. package/dist/git/index.d.ts +3 -1
  80. package/dist/index.d.ts +73 -17
  81. package/dist/index.js +52979 -18657
  82. package/dist/index.js.map +1 -1
  83. package/dist/index.mjs +59496 -25355
  84. package/dist/index.mjs.map +1 -1
  85. package/dist/installer.d.ts +1 -4
  86. package/dist/ipc/local-ipc-server.d.ts +91 -0
  87. package/dist/launch.d.ts +1 -1
  88. package/dist/logging/async-batch-writer.d.ts +10 -0
  89. package/dist/logging/debug-config.d.ts +16 -0
  90. package/dist/logging/log-redactor.d.ts +24 -0
  91. package/dist/logging/log-tail-reader.d.ts +81 -0
  92. package/dist/logging/logger.d.ts +1 -1
  93. package/dist/mesh/contracts.d.ts +230 -0
  94. package/dist/mesh/coordinator-prompt.d.ts +76 -0
  95. package/dist/mesh/coordinator-registry.d.ts +59 -0
  96. package/dist/mesh/mesh-active-work.d.ts +203 -0
  97. package/dist/mesh/mesh-clone-grace.d.ts +8 -0
  98. package/dist/mesh/mesh-completion-synthesis.d.ts +4 -0
  99. package/dist/mesh/mesh-coordinator-config.d.ts +21 -0
  100. package/dist/mesh/mesh-delivery-policy.d.ts +104 -0
  101. package/dist/mesh/mesh-event-classify.d.ts +7 -0
  102. package/dist/mesh/mesh-event-forwarding.d.ts +49 -0
  103. package/dist/mesh/mesh-event-trace.d.ts +21 -0
  104. package/dist/mesh/mesh-events-coordinator.d.ts +4 -0
  105. package/dist/mesh/mesh-events-pending.d.ts +246 -0
  106. package/dist/mesh/mesh-events-stale.d.ts +61 -0
  107. package/dist/mesh/mesh-events-utils.d.ts +75 -0
  108. package/dist/mesh/mesh-events.d.ts +6 -49
  109. package/dist/mesh/mesh-fast-forward.d.ts +81 -0
  110. package/dist/mesh/mesh-host-ownership.d.ts +29 -0
  111. package/dist/mesh/mesh-init.d.ts +128 -0
  112. package/dist/mesh/mesh-ledger-reconciliation.d.ts +23 -4
  113. package/dist/mesh/mesh-ledger.d.ts +139 -1
  114. package/dist/mesh/mesh-magi-status.d.ts +63 -0
  115. package/dist/mesh/mesh-missions.d.ts +281 -0
  116. package/dist/mesh/mesh-node-identity.d.ts +310 -0
  117. package/dist/mesh/mesh-queue-assignment.d.ts +158 -0
  118. package/dist/mesh/mesh-reconcile-acked-hold.d.ts +17 -0
  119. package/dist/mesh/mesh-reconcile-config.d.ts +6 -0
  120. package/dist/mesh/mesh-reconcile-identity.d.ts +6 -0
  121. package/dist/mesh/mesh-reconcile-loop.d.ts +80 -0
  122. package/dist/mesh/mesh-reconcile-v2-backstop.d.ts +16 -0
  123. package/dist/mesh/mesh-refine-batch.d.ts +68 -0
  124. package/dist/mesh/mesh-refine-gates.d.ts +428 -0
  125. package/dist/mesh/mesh-refine-status.d.ts +62 -0
  126. package/dist/mesh/mesh-remote-event-pull.d.ts +15 -0
  127. package/dist/mesh/mesh-review-inbox.d.ts +90 -0
  128. package/dist/mesh/mesh-routing.d.ts +70 -0
  129. package/dist/mesh/mesh-runtime-store.d.ts +508 -0
  130. package/dist/mesh/mesh-scheduling-runtime.d.ts +78 -0
  131. package/dist/mesh/mesh-task-inflight.d.ts +46 -0
  132. package/dist/mesh/mesh-task-stats.d.ts +49 -0
  133. package/dist/mesh/mesh-unresolved-forward-outbox.d.ts +30 -0
  134. package/dist/mesh/mesh-warmup-deadline.d.ts +68 -0
  135. package/dist/mesh/mesh-work-queue.d.ts +363 -5
  136. package/dist/mesh/preview-freshness.d.ts +28 -0
  137. package/dist/mesh/refine-config.d.ts +216 -0
  138. package/dist/mesh/worktree-bootstrap-config.d.ts +219 -0
  139. package/dist/providers/acp-provider-instance.d.ts +5 -0
  140. package/dist/providers/approval-utils.d.ts +46 -0
  141. package/dist/providers/chat-message-normalization.d.ts +58 -1
  142. package/dist/providers/cli-provider-effect-format.d.ts +30 -0
  143. package/dist/providers/cli-provider-history-dedup.d.ts +17 -0
  144. package/dist/providers/cli-provider-input-prompt.d.ts +12 -0
  145. package/dist/providers/cli-provider-instance-types.d.ts +45 -0
  146. package/dist/providers/cli-provider-instance.d.ts +406 -30
  147. package/dist/providers/cli-provider-status-helpers.d.ts +46 -0
  148. package/dist/providers/cli-provider-transcript-merge.d.ts +7 -0
  149. package/dist/providers/contracts.d.ts +149 -6
  150. package/dist/providers/external-sources.d.ts +71 -0
  151. package/dist/providers/manual-attendance.d.ts +79 -0
  152. package/dist/providers/native-history/antigravity-claim-registry.d.ts +39 -0
  153. package/dist/providers/native-history/antigravity-cli-transcript.d.ts +137 -0
  154. package/dist/providers/native-history/claude-cli-transcript.d.ts +70 -0
  155. package/dist/providers/native-history/codex-cli-transcript.d.ts +73 -0
  156. package/dist/providers/native-history/constants.d.ts +12 -0
  157. package/dist/providers/native-history/dispatcher.d.ts +44 -0
  158. package/dist/providers/native-history/hermes-cli-transcript.d.ts +30 -0
  159. package/dist/providers/native-history/index.d.ts +13 -0
  160. package/dist/providers/provider-instance-manager.d.ts +29 -0
  161. package/dist/providers/provider-instance.d.ts +30 -1
  162. package/dist/providers/provider-loader.d.ts +42 -5
  163. package/dist/providers/provider-trust.d.ts +31 -0
  164. package/dist/providers/read-chat-contract.d.ts +29 -0
  165. package/dist/providers/sdk/v1/builders/acp/detect-status.d.ts +68 -0
  166. package/dist/providers/sdk/v1/builders/cli/detect-status.d.ts +86 -0
  167. package/dist/providers/sdk/v1/builders/cli/parse-approval-squash.d.ts +59 -0
  168. package/dist/providers/sdk/v1/builders/cli/parse-approval.d.ts +65 -0
  169. package/dist/providers/sdk/v1/builders/cli/parse-session.d.ts +91 -0
  170. package/dist/providers/sdk/v1/builders/cli/visible-region.d.ts +42 -0
  171. package/dist/providers/sdk/v1/fixture-tooling/format.d.ts +126 -0
  172. package/dist/providers/sdk/v1/fixture-tooling/index.d.ts +8 -0
  173. package/dist/providers/sdk/v1/fixture-tooling/replay.d.ts +38 -0
  174. package/dist/providers/sdk/v1/index.d.ts +30 -0
  175. package/dist/providers/sdk/v1/sandbox/README-design.d.ts +193 -0
  176. package/dist/providers/sdk/v1/sandbox/require-whitelist.d.ts +74 -0
  177. package/dist/providers/sdk/v1/sandbox/script-runner.d.ts +98 -0
  178. package/dist/providers/sdk/v1/types/cli/index.d.ts +277 -0
  179. package/dist/providers/sdk/v1/types/common/index.d.ts +203 -0
  180. package/dist/providers/sdk/v1/validators/index.d.ts +5 -0
  181. package/dist/providers/sdk/v1/validators/manifest.d.ts +40 -0
  182. package/dist/providers/sdk/v1/validators/taint.d.ts +52 -0
  183. package/dist/providers/spec/adapter.d.ts +98 -0
  184. package/dist/providers/spec/cli-adapter.d.ts +223 -0
  185. package/dist/providers/spec/evaluator.d.ts +45 -0
  186. package/dist/providers/spec/fsm-driver.d.ts +449 -0
  187. package/dist/providers/spec/fsm-evaluator.d.ts +93 -0
  188. package/dist/providers/spec/fsm-loader.d.ts +14 -0
  189. package/dist/providers/spec/fsm-types.d.ts +202 -0
  190. package/dist/providers/spec/native-history-executor.d.ts +41 -0
  191. package/dist/providers/spec/pre-launch-trust.d.ts +16 -0
  192. package/dist/providers/spec/route.d.ts +4 -0
  193. package/dist/providers/spec/types.d.ts +244 -0
  194. package/dist/providers/status-monitor.d.ts +7 -7
  195. package/dist/providers/transcript-v2.d.ts +176 -0
  196. package/dist/providers/types/interactive-prompt.d.ts +84 -0
  197. package/dist/providers/working-dir.d.ts +17 -0
  198. package/dist/repo-mesh-types.d.ts +629 -12
  199. package/dist/runtime-defaults.d.ts +2 -0
  200. package/dist/session-host/managed-host.d.ts +64 -0
  201. package/dist/session-host/runtime-surface.d.ts +10 -16
  202. package/dist/sessions/registry.d.ts +26 -0
  203. package/dist/shared-types-extra.d.ts +2 -4
  204. package/dist/shared-types.d.ts +98 -55
  205. package/dist/status/chat-tail-hot-sessions.d.ts +40 -0
  206. package/dist/status/normalize.d.ts +1 -1
  207. package/dist/status/normalize.js +1 -0
  208. package/dist/status/normalize.js.map +1 -1
  209. package/dist/status/normalize.mjs +1 -0
  210. package/dist/status/normalize.mjs.map +1 -1
  211. package/dist/status/reporter.d.ts +2 -0
  212. package/dist/status/snapshot.d.ts +26 -0
  213. package/dist/system/hash.d.ts +8 -0
  214. package/dist/system/load-better-sqlite3.d.ts +21 -0
  215. package/dist/types.d.ts +5 -0
  216. package/package.json +11 -4
  217. package/src/agent-stream/poller.ts +2 -3
  218. package/src/agent-stream/provider-adapter.ts +1 -1
  219. package/src/boot/daemon-lifecycle.ts +80 -14
  220. package/src/boot/process-hardening.ts +89 -0
  221. package/src/build-info.ts +73 -0
  222. package/src/chat/source-machine.ts +534 -0
  223. package/src/chat/source-resolver.ts +0 -0
  224. package/src/chat/subscription-updates.ts +20 -1
  225. package/src/cli-adapter-types.d.ts +3 -1
  226. package/src/cli-adapter-types.ts +68 -2
  227. package/src/cli-adapters/cli-script-runner.ts +421 -0
  228. package/src/cli-adapters/cli-state-engine.ts +1282 -0
  229. package/src/cli-adapters/provider-cli-adapter.d.ts +1 -1
  230. package/src/cli-adapters/provider-cli-adapter.ts +954 -1144
  231. package/src/cli-adapters/provider-cli-parse.d.ts +2 -0
  232. package/src/cli-adapters/provider-cli-parse.ts +19 -0
  233. package/src/cli-adapters/provider-cli-runtime.ts +56 -7
  234. package/src/cli-adapters/provider-cli-shared.d.ts +4 -0
  235. package/src/cli-adapters/provider-cli-shared.ts +121 -11
  236. package/src/cli-adapters/pty-transport.ts +2 -1
  237. package/src/cli-adapters/pty-write-chunking.ts +106 -0
  238. package/src/cli-adapters/raw-terminal-io.ts +252 -0
  239. package/src/cli-adapters/resolve-executable.ts +204 -0
  240. package/src/cli-adapters/session-host-transport.ts +2 -1
  241. package/src/cli-adapters/terminal-backends/ghostty-vt-backend.ts +63 -29
  242. package/src/cli-adapters/terminal-backends/types.ts +10 -3
  243. package/src/cli-adapters/terminal-screen.ts +16 -81
  244. package/src/commands/chat-commands-debug-bundle.ts +398 -0
  245. package/src/commands/chat-commands-read.ts +2767 -0
  246. package/src/commands/chat-commands-scope.ts +54 -0
  247. package/src/commands/chat-commands-shared.ts +114 -0
  248. package/src/commands/chat-commands-write.ts +891 -0
  249. package/src/commands/chat-commands.ts +19 -1841
  250. package/src/commands/cli-manager.d.ts +2 -0
  251. package/src/commands/cli-manager.ts +659 -27
  252. package/src/commands/handler.ts +845 -2
  253. package/src/commands/high-family/index.ts +28 -0
  254. package/src/commands/high-family/mesh-coordinator-launch.ts +680 -0
  255. package/src/commands/high-family/mesh-events.ts +102 -0
  256. package/src/commands/high-family/mesh-status.ts +849 -0
  257. package/src/commands/high-family/types.ts +80 -0
  258. package/src/commands/low-family/coordinator-prompt.ts +72 -0
  259. package/src/commands/low-family/daemon-lifecycle.ts +120 -0
  260. package/src/commands/low-family/diagnostics.ts +57 -0
  261. package/src/commands/low-family/index.ts +37 -0
  262. package/src/commands/low-family/mesh-ledger.ts +62 -0
  263. package/src/commands/low-family/mesh-node-logs.ts +87 -0
  264. package/src/commands/low-family/notification.ts +116 -0
  265. package/src/commands/low-family/refine-config.ts +106 -0
  266. package/src/commands/low-family/session-host.ts +282 -0
  267. package/src/commands/low-family/spec-providerdev.ts +217 -0
  268. package/src/commands/low-family/status-meta.ts +112 -0
  269. package/src/commands/low-family/types.ts +39 -0
  270. package/src/commands/med-family/cli-agent.ts +241 -0
  271. package/src/commands/med-family/fast-forward.ts +230 -0
  272. package/src/commands/med-family/ide.ts +163 -0
  273. package/src/commands/med-family/index.ts +37 -0
  274. package/src/commands/med-family/mesh-crud.ts +1287 -0
  275. package/src/commands/med-family/mesh-host-pairing.ts +234 -0
  276. package/src/commands/med-family/mesh-queue.ts +167 -0
  277. package/src/commands/med-family/mesh-restart.ts +92 -0
  278. package/src/commands/med-family/types.ts +153 -0
  279. package/src/commands/mesh-coordinator.ts +334 -124
  280. package/src/commands/router-aggregate-status.ts +209 -0
  281. package/src/commands/router-mesh-session-owner.ts +114 -0
  282. package/src/commands/router-refine.ts +1711 -0
  283. package/src/commands/router-worktree-cleanup.ts +870 -0
  284. package/src/commands/router.ts +795 -4143
  285. package/src/commands/stream-commands.ts +8 -0
  286. package/src/commands/upgrade-helper.ts +310 -45
  287. package/src/config/chat-history.ts +517 -24
  288. package/src/config/config.ts +30 -0
  289. package/src/config/mesh-config.ts +632 -23
  290. package/src/config/mesh-json-config.ts +376 -0
  291. package/src/config/recent-activity.ts +8 -2
  292. package/src/config/registry-resolver.ts +100 -0
  293. package/src/config/repo-settings.ts +111 -0
  294. package/src/config/state-store.ts +55 -0
  295. package/src/daemon/dev-auto-implement.ts +3 -2
  296. package/src/daemon/dev-cli-debug.ts +10 -1
  297. package/src/daemon/dev-server.ts +0 -541
  298. package/src/detection/cli-detector.ts +94 -9
  299. package/src/detection/ide-detector.ts +55 -16
  300. package/src/detection/win32-ide-version.ts +106 -0
  301. package/src/git/change-impact-config.ts +354 -0
  302. package/src/git/git-commands.ts +112 -16
  303. package/src/git/git-diff.ts +81 -11
  304. package/src/git/git-executor.ts +12 -0
  305. package/src/git/git-status.ts +767 -48
  306. package/src/git/git-types.ts +14 -62
  307. package/src/git/git-worktree.ts +261 -4
  308. package/src/git/index.ts +17 -0
  309. package/src/index.ts +213 -13
  310. package/src/installer.d.ts +1 -1
  311. package/src/installer.ts +8 -6
  312. package/src/ipc/local-ipc-server.ts +278 -0
  313. package/src/launch.d.ts +1 -1
  314. package/src/launch.ts +37 -28
  315. package/src/logging/async-batch-writer.ts +55 -0
  316. package/src/logging/command-log.ts +7 -5
  317. package/src/logging/debug-config.ts +25 -0
  318. package/src/logging/debug-trace.ts +7 -2
  319. package/src/logging/log-redactor.ts +100 -0
  320. package/src/logging/log-tail-reader.ts +341 -0
  321. package/src/logging/logger.ts +14 -7
  322. package/src/mesh/contracts.ts +469 -0
  323. package/src/mesh/coordinator-prompt.ts +572 -34
  324. package/src/mesh/coordinator-registry.ts +121 -0
  325. package/src/mesh/mesh-active-work.ts +706 -0
  326. package/src/mesh/mesh-clone-grace.ts +68 -0
  327. package/src/mesh/mesh-completion-synthesis.ts +416 -0
  328. package/src/mesh/mesh-coordinator-config.ts +97 -0
  329. package/src/mesh/mesh-delivery-policy.ts +284 -0
  330. package/src/mesh/mesh-event-classify.ts +78 -0
  331. package/src/mesh/mesh-event-forwarding.ts +2002 -0
  332. package/src/mesh/mesh-event-trace.ts +67 -0
  333. package/src/mesh/mesh-events-coordinator.ts +32 -0
  334. package/src/mesh/mesh-events-pending.ts +1494 -0
  335. package/src/mesh/mesh-events-stale.ts +452 -0
  336. package/src/mesh/mesh-events-utils.ts +443 -0
  337. package/src/mesh/mesh-events.ts +43 -1035
  338. package/src/mesh/mesh-fast-forward.ts +856 -0
  339. package/src/mesh/mesh-host-ownership.ts +111 -0
  340. package/src/mesh/mesh-init.ts +350 -0
  341. package/src/mesh/mesh-ledger-reconciliation.ts +12 -7
  342. package/src/mesh/mesh-ledger.ts +898 -102
  343. package/src/mesh/mesh-magi-status.ts +223 -0
  344. package/src/mesh/mesh-missions.ts +695 -0
  345. package/src/mesh/mesh-node-identity.ts +2065 -0
  346. package/src/mesh/mesh-queue-assignment.ts +2263 -0
  347. package/src/mesh/mesh-reconcile-acked-hold.ts +230 -0
  348. package/src/mesh/mesh-reconcile-config.ts +66 -0
  349. package/src/mesh/mesh-reconcile-identity.ts +103 -0
  350. package/src/mesh/mesh-reconcile-loop.ts +1473 -0
  351. package/src/mesh/mesh-reconcile-v2-backstop.ts +62 -0
  352. package/src/mesh/mesh-refine-batch.ts +205 -0
  353. package/src/mesh/mesh-refine-gates.ts +1686 -0
  354. package/src/mesh/mesh-refine-status.ts +231 -0
  355. package/src/mesh/mesh-remote-event-pull.ts +279 -0
  356. package/src/mesh/mesh-review-inbox.ts +308 -0
  357. package/src/mesh/mesh-routing.ts +291 -0
  358. package/src/mesh/mesh-runtime-store.ts +2150 -0
  359. package/src/mesh/mesh-scheduling-runtime.ts +198 -0
  360. package/src/mesh/mesh-task-inflight.ts +70 -0
  361. package/src/mesh/mesh-task-stats.ts +161 -0
  362. package/src/mesh/mesh-unresolved-forward-outbox.ts +185 -0
  363. package/src/mesh/mesh-warmup-deadline.ts +152 -0
  364. package/src/mesh/mesh-work-queue.ts +1437 -147
  365. package/src/mesh/preview-freshness.ts +163 -0
  366. package/src/mesh/refine-config.ts +423 -0
  367. package/src/mesh/worktree-bootstrap-config.ts +599 -0
  368. package/src/providers/acp-provider-instance.ts +43 -10
  369. package/src/providers/approval-utils.d.ts +5 -0
  370. package/src/providers/approval-utils.ts +100 -6
  371. package/src/providers/chat-message-normalization.ts +126 -4
  372. package/src/providers/cli-provider-effect-format.ts +53 -0
  373. package/src/providers/cli-provider-history-dedup.ts +75 -0
  374. package/src/providers/cli-provider-input-prompt.ts +133 -0
  375. package/src/providers/cli-provider-instance-types.ts +131 -0
  376. package/src/providers/cli-provider-instance.ts +2709 -499
  377. package/src/providers/cli-provider-status-helpers.ts +123 -0
  378. package/src/providers/cli-provider-transcript-merge.ts +114 -0
  379. package/src/providers/contracts.d.ts +55 -0
  380. package/src/providers/contracts.ts +160 -6
  381. package/src/providers/extension-provider-instance.ts +12 -7
  382. package/src/providers/external-sources.ts +218 -0
  383. package/src/providers/ide-provider-instance.ts +35 -12
  384. package/src/providers/manual-attendance.ts +105 -0
  385. package/src/providers/native-history/antigravity-claim-registry.ts +138 -0
  386. package/src/providers/native-history/antigravity-cli-transcript.ts +1275 -0
  387. package/src/providers/native-history/claude-cli-transcript.ts +396 -0
  388. package/src/providers/native-history/codex-cli-transcript.ts +479 -0
  389. package/src/providers/native-history/constants.ts +19 -0
  390. package/src/providers/native-history/dispatcher.ts +617 -0
  391. package/src/providers/native-history/hermes-cli-transcript.ts +312 -0
  392. package/src/providers/native-history/index.ts +30 -0
  393. package/src/providers/provider-instance-manager.ts +71 -0
  394. package/src/providers/provider-instance.ts +24 -1
  395. package/src/providers/provider-loader.ts +700 -55
  396. package/src/providers/provider-schema.ts +87 -14
  397. package/src/providers/provider-trust.ts +114 -0
  398. package/src/providers/read-chat-contract.ts +76 -16
  399. package/src/providers/sdk/README.md +49 -0
  400. package/src/providers/sdk/v1/builders/acp/detect-status.ts +144 -0
  401. package/src/providers/sdk/v1/builders/cli/detect-status.ts +313 -0
  402. package/src/providers/sdk/v1/builders/cli/parse-approval-squash.ts +158 -0
  403. package/src/providers/sdk/v1/builders/cli/parse-approval.ts +261 -0
  404. package/src/providers/sdk/v1/builders/cli/parse-session.ts +276 -0
  405. package/src/providers/sdk/v1/builders/cli/visible-region.ts +143 -0
  406. package/src/providers/sdk/v1/fixture-tooling/format.ts +130 -0
  407. package/src/providers/sdk/v1/fixture-tooling/index.ts +22 -0
  408. package/src/providers/sdk/v1/fixture-tooling/replay.ts +352 -0
  409. package/src/providers/sdk/v1/index.ts +152 -0
  410. package/src/providers/sdk/v1/sandbox/README-design.ts +195 -0
  411. package/src/providers/sdk/v1/sandbox/require-whitelist.ts +472 -0
  412. package/src/providers/sdk/v1/sandbox/script-runner.ts +150 -0
  413. package/src/providers/sdk/v1/schemas/cli/provider.schema.json +509 -0
  414. package/src/providers/sdk/v1/schemas/primitives/acp-session-protocol-v1.json +131 -0
  415. package/src/providers/sdk/v1/schemas/primitives/native-history-codex-rollout-v1.json +66 -0
  416. package/src/providers/sdk/v1/schemas/primitives/tui-approval-squash-v1.json +91 -0
  417. package/src/providers/sdk/v1/schemas/primitives/tui-assistant-block-v1.json +91 -0
  418. package/src/providers/sdk/v1/schemas/primitives/tui-cue-ordering-v1.json +47 -0
  419. package/src/providers/sdk/v1/schemas/primitives/tui-dispatch-order-v1.json +32 -0
  420. package/src/providers/sdk/v1/schemas/primitives/tui-footer-chrome-v1.json +42 -0
  421. package/src/providers/sdk/v1/schemas/primitives/tui-index-finder-v1.json +27 -0
  422. package/src/providers/sdk/v1/schemas/primitives/tui-modal-v1.json +125 -0
  423. package/src/providers/sdk/v1/schemas/primitives/tui-prompt-marker-v1.json +45 -0
  424. package/src/providers/sdk/v1/schemas/primitives/tui-session-id-extraction-v1.json +46 -0
  425. package/src/providers/sdk/v1/schemas/primitives/tui-settled-prompt-v1.json +71 -0
  426. package/src/providers/sdk/v1/schemas/primitives/tui-spinner-v1.json +83 -0
  427. package/src/providers/sdk/v1/schemas/primitives/tui-transcript-pty-v1.json +83 -0
  428. package/src/providers/sdk/v1/schemas/primitives/tui-visible-region-v1.json +57 -0
  429. package/src/providers/sdk/v1/schemas/primitives/tui-welcome-screen-v1.json +35 -0
  430. package/src/providers/sdk/v1/types/cli/index.ts +365 -0
  431. package/src/providers/sdk/v1/types/common/index.ts +229 -0
  432. package/src/providers/sdk/v1/validators/index.ts +19 -0
  433. package/src/providers/sdk/v1/validators/manifest.ts +110 -0
  434. package/src/providers/sdk/v1/validators/taint.ts +309 -0
  435. package/src/providers/spec/adapter.ts +246 -0
  436. package/src/providers/spec/cli-adapter.ts +1064 -0
  437. package/src/providers/spec/evaluator.ts +407 -0
  438. package/src/providers/spec/fsm-driver.ts +1528 -0
  439. package/src/providers/spec/fsm-evaluator.ts +290 -0
  440. package/src/providers/spec/fsm-loader.ts +128 -0
  441. package/src/providers/spec/fsm-types.ts +301 -0
  442. package/src/providers/spec/native-history-executor.ts +1241 -0
  443. package/src/providers/spec/pre-launch-trust.ts +104 -0
  444. package/src/providers/spec/route.ts +51 -0
  445. package/src/providers/spec/types.ts +284 -0
  446. package/src/providers/status-monitor.d.ts +7 -7
  447. package/src/providers/status-monitor.ts +37 -22
  448. package/src/providers/transcript-v2.ts +567 -0
  449. package/src/providers/types/interactive-prompt.ts +536 -0
  450. package/src/providers/version-archive.ts +64 -24
  451. package/src/providers/working-dir.ts +23 -0
  452. package/src/repo-mesh-types.ts +868 -13
  453. package/src/runtime-defaults.ts +39 -0
  454. package/src/session-host/managed-host.ts +218 -0
  455. package/src/session-host/runtime-surface.ts +20 -80
  456. package/src/sessions/registry.ts +44 -0
  457. package/src/shared-types-extra.ts +2 -4
  458. package/src/shared-types.d.ts +8 -0
  459. package/src/shared-types.ts +136 -55
  460. package/src/status/builders.ts +26 -6
  461. package/src/status/chat-tail-hot-sessions.ts +117 -2
  462. package/src/status/normalize.ts +2 -0
  463. package/src/status/reporter.ts +19 -1
  464. package/src/status/snapshot.ts +95 -26
  465. package/src/system/hash.ts +23 -0
  466. package/src/system/host-memory.ts +29 -12
  467. package/src/system/load-better-sqlite3.ts +68 -0
  468. package/src/types.ts +5 -0
  469. package/dist/cli-adapters/terminal-backends/xterm-backend.d.ts +0 -17
  470. package/dist/mesh/mesh-sync.d.ts +0 -53
  471. package/src/cli-adapters/terminal-backends/ghostty-vt-backend.d.ts +0 -16
  472. package/src/cli-adapters/terminal-backends/xterm-backend.ts +0 -97
  473. package/src/mesh/mesh-sync.ts +0 -111
@@ -0,0 +1,1275 @@
1
+ /**
2
+ * antigravity-cli-transcript — Daemon-side built-in native history adapter for antigravity-cli.
3
+ *
4
+ * Antigravity CLI stores conversations in multiple complementary locations:
5
+ *
6
+ * 1. ~/.gemini/antigravity-cli/history.jsonl
7
+ * Lightweight index of user prompts, one row per turn:
8
+ * { conversationId: string, display: string, workspace: string, timestamp: number }
9
+ *
10
+ * 2. ~/.gemini/antigravity-cli/brain/<uuid>/.system_generated/logs/transcript*.jsonl
11
+ * Per-session full transcript, one row per agent step:
12
+ * { source: 'USER_EXPLICIT'|'MODEL', type: string, content: string, status: 'DONE'|..., created_at: number }
13
+ *
14
+ * 3. ~/.gemini/antigravity-cli/conversations/<uuid>.pb
15
+ * Legacy protobuf binary — schema not publicly documented. Adapter extracts
16
+ * printable UTF-8 text runs as best-effort content (no proto library needed).
17
+ *
18
+ * 4. ~/.gemini/antigravity-cli/conversations/<uuid>.db ← current format
19
+ * Per-session SQLite database. Recent antigravity migrated conversation
20
+ * storage from .pb (+ brain/*.jsonl) to a per-session SQLite db. The
21
+ * schema is a trajectory of `steps`, NOT a simple messages(role,content)
22
+ * table:
23
+ * steps(idx INTEGER PK, step_type INTEGER, status INTEGER,
24
+ * step_payload BLOB [protobuf], ...)
25
+ * Each `step_payload` is a protobuf message. Empirically (introspected
26
+ * from real stores):
27
+ * - step_type 14 → a USER turn. The prompt text is the largest
28
+ * contiguous UTF-8 run inside the payload (field 19 subtree).
29
+ * - step_type 15 → a MODEL/assistant turn. The assistant's final
30
+ * natural-language answer lives at payload field 20 → field 1
31
+ * (identical to field 8). Field 20 → field 3 is the internal
32
+ * reasoning summary and is intentionally NOT surfaced.
33
+ * - other step types are tool calls / ephemeral system context.
34
+ * We read the blobs with a tiny dependency-free protobuf field walker
35
+ * (no proto schema / codegen needed) and map the two message step types.
36
+ * Because the daemon does NOT read this db, native history previously
37
+ * returned 0 rows for these sessions and read_chat fell back to the
38
+ * pty parser (which only echoes the user's own input) — assistant
39
+ * answers appeared lost even though they were on disk.
40
+ *
41
+ * Schema-drift resilience: the exact field path (20 → 1/8 for the answer,
42
+ * 19 → 2/3 for the prompt) is empirically verified against real stores, but
43
+ * antigravity may move it in a future build. So when the known path yields
44
+ * no text, instead of silently dropping the turn we fall back to a UTF-8
45
+ * printable-run scan of the payload (recoverMessageText) that recovers the
46
+ * answer/prompt even if the field number drifted — while explicitly
47
+ * EXCLUDING the reasoning subtree (20 → 3) so internal reasoning is never
48
+ * surfaced as the answer. Only when even that finds nothing beyond
49
+ * reasoning/metadata is the step dropped, and a content-free DEBUG
50
+ * breadcrumb is logged so a real drift is greppable rather than invisible.
51
+ *
52
+ * This adapter provides:
53
+ * - Full coverage from a per-session .db (current format) — preferred.
54
+ * - Full coverage when a brain transcript exists (legacy authoritative source).
55
+ * - Partial coverage (user prompts only) from history.jsonl as fallback.
56
+ * - Best-effort raw-string extraction from .pb files when no other source exists.
57
+ *
58
+ * watchPath (from provider.v1.json):
59
+ * ~/.gemini/antigravity-cli/history.jsonl
60
+ * ~/.gemini/antigravity-cli/brain/{uuid}/.system_generated/logs/transcript*.jsonl
61
+ * ~/.gemini/antigravity-cli/conversations/{uuid}.pb
62
+ * ~/.gemini/antigravity-cli/conversations/{uuid}.db
63
+ *
64
+ * OSS code (AGPL-3.0). Must not import from packages/ (proprietary).
65
+ */
66
+
67
+ import * as fs from 'fs';
68
+ import * as path from 'path';
69
+ import * as os from 'os';
70
+ import { loadBetterSqlite3 } from '../../system/load-better-sqlite3.js';
71
+ import { LOG } from '../../logging/logger.js';
72
+
73
+ // ─── Types ─────────────────────────────────────────────────────────────────
74
+
75
+ export type NativeHistoryRole = 'user' | 'assistant' | 'system';
76
+ export type NativeHistoryKind = 'standard' | 'tool' | 'session_start';
77
+
78
+ export interface NativeHistoryMessage {
79
+ ts: string;
80
+ receivedAt: number;
81
+ role: NativeHistoryRole;
82
+ content: string;
83
+ kind: NativeHistoryKind;
84
+ senderName?: string;
85
+ agent: 'antigravity-cli';
86
+ historySessionId: string;
87
+ workspace?: string;
88
+ /** Stable per-message identity (v2 contract). */
89
+ providerUnitKey?: string;
90
+ }
91
+
92
+ export interface NativeHistorySession {
93
+ messages: NativeHistoryMessage[];
94
+ providerSessionId: string;
95
+ source: 'provider-native';
96
+ sourcePath: string;
97
+ sourceMtimeMs: number;
98
+ /**
99
+ * 'full' — brain transcript was available and parsed.
100
+ * 'partial' — only history.jsonl user-prompts are available.
101
+ * 'best-effort' — .pb file bytes were used for raw text extraction.
102
+ */
103
+ nativeHistoryCoverage: 'full' | 'partial' | 'best-effort';
104
+ workspace?: string;
105
+ partialReason?: string;
106
+ }
107
+
108
+ export interface NativeHistorySessionMeta {
109
+ historySessionId: string;
110
+ sessionId: string;
111
+ sourcePath: string;
112
+ sourceMtimeMs: number;
113
+ messageCount: number;
114
+ firstMessageAt: number;
115
+ lastMessageAt: number;
116
+ sessionTitle?: string;
117
+ preview?: string;
118
+ workspace?: string;
119
+ agent: 'antigravity-cli';
120
+ source: 'provider-native';
121
+ nativeHistoryCoverage: 'full' | 'partial' | 'best-effort';
122
+ partialReason?: string;
123
+ }
124
+
125
+ // ─── Internal helpers ───────────────────────────────────────────────────────
126
+
127
+ function extractTimestampValue(value: unknown): number {
128
+ if (typeof value === 'number' && Number.isFinite(value) && value > 0) return value;
129
+ if (typeof value === 'string') {
130
+ const numeric = Number(value);
131
+ if (Number.isFinite(numeric) && numeric > 0) return numeric;
132
+ const parsed = Date.parse(value);
133
+ if (Number.isFinite(parsed) && parsed > 0) return parsed;
134
+ }
135
+ return 0;
136
+ }
137
+
138
+ function statMtimeMs(filePath: string): number {
139
+ try { return fs.statSync(filePath).mtimeMs; } catch { return 0; }
140
+ }
141
+
142
+ function isUuidLike(value: string): boolean {
143
+ return /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(value);
144
+ }
145
+
146
+ function antigravityRoot(): string {
147
+ return path.join(os.homedir(), '.gemini', 'antigravity-cli');
148
+ }
149
+
150
+ function historyJsonlPath(): string {
151
+ return path.join(antigravityRoot(), 'history.jsonl');
152
+ }
153
+
154
+ function brainRoot(): string {
155
+ return path.join(antigravityRoot(), 'brain');
156
+ }
157
+
158
+ function conversationsRoot(): string {
159
+ return path.join(antigravityRoot(), 'conversations');
160
+ }
161
+
162
+ function resolvePathInside(root: string, ...segments: string[]): string | null {
163
+ const rootPath = path.resolve(root);
164
+ const targetPath = path.resolve(rootPath, ...segments);
165
+ if (targetPath !== rootPath && !targetPath.startsWith(rootPath + path.sep)) return null;
166
+ return targetPath;
167
+ }
168
+
169
+ /**
170
+ * Find the brain transcript file for a given session UUID.
171
+ * Prefers transcript_full.jsonl over transcript.jsonl when both exist.
172
+ */
173
+ function findBrainTranscriptPath(sessionId: string): string | null {
174
+ if (!isUuidLike(sessionId)) return null;
175
+ const logsRoot = resolvePathInside(brainRoot(), sessionId, '.system_generated', 'logs');
176
+ if (!logsRoot || !fs.existsSync(logsRoot)) return null;
177
+
178
+ const candidates = (['transcript_full.jsonl', 'transcript.jsonl'] as const)
179
+ .map((file) => resolvePathInside(logsRoot, file))
180
+ .filter((p): p is string => p !== null && fs.existsSync(p));
181
+
182
+ if (candidates.length === 0) {
183
+ // Look for any transcript*.jsonl
184
+ let entries: fs.Dirent[] = [];
185
+ try { entries = fs.readdirSync(logsRoot, { withFileTypes: true }); } catch { return null; }
186
+ const transcriptFiles = entries
187
+ .filter((e) => e.isFile() && /^transcript.*\.jsonl$/.test(e.name))
188
+ .map((e) => path.join(logsRoot, e.name));
189
+ if (transcriptFiles.length === 0) return null;
190
+ transcriptFiles.sort((a, b) => statMtimeMs(b) - statMtimeMs(a));
191
+ return transcriptFiles[0];
192
+ }
193
+
194
+ candidates.sort((a, b) => statMtimeMs(b) - statMtimeMs(a));
195
+ return candidates[0];
196
+ }
197
+
198
+ /**
199
+ * List all session UUIDs that have a brain transcript directory.
200
+ */
201
+ function listBrainSessions(): Array<{ sessionId: string; transcriptPath: string; sourceMtimeMs: number }> {
202
+ const root = brainRoot();
203
+ let entries: fs.Dirent[] = [];
204
+ try { entries = fs.readdirSync(root, { withFileTypes: true }); } catch { return []; }
205
+
206
+ const results: Array<{ sessionId: string; transcriptPath: string; sourceMtimeMs: number }> = [];
207
+ for (const entry of entries) {
208
+ if (!entry.isDirectory() || !isUuidLike(entry.name)) continue;
209
+ const transcriptPath = findBrainTranscriptPath(entry.name);
210
+ if (!transcriptPath) continue;
211
+ results.push({
212
+ sessionId: entry.name,
213
+ transcriptPath,
214
+ sourceMtimeMs: statMtimeMs(transcriptPath),
215
+ });
216
+ }
217
+ return results;
218
+ }
219
+
220
+ /**
221
+ * Strip USER_REQUEST XML wrapper from antigravity user prompts.
222
+ * Antigravity wraps structured user input in <USER_REQUEST>...</USER_REQUEST>.
223
+ */
224
+ function extractUserRequestContent(content: string): string {
225
+ const raw = content.trim();
226
+ const match = raw.match(/<USER_REQUEST>\s*([\s\S]*?)\s*<\/USER_REQUEST>/i);
227
+ if (match) return match[1].trim();
228
+ return raw
229
+ .replace(/<ADDITIONAL_METADATA>[\s\S]*?<\/ADDITIONAL_METADATA>/gi, '')
230
+ .replace(/<USER_SETTINGS_CHANGE>[\s\S]*?<\/USER_SETTINGS_CHANGE>/gi, '')
231
+ .replace(/<\/?USER_REQUEST>/gi, '')
232
+ .trim();
233
+ }
234
+
235
+ /**
236
+ * Determine the NativeHistoryKind from an antigravity transcript row type.
237
+ */
238
+ function antigravityRowKind(rowType: string): NativeHistoryKind {
239
+ if (rowType === 'PLANNER_RESPONSE') return 'standard';
240
+ if (rowType && rowType !== 'USER_INPUT') return 'tool';
241
+ return 'standard';
242
+ }
243
+
244
+ // ─── Brain transcript parser ─────────────────────────────────────────────────
245
+
246
+ interface TranscriptRow {
247
+ source?: string;
248
+ type?: string;
249
+ content?: string;
250
+ status?: string;
251
+ created_at?: unknown;
252
+ }
253
+
254
+ /**
255
+ * Parse a brain transcript JSONL file into NativeHistoryMessages.
256
+ * Only rows with status === 'DONE' are processed.
257
+ * USER_EXPLICIT / USER_INPUT rows → user role.
258
+ * MODEL rows → assistant role.
259
+ */
260
+ function parseBrainTranscript(
261
+ filePath: string,
262
+ sessionId: string,
263
+ workspace?: string,
264
+ ): NativeHistoryMessage[] | null {
265
+ let raw: string;
266
+ try { raw = fs.readFileSync(filePath, 'utf-8'); } catch { return null; }
267
+
268
+ const lines = raw.split('\n').filter(Boolean);
269
+ if (lines.length === 0) return null;
270
+
271
+ const records: NativeHistoryMessage[] = [];
272
+ const normalizedWorkspace = typeof workspace === 'string' ? workspace.trim() : '';
273
+ let fallbackTs = statMtimeMs(filePath) || Date.now();
274
+
275
+ for (const line of lines) {
276
+ let row: TranscriptRow | null = null;
277
+ try { row = JSON.parse(line) as TranscriptRow; } catch { continue; }
278
+ if (!row || typeof row !== 'object') continue;
279
+ if (row.status !== 'DONE') continue;
280
+
281
+ const rowSource = String(row.source ?? '').trim();
282
+ const rowType = String(row.type ?? '').trim();
283
+ const rawContent = typeof row.content === 'string' ? row.content : '';
284
+ const receivedAt = extractTimestampValue(row.created_at) || fallbackTs + records.length;
285
+
286
+ if (rowSource === 'USER_EXPLICIT' && rowType === 'USER_INPUT') {
287
+ const content = extractUserRequestContent(rawContent);
288
+ if (!content) continue;
289
+ const msg: NativeHistoryMessage = {
290
+ ts: new Date(receivedAt).toISOString(),
291
+ receivedAt,
292
+ role: 'user',
293
+ content,
294
+ kind: 'standard',
295
+ agent: 'antigravity-cli',
296
+ historySessionId: sessionId,
297
+ };
298
+ if (normalizedWorkspace) msg.workspace = normalizedWorkspace;
299
+ records.push(msg);
300
+ } else if (rowSource === 'MODEL') {
301
+ const content = rawContent.trim();
302
+ if (!content) continue;
303
+ const kind = antigravityRowKind(rowType);
304
+ const msg: NativeHistoryMessage = {
305
+ ts: new Date(receivedAt).toISOString(),
306
+ receivedAt,
307
+ role: 'assistant',
308
+ content,
309
+ kind,
310
+ agent: 'antigravity-cli',
311
+ historySessionId: sessionId,
312
+ };
313
+ if (kind === 'tool') msg.senderName = 'Tool';
314
+ if (normalizedWorkspace) msg.workspace = normalizedWorkspace;
315
+ records.push(msg);
316
+ }
317
+ }
318
+
319
+ return records.length > 0 ? records : null;
320
+ }
321
+
322
+ // ─── History JSONL parser ────────────────────────────────────────────────────
323
+
324
+ interface HistoryRow {
325
+ conversationId: string;
326
+ display: string;
327
+ workspace: string;
328
+ receivedAt: number;
329
+ }
330
+
331
+ function readHistoryRows(): HistoryRow[] {
332
+ const sourcePath = historyJsonlPath();
333
+ let lines: string[] = [];
334
+ try { lines = fs.readFileSync(sourcePath, 'utf-8').split('\n').filter(Boolean); } catch { return []; }
335
+
336
+ const rows: HistoryRow[] = [];
337
+ for (const line of lines) {
338
+ let parsed: unknown = null;
339
+ try { parsed = JSON.parse(line); } catch { continue; }
340
+ if (!parsed || typeof parsed !== 'object') continue;
341
+
342
+ const row = parsed as Record<string, unknown>;
343
+ const conversationId = String(row.conversationId ?? '').trim();
344
+ const display = typeof row.display === 'string' ? row.display.trim() : '';
345
+ const workspace = typeof row.workspace === 'string' ? row.workspace.trim() : '';
346
+ const receivedAt = extractTimestampValue(row.timestamp);
347
+
348
+ if (!conversationId || !isUuidLike(conversationId) || !display || !receivedAt) continue;
349
+ rows.push({ conversationId, display, workspace, receivedAt });
350
+ }
351
+ return rows;
352
+ }
353
+
354
+ // ─── Protobuf best-effort text extraction ────────────────────────────────────
355
+
356
+ const MIN_PRINTABLE_RUN = 8;
357
+
358
+ /**
359
+ * Extract printable UTF-8 text runs from raw binary data (similar to the `strings` command).
360
+ * Runs of printable ASCII/UTF-8 characters of length >= MIN_PRINTABLE_RUN are collected.
361
+ * This is used as a best-effort fallback when the .pb schema is not available.
362
+ */
363
+ function extractStringsFromBuffer(buf: Buffer): string[] {
364
+ const strings: string[] = [];
365
+ let current: number[] = [];
366
+
367
+ for (let i = 0; i < buf.length; i++) {
368
+ const byte = buf[i];
369
+ // Accept printable ASCII (32-126) and common whitespace (9=tab, 10=LF, 13=CR)
370
+ if ((byte >= 32 && byte <= 126) || byte === 9 || byte === 10 || byte === 13) {
371
+ current.push(byte);
372
+ } else {
373
+ if (current.length >= MIN_PRINTABLE_RUN) {
374
+ const str = Buffer.from(current).toString('utf-8').trim();
375
+ if (str) strings.push(str);
376
+ }
377
+ current = [];
378
+ }
379
+ }
380
+ // Flush remaining
381
+ if (current.length >= MIN_PRINTABLE_RUN) {
382
+ const str = Buffer.from(current).toString('utf-8').trim();
383
+ if (str) strings.push(str);
384
+ }
385
+
386
+ return strings;
387
+ }
388
+
389
+ /**
390
+ * Read a .pb conversation file and extract best-effort text content.
391
+ * Returns a single NativeHistoryMessage with the extracted content,
392
+ * or null if nothing readable was found.
393
+ */
394
+ function parsePbFile(
395
+ filePath: string,
396
+ sessionId: string,
397
+ ): NativeHistoryMessage[] | null {
398
+ let buf: Buffer;
399
+ try { buf = fs.readFileSync(filePath); } catch { return null; }
400
+ if (buf.length === 0) return null;
401
+
402
+ const strings = extractStringsFromBuffer(buf);
403
+ // Filter out very short or likely-binary strings
404
+ const meaningful = strings.filter((s) => s.length >= MIN_PRINTABLE_RUN && /\w/.test(s));
405
+ if (meaningful.length === 0) return null;
406
+
407
+ const content = meaningful.join('\n');
408
+ const sourceMtimeMs = statMtimeMs(filePath);
409
+
410
+ return [
411
+ {
412
+ ts: new Date(sourceMtimeMs).toISOString(),
413
+ receivedAt: sourceMtimeMs,
414
+ role: 'assistant',
415
+ content,
416
+ kind: 'standard',
417
+ agent: 'antigravity-cli',
418
+ historySessionId: sessionId,
419
+ },
420
+ ];
421
+ }
422
+
423
+ // ─── SQLite (.db) conversation reader ────────────────────────────────────────
424
+ //
425
+ // Recent antigravity stores each conversation in a per-session SQLite db at
426
+ // conversations/<uuid>.db. See the file header for the schema. We decode the
427
+ // protobuf `step_payload` blobs with a minimal, dependency-free field walker —
428
+ // we only need two leaf strings (user prompt / assistant answer), so a full
429
+ // proto schema is unnecessary.
430
+
431
+ /** Antigravity step_type values that map to a chat message. */
432
+ const AGY_STEP_TYPE_USER = 14;
433
+ const AGY_STEP_TYPE_MODEL = 15;
434
+
435
+ interface ProtoField {
436
+ field: number;
437
+ wireType: number;
438
+ /** For wireType 2 (length-delimited): the raw bytes. */
439
+ bytes?: Buffer;
440
+ /** For wireType 0 (varint): the value. */
441
+ varint?: number;
442
+ }
443
+
444
+ /**
445
+ * Read a base-128 varint starting at `offset`. Returns [value, nextOffset].
446
+ * Values are read as JS numbers (safe: the fields we consume are small).
447
+ */
448
+ function readVarint(buf: Buffer, offset: number): [number, number] {
449
+ let result = 0;
450
+ let shift = 0;
451
+ let i = offset;
452
+ while (i < buf.length) {
453
+ const byte = buf[i];
454
+ i += 1;
455
+ result += (byte & 0x7f) * Math.pow(2, shift);
456
+ if ((byte & 0x80) === 0) return [result, i];
457
+ shift += 7;
458
+ if (shift > 63) break; // malformed / oversized
459
+ }
460
+ return [result, i];
461
+ }
462
+
463
+ /**
464
+ * Decode the top-level fields of a protobuf message. Best-effort: stops on the
465
+ * first malformed byte rather than throwing, so a partially-corrupt blob still
466
+ * yields the fields decoded so far.
467
+ */
468
+ function decodeProtoFields(buf: Buffer): ProtoField[] {
469
+ const fields: ProtoField[] = [];
470
+ let i = 0;
471
+ while (i < buf.length) {
472
+ const [key, afterKey] = readVarint(buf, i);
473
+ if (afterKey === i) break;
474
+ i = afterKey;
475
+ const field = Math.floor(key / 8);
476
+ const wireType = key & 7;
477
+ if (field <= 0) break;
478
+ if (wireType === 0) {
479
+ const [value, next] = readVarint(buf, i);
480
+ if (next === i) break;
481
+ i = next;
482
+ fields.push({ field, wireType, varint: value });
483
+ } else if (wireType === 2) {
484
+ const [len, afterLen] = readVarint(buf, i);
485
+ i = afterLen;
486
+ if (len < 0 || i + len > buf.length) break;
487
+ fields.push({ field, wireType, bytes: buf.subarray(i, i + len) });
488
+ i += len;
489
+ } else if (wireType === 5) {
490
+ i += 4;
491
+ } else if (wireType === 1) {
492
+ i += 8;
493
+ } else {
494
+ break; // wireType 3/4 (groups) — unused by antigravity payloads
495
+ }
496
+ }
497
+ return fields;
498
+ }
499
+
500
+ /** Return the bytes of the first length-delimited field with number `field`. */
501
+ function firstLenField(buf: Buffer, field: number): Buffer | null {
502
+ for (const f of decodeProtoFields(buf)) {
503
+ if (f.field === field && f.wireType === 2 && f.bytes) return f.bytes;
504
+ }
505
+ return null;
506
+ }
507
+
508
+ /** Heuristic: is this buffer (mostly) printable UTF-8 text? */
509
+ function looksLikeText(buf: Buffer): boolean {
510
+ if (buf.length === 0) return false;
511
+ let printable = 0;
512
+ for (let i = 0; i < buf.length; i++) {
513
+ const b = buf[i];
514
+ // ASCII printable + common whitespace, or any high byte (UTF-8 lead/cont).
515
+ if ((b >= 32 && b <= 126) || b === 9 || b === 10 || b === 13 || b >= 0x80) printable += 1;
516
+ }
517
+ return printable / buf.length >= 0.9;
518
+ }
519
+
520
+ /**
521
+ * Antigravity prefixes some assistant answers with a literal `MARKER_V1`
522
+ * sentinel followed by a blank line. Strip it so the user-visible bubble starts
523
+ * at the real text.
524
+ */
525
+ function stripAnswerMarker(text: string): string {
526
+ return text.replace(/^\s*MARKER_V1\s*/, '');
527
+ }
528
+
529
+ /**
530
+ * Extract the assistant's final natural-language answer from a step_type 15
531
+ * payload: field 20 → field 1 (identical to field 8). Field 20 → field 3 is the
532
+ * private reasoning summary and is deliberately skipped. Returns '' if absent
533
+ * (e.g. a pure reasoning / tool-only model step, which carries no user-visible
534
+ * answer text).
535
+ */
536
+ function extractModelAnswer(payload: Buffer): string {
537
+ const inner = firstLenField(payload, 20);
538
+ if (!inner) return '';
539
+ const answer = firstLenField(inner, 1) ?? firstLenField(inner, 8);
540
+ if (!answer || !looksLikeText(answer)) return '';
541
+ return stripAnswerMarker(answer.toString('utf-8')).trim();
542
+ }
543
+
544
+ /**
545
+ * Extract the user prompt from a step_type 14 payload: field 19 → field 2 (the
546
+ * clean prompt text; field 19 → field 3 wraps the same string with a leading
547
+ * newline and is used only as a fallback). The USER_REQUEST XML wrapper, when
548
+ * present, is unwrapped to match the brain-transcript reader's output.
549
+ */
550
+ function extractUserPrompt(payload: Buffer): string {
551
+ const inner = firstLenField(payload, 19);
552
+ if (!inner) return '';
553
+ const raw = firstLenField(inner, 2) ?? firstLenField(inner, 3);
554
+ if (!raw || !looksLikeText(raw)) return '';
555
+ const text = raw.toString('utf-8').trim();
556
+ if (!text) return '';
557
+ return extractUserRequestContent(text);
558
+ }
559
+
560
+ /**
561
+ * The model step's private reasoning summary lives at field 20 → field 3. We
562
+ * surface it nowhere, but we DO need it: the schema-drift recovery below scans
563
+ * the raw payload for a plausible answer run, and the reasoning is itself a long
564
+ * natural-language run — so we extract it here purely to EXCLUDE it and avoid
565
+ * accidentally surfacing internal reasoning as the assistant answer.
566
+ */
567
+ function extractModelReasoning(payload: Buffer): string {
568
+ const inner = firstLenField(payload, 20);
569
+ if (!inner) return '';
570
+ const reasoning = firstLenField(inner, 3);
571
+ if (!reasoning || !looksLikeText(reasoning)) return '';
572
+ return reasoning.toString('utf-8').trim();
573
+ }
574
+
575
+ /** Top-level protobuf field numbers present in a payload (for drift breadcrumbs). */
576
+ function topLevelFieldNumbers(payload: Buffer): number[] {
577
+ return decodeProtoFields(payload).map((f) => f.field);
578
+ }
579
+
580
+ const MIN_RECOVERED_MESSAGE_CHARS = 12;
581
+
582
+ /**
583
+ * Split a payload into UTF-8 text runs, schema-agnostically. Unlike
584
+ * extractStringsFromBuffer (ASCII-only, used for legacy .pb), this is UTF-8 aware
585
+ * so CJK / accented answers survive intact: we decode the whole blob as UTF-8
586
+ * (invalid byte sequences collapse to U+FFFD) and split on runs of C0/C1 control
587
+ * chars + the replacement char. Protobuf framing bytes (field tags, varint length
588
+ * prefixes) are almost always control or invalid-UTF-8, so each natural-language
589
+ * string field emerges as its own run while binary framing is discarded.
590
+ */
591
+ function extractUtf8TextRuns(buf: Buffer): string[] {
592
+ if (buf.length === 0) return [];
593
+ const decoded = buf.toString('utf-8');
594
+ // Keep tab/newline/CR (0x09/0x0A/0x0D) inside runs — answers contain newlines.
595
+ // Everything else in C0 (incl. 0x1A, the field-3 tag that separates reasoning
596
+ // from the answer), DEL, and the replacement char are run separators.
597
+ const parts = decoded.split(/[\u0000-\u0008\u000B\u000C\u000E-\u001F\u007F\uFFFD]+/);
598
+ const runs: string[] = [];
599
+ for (const part of parts) {
600
+ const trimmed = part.trim();
601
+ if (trimmed.length >= MIN_PRINTABLE_RUN) runs.push(trimmed);
602
+ }
603
+ return runs;
604
+ }
605
+
606
+ /**
607
+ * Is this run plausibly a user-visible prose message, as opposed to the other
608
+ * text the payload also carries — internal reasoning (excluded separately),
609
+ * tool-call JSON arguments, code blobs, file paths, and uuid/session-id
610
+ * metadata? These filters were tuned against real antigravity stores so that a
611
+ * blind printable-run scan recovers a genuinely drifted answer while surfacing
612
+ * NONE of the tool-call / metadata runs that legitimately answer-less steps
613
+ * carry (verified: zero false recoveries across real conversation dbs).
614
+ */
615
+ function isPlausibleMessageText(s: string): boolean {
616
+ if (s.length < MIN_RECOVERED_MESSAGE_CHARS) return false;
617
+ if (!/[A-Za-zÀ-￿]/.test(s)) return false; // must contain letters (incl. CJK)
618
+ if (/^(file:\/\/|[A-Za-z]:[\\/]|\/[A-Za-z0-9._-]+\/)/.test(s)) return false; // path/URI
619
+ // Prose is multi-word: real answers have several spaces; uuids / ids / tokens
620
+ // have none. This is the single strongest prose-vs-metadata discriminator.
621
+ if ((s.match(/ /g) ?? []).length < 2) return false;
622
+ // Reject structured tool-call args / JSON / code blobs. A model tool step
623
+ // carries its arguments as JSON (e.g. {"Query":...}, {"CommandLine":...});
624
+ // those must never be surfaced as an assistant answer.
625
+ if (/[[{]\s*"/.test(s)) return false;
626
+ const structural = (s.match(/[{}[\]":\\]/g) ?? []).length;
627
+ if (structural / s.length > 0.12) return false;
628
+ return true;
629
+ }
630
+
631
+ /**
632
+ * Schema-agnostic recovery of a message's text when the known field path yields
633
+ * nothing (a possible antigravity step_payload schema drift). Scans the payload
634
+ * for UTF-8 text runs and returns the longest plausible message run, EXCLUDING
635
+ * any run that matches one of `excludeTexts` (e.g. the reasoning subtree) so we
636
+ * never surface internal reasoning as the answer. Returns '' when nothing beyond
637
+ * reasoning/metadata is present — i.e. a legitimately answer-less step.
638
+ */
639
+ function recoverMessageText(payload: Buffer, excludeTexts: string[]): string {
640
+ const exclusions = excludeTexts.map((t) => t.trim()).filter(Boolean);
641
+ let best = '';
642
+ for (const run of extractUtf8TextRuns(payload)) {
643
+ const candidate = stripAnswerMarker(run).trim();
644
+ if (!isPlausibleMessageText(candidate)) continue;
645
+ // Drop runs that are (or are contained in / contain) an excluded subtree.
646
+ if (exclusions.some((e) => e === candidate || e.includes(candidate) || candidate.includes(e))) {
647
+ continue;
648
+ }
649
+ if (candidate.length > best.length) best = candidate;
650
+ }
651
+ return best;
652
+ }
653
+
654
+ interface AgyDbStepRow {
655
+ idx: number;
656
+ step_type: number;
657
+ step_payload: Buffer | null;
658
+ }
659
+
660
+ /**
661
+ * Parse a per-session conversations/<uuid>.db (SQLite) into NativeHistoryMessages.
662
+ * Returns null when the db is unreadable, empty, or yields no chat messages.
663
+ */
664
+ /**
665
+ * True when an error thrown by better-sqlite3 open/read is a transient
666
+ * SQLITE_BUSY / "database is locked" condition rather than a permanent one.
667
+ *
668
+ * On win32 antigravity holds a mandatory WAL write/checkpoint lock while it
669
+ * persists a step; a readonly open racing that lock throws SQLITE_BUSY. That
670
+ * is transient — the answer IS already on disk — so it must be retried, NOT
671
+ * collapsed to "no session" (which erases the just-written assistant answer on
672
+ * a chat_history re-query). macOS advisory locking + WAL reader-doesn't-block-
673
+ * writer masks this, which is why it is win32-specific.
674
+ */
675
+ function isSqliteBusyError(err: unknown): boolean {
676
+ if (!err) return false;
677
+ const code = (err as any).code;
678
+ if (typeof code === 'string' && code.includes('SQLITE_BUSY')) return true;
679
+ const msg = err instanceof Error ? err.message : String(err);
680
+ return /SQLITE_BUSY|database is locked|database table is locked/i.test(msg);
681
+ }
682
+
683
+ const AGY_DB_BUSY_TIMEOUT_MS = 3000;
684
+ const AGY_DB_MAX_ATTEMPTS = 4;
685
+ const AGY_DB_RETRY_BACKOFF_MS = [50, 100, 150];
686
+
687
+ function sleepBusy(ms: number): void {
688
+ // Synchronous busy-wait: parseConversationDb is a sync function called from a
689
+ // sync read path, and better-sqlite3 itself is synchronous. The waits are
690
+ // tiny (≤150ms) and only occur under genuine lock contention, so a short
691
+ // spin-sleep is acceptable and keeps the call site synchronous.
692
+ const end = Date.now() + ms;
693
+ while (Date.now() < end) { /* spin */ }
694
+ }
695
+
696
+ function parseConversationDb(
697
+ filePath: string,
698
+ sessionId: string,
699
+ workspace?: string,
700
+ ): NativeHistoryMessage[] | null {
701
+ let Database: any;
702
+ try {
703
+ Database = loadBetterSqlite3();
704
+ } catch (err) {
705
+ // better-sqlite3 binding genuinely unavailable (ABI mismatch / not built
706
+ // into this bundle). This is the only true "cannot read at all" case — a
707
+ // real load failure, distinct from transient lock contention below. Warn
708
+ // once at WARN so it is greppable; the reader degrades gracefully (returns
709
+ // null → dispatcher falls back to brain/.pb).
710
+ LOG.warn(
711
+ 'NativeHistory',
712
+ `antigravity .db reader could not load better-sqlite3 for ${path.basename(filePath)}: ${err instanceof Error ? err.message : String(err)} (native binding unavailable — assistant answers in this .db will not surface)`,
713
+ );
714
+ return null;
715
+ }
716
+
717
+ let rows: AgyDbStepRow[] | null = null;
718
+ let lastBusyErr: unknown;
719
+
720
+ for (let attempt = 1; attempt <= AGY_DB_MAX_ATTEMPTS; attempt++) {
721
+ let db: any;
722
+ try {
723
+ db = new Database(filePath, { readonly: true, fileMustExist: true });
724
+ // Ask SQLite itself to wait (rather than failing fast) if the WAL
725
+ // lock is momentarily held by antigravity. Set as early as possible
726
+ // after open so the prepare/all below inherits the wait.
727
+ try { db.pragma(`busy_timeout = ${AGY_DB_BUSY_TIMEOUT_MS}`); } catch { /* ignore */ }
728
+ rows = db
729
+ .prepare(
730
+ `SELECT idx, step_type, step_payload
731
+ FROM steps
732
+ WHERE step_type IN (${AGY_STEP_TYPE_USER}, ${AGY_STEP_TYPE_MODEL})
733
+ ORDER BY idx ASC`,
734
+ )
735
+ .all() as AgyDbStepRow[];
736
+ break; // success
737
+ } catch (err) {
738
+ if (isSqliteBusyError(err)) {
739
+ // Transient WAL lock contention. Do NOT collapse to null on the first
740
+ // failure — the assistant answer is already persisted; treating a busy
741
+ // lock as "no session" is exactly what erased answers on re-query.
742
+ // Retry with a small backoff; only give up after attempts exhausted.
743
+ lastBusyErr = err;
744
+ if (attempt < AGY_DB_MAX_ATTEMPTS) {
745
+ sleepBusy(AGY_DB_RETRY_BACKOFF_MS[attempt - 1] ?? 150);
746
+ continue;
747
+ }
748
+ LOG.warn(
749
+ 'NativeHistory',
750
+ `antigravity .db ${path.basename(filePath)} stayed locked (SQLITE_BUSY) after ${AGY_DB_MAX_ATTEMPTS} attempts: ${err instanceof Error ? err.message : String(err)} (WAL write/checkpoint lock contention — assistant answers may transiently not surface this read)`,
751
+ );
752
+ return null;
753
+ }
754
+ // `steps` table absent / unexpected schema, or a genuine open/parse
755
+ // failure that is not lock contention — a real (but recoverable) shape
756
+ // mismatch. Log at debug so a schema drift in a future antigravity
757
+ // release is diagnosable without spamming logs for every legacy db.
758
+ LOG.debug(
759
+ 'NativeHistory',
760
+ `antigravity .db ${path.basename(filePath)} not readable: ${err instanceof Error ? err.message : String(err)}`,
761
+ );
762
+ return null;
763
+ } finally {
764
+ try { db?.close(); } catch { /* ignore */ }
765
+ }
766
+ }
767
+
768
+ void lastBusyErr; // referenced only for retry bookkeeping above
769
+
770
+ if (!Array.isArray(rows) || rows.length === 0) return null;
771
+
772
+ const normalizedWorkspace = typeof workspace === 'string' ? workspace.trim() : '';
773
+ const baseTs = statMtimeMs(filePath) || Date.now();
774
+ const messages: NativeHistoryMessage[] = [];
775
+
776
+ for (const row of rows) {
777
+ const payload = row.step_payload;
778
+ if (!payload || !Buffer.isBuffer(payload) || payload.length === 0) continue;
779
+
780
+ // Steps are ordered by idx; the db carries no per-step timestamp we can
781
+ // trust as ms, so synthesize a monotonically increasing receivedAt that
782
+ // preserves order (idx-derived) around the file mtime.
783
+ const receivedAt = baseTs + messages.length;
784
+
785
+ if (row.step_type === AGY_STEP_TYPE_USER) {
786
+ let content = extractUserPrompt(payload);
787
+ if (!content) {
788
+ // Primary field path (field 19 → 2/3) missed. Recover schema-agnostically
789
+ // rather than silently drop a user turn: scan the payload for the longest
790
+ // plausible prompt run (metadata/paths/tokens are filtered out). There is
791
+ // no reasoning subtree to exclude on the user side.
792
+ const recovered = extractUserRequestContent(recoverMessageText(payload, []));
793
+ if (recovered) {
794
+ content = recovered;
795
+ LOG.debug(
796
+ 'NativeHistory',
797
+ `antigravity .db ${path.basename(filePath)} step ${row.idx} (type ${row.step_type}): user prompt absent at field 19; recovered ${content.length} chars via printable-run fallback — possible step_payload schema drift`,
798
+ );
799
+ } else {
800
+ LOG.debug(
801
+ 'NativeHistory',
802
+ `antigravity .db ${path.basename(filePath)} step ${row.idx} (type ${row.step_type}) dropped: no user prompt text (payload ${payload.length}B, top-level fields [${topLevelFieldNumbers(payload).join(',')}])`,
803
+ );
804
+ continue;
805
+ }
806
+ }
807
+ const msg: NativeHistoryMessage = {
808
+ ts: new Date(receivedAt).toISOString(),
809
+ receivedAt,
810
+ role: 'user',
811
+ content,
812
+ kind: 'standard',
813
+ agent: 'antigravity-cli',
814
+ historySessionId: sessionId,
815
+ };
816
+ if (normalizedWorkspace) msg.workspace = normalizedWorkspace;
817
+ messages.push(msg);
818
+ } else if (row.step_type === AGY_STEP_TYPE_MODEL) {
819
+ let content = extractModelAnswer(payload);
820
+ if (!content) {
821
+ // The known answer path (field 20 → 1/8) yielded nothing. This is either
822
+ // (a) a legitimate reasoning-only / tool-planning step — the common case,
823
+ // which carries no user-visible answer — or (b) antigravity moved the
824
+ // answer to a different field/subtree (schema drift). Attempt a
825
+ // schema-agnostic recovery that EXCLUDES the reasoning subtree (field
826
+ // 20 → 3) so internal reasoning is never surfaced as the answer.
827
+ const reasoning = extractModelReasoning(payload);
828
+ const recovered = recoverMessageText(payload, reasoning ? [reasoning] : []);
829
+ if (recovered) {
830
+ content = recovered;
831
+ LOG.debug(
832
+ 'NativeHistory',
833
+ `antigravity .db ${path.basename(filePath)} step ${row.idx} (type ${row.step_type}): answer absent at field 20; recovered ${content.length} chars via printable-run fallback — possible step_payload schema drift`,
834
+ );
835
+ } else {
836
+ // No answer at the primary path and nothing recoverable beyond
837
+ // reasoning/metadata → drop. Content-free breadcrumb so a genuine
838
+ // future drift (answer present but unreadable) is greppable, and the
839
+ // expected reasoning-only case is distinguishable via reasoningOnly.
840
+ LOG.debug(
841
+ 'NativeHistory',
842
+ `antigravity .db ${path.basename(filePath)} step ${row.idx} (type ${row.step_type}) dropped: no answer text (payload ${payload.length}B, top-level fields [${topLevelFieldNumbers(payload).join(',')}], reasoningOnly=${reasoning ? 'yes' : 'no'})`,
843
+ );
844
+ continue;
845
+ }
846
+ }
847
+ const msg: NativeHistoryMessage = {
848
+ ts: new Date(receivedAt).toISOString(),
849
+ receivedAt,
850
+ role: 'assistant',
851
+ content,
852
+ kind: 'standard',
853
+ agent: 'antigravity-cli',
854
+ historySessionId: sessionId,
855
+ };
856
+ if (normalizedWorkspace) msg.workspace = normalizedWorkspace;
857
+ messages.push(msg);
858
+ }
859
+ }
860
+
861
+ return messages.length > 0 ? messages : null;
862
+ }
863
+
864
+ /**
865
+ * Assemble the history.jsonl user-prompt index for a single session id into a
866
+ * partial NativeHistorySession (user prompts only — history.jsonl never carries
867
+ * assistant answers). Returns null when the index has no rows for this session.
868
+ * Shared by the history.jsonl read case and the .db sibling fallback.
869
+ */
870
+ function readHistoryJsonlSession(
871
+ resolvedSessionId: string,
872
+ workspace?: string,
873
+ ): NativeHistorySession | null {
874
+ if (!isUuidLike(resolvedSessionId)) return null;
875
+ const rows = readHistoryRows().filter((r) => r.conversationId === resolvedSessionId);
876
+ if (rows.length === 0) return null;
877
+
878
+ rows.sort((a, b) => a.receivedAt - b.receivedAt);
879
+ const firstWorkspace = workspace || rows.find((r) => r.workspace)?.workspace || '';
880
+
881
+ const messages: NativeHistoryMessage[] = [];
882
+ if (firstWorkspace) {
883
+ messages.push({
884
+ ts: new Date(rows[0].receivedAt).toISOString(),
885
+ receivedAt: rows[0].receivedAt,
886
+ role: 'system',
887
+ content: firstWorkspace,
888
+ kind: 'session_start',
889
+ agent: 'antigravity-cli',
890
+ historySessionId: resolvedSessionId,
891
+ workspace: firstWorkspace,
892
+ });
893
+ }
894
+
895
+ for (const row of rows) {
896
+ const msg: NativeHistoryMessage = {
897
+ ts: new Date(row.receivedAt).toISOString(),
898
+ receivedAt: row.receivedAt,
899
+ role: 'user',
900
+ content: row.display,
901
+ kind: 'standard',
902
+ agent: 'antigravity-cli',
903
+ historySessionId: resolvedSessionId,
904
+ };
905
+ if (row.workspace) msg.workspace = row.workspace;
906
+ messages.push(msg);
907
+ }
908
+
909
+ return {
910
+ messages,
911
+ providerSessionId: resolvedSessionId,
912
+ source: 'provider-native',
913
+ sourcePath: historyJsonlPath(),
914
+ sourceMtimeMs: statMtimeMs(historyJsonlPath()),
915
+ nativeHistoryCoverage: 'partial',
916
+ partialReason: 'antigravity_cli_history_jsonl_contains_user_prompts_only',
917
+ };
918
+ }
919
+
920
+ /**
921
+ * ANTIGRAVITY-COMPLETION-DASHBOARD-GAP: recover a bound session's transcript
922
+ * from the legacy sibling sources when its per-session .db read came back empty
923
+ * (transient WAL lock, assistant step not yet flushed, or a decode miss on a
924
+ * future step_payload schema). Tried in descending fidelity for the SAME
925
+ * session id — never cross-binds to another conversation:
926
+ * 1. brain transcript (full coverage, authoritative when present),
927
+ * 2. sibling conversations/<uuid>.pb (best-effort raw text),
928
+ * 3. history.jsonl (partial — user prompts only).
929
+ * Returns null when no sibling yields any message.
930
+ */
931
+ function readAntigravitySiblingFallback(
932
+ sessionId: string,
933
+ workspace?: string,
934
+ ): NativeHistorySession | null {
935
+ if (!isUuidLike(sessionId)) return null;
936
+
937
+ // (1) brain transcript — full coverage when antigravity actually wrote it.
938
+ const brainPath = findBrainTranscriptPath(sessionId);
939
+ if (brainPath && statMtimeMs(brainPath) > 0) {
940
+ const brainMessages = parseBrainTranscript(brainPath, sessionId, workspace);
941
+ if (brainMessages && brainMessages.length > 0) {
942
+ return {
943
+ messages: brainMessages,
944
+ providerSessionId: sessionId,
945
+ source: 'provider-native',
946
+ sourcePath: brainPath,
947
+ sourceMtimeMs: statMtimeMs(brainPath),
948
+ nativeHistoryCoverage: 'full',
949
+ workspace,
950
+ };
951
+ }
952
+ }
953
+
954
+ // (2) sibling .pb — best-effort raw text extraction.
955
+ const pbPath = resolvePathInside(conversationsRoot(), `${sessionId}.pb`);
956
+ if (pbPath && fs.existsSync(pbPath)) {
957
+ const pbMessages = parsePbFile(pbPath, sessionId);
958
+ if (pbMessages && pbMessages.length > 0) {
959
+ return {
960
+ messages: pbMessages,
961
+ providerSessionId: sessionId,
962
+ source: 'provider-native',
963
+ sourcePath: pbPath,
964
+ sourceMtimeMs: statMtimeMs(pbPath),
965
+ nativeHistoryCoverage: 'best-effort',
966
+ partialReason: 'antigravity_cli_pb_raw_text_extraction',
967
+ workspace,
968
+ };
969
+ }
970
+ }
971
+
972
+ // (3) history.jsonl — partial (user prompts only). Last resort so the
973
+ // dashboard at least shows the prompt when no answer source is readable.
974
+ return readHistoryJsonlSession(sessionId, workspace);
975
+ }
976
+
977
+ // ─── Public API ─────────────────────────────────────────────────────────────
978
+
979
+ /**
980
+ * Read a single Antigravity CLI session by its source file path.
981
+ *
982
+ * `sessionPath` is the absolute path to one of:
983
+ * - A brain transcript JSONL: ~/.gemini/antigravity-cli/brain/<uuid>/.system_generated/logs/transcript*.jsonl
984
+ * - The shared history.jsonl: ~/.gemini/antigravity-cli/history.jsonl
985
+ * - A conversation SQLite db: ~/.gemini/antigravity-cli/conversations/<uuid>.db
986
+ * - A conversation protobuf: ~/.gemini/antigravity-cli/conversations/<uuid>.pb
987
+ *
988
+ * The session UUID is inferred from the directory name (brain), filename (pb), or
989
+ * must be provided explicitly via `sessionId` for history.jsonl paths.
990
+ *
991
+ * Returns `null` when the file is missing, empty, or yields no parseable messages.
992
+ */
993
+ export function readSession(
994
+ sessionPath: string,
995
+ sessionId?: string,
996
+ workspace?: string,
997
+ ): NativeHistorySession | null {
998
+ if (!sessionPath || !path.isAbsolute(sessionPath)) return null;
999
+ if (!fs.existsSync(sessionPath)) return null;
1000
+
1001
+ const sourceMtimeMs = statMtimeMs(sessionPath);
1002
+
1003
+ // ── Case 1: brain transcript JSONL ──────────────────────────────────────
1004
+ const brainRootPath = brainRoot();
1005
+ if (sessionPath.startsWith(brainRootPath + path.sep) && sessionPath.endsWith('.jsonl')) {
1006
+ // Extract UUID from the path: brain/<uuid>/.system_generated/...
1007
+ const relative = sessionPath.slice(brainRootPath.length + 1);
1008
+ const uuidFromPath = relative.split(path.sep)[0];
1009
+ const resolvedSessionId = sessionId || (isUuidLike(uuidFromPath) ? uuidFromPath : '');
1010
+ if (!resolvedSessionId) return null;
1011
+
1012
+ const messages = parseBrainTranscript(sessionPath, resolvedSessionId, workspace);
1013
+ if (!messages || messages.length === 0) return null;
1014
+
1015
+ return {
1016
+ messages,
1017
+ providerSessionId: resolvedSessionId,
1018
+ source: 'provider-native',
1019
+ sourcePath: sessionPath,
1020
+ sourceMtimeMs,
1021
+ nativeHistoryCoverage: 'full',
1022
+ workspace,
1023
+ };
1024
+ }
1025
+
1026
+ // ── Case 2a: .db conversation file (current per-session SQLite format) ───
1027
+ if (sessionPath.endsWith('.db')) {
1028
+ const dbSessionId = sessionId || path.basename(sessionPath, '.db');
1029
+ if (!isUuidLike(dbSessionId)) return null;
1030
+
1031
+ const messages = parseConversationDb(sessionPath, dbSessionId, workspace);
1032
+ if (messages && messages.length > 0) {
1033
+ return {
1034
+ messages,
1035
+ providerSessionId: dbSessionId,
1036
+ source: 'provider-native',
1037
+ sourcePath: sessionPath,
1038
+ sourceMtimeMs,
1039
+ nativeHistoryCoverage: 'full',
1040
+ workspace,
1041
+ };
1042
+ }
1043
+
1044
+ // ANTIGRAVITY-COMPLETION-DASHBOARD-GAP: the bound .db yielded nothing this
1045
+ // read — transient (WAL lock / assistant step not yet flushed) or a decode
1046
+ // miss on a future schema. The dispatcher binds STRAIGHT to <uuid>.db once
1047
+ // the session id is known and never re-resolves to a sibling source, so a
1048
+ // null here previously collapsed the whole read to native-unavailable even
1049
+ // when the SAME session's assistant answer was recoverable from the legacy
1050
+ // brain transcript / .pb / history.jsonl. Fall back across those sibling
1051
+ // sources for the same session id before giving up. This is read-only,
1052
+ // scoped to the bound session, and cannot cross-bind to another conversation.
1053
+ const siblingFallback = readAntigravitySiblingFallback(dbSessionId, workspace);
1054
+ if (siblingFallback) return siblingFallback;
1055
+
1056
+ return null;
1057
+ }
1058
+
1059
+ // ── Case 2b: .pb conversation file (legacy protobuf) ─────────────────────
1060
+ if (sessionPath.endsWith('.pb')) {
1061
+ const pbSessionId = sessionId || path.basename(sessionPath, '.pb');
1062
+ if (!isUuidLike(pbSessionId)) return null;
1063
+
1064
+ const messages = parsePbFile(sessionPath, pbSessionId);
1065
+ if (!messages || messages.length === 0) return null;
1066
+
1067
+ return {
1068
+ messages,
1069
+ providerSessionId: pbSessionId,
1070
+ source: 'provider-native',
1071
+ sourcePath: sessionPath,
1072
+ sourceMtimeMs,
1073
+ nativeHistoryCoverage: 'best-effort',
1074
+ partialReason: 'antigravity_cli_pb_raw_text_extraction',
1075
+ };
1076
+ }
1077
+
1078
+ // ── Case 3: history.jsonl (user prompts index) ──────────────────────────
1079
+ if (path.basename(sessionPath) === 'history.jsonl') {
1080
+ return readHistoryJsonlSession(sessionId || '', workspace);
1081
+ }
1082
+
1083
+ return null;
1084
+ }
1085
+
1086
+ /**
1087
+ * List all Antigravity CLI sessions found across all watchPath locations.
1088
+ *
1089
+ * Priority:
1090
+ * 1. Brain transcript sessions (full coverage) — preferred source.
1091
+ * 2. History JSONL entries without a brain transcript (partial coverage).
1092
+ * 3. .pb conversation files without any other source (best-effort).
1093
+ *
1094
+ * Returns summary metadata sorted by most recently updated first.
1095
+ */
1096
+ export async function listSessions(_watchPath: string): Promise<NativeHistorySessionMeta[]> {
1097
+ const results: NativeHistorySessionMeta[] = [];
1098
+ const seen = new Set<string>();
1099
+
1100
+ // ── Step 1: brain transcripts (full coverage) ──────────────────────────
1101
+ const historyRows = readHistoryRows();
1102
+ const workspaceBySession = new Map<string, string>();
1103
+ for (const row of historyRows) {
1104
+ if (row.workspace && !workspaceBySession.has(row.conversationId)) {
1105
+ workspaceBySession.set(row.conversationId, row.workspace);
1106
+ }
1107
+ }
1108
+
1109
+ for (const { sessionId, transcriptPath, sourceMtimeMs } of listBrainSessions()) {
1110
+ const workspace = workspaceBySession.get(sessionId);
1111
+ const messages = parseBrainTranscript(transcriptPath, sessionId, workspace);
1112
+ if (!messages || messages.length === 0) continue;
1113
+
1114
+ const visible = messages.filter((m) => m.kind !== 'session_start');
1115
+ if (visible.length === 0) continue;
1116
+
1117
+ const firstMsg = visible[0];
1118
+ const lastMsg = visible[visible.length - 1];
1119
+
1120
+ results.push({
1121
+ historySessionId: sessionId,
1122
+ sessionId,
1123
+ sourcePath: transcriptPath,
1124
+ sourceMtimeMs,
1125
+ messageCount: visible.length,
1126
+ firstMessageAt: firstMsg.receivedAt || sourceMtimeMs,
1127
+ lastMessageAt: lastMsg.receivedAt || sourceMtimeMs,
1128
+ sessionTitle: lastMsg.content,
1129
+ preview: lastMsg.content,
1130
+ workspace,
1131
+ agent: 'antigravity-cli',
1132
+ source: 'provider-native',
1133
+ nativeHistoryCoverage: 'full',
1134
+ });
1135
+ seen.add(sessionId);
1136
+ }
1137
+
1138
+ // ── Step 2: history.jsonl entries without brain transcripts ────────────
1139
+ const grouped = new Map<
1140
+ string,
1141
+ {
1142
+ firstMessageAt: number;
1143
+ lastMessageAt: number;
1144
+ messageCount: number;
1145
+ sessionTitle?: string;
1146
+ preview?: string;
1147
+ workspace?: string;
1148
+ }
1149
+ >();
1150
+ for (const row of historyRows) {
1151
+ const existing = grouped.get(row.conversationId) ?? {
1152
+ firstMessageAt: row.receivedAt,
1153
+ lastMessageAt: row.receivedAt,
1154
+ messageCount: 0,
1155
+ };
1156
+ existing.messageCount += 1;
1157
+ existing.firstMessageAt = Math.min(existing.firstMessageAt, row.receivedAt);
1158
+ if (row.receivedAt >= existing.lastMessageAt) {
1159
+ existing.lastMessageAt = row.receivedAt;
1160
+ existing.sessionTitle = row.display;
1161
+ existing.preview = row.display;
1162
+ if (row.workspace) existing.workspace = row.workspace;
1163
+ }
1164
+ grouped.set(row.conversationId, existing);
1165
+ }
1166
+
1167
+ const historySrc = historyJsonlPath();
1168
+ const historyMtime = statMtimeMs(historySrc);
1169
+
1170
+ for (const [sessionId, info] of grouped.entries()) {
1171
+ if (seen.has(sessionId)) continue;
1172
+ results.push({
1173
+ historySessionId: sessionId,
1174
+ sessionId,
1175
+ sourcePath: historySrc,
1176
+ sourceMtimeMs: historyMtime,
1177
+ messageCount: info.messageCount,
1178
+ firstMessageAt: info.firstMessageAt,
1179
+ lastMessageAt: info.lastMessageAt,
1180
+ sessionTitle: info.sessionTitle,
1181
+ preview: info.preview,
1182
+ workspace: info.workspace,
1183
+ agent: 'antigravity-cli',
1184
+ source: 'provider-native',
1185
+ nativeHistoryCoverage: 'partial',
1186
+ partialReason: 'antigravity_cli_history_jsonl_contains_user_prompts_only',
1187
+ });
1188
+ seen.add(sessionId);
1189
+ }
1190
+
1191
+ // ── Step 3: conversations/<uuid>.db and .pb without any other source ────
1192
+ //
1193
+ // Current antigravity writes per-session SQLite dbs; older sessions kept a
1194
+ // .pb protobuf. Both can coexist in conversations/. Discover both, but when
1195
+ // the same uuid has a .db, prefer it (full coverage) over the .pb (best
1196
+ // effort). brain/history sources already in `seen` still win over either.
1197
+ const convRoot = conversationsRoot();
1198
+ if (fs.existsSync(convRoot)) {
1199
+ let entries: fs.Dirent[] = [];
1200
+ try { entries = fs.readdirSync(convRoot, { withFileTypes: true }); } catch { /* ignore */ }
1201
+
1202
+ // Group by uuid so a .db supersedes a sibling .pb of the same session.
1203
+ const byUuid = new Map<string, { db?: string; pb?: string }>();
1204
+ for (const entry of entries) {
1205
+ if (!entry.isFile()) continue;
1206
+ const dbMatch = /^([0-9a-f-]+)\.db$/i.exec(entry.name);
1207
+ const pbMatch = /^([0-9a-f-]+)\.pb$/i.exec(entry.name);
1208
+ if (dbMatch && isUuidLike(dbMatch[1])) {
1209
+ const g = byUuid.get(dbMatch[1]) ?? {};
1210
+ g.db = path.join(convRoot, entry.name);
1211
+ byUuid.set(dbMatch[1], g);
1212
+ } else if (pbMatch && isUuidLike(pbMatch[1])) {
1213
+ const g = byUuid.get(pbMatch[1]) ?? {};
1214
+ g.pb = path.join(convRoot, entry.name);
1215
+ byUuid.set(pbMatch[1], g);
1216
+ }
1217
+ }
1218
+
1219
+ for (const [uuid, files] of byUuid.entries()) {
1220
+ if (seen.has(uuid)) continue;
1221
+
1222
+ if (files.db) {
1223
+ // Parse the db so listSessions reports accurate counts/preview and the
1224
+ // session surfaces with full coverage (assistant answers included).
1225
+ const workspace = workspaceBySession.get(uuid);
1226
+ const messages = parseConversationDb(files.db, uuid, workspace);
1227
+ const dbMtime = statMtimeMs(files.db);
1228
+ if (messages && messages.length > 0) {
1229
+ const lastMsg = messages[messages.length - 1];
1230
+ const firstMsg = messages[0];
1231
+ results.push({
1232
+ historySessionId: uuid,
1233
+ sessionId: uuid,
1234
+ sourcePath: files.db,
1235
+ sourceMtimeMs: dbMtime,
1236
+ messageCount: messages.length,
1237
+ firstMessageAt: firstMsg.receivedAt || dbMtime,
1238
+ lastMessageAt: lastMsg.receivedAt || dbMtime,
1239
+ sessionTitle: lastMsg.content,
1240
+ preview: lastMsg.content,
1241
+ workspace,
1242
+ agent: 'antigravity-cli',
1243
+ source: 'provider-native',
1244
+ nativeHistoryCoverage: 'full',
1245
+ });
1246
+ seen.add(uuid);
1247
+ continue;
1248
+ }
1249
+ // db unreadable/empty (e.g. sqlite binding unavailable) → fall through
1250
+ // to the .pb best-effort entry below if one exists.
1251
+ }
1252
+
1253
+ if (files.pb) {
1254
+ const pbMtime = statMtimeMs(files.pb);
1255
+ results.push({
1256
+ historySessionId: uuid,
1257
+ sessionId: uuid,
1258
+ sourcePath: files.pb,
1259
+ sourceMtimeMs: pbMtime,
1260
+ messageCount: 0,
1261
+ firstMessageAt: pbMtime,
1262
+ lastMessageAt: pbMtime,
1263
+ agent: 'antigravity-cli',
1264
+ source: 'provider-native',
1265
+ nativeHistoryCoverage: 'best-effort',
1266
+ partialReason: 'antigravity_cli_pb_raw_text_extraction',
1267
+ });
1268
+ seen.add(uuid);
1269
+ }
1270
+ }
1271
+ }
1272
+
1273
+ results.sort((a, b) => (b.lastMessageAt || 0) - (a.lastMessageAt || 0));
1274
+ return results;
1275
+ }