@pellux/goodvibes-tui 1.27.1 → 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 (405) hide show
  1. package/CHANGELOG.md +116 -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 +3 -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 +4 -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 -693
  288. package/src/daemon/fatal-boot-report.ts +0 -111
  289. package/src/daemon/handlers/context.ts +0 -29
  290. package/src/daemon/handlers/contracts.ts +0 -77
  291. package/src/daemon/handlers/credentials.ts +0 -139
  292. package/src/daemon/handlers/drafts/draft-store.ts +0 -427
  293. package/src/daemon/handlers/drafts/index.ts +0 -17
  294. package/src/daemon/handlers/drafts/register.ts +0 -331
  295. package/src/daemon/handlers/errors.ts +0 -18
  296. package/src/daemon/handlers/inbox/cursor-store.ts +0 -457
  297. package/src/daemon/handlers/inbox/index.ts +0 -284
  298. package/src/daemon/handlers/inbox/mapping.ts +0 -192
  299. package/src/daemon/handlers/inbox/poller.ts +0 -212
  300. package/src/daemon/handlers/inbox/provider-adapter.ts +0 -156
  301. package/src/daemon/handlers/inbox/providers/discord.ts +0 -252
  302. package/src/daemon/handlers/inbox/providers/email.ts +0 -152
  303. package/src/daemon/handlers/inbox/providers/imap-client.ts +0 -300
  304. package/src/daemon/handlers/inbox/providers/route-util.ts +0 -24
  305. package/src/daemon/handlers/inbox/providers/slack.ts +0 -263
  306. package/src/daemon/handlers/index.ts +0 -117
  307. package/src/daemon/handlers/register.ts +0 -168
  308. package/src/daemon/handlers/remote/backends/cloud-terminal.ts +0 -142
  309. package/src/daemon/handlers/remote/backends/docker.ts +0 -79
  310. package/src/daemon/handlers/remote/backends/index.ts +0 -40
  311. package/src/daemon/handlers/remote/backends/local-process.ts +0 -113
  312. package/src/daemon/handlers/remote/backends/process-runner.ts +0 -127
  313. package/src/daemon/handlers/remote/backends/ssh.ts +0 -125
  314. package/src/daemon/handlers/remote/backends/types.ts +0 -97
  315. package/src/daemon/handlers/remote/dispatcher.ts +0 -181
  316. package/src/daemon/handlers/remote/index.ts +0 -119
  317. package/src/daemon/handlers/remote/peer-registry.ts +0 -357
  318. package/src/daemon/handlers/remote/service.ts +0 -191
  319. package/src/daemon/handlers/routing/inbox-bridge.ts +0 -71
  320. package/src/daemon/handlers/routing/index.ts +0 -261
  321. package/src/daemon/handlers/routing/route-store.ts +0 -319
  322. package/src/daemon/handlers/routing/routing-resolver.ts +0 -75
  323. package/src/daemon/handlers/sqlite-store.ts +0 -278
  324. package/src/daemon/handlers/triage/index.ts +0 -57
  325. package/src/daemon/handlers/triage/integration.ts +0 -213
  326. package/src/daemon/handlers/triage/pipeline.ts +0 -274
  327. package/src/daemon/handlers/triage/scorer.ts +0 -287
  328. package/src/daemon/handlers/triage/tagger/discord.ts +0 -187
  329. package/src/daemon/handlers/triage/tagger/imap.ts +0 -384
  330. package/src/daemon/handlers/triage/tagger/index.ts +0 -184
  331. package/src/daemon/handlers/triage/tagger/shared.ts +0 -70
  332. package/src/daemon/handlers/triage/tagger/slack.ts +0 -69
  333. package/src/daemon/handlers/triage/types.ts +0 -50
  334. package/src/daemon/lifecycle.ts +0 -41
  335. package/src/daemon/provision-wake-model.ts +0 -81
  336. package/src/daemon/safe-serve.ts +0 -61
  337. package/src/daemon/send/channels.ts +0 -200
  338. package/src/daemon/send/command.ts +0 -332
  339. package/src/daemon/send/composition.ts +0 -99
  340. package/src/daemon/send/failure-text.ts +0 -93
  341. package/src/daemon/send/inert-text.ts +0 -225
  342. package/src/daemon/send/stdin.ts +0 -24
  343. package/src/daemon/service-commands.ts +0 -329
  344. package/src/export/cost-utils.ts +0 -319
  345. package/src/input/selection.ts +0 -135
  346. package/src/plugins/loader.ts +0 -15
  347. package/src/renderer/bottom-bar.ts +0 -58
  348. package/src/renderer/conversation-tree.ts +0 -228
  349. package/src/runtime/agent-graph-composition.ts +0 -78
  350. package/src/runtime/channel-composition.ts +0 -46
  351. package/src/runtime/cluster-composition.ts +0 -124
  352. package/src/runtime/cluster-group-composition.ts +0 -284
  353. package/src/runtime/code-index-services.ts +0 -167
  354. package/src/runtime/daemon-handler-composition.ts +0 -76
  355. package/src/runtime/device-posture-composition.ts +0 -110
  356. package/src/runtime/durability-housekeeping.ts +0 -183
  357. package/src/runtime/durability-services.ts +0 -155
  358. package/src/runtime/embedded-service-factories.ts +0 -74
  359. package/src/runtime/feature-settings.ts +0 -154
  360. package/src/runtime/fleet-needs-input-push.ts +0 -70
  361. package/src/runtime/fleet-services.ts +0 -41
  362. package/src/runtime/idle-power-services.ts +0 -85
  363. package/src/runtime/mail-composition.ts +0 -65
  364. package/src/runtime/memory-fold.ts +0 -55
  365. package/src/runtime/operator-token-cleanup.ts +0 -109
  366. package/src/runtime/path-shadow.ts +0 -418
  367. package/src/runtime/perf/panel-health-monitor.ts +0 -18
  368. package/src/runtime/provider-optimizer-wiring.ts +0 -55
  369. package/src/runtime/reachability-notice.ts +0 -100
  370. package/src/runtime/remote-execution-composition.ts +0 -62
  371. package/src/runtime/session-liveness-marker.ts +0 -257
  372. package/src/runtime/session-pointer-surface.ts +0 -22
  373. package/src/runtime/session-storage-services.ts +0 -41
  374. package/src/runtime/surface-feature-flags.ts +0 -96
  375. package/src/runtime/terminal-output-guard.ts +0 -235
  376. package/src/runtime/trust/workspace-registration.ts +0 -192
  377. package/src/runtime/trust/workspace-trust.ts +0 -229
  378. package/src/runtime/ui/index.ts +0 -39
  379. package/src/runtime/ui/model-picker/data-provider.ts +0 -182
  380. package/src/runtime/ui/model-picker/health-enrichment.ts +0 -231
  381. package/src/runtime/ui/model-picker/index.ts +0 -59
  382. package/src/runtime/ui/model-picker/types.ts +0 -153
  383. package/src/runtime/ui/provider-health/data-provider.ts +0 -244
  384. package/src/runtime/ui/provider-health/fallback-visualizer.ts +0 -71
  385. package/src/runtime/ui/provider-health/index.ts +0 -46
  386. package/src/runtime/ui/provider-health/types.ts +0 -146
  387. package/src/runtime/ui-events.ts +0 -1
  388. package/src/runtime/ui-read-model-helpers.ts +0 -1
  389. package/src/runtime/ui-read-models-observability-maintenance.ts +0 -1
  390. package/src/runtime/ui-read-models-observability-options.ts +0 -1
  391. package/src/runtime/ui-read-models-observability-remote.ts +0 -1
  392. package/src/runtime/ui-read-models-observability-security.ts +0 -1
  393. package/src/runtime/ui-read-models-observability-system.ts +0 -1
  394. package/src/runtime/ui-read-models-observability.ts +0 -1
  395. package/src/runtime/ui-service-queries.ts +0 -1
  396. package/src/runtime/voice-setup-services.ts +0 -98
  397. package/src/runtime/workstream-attempts-validation.ts +0 -77
  398. package/src/runtime/workstream-draft-edits.ts +0 -95
  399. package/src/runtime/workstream-draft-store.ts +0 -274
  400. package/src/runtime/workstream-draft-types.ts +0 -42
  401. package/src/runtime/workstream-services.ts +0 -373
  402. package/src/tools/index.ts +0 -1
  403. package/src/types/grid.ts +0 -48
  404. package/src/types/sql-js.d.ts +0 -15
  405. 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
  }