@pellux/goodvibes-tui 1.27.1 → 2.0.2

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 +127 -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 +9 -122
  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 +21 -33
  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
@@ -10,32 +10,31 @@
10
10
  * - lifecycle.ts: save/shutdown helpers
11
11
  */
12
12
  import { join } from 'node:path';
13
- import { Orchestrator, type OrchestratorUserInputOptions } from '../core/orchestrator.ts';
13
+ import { Orchestrator, type OrchestratorUserInputOptions } from '@pellux/goodvibes-sdk/platform/core';
14
14
  import { AcpManager } from '@pellux/goodvibes-sdk/platform/acp';
15
15
  import { getTierPromptSupplement, getTierForContextWindow } from '@pellux/goodvibes-sdk/platform/providers';
16
16
  import { logger, summarizeError } from '@pellux/goodvibes-sdk/platform/utils';
17
17
  import type { PermissionRequestHandler } from '@pellux/goodvibes-sdk/platform/permissions';
18
- import type { WorkspaceTrustLevel } from './trust/workspace-trust.ts';
18
+ import type { WorkspaceTrustLevel } from '@pellux/goodvibes-sdk/platform/runtime/operations';
19
19
  import type { CommandContext } from '../input/command-registry.ts';
20
20
  import type { InputHistory } from '../input/input-history.ts';
21
21
  import type { GitStatusProvider, GitHeaderInfo } from '../renderer/git-status.ts';
22
- import type { SelectionManager } from '../input/selection.ts';
22
+ import type { SelectionManager } from '@pellux/goodvibes-terminal-shell';
23
23
  import type { Compositor } from '../renderer/compositor.ts';
24
24
 
25
25
  import type { RuntimeContext, BootstrapOptions } from './context.ts';
26
26
  import type { SystemMessageRouter } from '../core/system-message-router.ts';
27
27
  import {
28
28
  shutdownRuntime, fireSessionStart, createTaskManager, OpsControlPlane, AcpTaskAdapter,
29
- emitSessionReady, emitSessionStarted, loadLastConversation,
29
+ emitSessionReady, emitSessionStarted, loadLastConversation, leaveHostedSessionOnExit,
30
30
  scheduleBackgroundMcpDiscovery, startBackgroundProviderRegistration, restoreSavedModel, startExternalServices,
31
31
  type ExternalServicesHandle, type HostServiceStatus, createHttpTransport, createDeferredStartupCoordinator,
32
32
  } from '@/runtime/index.ts';
33
- import { bindWriteLastSessionPointerToSurface } from './session-pointer-surface.ts';
33
+ import { bindWriteLastSessionPointerToSurface } from '@pellux/goodvibes-sdk/platform/runtime/operations';
34
34
  import { foldLegacySpineStore, deriveSpineFooterStatus } from '@pellux/goodvibes-sdk/platform/runtime/session-spine';
35
- import { createTuiSpineTransport, type SpineSessionsClient } from './session-spine-transport.ts';
36
- import { syncMemorySpineToHostStatus, type MemorySpineActiveRef } from './memory-spine-transport.ts';
35
+ import { createSpineAdoptionSync } from './client/spine-adoption.ts';
37
36
  import { pruneStaleOperatorTokens } from '@pellux/goodvibes-sdk/platform/pairing';
38
- import { resolveDaemonCompanionToken, workspaceOperatorTokenCandidates } from './operator-token-cleanup.ts';
37
+ import { resolveDaemonCompanionToken, workspaceOperatorTokenCandidates } from '@pellux/goodvibes-sdk/platform/runtime/operations';
39
38
  import type { UiRuntimeServices } from './ui-services.ts';
40
39
  import { initializeBootstrapCore } from './bootstrap-core.ts';
41
40
  import { ensureBootModelResolvable } from './provider-fallback.ts';
@@ -43,11 +42,16 @@ import { createBootstrapShell } from './bootstrap-shell.ts';
43
42
  import { announceResumeState } from './resume-notice.ts';
44
43
  import { announceInstallHealth } from './install-self-check-startup.ts';
45
44
  import { buildSharedOrchestratorCoreServices, refreshMemoryRecallSnapshot } from './orchestrator-core-services.ts';
46
- import { consumeDaemonAttachNotices, consumeExternalDaemonAttachNotices } from './daemon-attach-notices.ts';
45
+ import { consumeDaemonAttachNotices } from './daemon-attach-notices.ts';
47
46
  import { wireContextAccountingSource } from './context-accounting-source.ts';
48
- import { createEmbeddedServiceFactories } from './embedded-service-factories.ts';
49
- import { buildRelayExternalServiceMethods } from './relay-reachability-bridge.ts';
47
+ import { autostartInstalledDaemon, createDaemonServiceControl, describeDaemonAutostart } from '@pellux/goodvibes-sdk/platform/runtime/client';
48
+ import { createDaemonAttachHandshake } from './client/daemon-attach-handshake.ts';
49
+ import {
50
+ createPendingServiceStatus, hostServiceIsActive, hostServiceIsBlocked,
51
+ } from './client/host-service-status.ts';
52
+ import { relayReadAccessors } from './relay-reachability-bridge.ts';
50
53
  import { startMcpConfigAutoReload } from '../mcp/runtime-reload.ts';
54
+ import { GOODVIBES_TUI_SURFACE_ROOT } from '../config/surface.ts';
51
55
 
52
56
  type ExternalServiceFactories = NonNullable<Parameters<typeof startExternalServices>[4]>;
53
57
 
@@ -196,9 +200,6 @@ export async function bootstrapRuntime(
196
200
  } = services;
197
201
  // A saved custom-provider model must never crash boot — see provider-fallback.ts.
198
202
  await ensureBootModelResolvable(providerRegistry, configManager);
199
- // A liveness flip is only PAINTED once something calls requestRender(); without
200
- // this, the footer's spine segment sat correct-but-undrawn until incidental activity redrew it (minutes, during an idle stretch).
201
- sessionUnionCache.setOnTransition(() => requestRender());
202
203
 
203
204
  // ── Phase 6: Orchestrator + AcpManager ───────────────────────────────────
204
205
 
@@ -350,103 +351,28 @@ export async function bootstrapRuntime(
350
351
 
351
352
  const deferredStartup = createDeferredStartupCoordinator();
352
353
 
353
- const formatHostServiceBaseUrl = (host: string, port: number): string => {
354
- const normalized = host.trim().toLowerCase();
355
- const probeHost = normalized === '0.0.0.0'
356
- ? '127.0.0.1'
357
- : normalized === '::' || normalized === '[::]'
358
- ? '::1'
359
- : host;
360
- const urlHost = probeHost.includes(':') && !probeHost.startsWith('[') ? `[${probeHost}]` : probeHost;
361
- return `http://${urlHost}:${port}`;
362
- };
363
-
364
- const createPendingServiceStatus = (
365
- service: 'daemon' | 'httpListener',
366
- ): HostServiceStatus => {
367
- const host = String(configManager.get(service === 'daemon' ? 'controlPlane.host' : 'httpListener.host') ?? '127.0.0.1');
368
- const port = Number(configManager.get(service === 'daemon' ? 'controlPlane.port' : 'httpListener.port') ?? (service === 'daemon' ? 3421 : 3422));
369
- return {
370
- mode: 'unavailable',
371
- host,
372
- port,
373
- baseUrl: formatHostServiceBaseUrl(host, port),
374
- reason: 'Background service startup has not completed yet',
375
- };
376
- };
377
-
378
- const hostServiceIsActive = (status: HostServiceStatus): boolean => status.mode === 'embedded' || status.mode === 'external';
379
-
380
- // 'blocked' (occupied by an unverified process) and 'incompatible' (occupied
381
- // by a GoodVibes daemon we refused to adopt on a wire-version mismatch) both
382
- // mean the configured port is held and unusable by this TUI instance.
383
- const hostServiceIsBlocked = (status: HostServiceStatus): boolean => status.mode === 'blocked' || status.mode === 'incompatible';
384
-
385
- // PERMANENT DESIGN (docs/decisions/2026-07-06-session-spine-mode-branch-is-permanent.md):
386
- // 'embedded'/'external' are two distinct, both-supported daemon topologies, not
387
- // migration stages — the ONE client-selection point for the session spine, driven by
388
- // the SAME authoritative HostServiceMode adopt-or-start already computed above.
389
- // 'embedded' (daemon.embedInProcess=true) means THIS process's own SharedSessionBroker
390
- // already IS the daemon's broker — nothing to mirror TO, so the spine stays
391
- // permanently dormant. Every other mode ('disabled'/'blocked'/'incompatible'/
392
- // 'unavailable') also stays local-only and honest. Only 'external' (a separately-
393
- // running daemon this TUI adopted) activates the wire mirror.
394
- let spineActiveForBaseUrl: string | null = null;
395
- // Same adoption signal drives the memory spine (WO memory-adopt) — see
396
- // memory-spine-transport.ts. 'embedded' (this process hosts its own daemon)
397
- // stays local: the daemon's canonical store IS this process's own
398
- // memoryRegistry, so there is no wire hop to make.
399
- const memorySpineActiveRef: MemorySpineActiveRef = { value: null };
400
- const syncSessionSpineToHostStatus = (daemonStatus: HostServiceStatus, sharedDaemonToken: string): void => {
401
- syncMemorySpineToHostStatus(services.memorySpine, daemonStatus.mode, daemonStatus.baseUrl, sharedDaemonToken, memorySpineActiveRef, logger);
402
- if (daemonStatus.mode !== 'external') {
403
- if (spineActiveForBaseUrl !== null) {
404
- sessionSpine.deactivate(`daemon mode changed to '${daemonStatus.mode}'`);
405
- sessionInboundInputs.deactivate(`daemon mode changed to '${daemonStatus.mode}'`);
406
- spineActiveForBaseUrl = null;
407
- }
408
- // Keep the read facade honest per topology (permanent, not staged): 'embedded'
409
- // means this process's broker IS the daemon's broker (local reads are the whole
410
- // truth); every other non-external mode is local-only/dormant.
411
- if (daemonStatus.mode === 'embedded') sessionUnionCache.markEmbedded();
412
- else sessionUnionCache.deactivate(`daemon mode '${daemonStatus.mode}'`);
413
- logger.info(`[bootstrap] session spine: daemon mode '${daemonStatus.mode}' — local-only (no spine mirror)`);
414
- return;
415
- }
416
- const baseUrl = daemonStatus.baseUrl;
417
- if (spineActiveForBaseUrl === baseUrl) return; // already wired to this exact adopted daemon
418
- const httpTransport = createHttpTransport({ baseUrl, authToken: sharedDaemonToken });
419
- const sessionsClient: SpineSessionsClient = {
420
- register: (input) => httpTransport.operator.sessions.register(input),
421
- close: (sessionId) => httpTransport.operator.sessions.close(sessionId),
422
- };
423
- sessionSpine.activate(createTuiSpineTransport(sessionsClient));
424
- // Adopt the same daemon's wire for the INBOUND steer path — collect
425
- // steer/follow-up inputs another live surface queued for this session and
426
- // inject them into the turn machinery (acking delivery on the wire).
427
- sessionInboundInputs.activate({
428
- listInputs: async (sessionId, opts) => ({
429
- inputs: await httpTransport.operator.sessions.inputs(sessionId, opts.limit, { state: opts.state, since: opts.since }),
430
- }),
431
- deliverInput: (sessionId, inputId, opts) => httpTransport.operator.sessions.deliverInput(sessionId, inputId, opts),
432
- });
433
- // 'external' only: adopt the same daemon's wire as the read facade's cross-surface
434
- // union source (interval-refreshed; served synchronously to panels).
435
- sessionUnionCache.activate({ list: (limit) => httpTransport.operator.sessions.list(limit) });
436
- spineActiveForBaseUrl = baseUrl;
437
- logger.info(`[bootstrap] session spine: adopted external daemon at ${baseUrl} — mirroring session identity`);
438
- // Legacy fold: one-time (marker-guarded) import of this project's own
439
- // pre-spine control-plane sessions.json into the now-adopted daemon.
440
- const legacyStorePath = services.shellPaths.resolveProjectPath('tui', 'control-plane', 'sessions.json');
441
- const fold = foldLegacySpineStore(sessionSpine, {
442
- storePath: legacyStorePath,
443
- markerPath: `${legacyStorePath}.spine-migrated`,
444
- project: services.workingDirectory,
445
- });
446
- if (fold.folded > 0) {
447
- logger.info(`[bootstrap] session spine: folded ${fold.folded} legacy local session(s) into the adopted daemon`);
448
- }
449
- };
354
+ // Adopting a daemon: session identity, the inbound steer path, the
355
+ // cross-surface union read, and the memory spine — see client/spine-adoption.ts.
356
+ const syncSessionSpineToHostStatus = createSpineAdoptionSync({
357
+ sessionSpine,
358
+ memorySpine: services.memorySpine,
359
+ sessionInboundInputs: sessionInboundInputs as unknown as Parameters<typeof createSpineAdoptionSync>[0]['sessionInboundInputs'],
360
+ sessionUnionCache: sessionUnionCache as unknown as Parameters<typeof createSpineAdoptionSync>[0]['sessionUnionCache'],
361
+ legacyStorePath: services.shellPaths.resolveProjectPath('tui', 'control-plane', 'sessions.json'),
362
+ workingDirectory: services.workingDirectory,
363
+ // Both ride adoption. A message submitted into a session this surface hosts
364
+ // must reach the loop here, because the loop is here; and a rewind driven
365
+ // from the web app or another terminal is answerable only by the process
366
+ // holding the messages, which is also here.
367
+ onAdopted: (client) => {
368
+ services.wireSessionDispatch.activate(client as never);
369
+ if (runtime.sessionId) services.conversationRewindHost.start(runtime.sessionId);
370
+ },
371
+ onDetached: (reason) => {
372
+ services.wireSessionDispatch.deactivate(reason);
373
+ void services.conversationRewindHost.stop();
374
+ },
375
+ });
450
376
 
451
377
  const inspectExternalServices = () => {
452
378
  const daemonStatus = externalServices.daemonStatus;
@@ -467,29 +393,61 @@ export async function bootstrapRuntime(
467
393
  };
468
394
  };
469
395
 
470
- const waitForConfigDrivenRestarts = async (handle: ExternalServicesHandle): Promise<void> => {
471
- const waitForRestart = async (service: unknown): Promise<void> => {
472
- const maybeRestarting = service as { waitForRestart?: unknown } | null;
473
- if (typeof maybeRestarting?.waitForRestart === 'function') {
474
- await maybeRestarting.waitForRestart();
475
- }
476
- };
396
+ // ADOPT ONLY. This app never constructs a DaemonServer or an HttpListener: the
397
+ // daemon is a separate product with its own binary and its own service unit.
398
+ // The factories carry the shared bearer and the daemon's state directory so an
399
+ // adopted daemon is authenticated the same way it always was — no factory that
400
+ // BUILDS anything is passed, and `adoptOnly` makes the SDK refuse to start one
401
+ // even if a future caller did.
402
+ //
403
+ // `daemonRuntimeDir` names the daemon's own STATE directory
404
+ // (`<home>/.goodvibes/daemon`), which is where operator-tokens.json,
405
+ // auth-users.json and daemon-settings.json live — not the home above it. Every
406
+ // reader in this repository resolves it that way.
407
+ const createExternalServiceFactories = (token: string): ExternalServiceFactories => ({
408
+ sharedDaemonToken: token,
409
+ daemonRuntimeDir: join(services.homeDirectory, '.goodvibes', 'daemon'),
410
+ adoptOnly: true,
411
+ });
477
412
 
478
- await Promise.all([
479
- waitForRestart(handle.daemonServer),
480
- waitForRestart(handle.httpListener),
481
- ]);
413
+ // The one bounded recovery step: a daemon that is INSTALLED on this machine
414
+ // and simply stopped gets started once and waited for, rather than becoming
415
+ // the user's homework. Every boundary is in the SDK's client/daemon-autostart.ts.
416
+ const maybeStartInstalledDaemon = async (): Promise<void> => {
417
+ try {
418
+ const outcome = await autostartInstalledDaemon({
419
+ daemonMode: externalServices.daemonStatus.mode,
420
+ control: createDaemonServiceControl({
421
+ configManager,
422
+ workingDirectory: services.workingDirectory,
423
+ homeDirectory: services.homeDirectory,
424
+ }),
425
+ isReachable: async () => await sessionSpine.probeReachability() === 'online',
426
+ });
427
+ if (outcome.action === 'started' || outcome.action === 'came-online') {
428
+ const companionTokenRecord = resolveDaemonCompanionToken(join(services.homeDirectory, '.goodvibes', 'daemon'), GOODVIBES_TUI_SURFACE_ROOT);
429
+ externalServicesPromise = startExternalServices(
430
+ configManager, runtimeBus, hookDispatcher, services,
431
+ createExternalServiceFactories(companionTokenRecord.token),
432
+ );
433
+ externalServices = await externalServicesPromise;
434
+ await daemonHandshake.attach(companionTokenRecord.token);
435
+ }
436
+ const notice = describeDaemonAutostart(
437
+ outcome,
438
+ externalServices.daemonStatus.mode === 'external',
439
+ externalServices.daemonStatus.reason ?? externalServices.daemonStatus.mode,
440
+ );
441
+ if (notice) systemMessageRouter[notice.level](notice.text);
442
+ } catch (error) {
443
+ logger.debug('Boot-time daemon start check failed', { error: summarizeError(error) });
444
+ }
482
445
  };
483
446
 
484
- // Embedded DaemonServer/HttpListener factories (incl. the facade auto-update
485
- // suppression), plus the daemon STATE directory — see embedded-service-factories.ts.
486
- const createExternalServiceFactories = (token: string): ExternalServiceFactories =>
487
- createEmbeddedServiceFactories(token, services.clusterCoordinator, services.clusterGroup.verbs, join(services.homeDirectory, '.goodvibes', 'daemon'));
488
-
489
447
  let externalServices: ExternalServicesHandle = {
490
448
  daemonServer: null, httpListener: null,
491
- daemonStatus: createPendingServiceStatus('daemon'),
492
- httpListenerStatus: createPendingServiceStatus('httpListener'),
449
+ daemonStatus: createPendingServiceStatus(configManager, 'daemon'),
450
+ httpListenerStatus: createPendingServiceStatus(configManager, 'httpListener'),
493
451
  listRecentControlPlaneEvents: () => [],
494
452
  async stop(): Promise<void> {},
495
453
  };
@@ -498,11 +456,12 @@ export async function bootstrapRuntime(
498
456
  externalServices: NonNullable<typeof uiServices.platform.externalServices>;
499
457
  };
500
458
  platformExternalServices.externalServices = {
501
- ...buildRelayExternalServiceMethods(() => externalServices.daemonServer),
459
+ // The relay is a DAEMON feature and reading an adopted daemon's relay state
460
+ // is a verb this contract does not carry, so these two report 'unavailable'
461
+ // rather than a state this process is in no position to know — see
462
+ // relay-reachability-bridge.ts.
463
+ ...relayReadAccessors,
502
464
  inspect: inspectExternalServices,
503
- // Undelivered daemon receipts (update/crash/migration) for the attach-time
504
- // consuming read; empty when no daemon runs. Marked delivered once served.
505
- collectDaemonReceipts: () => (externalServices.daemonServer as { collectDaemonReceipts?: () => readonly { id: string; text: string; at: number }[] } | null)?.collectDaemonReceipts?.() ?? [],
506
465
  restart: async () => {
507
466
  if (externalServicesPromise) {
508
467
  try {
@@ -511,10 +470,9 @@ export async function bootstrapRuntime(
511
470
  // A failed previous startup should not prevent a restart attempt.
512
471
  }
513
472
  }
514
- await waitForConfigDrivenRestarts(externalServices);
515
473
  await externalServices.stop();
516
474
  const daemonHomeDir = join(services.homeDirectory, '.goodvibes', 'daemon');
517
- const companionTokenRecord = resolveDaemonCompanionToken(daemonHomeDir);
475
+ const companionTokenRecord = resolveDaemonCompanionToken(daemonHomeDir, GOODVIBES_TUI_SURFACE_ROOT);
518
476
  externalServicesPromise = startExternalServices(
519
477
  configManager,
520
478
  runtimeBus,
@@ -524,23 +482,39 @@ export async function bootstrapRuntime(
524
482
  );
525
483
  externalServices = await externalServicesPromise;
526
484
  controlPlaneRecentEventsRef.value = (limit) => externalServices.listRecentControlPlaneEvents(limit);
527
- syncSessionSpineToHostStatus(externalServices.daemonStatus, companionTokenRecord.token);
528
- await renderDaemonAttachNotices(companionTokenRecord.token);
485
+ await daemonHandshake.attach(companionTokenRecord.token);
529
486
  requestRender();
530
487
  return inspectExternalServices();
531
488
  },
532
489
  };
533
- // The attach-time consuming read, exactly once as notices. An adopted EXTERNAL daemon has no in-process handle (daemonServer null), so read its own /status?receipts=consume over HTTP; the embedded daemon keeps the in-process fold.
534
- const renderDaemonAttachNotices = async (daemonToken: string): Promise<void> => {
535
- const daemonStatus = externalServices.daemonStatus;
536
- const notices = daemonStatus.mode === 'external' && daemonStatus.baseUrl
537
- ? await consumeExternalDaemonAttachNotices({ baseUrl: daemonStatus.baseUrl, authToken: daemonToken })
538
- : consumeDaemonAttachNotices({ configManager, collectReceipts: () => platformExternalServices.externalServices.collectDaemonReceipts() });
539
- for (const notice of notices) systemMessageRouter.high(`[Daemon] ${notice}`);
540
- };
490
+ // Attaching to a daemon settles both build floors and its undelivered
491
+ // receipts off one /status read — see client/daemon-attach-handshake.ts.
492
+ const daemonHandshake = createDaemonAttachHandshake({
493
+ clientBuildGuard: services.clientBuildGuard,
494
+ readDaemonStatus: () => externalServices.daemonStatus,
495
+ recordRefusal: (status) => { externalServices = { ...externalServices, daemonStatus: status }; },
496
+ adopt: (status, daemonToken) => syncSessionSpineToHostStatus(status, daemonToken),
497
+ notify: (text) => { systemMessageRouter.high(`[Daemon] ${text}`); requestRender(); },
498
+ });
499
+ // A liveness flip is only PAINTED once something calls requestRender(); without
500
+ // this, the footer's spine segment sat correct-but-undrawn until incidental activity redrew it (minutes, during an idle stretch).
501
+ // The same flip is what tells the handshake a daemon came back.
502
+ sessionUnionCache.setOnTransition((online) => {
503
+ requestRender();
504
+ daemonHandshake.onLivenessTransition(online);
505
+ });
541
506
  deferredStartup.schedule({
542
507
  label: 'plugins',
543
508
  run: async () => {
509
+ // Plugin loading stays here: a plugin's commands, tools, providers, voice
510
+ // and media providers and embedding providers all reach things a turn
511
+ // needs in THIS process. Three of the eleven registries a plugin can
512
+ // reach are the daemon's now — gateway methods, channel plugins and
513
+ // delivery strategies — and a registration into those reaches nothing
514
+ // from here. No plugin is affected today (none is installed, and this
515
+ // package bundles none); the classification and the open question about
516
+ // where a daemon-side plugin is loaded are recorded in
517
+ // docs/decisions/2026-07-30-plugin-registrations-split-verb-side-and-surface-side.md.
544
518
  await pluginManager.init({
545
519
  runtimeBus,
546
520
  commandRegistry: pluginCommandRegistry,
@@ -570,9 +544,9 @@ export async function bootstrapRuntime(
570
544
  run: async () => {
571
545
  // Register the persistent companion-pairing token as the daemon's shared
572
546
  // bearer, so tokens scanned from the /qrcode panel's QR actually
573
- // authenticate against the embedded daemon this surface starts.
547
+ // authenticate against the daemon this surface adopts.
574
548
  const daemonHomeDir = join(services.homeDirectory, '.goodvibes', 'daemon');
575
- const companionTokenRecord = resolveDaemonCompanionToken(daemonHomeDir);
549
+ const companionTokenRecord = resolveDaemonCompanionToken(daemonHomeDir, GOODVIBES_TUI_SURFACE_ROOT);
576
550
  // Fix (TUI 0.19.20): remove stale pre-0.21.28 workspace-scoped operator
577
551
  // token files so only the canonical <daemonHomeDir>/operator-tokens.json survives.
578
552
  // The prune is best-effort — it silently skips missing files, no-ops when tokens
@@ -580,7 +554,7 @@ export async function bootstrapRuntime(
580
554
  // See `pruneStaleOperatorTokens` in the SDK for semantics.
581
555
  const prune = pruneStaleOperatorTokens({
582
556
  daemonHomeDir,
583
- candidatePaths: workspaceOperatorTokenCandidates(services.workingDirectory),
557
+ candidatePaths: workspaceOperatorTokenCandidates(services.workingDirectory, GOODVIBES_TUI_SURFACE_ROOT),
584
558
  });
585
559
  if (prune.prunedPaths.length > 0) {
586
560
  logger.info(`[bootstrap] Pruned ${prune.prunedPaths.length} stale operator-token file(s): ${prune.prunedPaths.join(', ')}`);
@@ -596,13 +570,10 @@ export async function bootstrapRuntime(
596
570
  createExternalServiceFactories(companionTokenRecord.token),
597
571
  );
598
572
  externalServices = await externalServicesPromise;
599
- // Join the coordination group even with no embedded daemon: the inbox
600
- // poller is gated on leadership and nothing else would start it when
601
- // daemon.enabled is false. Idempotent with the DaemonServer's own call.
602
- await services.startCluster();
573
+ // Installed-but-stopped recovery, before anything reads the status.
574
+ await maybeStartInstalledDaemon();
603
575
  controlPlaneRecentEventsRef.value = (limit) => externalServices.listRecentControlPlaneEvents(limit);
604
- syncSessionSpineToHostStatus(externalServices.daemonStatus, companionTokenRecord.token);
605
- await renderDaemonAttachNotices(companionTokenRecord.token);
576
+ await daemonHandshake.attach(companionTokenRecord.token);
606
577
  requestRender();
607
578
  },
608
579
  onError: (error) => {
@@ -743,9 +714,9 @@ export async function bootstrapRuntime(
743
714
  // a no-op when the spine was never activated (embedded/local-only topology).
744
715
  sessionSpine.close(runtime.sessionId);
745
716
  sessionSpine.dispose();
746
- sessionInboundInputs.dispose(); // stop the inbound steer poll interval on exit
747
- sessionUnionCache.dispose(); // stop the wire-refresh interval on exit
748
- await deferredStartup.drain(100);
717
+ sessionInboundInputs.dispose(); sessionUnionCache.dispose(); // stop the inbound-steer poll and the wire-refresh interval on exit
718
+ // Quitting has to SAY it is leaving: a hosted session's detach policy applies when the LAST client detaches, so an exit that never detaches leaves a kill-policy session (the shipped default) alive, attached to a process that is gone. Bounded and non-throwing — see client/hosted-exit.ts.
719
+ await Promise.all([leaveHostedSessionOnExit({ configManager, homeDirectory: services.homeDirectory }), deferredStartup.drain(100)]);
749
720
  if (externalServicesPromise) {
750
721
  try {
751
722
  externalServices = await externalServicesPromise;
@@ -0,0 +1,82 @@
1
+ /**
2
+ * approval-updates.ts — this terminal's subscription to approval decisions made
3
+ * somewhere else.
4
+ *
5
+ * ── What this replaces ────────────────────────────────────────────────────
6
+ *
7
+ * The SDK's client raiser (approval-raiser.ts) raises an ask on the daemon and
8
+ * prompts locally at the same time, and then has to learn about a decision made
9
+ * on another surface — a phone, the web UI, a second terminal. Without a push
10
+ * channel wired it learns by RE-READING the record on an interval, which is
11
+ * both slower than it needs to be and the thing the `control.approval_update`
12
+ * event exists to end. Wiring this seam makes a remote decision arrive in the
13
+ * time one frame takes.
14
+ *
15
+ * The interval is deliberately KEPT as the fallback, not removed: a terminal
16
+ * whose daemon refuses the stream (no daemon, a 401, a proxy that will not hold
17
+ * a connection) still has to be able to answer a tool call. `watchApprovalUpdates`
18
+ * reports failure by returning null rather than throwing, and the raiser then
19
+ * behaves exactly as it did before this seam existed. Push is the fast path, not
20
+ * a new dependency — and the raiser's own discipline of doing one immediate read
21
+ * after subscribing stays intact, because a decision can land between the raise
22
+ * and the subscription and no push channel can deliver what happened before it
23
+ * opened.
24
+ *
25
+ * ── Why the product owns this and the SDK does not ────────────────────────
26
+ *
27
+ * Resolving "which daemon" and proving this surface may subscribe to it are
28
+ * trust-boundary concerns the SDK core deliberately never reaches into (the
29
+ * same carve-out `DaemonVerbCaller` records). The resolution here is the one
30
+ * every other client seam in this repo uses: operator-endpoint.ts.
31
+ */
32
+ import { watchApprovalUpdates } from '@pellux/goodvibes-sdk/platform/runtime/client';
33
+ import type { ApprovalUpdateSubscriber } from '@pellux/goodvibes-sdk/platform/runtime/client';
34
+ import type { ConfigManager } from '@pellux/goodvibes-sdk/platform/config';
35
+ import { resolveDaemonEnabled } from '@pellux/goodvibes-sdk/platform/config';
36
+ import { getOrCreateCompanionToken } from '@pellux/goodvibes-sdk/platform/pairing';
37
+ import { logger, summarizeError } from '@pellux/goodvibes-sdk/platform/utils';
38
+ import { resolveControlPlaneBaseUrl, resolveDaemonStateDirectory } from './operator-endpoint.ts';
39
+
40
+ export interface TerminalApprovalUpdateSubscriberOptions {
41
+ readonly configManager: ConfigManager;
42
+ readonly homeDirectory: string | (() => string);
43
+ /** Injectable fetch (tests, or a relay-tunnelled fetch). */
44
+ readonly fetchImpl?: typeof fetch | undefined;
45
+ }
46
+
47
+ /**
48
+ * Build the subscriber the SDK raiser takes.
49
+ *
50
+ * Every refusal is a null return, never a throw: this runs on the path that
51
+ * decides a tool call, and a subscription that cannot be opened must degrade to
52
+ * the interval rather than take the ask down with it.
53
+ */
54
+ export function createTerminalApprovalUpdateSubscriber(
55
+ options: TerminalApprovalUpdateSubscriberOptions,
56
+ ): ApprovalUpdateSubscriber {
57
+ return async (onUpdate) => {
58
+ if (!resolveDaemonEnabled(options.configManager)) return null;
59
+ const baseUrl = resolveControlPlaneBaseUrl(options.configManager);
60
+ if (!baseUrl) return null;
61
+ let token: string;
62
+ try {
63
+ const homeDirectory = typeof options.homeDirectory === 'function'
64
+ ? options.homeDirectory()
65
+ : options.homeDirectory;
66
+ token = getOrCreateCompanionToken('tui', {
67
+ daemonHomeDir: resolveDaemonStateDirectory(homeDirectory),
68
+ }).token;
69
+ } catch (error) {
70
+ logger.debug('[approvals] no bearer token could be read for the approval-update stream', {
71
+ error: summarizeError(error),
72
+ });
73
+ return null;
74
+ }
75
+ return await watchApprovalUpdates({
76
+ baseUrl,
77
+ getAuthToken: () => token,
78
+ onUpdate,
79
+ ...(options.fetchImpl ? { fetchImpl: options.fetchImpl } : {}),
80
+ });
81
+ };
82
+ }