@pellux/goodvibes-tui 1.27.0 → 2.0.1

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 (404) hide show
  1. package/CHANGELOG.md +125 -2
  2. package/README.md +7 -9
  3. package/bin/goodvibes +2 -2
  4. package/bin/launcher-support.js +8 -4
  5. package/docs/README.md +11 -2
  6. package/docs/foundation-artifacts/operator-contract.json +6403 -2347
  7. package/package.json +14 -25
  8. package/scripts/postinstall.js +35 -35
  9. package/src/audio/spoken-turn-model-routing.ts +1 -1
  10. package/src/cli/bundle-command.ts +5 -5
  11. package/src/cli/completions/generate.ts +1 -1
  12. package/src/cli/doctor.ts +3 -3
  13. package/src/cli/entrypoint.ts +12 -7
  14. package/src/cli/help.ts +4 -5
  15. package/src/cli/hooks-command.ts +1 -1
  16. package/src/cli/index.ts +1 -4
  17. package/src/cli/launch-auto-update.ts +59 -7
  18. package/src/cli/management-commands.ts +6 -6
  19. package/src/cli/management-utils.ts +5 -5
  20. package/src/cli/management.ts +7 -7
  21. package/src/cli/network-posture.ts +1 -1
  22. package/src/cli/package-verification.ts +7 -6
  23. package/src/cli/plugin-bundles-command.ts +1 -1
  24. package/src/cli/plugin-command.ts +2 -2
  25. package/src/cli/service-command.ts +2 -2
  26. package/src/cli/service-posture.ts +4 -4
  27. package/src/cli/status.ts +9 -8
  28. package/src/cli/surface-command.ts +4 -4
  29. package/src/cli/tui-startup.ts +12 -3
  30. package/src/cli-flags.ts +3 -3
  31. package/src/config/goodvibes-home-audit.ts +0 -2
  32. package/src/config/secret-config.ts +37 -5
  33. package/src/config/tui-extension-settings.ts +1 -1
  34. package/src/core/approval-alert.ts +2 -2
  35. package/src/core/budget-breach-notifier.ts +3 -3
  36. package/src/core/conversation-compaction-render.ts +1 -1
  37. package/src/core/conversation-line-cache.ts +1 -1
  38. package/src/core/conversation-render-context.ts +1 -1
  39. package/src/core/conversation-rendering.ts +2 -2
  40. package/src/core/conversation.ts +10 -10
  41. package/src/core/long-task-notifier.ts +2 -2
  42. package/src/core/memory-diagnostics-gateway.ts +1 -1
  43. package/src/core/scriptable-statusline.ts +1 -1
  44. package/src/core/session-auto-titler.ts +1 -1
  45. package/src/core/session-resume-core.ts +2 -2
  46. package/src/core/stream-event-wiring.ts +24 -44
  47. package/src/core/terminal-notifier.ts +3 -3
  48. package/src/core/turn-cancellation.ts +1 -1
  49. package/src/core/turn-event-wiring.ts +5 -5
  50. package/src/input/command-registry.ts +6 -6
  51. package/src/input/commands/channel-pairing.ts +1 -1
  52. package/src/input/commands/cluster-runtime.ts +1 -1
  53. package/src/input/commands/codebase-runtime.ts +1 -1
  54. package/src/input/commands/devices-runtime.ts +1 -1
  55. package/src/input/commands/hosted-runtime.ts +330 -0
  56. package/src/input/commands/local-setup-review.ts +1 -1
  57. package/src/input/commands/local-setup-transfer.ts +2 -2
  58. package/src/input/commands/local-setup.ts +3 -3
  59. package/src/input/commands/managed-runtime.ts +2 -2
  60. package/src/input/commands/operator-rpc.ts +24 -110
  61. package/src/input/commands/payment-card-intake.ts +1 -1
  62. package/src/input/commands/permissions-provenance.ts +2 -2
  63. package/src/input/commands/plugin-runtime.ts +1 -1
  64. package/src/input/commands/provider.ts +1 -1
  65. package/src/input/commands/relay-runtime.ts +18 -9
  66. package/src/input/commands/rewind-runtime.ts +1 -1
  67. package/src/input/commands/runtime-services.ts +1 -1
  68. package/src/input/commands/services-runtime.ts +1 -1
  69. package/src/input/commands/session-workflow.ts +1 -1
  70. package/src/input/commands/settings-sync-runtime.ts +2 -2
  71. package/src/input/commands/share-runtime.ts +1 -1
  72. package/src/input/commands/tasks-runtime.ts +145 -55
  73. package/src/input/commands/update-runtime.ts +31 -28
  74. package/src/input/commands/work-plan-runtime.ts +2 -2
  75. package/src/input/commands/workstream-attempts.ts +1 -1
  76. package/src/input/commands/workstream-runtime.ts +2 -2
  77. package/src/input/commands.ts +2 -0
  78. package/src/input/feature-unit-layout.ts +1 -1
  79. package/src/input/feed-context-factory.ts +3 -3
  80. package/src/input/handler-content-actions.ts +2 -2
  81. package/src/input/handler-feed-routes.ts +1 -1
  82. package/src/input/handler-feed.ts +3 -3
  83. package/src/input/handler-modal-token-routes.ts +1 -1
  84. package/src/input/handler-onboarding-daemon-adopt.ts +3 -2
  85. package/src/input/handler-onboarding.ts +1 -1
  86. package/src/input/handler-types.ts +2 -2
  87. package/src/input/handler-ui-state.ts +1 -1
  88. package/src/input/handler.ts +4 -4
  89. package/src/input/input-history.ts +1 -1
  90. package/src/input/onboarding/onboarding-feature-units.ts +2 -2
  91. package/src/input/onboarding/onboarding-runtime-status.ts +15 -1
  92. package/src/input/onboarding/onboarding-wizard-apply.ts +17 -8
  93. package/src/input/onboarding/onboarding-wizard-cloudflare-step.ts +5 -3
  94. package/src/input/onboarding/onboarding-wizard-external-surfaces.ts +1 -1
  95. package/src/input/onboarding/onboarding-wizard-types.ts +1 -1
  96. package/src/input/payments-config.ts +6 -6
  97. package/src/input/search.ts +1 -1
  98. package/src/input/session-picker-modal.ts +46 -0
  99. package/src/input/settings-modal-activation.ts +1 -1
  100. package/src/input/settings-modal-behavior.ts +4 -4
  101. package/src/input/settings-modal-commit.ts +11 -0
  102. package/src/input/settings-modal-data.ts +13 -80
  103. package/src/input/settings-modal-mutations.ts +33 -0
  104. package/src/input/settings-modal-secrets.ts +34 -0
  105. package/src/input/settings-modal-subscriptions.ts +1 -1
  106. package/src/input/settings-modal-types.ts +15 -1
  107. package/src/input/settings-modal.ts +28 -3
  108. package/src/input/worktree-setup-config.ts +1 -1
  109. package/src/main.ts +11 -9
  110. package/src/panels/agent-inspector-shared.ts +2 -2
  111. package/src/panels/base-panel.ts +2 -2
  112. package/src/panels/builtin/operations.ts +6 -8
  113. package/src/panels/builtin/session.ts +17 -0
  114. package/src/panels/builtin/shared.ts +5 -5
  115. package/src/panels/builtin-modals.ts +3 -3
  116. package/src/panels/confirm-state.ts +1 -1
  117. package/src/panels/cost-tracker-panel.ts +4 -4
  118. package/src/panels/diff-panel.ts +2 -2
  119. package/src/panels/diff-review-panel.ts +2 -2
  120. package/src/panels/expandable-list-panel.ts +2 -2
  121. package/src/panels/fleet-acts.ts +1 -1
  122. package/src/panels/fleet-observed-render.ts +1 -1
  123. package/src/panels/fleet-panel-format.ts +1 -1
  124. package/src/panels/fleet-panel.ts +1 -1
  125. package/src/panels/fleet-spawn.ts +1 -1
  126. package/src/panels/fleet-steer.ts +1 -1
  127. package/src/panels/fleet-transcript.ts +1 -1
  128. package/src/panels/git-panel.ts +1 -1
  129. package/src/panels/hosted-session-feed.ts +352 -0
  130. package/src/panels/hosted-session-panel.ts +221 -0
  131. package/src/panels/local-auth-panel.ts +3 -3
  132. package/src/panels/modals/devices-modal.ts +1 -1
  133. package/src/panels/modals/local-auth-modal.ts +1 -1
  134. package/src/panels/modals/pairing-modal.ts +1 -1
  135. package/src/panels/modals/services-modal.ts +1 -1
  136. package/src/panels/modals/settings-sync-modal.ts +1 -1
  137. package/src/panels/modals/subscription-modal.ts +1 -1
  138. package/src/panels/modals/work-plan-modal.ts +1 -1
  139. package/src/panels/notifications-panel.ts +1 -1
  140. package/src/panels/panel-confirm-overlay.ts +1 -1
  141. package/src/panels/plugins-panel.ts +2 -2
  142. package/src/panels/polish-core.ts +2 -2
  143. package/src/panels/polish-tables.ts +1 -1
  144. package/src/panels/polish.ts +2 -2
  145. package/src/panels/scrollable-list-panel.ts +3 -3
  146. package/src/panels/skills-panel.ts +3 -3
  147. package/src/panels/token-budget-panel.ts +5 -5
  148. package/src/panels/types.ts +1 -2
  149. package/src/permissions/prompt.ts +1 -1
  150. package/src/renderer/autocomplete-overlay.ts +1 -1
  151. package/src/renderer/block-actions-overlay.ts +1 -1
  152. package/src/renderer/bookmark-modal.ts +1 -1
  153. package/src/renderer/buffer.ts +1 -1
  154. package/src/renderer/code-block.ts +1 -1
  155. package/src/renderer/composer-fragments.ts +1 -1
  156. package/src/renderer/compositor.ts +2 -2
  157. package/src/renderer/config-modal.ts +1 -1
  158. package/src/renderer/context-inspector.ts +1 -1
  159. package/src/renderer/conversation-layout.ts +2 -2
  160. package/src/renderer/conversation-overlays.ts +1 -1
  161. package/src/renderer/conversation-surface.ts +2 -2
  162. package/src/renderer/diff-view.ts +1 -1
  163. package/src/renderer/diff.ts +1 -1
  164. package/src/renderer/file-picker-overlay.ts +1 -1
  165. package/src/renderer/fleet-tab-strip.ts +1 -1
  166. package/src/renderer/fullscreen-primitives.ts +2 -2
  167. package/src/renderer/fullscreen-workspace.ts +1 -1
  168. package/src/renderer/header-line.ts +1 -1
  169. package/src/renderer/help-overlay.ts +1 -1
  170. package/src/renderer/history-search-overlay.ts +2 -2
  171. package/src/renderer/layout.ts +5 -8
  172. package/src/renderer/markdown-inline.ts +1 -1
  173. package/src/renderer/markdown-table.ts +1 -1
  174. package/src/renderer/markdown.ts +1 -1
  175. package/src/renderer/mcp-workspace.ts +1 -1
  176. package/src/renderer/modal-factory.ts +2 -2
  177. package/src/renderer/model-workspace.ts +2 -2
  178. package/src/renderer/onboarding/onboarding-wizard.ts +1 -1
  179. package/src/renderer/overlay-box.ts +1 -1
  180. package/src/renderer/panel-composite.ts +1 -1
  181. package/src/renderer/panel-workspace-bar.ts +1 -1
  182. package/src/renderer/process-indicator.ts +1 -1
  183. package/src/renderer/profile-picker-modal.ts +1 -1
  184. package/src/renderer/search-overlay.ts +2 -2
  185. package/src/renderer/selection-modal-overlay.ts +1 -1
  186. package/src/renderer/session-picker-modal.ts +124 -3
  187. package/src/renderer/settings-modal-helpers.ts +2 -0
  188. package/src/renderer/settings-modal.ts +2 -2
  189. package/src/renderer/shell-surface.ts +2 -2
  190. package/src/renderer/system-message.ts +1 -1
  191. package/src/renderer/tab-strip.ts +1 -1
  192. package/src/renderer/theme-mode-config.ts +1 -1
  193. package/src/renderer/theme.ts +11 -12
  194. package/src/renderer/thinking.ts +1 -1
  195. package/src/renderer/tool-call.ts +2 -2
  196. package/src/renderer/tool-result-expanded-lines.ts +1 -1
  197. package/src/renderer/turn-injection.ts +7 -8
  198. package/src/renderer/ui-factory.ts +3 -3
  199. package/src/runtime/bootstrap-command-context.ts +4 -4
  200. package/src/runtime/bootstrap-command-parts.ts +5 -5
  201. package/src/runtime/bootstrap-core.ts +30 -30
  202. package/src/runtime/bootstrap-shell.ts +4 -4
  203. package/src/runtime/bootstrap.ts +128 -157
  204. package/src/runtime/client/approval-updates.ts +82 -0
  205. package/src/runtime/client/build-floors.ts +167 -0
  206. package/src/runtime/client/daemon-attach-handshake.ts +115 -0
  207. package/src/runtime/client/fleet-union.ts +147 -0
  208. package/src/runtime/client/host-service-status.ts +71 -0
  209. package/src/runtime/client/hosted-exit.ts +86 -0
  210. package/src/runtime/client/hosted-roster.ts +110 -0
  211. package/src/runtime/client/hosted-session-stream.ts +178 -0
  212. package/src/runtime/client/hosted-sessions.ts +187 -0
  213. package/src/runtime/client/operator-endpoint.ts +213 -0
  214. package/src/runtime/client/spine-adoption.ts +99 -0
  215. package/src/runtime/cloudflare-control-plane.ts +1 -1
  216. package/src/runtime/context-accounting-source.ts +1 -1
  217. package/src/runtime/context.ts +2 -2
  218. package/src/runtime/conversation-rewind-port.ts +42 -14
  219. package/src/runtime/daemon-attach-notices.ts +55 -19
  220. package/src/runtime/diagnostics/panels/ops.ts +1 -1
  221. package/src/runtime/disposal-wiring.ts +4 -24
  222. package/src/runtime/index.ts +119 -105
  223. package/src/runtime/install-self-check-startup.ts +2 -2
  224. package/src/runtime/interaction-seams.ts +1 -1
  225. package/src/runtime/knowledge-services.ts +8 -2
  226. package/src/runtime/legacy-daemon-migration.ts +15 -3
  227. package/src/runtime/legacy-daemon-reconcile.ts +3 -2
  228. package/src/runtime/memory-spine-transport.ts +37 -267
  229. package/src/runtime/notification-dispatch.ts +2 -2
  230. package/src/runtime/onboarding/apply.ts +2 -2
  231. package/src/runtime/onboarding/derivation.ts +2 -2
  232. package/src/runtime/onboarding/markers.ts +2 -2
  233. package/src/runtime/onboarding/progress.ts +2 -2
  234. package/src/runtime/onboarding/snapshot.ts +1 -1
  235. package/src/runtime/onboarding/state.ts +2 -2
  236. package/src/runtime/onboarding/types.ts +2 -2
  237. package/src/runtime/orchestrator-core-services.ts +1 -1
  238. package/src/runtime/path-shadow-startup.ts +28 -221
  239. package/src/runtime/power-keepawake-remote.ts +3 -2
  240. package/src/runtime/process-lifecycle.ts +2 -2
  241. package/src/runtime/provider-fallback.ts +10 -66
  242. package/src/runtime/recovery-autosave.ts +1 -1
  243. package/src/runtime/recovery-offer-wiring.ts +39 -23
  244. package/src/runtime/recovery-prompt.ts +31 -17
  245. package/src/runtime/relay-reachability-bridge.ts +53 -42
  246. package/src/runtime/release-artifacts.ts +2 -1
  247. package/src/runtime/runtime-services-types.ts +82 -35
  248. package/src/runtime/services.ts +305 -300
  249. package/src/runtime/session-resume-liveness-confirm.ts +1 -1
  250. package/src/runtime/ui-services.ts +7 -6
  251. package/src/runtime/update-check.ts +23 -43
  252. package/src/runtime/workspace-checkpointing.ts +1 -1
  253. package/src/runtime/wrfc-persistence.ts +2 -2
  254. package/src/shell/ui-openers.ts +30 -7
  255. package/src/utils/clipboard.ts +1 -1
  256. package/src/verification/verification-ledger.ts +10 -8
  257. package/src/version.ts +1 -1
  258. package/bin/goodvibes-daemon +0 -90
  259. package/src/cli/config-overrides.ts +0 -267
  260. package/src/cli/endpoints.ts +0 -109
  261. package/src/cli/parser.ts +0 -395
  262. package/src/cli/redaction.ts +0 -170
  263. package/src/cli/types.ts +0 -84
  264. package/src/cluster/commands.ts +0 -382
  265. package/src/cluster/remote-daemon-target.ts +0 -225
  266. package/src/cluster/render.ts +0 -268
  267. package/src/config/atomic-write.ts +0 -70
  268. package/src/config/credential-availability.ts +0 -122
  269. package/src/config/goodvibes-home.ts +0 -149
  270. package/src/config/index.ts +0 -88
  271. package/src/config/provider-model.ts +0 -23
  272. package/src/config/read-versioned.ts +0 -228
  273. package/src/core/alert-gating.ts +0 -67
  274. package/src/core/consolidation-receipt.ts +0 -41
  275. package/src/core/focus-tracker.ts +0 -41
  276. package/src/core/history.ts +0 -45
  277. package/src/core/memory-status.ts +0 -118
  278. package/src/core/orchestrator.ts +0 -7
  279. package/src/core/pairing-devices.ts +0 -49
  280. package/src/core/pairing-handoff.ts +0 -101
  281. package/src/core/pairing-offers.ts +0 -83
  282. package/src/core/pairing-origin.ts +0 -78
  283. package/src/core/rewind-turn-anchors.ts +0 -311
  284. package/src/core/session-recovery.ts +0 -210
  285. package/src/core/stable-host.ts +0 -171
  286. package/src/core/transcript-journal.ts +0 -503
  287. package/src/daemon/cli.ts +0 -641
  288. package/src/daemon/handlers/context.ts +0 -29
  289. package/src/daemon/handlers/contracts.ts +0 -77
  290. package/src/daemon/handlers/credentials.ts +0 -139
  291. package/src/daemon/handlers/drafts/draft-store.ts +0 -427
  292. package/src/daemon/handlers/drafts/index.ts +0 -17
  293. package/src/daemon/handlers/drafts/register.ts +0 -331
  294. package/src/daemon/handlers/errors.ts +0 -18
  295. package/src/daemon/handlers/inbox/cursor-store.ts +0 -457
  296. package/src/daemon/handlers/inbox/index.ts +0 -284
  297. package/src/daemon/handlers/inbox/mapping.ts +0 -192
  298. package/src/daemon/handlers/inbox/poller.ts +0 -212
  299. package/src/daemon/handlers/inbox/provider-adapter.ts +0 -156
  300. package/src/daemon/handlers/inbox/providers/discord.ts +0 -252
  301. package/src/daemon/handlers/inbox/providers/email.ts +0 -152
  302. package/src/daemon/handlers/inbox/providers/imap-client.ts +0 -300
  303. package/src/daemon/handlers/inbox/providers/route-util.ts +0 -24
  304. package/src/daemon/handlers/inbox/providers/slack.ts +0 -263
  305. package/src/daemon/handlers/index.ts +0 -117
  306. package/src/daemon/handlers/register.ts +0 -168
  307. package/src/daemon/handlers/remote/backends/cloud-terminal.ts +0 -142
  308. package/src/daemon/handlers/remote/backends/docker.ts +0 -79
  309. package/src/daemon/handlers/remote/backends/index.ts +0 -40
  310. package/src/daemon/handlers/remote/backends/local-process.ts +0 -113
  311. package/src/daemon/handlers/remote/backends/process-runner.ts +0 -127
  312. package/src/daemon/handlers/remote/backends/ssh.ts +0 -125
  313. package/src/daemon/handlers/remote/backends/types.ts +0 -97
  314. package/src/daemon/handlers/remote/dispatcher.ts +0 -181
  315. package/src/daemon/handlers/remote/index.ts +0 -119
  316. package/src/daemon/handlers/remote/peer-registry.ts +0 -357
  317. package/src/daemon/handlers/remote/service.ts +0 -191
  318. package/src/daemon/handlers/routing/inbox-bridge.ts +0 -71
  319. package/src/daemon/handlers/routing/index.ts +0 -261
  320. package/src/daemon/handlers/routing/route-store.ts +0 -319
  321. package/src/daemon/handlers/routing/routing-resolver.ts +0 -75
  322. package/src/daemon/handlers/sqlite-store.ts +0 -278
  323. package/src/daemon/handlers/triage/index.ts +0 -57
  324. package/src/daemon/handlers/triage/integration.ts +0 -213
  325. package/src/daemon/handlers/triage/pipeline.ts +0 -274
  326. package/src/daemon/handlers/triage/scorer.ts +0 -287
  327. package/src/daemon/handlers/triage/tagger/discord.ts +0 -187
  328. package/src/daemon/handlers/triage/tagger/imap.ts +0 -384
  329. package/src/daemon/handlers/triage/tagger/index.ts +0 -184
  330. package/src/daemon/handlers/triage/tagger/shared.ts +0 -70
  331. package/src/daemon/handlers/triage/tagger/slack.ts +0 -69
  332. package/src/daemon/handlers/triage/types.ts +0 -50
  333. package/src/daemon/lifecycle.ts +0 -41
  334. package/src/daemon/provision-wake-model.ts +0 -81
  335. package/src/daemon/safe-serve.ts +0 -61
  336. package/src/daemon/send/channels.ts +0 -200
  337. package/src/daemon/send/command.ts +0 -332
  338. package/src/daemon/send/composition.ts +0 -99
  339. package/src/daemon/send/failure-text.ts +0 -93
  340. package/src/daemon/send/inert-text.ts +0 -225
  341. package/src/daemon/send/stdin.ts +0 -24
  342. package/src/daemon/service-commands.ts +0 -329
  343. package/src/export/cost-utils.ts +0 -319
  344. package/src/input/selection.ts +0 -135
  345. package/src/plugins/loader.ts +0 -15
  346. package/src/renderer/bottom-bar.ts +0 -58
  347. package/src/renderer/conversation-tree.ts +0 -228
  348. package/src/runtime/agent-graph-composition.ts +0 -78
  349. package/src/runtime/channel-composition.ts +0 -46
  350. package/src/runtime/cluster-composition.ts +0 -124
  351. package/src/runtime/cluster-group-composition.ts +0 -284
  352. package/src/runtime/code-index-services.ts +0 -167
  353. package/src/runtime/daemon-handler-composition.ts +0 -76
  354. package/src/runtime/device-posture-composition.ts +0 -110
  355. package/src/runtime/durability-housekeeping.ts +0 -183
  356. package/src/runtime/durability-services.ts +0 -155
  357. package/src/runtime/embedded-service-factories.ts +0 -58
  358. package/src/runtime/feature-settings.ts +0 -154
  359. package/src/runtime/fleet-needs-input-push.ts +0 -70
  360. package/src/runtime/fleet-services.ts +0 -41
  361. package/src/runtime/idle-power-services.ts +0 -85
  362. package/src/runtime/mail-composition.ts +0 -65
  363. package/src/runtime/memory-fold.ts +0 -55
  364. package/src/runtime/operator-token-cleanup.ts +0 -109
  365. package/src/runtime/path-shadow.ts +0 -418
  366. package/src/runtime/perf/panel-health-monitor.ts +0 -18
  367. package/src/runtime/provider-optimizer-wiring.ts +0 -55
  368. package/src/runtime/reachability-notice.ts +0 -100
  369. package/src/runtime/remote-execution-composition.ts +0 -62
  370. package/src/runtime/session-liveness-marker.ts +0 -257
  371. package/src/runtime/session-pointer-surface.ts +0 -22
  372. package/src/runtime/session-storage-services.ts +0 -41
  373. package/src/runtime/surface-feature-flags.ts +0 -96
  374. package/src/runtime/terminal-output-guard.ts +0 -235
  375. package/src/runtime/trust/workspace-registration.ts +0 -192
  376. package/src/runtime/trust/workspace-trust.ts +0 -229
  377. package/src/runtime/ui/index.ts +0 -39
  378. package/src/runtime/ui/model-picker/data-provider.ts +0 -182
  379. package/src/runtime/ui/model-picker/health-enrichment.ts +0 -231
  380. package/src/runtime/ui/model-picker/index.ts +0 -59
  381. package/src/runtime/ui/model-picker/types.ts +0 -153
  382. package/src/runtime/ui/provider-health/data-provider.ts +0 -244
  383. package/src/runtime/ui/provider-health/fallback-visualizer.ts +0 -71
  384. package/src/runtime/ui/provider-health/index.ts +0 -46
  385. package/src/runtime/ui/provider-health/types.ts +0 -146
  386. package/src/runtime/ui-events.ts +0 -1
  387. package/src/runtime/ui-read-model-helpers.ts +0 -1
  388. package/src/runtime/ui-read-models-observability-maintenance.ts +0 -1
  389. package/src/runtime/ui-read-models-observability-options.ts +0 -1
  390. package/src/runtime/ui-read-models-observability-remote.ts +0 -1
  391. package/src/runtime/ui-read-models-observability-security.ts +0 -1
  392. package/src/runtime/ui-read-models-observability-system.ts +0 -1
  393. package/src/runtime/ui-read-models-observability.ts +0 -1
  394. package/src/runtime/ui-service-queries.ts +0 -1
  395. package/src/runtime/voice-setup-services.ts +0 -98
  396. package/src/runtime/workstream-attempts-validation.ts +0 -77
  397. package/src/runtime/workstream-draft-edits.ts +0 -95
  398. package/src/runtime/workstream-draft-store.ts +0 -274
  399. package/src/runtime/workstream-draft-types.ts +0 -42
  400. package/src/runtime/workstream-services.ts +0 -373
  401. package/src/tools/index.ts +0 -1
  402. package/src/types/grid.ts +0 -48
  403. package/src/types/sql-js.d.ts +0 -15
  404. package/src/work-plans/work-plan-store.ts +0 -702
@@ -1,243 +1,50 @@
1
1
  /**
2
- * path-shadow-startup.ts — the boot-time surfacing of "is this the build you
3
- * are actually reaching, and is it the current one".
2
+ * path-shadow-startup.ts — this terminal's boot-time reachability report.
4
3
  *
5
- * It wires the real host inputs (this process's executable, the real PATH,
6
- * existsSync/realpathSync, and a bounded `<path> --version` spawn) into the
7
- * pure scan in path-shadow.ts and the pure wording in reachability-notice.ts,
8
- * then prints the result through the same SystemMessageRouter the rest of
9
- * startup uses, at high priority, before the session gets going.
10
- *
11
- * Cost discipline, because this runs on every start:
12
- * - the first scan is existence-only: no process is spawned while there is
13
- * nothing to report, which is the overwhelmingly common case;
14
- * - versions are probed only after a shadow has already been found, and
15
- * only with `--version`, bounded by a short timeout;
16
- * - the latest-release lookup only happens when it can actually change what
17
- * the user should do: a package-managed or source install (which will
18
- * never swap itself), or an install that has just been found unreachable.
19
- * A healthy binary install has already been brought to the latest release
20
- * by the launch auto-updater, so asking again would be a network round
21
- * trip that can only confirm what just happened.
22
- *
23
- * Every failure is swallowed. A reachability check must never block or crash
24
- * boot.
4
+ * The check itself (the PATH scan, the `--version` probes, the cost discipline
5
+ * that keeps a healthy start free of spawns and network calls, and the wording)
6
+ * belongs to the platform. All this supplies is what only this product knows:
7
+ * which command name the shell resolves it by, which package a package-managed
8
+ * install would be upgraded through, which release lookup answers "what is
9
+ * current", and where the lines are printed.
25
10
  */
26
11
 
27
- import { spawnSync } from 'node:child_process';
28
- import { realpathSync, statSync } from 'node:fs';
29
- import { basename, dirname, join } from 'node:path';
30
- import { detectInstallKind, fallbackUpdateCommand, type UpdateFetchLike } from './update-check.ts';
12
+ import { announceReachability, boundedLatestRelease } from '@pellux/goodvibes-sdk/platform/runtime/operations';
13
+ import type { UpdateFetchLike } from './update-check.ts';
31
14
  import { checkForUpdate } from '../input/commands/update-runtime.ts';
32
- import {
33
- scanCommandShadows,
34
- splitPathEntries,
35
- type ShadowScanResult,
36
- } from './path-shadow.ts';
37
- import { buildReachabilityNotices, type ReachabilityNotice } from './reachability-notice.ts';
38
15
  import { VERSION } from '../version.ts';
39
16
  import type { SystemMessageRouter } from '../core/system-message-router.ts';
40
17
 
41
- /** The commands a goodvibes install places side by side in one directory. */
42
- export const INSTALLED_COMMANDS = ['goodvibes', 'goodvibes-daemon', 'goodvibes-agent'] as const;
43
-
44
- /** How long a `<path> --version` probe may take before it is abandoned. */
45
- const VERSION_PROBE_TIMEOUT_MS = 3000;
46
- /** How long the latest-release lookup may take before startup moves on without it. */
47
- const LATEST_LOOKUP_TIMEOUT_MS = 2500;
48
-
49
- function safeRealPath(path: string): string {
50
- try {
51
- return realpathSync(path);
52
- } catch {
53
- return path;
54
- }
55
- }
56
-
57
- function isExecutableFile(path: string): boolean {
58
- try {
59
- const stats = statSync(path);
60
- if (!stats.isFile()) return false;
61
- // Any execute bit is enough: the shell searches for an executable file,
62
- // and which bit applies depends on ownership we are not going to compute.
63
- return (stats.mode & 0o111) !== 0;
64
- } catch {
65
- return false;
66
- }
67
- }
68
-
69
- /**
70
- * Runs `<path> --version` and returns its first line. Never inherits stdio, so
71
- * a binary that tries to draw a terminal cannot disturb this one.
72
- */
73
- export function probeVersionLine(path: string): string | undefined {
74
- try {
75
- const result = spawnSync(path, ['--version'], {
76
- timeout: VERSION_PROBE_TIMEOUT_MS,
77
- encoding: 'utf8',
78
- stdio: ['ignore', 'pipe', 'ignore'],
79
- windowsHide: true,
80
- });
81
- if (result.error || typeof result.stdout !== 'string') return undefined;
82
- return result.stdout.split('\n')[0];
83
- } catch {
84
- return undefined;
85
- }
86
- }
87
-
88
- /**
89
- * The PATH directory that provides THIS running executable, which is the
90
- * position everything else is measured against.
91
- *
92
- * Preferred answer: the first PATH entry whose `<dir>/<command>` resolves,
93
- * through symlinks, to this same file. That is exactly "where the shell would
94
- * find me", and it is right for a linked package install as well as a
95
- * standalone binary.
96
- *
97
- * Fallback, for a standalone binary only: the directory the executable sits
98
- * in. That directory being absent from PATH is itself worth reporting — an
99
- * installed binary nobody can reach by name. For a package-managed install
100
- * the executable lives inside node_modules, which is never on PATH and never
101
- * meant to be, so there is nothing honest to say and the check stays silent.
102
- */
103
- export function resolveSelfDirectory(input: {
104
- readonly execPath: string;
105
- readonly command: string;
106
- readonly pathEntries: readonly string[];
107
- readonly realPath: (path: string) => string;
108
- readonly isExecutableFile: (path: string) => boolean;
109
- }): string | undefined {
110
- const self = input.realPath(input.execPath);
111
- for (const directory of input.pathEntries) {
112
- const candidate = join(directory, input.command);
113
- if (!input.isExecutableFile(candidate)) continue;
114
- if (input.realPath(candidate) === self) return directory;
115
- }
116
- if (detectInstallKind(input.execPath) === 'binary') return dirname(self);
117
- return undefined;
118
- }
119
-
120
- /** Which of the installed commands actually sit in this directory. */
121
- function commandsPresentIn(directory: string, exists: (path: string) => boolean): string[] {
122
- return INSTALLED_COMMANDS.filter((command) => exists(join(directory, command)));
123
- }
124
-
125
- export interface ReachabilityCheckResult {
126
- readonly notices: readonly ReachabilityNotice[];
127
- readonly scan?: ShadowScanResult | undefined;
128
- }
18
+ /** The package a package-managed install of this terminal is upgraded through. */
19
+ const TERMINAL_PACKAGE_NAME = '@pellux/goodvibes-tui';
129
20
 
130
- /**
131
- * The whole check, with the network lookup injected so tests never reach it.
132
- * Returns the notices to print; an empty list is the healthy case.
133
- */
134
- export async function runReachabilityCheck(input: {
135
- readonly execPath: string;
136
- readonly pathValue: string | undefined;
137
- readonly homeDir: string;
138
- readonly runningVersion: string;
139
- readonly commandName?: string | undefined;
140
- /** Resolves the newest released version, or undefined when it cannot be determined. */
141
- readonly resolveLatest: () => Promise<string | undefined>;
142
- // The host touches, injectable so a test drives a whole scenario with fake
143
- // paths and never spawns anything. Production passes none of these.
144
- readonly isExecutableFile?: ((path: string) => boolean) | undefined;
145
- readonly realPath?: ((path: string) => string) | undefined;
146
- readonly probeVersion?: ((path: string) => string | undefined) | undefined;
147
- }): Promise<ReachabilityCheckResult> {
148
- const command = input.commandName ?? basename(input.execPath);
149
- const pathEntries = splitPathEntries(input.pathValue);
150
- const installKind = detectInstallKind(input.execPath);
151
- const fileIsExecutable = input.isExecutableFile ?? isExecutableFile;
152
- const resolvePath = input.realPath ?? safeRealPath;
153
- const versionProbe = input.probeVersion ?? probeVersionLine;
21
+ /** The command name the shell resolves this terminal by. */
22
+ const TERMINAL_COMMAND_NAME = 'goodvibes';
154
23
 
155
- // A source checkout is not an install: there is no maintained copy to be
156
- // shadowed and no release to be behind.
157
- if (installKind === 'source') return { notices: [] };
158
-
159
- const selfDirectory = resolveSelfDirectory({
160
- execPath: input.execPath,
161
- command,
162
- pathEntries,
163
- realPath: resolvePath,
164
- isExecutableFile: fileIsExecutable,
24
+ /** The same release lookup `/update` uses — there is deliberately no second source of truth. */
25
+ function resolveLatestRelease(): Promise<string | undefined> {
26
+ return boundedLatestRelease(async () => {
27
+ const result = await checkForUpdate(fetch as UpdateFetchLike, VERSION);
28
+ return result.latestTag;
165
29
  });
166
- if (!selfDirectory) return { notices: [] };
167
-
168
- const commands = commandsPresentIn(selfDirectory, fileIsExecutable);
169
- const base = {
170
- commands: commands.length > 0 ? commands : [command],
171
- installDir: selfDirectory,
172
- pathEntries,
173
- homeDir: input.homeDir,
174
- isExecutableFile: fileIsExecutable,
175
- realPath: resolvePath,
176
- };
177
-
178
- // Existence-only first: nothing is spawned while there is nothing to report.
179
- const cheapScan = scanCommandShadows(base);
180
- const scan = cheapScan.hasProblem
181
- ? scanCommandShadows({ ...base, probeVersion: versionProbe })
182
- : cheapScan;
183
-
184
- const latestVersion = scan.hasProblem || installKind !== 'binary'
185
- ? await input.resolveLatest()
186
- : undefined;
187
-
188
- return {
189
- scan,
190
- notices: buildReachabilityNotices({
191
- scan,
192
- runningVersion: input.runningVersion,
193
- latestVersion,
194
- updateCommand: installKind === 'binary'
195
- ? 'curl -fsSL https://goodvibes.sh/install.sh | sh'
196
- : fallbackUpdateCommand(installKind),
197
- }),
198
- };
199
- }
200
-
201
- /** The real latest-release lookup, bounded so a slow network cannot hold up boot. */
202
- async function resolveLatestReleaseBounded(): Promise<string | undefined> {
203
- try {
204
- // The same release lookup /update uses — there is deliberately no second
205
- // source of truth for what the latest version is.
206
- const lookup = checkForUpdate(fetch as UpdateFetchLike, VERSION).then((result) => result.latestTag);
207
- const timed = await Promise.race([
208
- lookup,
209
- new Promise<undefined>((resolve) => {
210
- const timer = setTimeout(() => resolve(undefined), LATEST_LOOKUP_TIMEOUT_MS);
211
- timer.unref?.();
212
- }),
213
- ]);
214
- return timed ?? undefined;
215
- } catch {
216
- return undefined;
217
- }
218
30
  }
219
31
 
220
32
  /**
221
33
  * Boot-time wiring. Prints one high-priority system message per line so the
222
34
  * report reads as prose rather than a single wrapped paragraph, and swallows
223
- * everything.
35
+ * everything — a reachability check must never block or crash boot.
224
36
  */
225
- export async function announceReachability(router: SystemMessageRouter): Promise<void> {
226
- try {
227
- const result = await runReachabilityCheck({
37
+ export async function announceInstallReachability(router: SystemMessageRouter): Promise<void> {
38
+ await announceReachability(
39
+ {
228
40
  execPath: process.execPath,
229
41
  pathValue: process.env['PATH'],
230
42
  homeDir: process.env['HOME'] ?? '',
231
43
  runningVersion: VERSION,
232
- commandName: 'goodvibes',
233
- resolveLatest: resolveLatestReleaseBounded,
234
- });
235
- for (const notice of result.notices) {
236
- for (const line of notice.lines) {
237
- router.high(`[Install] ${line}`);
238
- }
239
- }
240
- } catch {
241
- // Best-effort — a reachability check must never block or crash boot.
242
- }
44
+ commandName: TERMINAL_COMMAND_NAME,
45
+ packageName: TERMINAL_PACKAGE_NAME,
46
+ resolveLatest: resolveLatestRelease,
47
+ },
48
+ (line) => router.high(`[Install] ${line}`),
49
+ );
243
50
  }
@@ -13,8 +13,9 @@
13
13
  // A single config subscriber carries ALL THREE toggle paths (/power, Alt+A, the
14
14
  // settings modal): each lands on the power.keepAwake config key — the local
15
15
  // PowerManager persists it on set, and the settings modal writes it directly —
16
- // so subscribing to that one key catches every origin. In the EMBEDDED topology
17
- // this is a no-op: the in-process manager already IS the daemon.
16
+ // so subscribing to that one key catches every origin. When there is no adopted
17
+ // external daemon (`isExternalDaemon()` false), this is a no-op: there is nothing
18
+ // to forward to.
18
19
  // ---------------------------------------------------------------------------
19
20
 
20
21
  import type { ConfigManager } from '@pellux/goodvibes-sdk/platform/config';
@@ -22,10 +22,10 @@ import { existsSync } from 'node:fs';
22
22
  import { flushActivityLogSync, logger, summarizeError } from '@pellux/goodvibes-sdk/platform/utils';
23
23
  import { createTerminalLifecycle, TERMINAL_ESCAPES } from '@pellux/goodvibes-terminal-shell';
24
24
  import { formatUserFacingErrorLine } from '../core/format-user-error.ts';
25
- import { allowTerminalWrite } from './terminal-output-guard.ts';
25
+ import { allowTerminalWrite } from '@pellux/goodvibes-terminal-shell/terminal-output-guard';
26
26
  import { buildPersistedSessionContext, deleteRecoveryFile } from '@/runtime/index.ts';
27
27
  import type { SessionSurface } from '@/runtime/index.ts';
28
- import { removeLivenessMarker } from './session-liveness-marker.ts';
28
+ import { removeLivenessMarker } from '@pellux/goodvibes-sdk/platform/runtime/operations';
29
29
  import type { BootstrapContext } from './bootstrap.ts';
30
30
  import type { InputHandler } from '../input/handler.ts';
31
31
  import type { ConversationMessageSnapshot } from '../core/conversation.ts';
@@ -1,74 +1,18 @@
1
1
  /**
2
- * provider-fallback.ts — the pre-catalog fallback registration for the
3
- * configured model, extracted verbatim from services.ts (file-size hygiene):
4
- * before the model catalog cache has loaded, the configured provider:model is
5
- * registered with SDK family-aware context-window inference so the meter and
6
- * compaction denominator agree with the post-catalog window.
2
+ * provider-fallback.ts — boot-time model routability, the part the SDK does
3
+ * not cover.
4
+ *
5
+ * The pre-catalog fallback registration for the configured model
6
+ * (`ensureConfiguredModelIsRoutable`) is the SDK's own
7
+ * (`@pellux/goodvibes-sdk/platform/providers`) — this file only imports it for
8
+ * `ensureBootModelResolvable` below, which is NOT in the SDK: it is the
9
+ * boot-time custom-provider-readiness wrapper this app's boot path needs and
10
+ * the SDK's floor does not compose.
7
11
  */
8
12
  import type { ConfigManager } from '@pellux/goodvibes-sdk/platform/config';
9
- import {
10
- inferFallbackContextWindow,
11
- resolveReasoningEffortSpec,
12
- type ModelDefinition,
13
- type ProviderRegistry,
14
- } from '@pellux/goodvibes-sdk/platform/providers';
13
+ import { ensureConfiguredModelIsRoutable, type ProviderRegistry } from '@pellux/goodvibes-sdk/platform/providers';
15
14
  import { logger } from '@pellux/goodvibes-sdk/platform/utils';
16
15
 
17
- function buildFallbackModelDefinition(provider: string, modelId: string): ModelDefinition {
18
- const providerLower = provider.toLowerCase();
19
- const isReasoningProvider = providerLower.includes('openai')
20
- || providerLower.includes('anthropic')
21
- || providerLower.includes('gemini')
22
- || providerLower.includes('google');
23
-
24
- return {
25
- id: modelId,
26
- provider,
27
- registryKey: `${provider}:${modelId}`,
28
- displayName: modelId,
29
- description: 'Configured model available before the model catalog cache has loaded.',
30
- capabilities: {
31
- toolCalling: true,
32
- codeEditing: true,
33
- reasoning: isReasoningProvider,
34
- multimodal: isReasoningProvider,
35
- },
36
- contextWindow: inferFallbackContextWindow(provider, modelId),
37
- contextWindowProvenance: 'fallback',
38
- selectable: true,
39
- tier: 'standard',
40
- // Which levels this model accepts is a property of the model, not of the
41
- // provider it sits behind: the hardcoded four ('instant', 'low', 'medium',
42
- // 'high') offered 'instant' to models that reject it and hid 'xhigh',
43
- // 'max' and 'none' from models that accept them. The SDK resolver answers
44
- // from the curated family table when the catalog has not loaded yet, and
45
- // otherwise returns its own labelled best guess, which the adapters treat
46
- // as "send nothing" rather than as verified levels.
47
- ...(isReasoningProvider
48
- ? { reasoningEffort: resolveReasoningEffortSpec({ modelId }) }
49
- : {}),
50
- };
51
- }
52
-
53
- export function ensureConfiguredModelIsRoutable(providerRegistry: ProviderRegistry, configManager: ConfigManager): void {
54
- const configuredModel = String(configManager.get('provider.model') ?? '').trim();
55
- if (!configuredModel.includes(':')) return;
56
- if (providerRegistry.listModels().some((model) => model.registryKey === configuredModel)) return;
57
-
58
- const [providerId, ...modelParts] = configuredModel.split(':');
59
- const modelId = modelParts.join(':').trim();
60
- if (!providerId || !modelId) return;
61
-
62
- const provider = providerRegistry.tryGet(providerId);
63
- if (!provider) return;
64
-
65
- providerRegistry.registerRuntimeProvider({
66
- provider,
67
- replace: true,
68
- models: [buildFallbackModelDefinition(providerId, modelId)],
69
- });
70
- }
71
-
72
16
  /**
73
17
  * Boot-time custom-provider readiness. Custom providers register
74
18
  * asynchronously (services.ts fires initCustomProviders() without awaiting),
@@ -14,7 +14,7 @@
14
14
  */
15
15
  import { buildPersistedSessionContext, writeRecoveryFile } from '@/runtime/index.ts';
16
16
  import type { SessionSurface } from '@/runtime/index.ts';
17
- import { writeLivenessMarker } from './session-liveness-marker.ts';
17
+ import { writeLivenessMarker } from '@pellux/goodvibes-sdk/platform/runtime/operations';
18
18
  import type { ConversationManager, ConversationMessageSnapshot } from '../core/conversation.ts';
19
19
 
20
20
  export interface StartRecoveryAutosaveOptions {
@@ -8,21 +8,25 @@
8
8
  * booted runtime).
9
9
  *
10
10
  * What accepting a snapshot actually does, and why each step is here:
11
- * - `runtime.sessionId` moves to the snapshot's session FIRST. The
12
- * transcript journal rebinds on that field (turn-event-wiring.ts), and
13
- * every later turn snapshot keys off it. Without this the restored
14
- * conversation would be written back under the fresh boot session id —
15
- * the recovery point would be gone and its content would live somewhere
16
- * the user never asked for.
17
- * - The conversation is rebuilt through core/session-recovery.ts's
18
- * `applyRecoverySnapshot`, which runs the same reset/fromJSON/rebuild +
19
- * journal-replay sequence a normal resume runs.
11
+ * - `runtime.sessionId` moves to the snapshot's session, once the restore has
12
+ * actually happened. The transcript journal rebinds on that field
13
+ * (turn-event-wiring.ts), and every later turn snapshot keys off it.
14
+ * Without this the restored conversation would be written back under the
15
+ * fresh boot session id — the recovery point would be gone and its content
16
+ * would live somewhere the user never asked for. It moves AFTER the apply
17
+ * rather than before, so a refusal (nothing on disk, a damaged file) leaves
18
+ * this boot on its own session instead of adopting one it never loaded.
19
+ * - The conversation is rebuilt through the SDK's `applyRecoverySnapshot`,
20
+ * which reads and retires the snapshot in one operation and runs the same
21
+ * reset/fromJSON/rebuild + journal-replay sequence a normal resume runs.
22
+ * This module supplies the live conversation, the persist callback and the
23
+ * user's answer; nothing else about the restore is decided here.
20
24
  * - The last-session pointer is written, so `--continue` on the next launch
21
25
  * reaches the session the user just recovered.
22
26
  * - Footer token counters are rehydrated from the restored history, so the
23
27
  * numbers on screen describe the conversation actually in memory.
24
28
  */
25
- import { applyRecoverySnapshot, type RecoverySnapshotPayload } from '../core/session-recovery.ts';
29
+ import { applyRecoverySnapshot, confirmRecoveryRestore } from '@pellux/goodvibes-sdk/platform/runtime/operations';
26
30
  import type { RecoveryPromptDeps } from './recovery-prompt.ts';
27
31
  import type { ConversationManager, ConversationMessageSnapshot } from '../core/conversation.ts';
28
32
  import type { CommandContext } from '../input/command-registry.ts';
@@ -50,12 +54,10 @@ export function buildRecoveryOfferWiring(input: RecoveryOfferWiringInput): Recov
50
54
  get openSelection() { return input.commandContext.openSelection; },
51
55
  receipt: input.receipt,
52
56
  render: input.render,
53
- applySnapshot: ({ snapshot, sessionId }) => {
54
- input.runtime.sessionId = sessionId;
55
- const payload = snapshot as unknown as RecoverySnapshotPayload;
57
+ applySnapshot: ({ sessionId }) => {
56
58
  const persist = (messages: ConversationMessageSnapshot[]): void => {
57
59
  input.sessionManager.save(sessionId, messages as never[], {
58
- title: input.conversation.title || payload.title || '',
60
+ title: input.conversation.title || '',
59
61
  model: input.runtime.model,
60
62
  provider: input.runtime.provider,
61
63
  timestamp: Date.now(),
@@ -65,25 +67,39 @@ export function buildRecoveryOfferWiring(input: RecoveryOfferWiringInput): Recov
65
67
  saveSource: 'user',
66
68
  });
67
69
  };
68
- const { messageCount } = applyRecoverySnapshot({
69
- snapshot: payload,
70
+ // The user picked "Resume it" in the offer modal — that answer, and
71
+ // nothing else, is what unlocks the restore.
72
+ const result = applyRecoverySnapshot({
70
73
  sessionId,
71
74
  conversation: input.conversation,
72
75
  surface: input.surface,
73
76
  persistSnapshot: persist,
77
+ confirmation: confirmRecoveryRestore(true)!,
74
78
  });
79
+ // A refusal leaves this boot exactly as it was: the fresh session id
80
+ // stands, nothing is persisted, and no last-session pointer is written.
81
+ // The prompt turns the refusal into a receipt.
82
+ if (!result.applied) return result;
83
+ // Now that the conversation really is the recovered one, the runtime
84
+ // moves to its session. The transcript journal rebinds on this field
85
+ // (turn-event-wiring.ts) and every later turn snapshot keys off it, so
86
+ // leaving it on the fresh boot session would write the restored
87
+ // conversation back under an id the user never asked for. Both the
88
+ // persist above and the one below name the session explicitly, so
89
+ // neither depends on this assignment having happened first.
90
+ input.runtime.sessionId = sessionId;
75
91
  // Unconditional, and the reason is the whole point of this feature: a
76
92
  // crash-recovery snapshot is NOT a session-store entry. The snapshot
77
- // file has just been retired by consumeRecovery, so until this write
78
- // lands the recovered conversation exists only in memory — the pointer
79
- // below would name a session nothing could load, and `/session resume
80
- // <id>` would still not reach it. The gap-closure persist above only
81
- // fires when the journal actually had records to replay, so it cannot
82
- // be relied on for this.
93
+ // file has just been retired by the read that loaded it, so until this
94
+ // write lands the recovered conversation exists only in memory — the
95
+ // pointer below would name a session nothing could load, and `/session
96
+ // resume <id>` would still not reach it. The gap-closure persist above
97
+ // only fires when the journal actually had records to replay, so it
98
+ // cannot be relied on for this.
83
99
  persist(input.conversation.getMessageSnapshot() as ConversationMessageSnapshot[]);
84
100
  input.writeLastSessionPointer(sessionId);
85
101
  input.commandContext.session?.hydrateSessionUsage?.();
86
- return messageCount;
102
+ return result;
87
103
  },
88
104
  };
89
105
  }
@@ -14,11 +14,14 @@
14
14
  * Rules this flow keeps:
15
15
  * - Nothing is ever applied without the user picking Resume. There is no
16
16
  * silent auto-restore path here, and this module never loads a snapshot
17
- * just to look at it — `checkRecoveryFile` reports, `consumeRecovery`
18
- * loads, and only the second one runs after an explicit yes.
19
- * - The SDK's `consumeRecovery` is load-then-delete: the snapshot file is
20
- * retired only after a successful load, so a failed read can never
21
- * destroy state that was never actually recovered.
17
+ * just to look at it — `checkRecoveryFile` reports, and only an explicit
18
+ * yes reaches the code that opens the file.
19
+ * - The SDK's `applyRecoverySnapshot` is what runs on that yes, and it will
20
+ * not run without the confirmation token this flow hands it. It reads,
21
+ * retires and applies in one operation, load-then-delete: the file is
22
+ * retired only after a successful load, so a failed read can never destroy
23
+ * state that was never actually recovered — and a successful load can
24
+ * never leave a restored snapshot behind to be offered again.
22
25
  * - Every fact shown is one we actually have (session id, snapshot age,
23
26
  * title when the snapshot carries one, byte size when the file is where
24
27
  * we can stat it). Nothing is estimated to fill the sentence out.
@@ -51,10 +54,11 @@
51
54
  * decisions cannot quietly grow forever and nothing is deleted in silence.
52
55
  */
53
56
  import { statSync } from 'node:fs';
54
- import { checkRecoveryFile, checkRecoveryForSession, consumeRecovery, removeRecoveryPoint } from '@/runtime/index.ts';
57
+ import { checkRecoveryFile, checkRecoveryForSession, removeRecoveryPoint } from '@/runtime/index.ts';
58
+ import type { ApplyRecoverySnapshotResult } from '@pellux/goodvibes-sdk/platform/runtime/operations';
55
59
  import type { RecoveryFileInfo, SessionSurface } from '@/runtime/index.ts';
56
60
  import { isRecoveryRemovalRecorded, pruneRecoveryDecisions, recordRecoveryRemoval } from './recovery-decisions.ts';
57
- import { checkSessionLiveness } from './session-liveness-marker.ts';
61
+ import { checkSessionLiveness } from '@pellux/goodvibes-sdk/platform/runtime/operations';
58
62
  import type { SelectionItem, SelectionResult } from '../input/selection-modal.ts';
59
63
 
60
64
  /** How the offer ended. Returned so startup wiring (and tests) can assert the real outcome. */
@@ -79,10 +83,16 @@ export interface RecoveryPromptDeps {
79
83
  readonly openSelection: SelectionOpener | undefined;
80
84
  /**
81
85
  * Apply an explicitly-accepted snapshot to the live conversation and report
82
- * how many messages ended up in it. Supplied by the startup wiring so this
83
- * module never reaches into conversation state itself.
86
+ * what happened. Supplied by the startup wiring so this module never reaches
87
+ * into conversation state itself.
88
+ *
89
+ * It takes a session id, not a loaded snapshot: reading the snapshot and
90
+ * retiring it are one operation in the SDK's `applyRecoverySnapshot`, so this
91
+ * flow cannot load one and then leave it behind. That is why "could not read
92
+ * it" arrives here as a refusal in the result rather than as a separate
93
+ * consume step this module runs first.
84
94
  */
85
- readonly applySnapshot: (payload: { readonly snapshot: Record<string, unknown>; readonly sessionId: string }) => number;
95
+ readonly applySnapshot: (payload: { readonly sessionId: string }) => ApplyRecoverySnapshotResult;
86
96
  /** One-line honest receipt into the transcript. */
87
97
  readonly receipt: (line: string) => void;
88
98
  readonly render: () => void;
@@ -301,16 +311,20 @@ export async function offerRecoverySnapshot(deps: RecoveryPromptDeps): Promise<R
301
311
  const answer = await ask(open, RECOVERY_OFFER_TITLE, buildRecoveryOfferItems(facts));
302
312
 
303
313
  if (answer === 'resume') {
304
- const { snapshot, consumed } = consumeRecovery(deps.surface, info.sessionId);
305
- if (!snapshot || !consumed) {
306
- // consumeRecovery leaves the file alone when the load fails, so the
307
- // snapshot is still there to try again next launch. Say so.
308
- deps.receipt('Recovery snapshot could not be read — it was left on disk and will be offered again next launch.');
314
+ const result = deps.applySnapshot({ sessionId: info.sessionId });
315
+ if (!result.applied) {
316
+ // Each refusal is a different fact and gets its own sentence, because
317
+ // "left on disk, try again next launch" and "the file was damaged and
318
+ // is gone" are not the same news.
319
+ deps.receipt(
320
+ result.refusal === 'no-snapshot'
321
+ ? 'Recovery snapshot could not be read — it was left on disk and will be offered again next launch.'
322
+ : `Recovery snapshot for session ${info.sessionId} could not be restored — the file was damaged, and reading it retired it. Nothing was changed in this session.`,
323
+ );
309
324
  deps.render();
310
325
  return 'resume-failed';
311
326
  }
312
- const messageCount = deps.applySnapshot({ snapshot: snapshot as unknown as Record<string, unknown>, sessionId: info.sessionId });
313
- deps.receipt(`Recovery snapshot restored: ${messageCount} message(s) from session ${info.sessionId}. The recovery point has been retired.`);
327
+ deps.receipt(`Recovery snapshot restored: ${result.messageCount} message(s) from session ${info.sessionId}. The recovery point has been retired.`);
314
328
  deps.render();
315
329
  return 'resumed';
316
330
  }