@pellux/goodvibes-tui 1.27.0 → 2.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (404) hide show
  1. package/CHANGELOG.md +125 -2
  2. package/README.md +7 -9
  3. package/bin/goodvibes +2 -2
  4. package/bin/launcher-support.js +8 -4
  5. package/docs/README.md +11 -2
  6. package/docs/foundation-artifacts/operator-contract.json +6403 -2347
  7. package/package.json +14 -25
  8. package/scripts/postinstall.js +35 -35
  9. package/src/audio/spoken-turn-model-routing.ts +1 -1
  10. package/src/cli/bundle-command.ts +5 -5
  11. package/src/cli/completions/generate.ts +1 -1
  12. package/src/cli/doctor.ts +3 -3
  13. package/src/cli/entrypoint.ts +12 -7
  14. package/src/cli/help.ts +4 -5
  15. package/src/cli/hooks-command.ts +1 -1
  16. package/src/cli/index.ts +1 -4
  17. package/src/cli/launch-auto-update.ts +59 -7
  18. package/src/cli/management-commands.ts +6 -6
  19. package/src/cli/management-utils.ts +5 -5
  20. package/src/cli/management.ts +7 -7
  21. package/src/cli/network-posture.ts +1 -1
  22. package/src/cli/package-verification.ts +7 -6
  23. package/src/cli/plugin-bundles-command.ts +1 -1
  24. package/src/cli/plugin-command.ts +2 -2
  25. package/src/cli/service-command.ts +2 -2
  26. package/src/cli/service-posture.ts +4 -4
  27. package/src/cli/status.ts +9 -8
  28. package/src/cli/surface-command.ts +4 -4
  29. package/src/cli/tui-startup.ts +12 -3
  30. package/src/cli-flags.ts +3 -3
  31. package/src/config/goodvibes-home-audit.ts +0 -2
  32. package/src/config/secret-config.ts +37 -5
  33. package/src/config/tui-extension-settings.ts +1 -1
  34. package/src/core/approval-alert.ts +2 -2
  35. package/src/core/budget-breach-notifier.ts +3 -3
  36. package/src/core/conversation-compaction-render.ts +1 -1
  37. package/src/core/conversation-line-cache.ts +1 -1
  38. package/src/core/conversation-render-context.ts +1 -1
  39. package/src/core/conversation-rendering.ts +2 -2
  40. package/src/core/conversation.ts +10 -10
  41. package/src/core/long-task-notifier.ts +2 -2
  42. package/src/core/memory-diagnostics-gateway.ts +1 -1
  43. package/src/core/scriptable-statusline.ts +1 -1
  44. package/src/core/session-auto-titler.ts +1 -1
  45. package/src/core/session-resume-core.ts +2 -2
  46. package/src/core/stream-event-wiring.ts +24 -44
  47. package/src/core/terminal-notifier.ts +3 -3
  48. package/src/core/turn-cancellation.ts +1 -1
  49. package/src/core/turn-event-wiring.ts +5 -5
  50. package/src/input/command-registry.ts +6 -6
  51. package/src/input/commands/channel-pairing.ts +1 -1
  52. package/src/input/commands/cluster-runtime.ts +1 -1
  53. package/src/input/commands/codebase-runtime.ts +1 -1
  54. package/src/input/commands/devices-runtime.ts +1 -1
  55. package/src/input/commands/hosted-runtime.ts +330 -0
  56. package/src/input/commands/local-setup-review.ts +1 -1
  57. package/src/input/commands/local-setup-transfer.ts +2 -2
  58. package/src/input/commands/local-setup.ts +3 -3
  59. package/src/input/commands/managed-runtime.ts +2 -2
  60. package/src/input/commands/operator-rpc.ts +24 -110
  61. package/src/input/commands/payment-card-intake.ts +1 -1
  62. package/src/input/commands/permissions-provenance.ts +2 -2
  63. package/src/input/commands/plugin-runtime.ts +1 -1
  64. package/src/input/commands/provider.ts +1 -1
  65. package/src/input/commands/relay-runtime.ts +18 -9
  66. package/src/input/commands/rewind-runtime.ts +1 -1
  67. package/src/input/commands/runtime-services.ts +1 -1
  68. package/src/input/commands/services-runtime.ts +1 -1
  69. package/src/input/commands/session-workflow.ts +1 -1
  70. package/src/input/commands/settings-sync-runtime.ts +2 -2
  71. package/src/input/commands/share-runtime.ts +1 -1
  72. package/src/input/commands/tasks-runtime.ts +145 -55
  73. package/src/input/commands/update-runtime.ts +31 -28
  74. package/src/input/commands/work-plan-runtime.ts +2 -2
  75. package/src/input/commands/workstream-attempts.ts +1 -1
  76. package/src/input/commands/workstream-runtime.ts +2 -2
  77. package/src/input/commands.ts +2 -0
  78. package/src/input/feature-unit-layout.ts +1 -1
  79. package/src/input/feed-context-factory.ts +3 -3
  80. package/src/input/handler-content-actions.ts +2 -2
  81. package/src/input/handler-feed-routes.ts +1 -1
  82. package/src/input/handler-feed.ts +3 -3
  83. package/src/input/handler-modal-token-routes.ts +1 -1
  84. package/src/input/handler-onboarding-daemon-adopt.ts +3 -2
  85. package/src/input/handler-onboarding.ts +1 -1
  86. package/src/input/handler-types.ts +2 -2
  87. package/src/input/handler-ui-state.ts +1 -1
  88. package/src/input/handler.ts +4 -4
  89. package/src/input/input-history.ts +1 -1
  90. package/src/input/onboarding/onboarding-feature-units.ts +2 -2
  91. package/src/input/onboarding/onboarding-runtime-status.ts +15 -1
  92. package/src/input/onboarding/onboarding-wizard-apply.ts +17 -8
  93. package/src/input/onboarding/onboarding-wizard-cloudflare-step.ts +5 -3
  94. package/src/input/onboarding/onboarding-wizard-external-surfaces.ts +1 -1
  95. package/src/input/onboarding/onboarding-wizard-types.ts +1 -1
  96. package/src/input/payments-config.ts +6 -6
  97. package/src/input/search.ts +1 -1
  98. package/src/input/session-picker-modal.ts +46 -0
  99. package/src/input/settings-modal-activation.ts +1 -1
  100. package/src/input/settings-modal-behavior.ts +4 -4
  101. package/src/input/settings-modal-commit.ts +11 -0
  102. package/src/input/settings-modal-data.ts +13 -80
  103. package/src/input/settings-modal-mutations.ts +33 -0
  104. package/src/input/settings-modal-secrets.ts +34 -0
  105. package/src/input/settings-modal-subscriptions.ts +1 -1
  106. package/src/input/settings-modal-types.ts +15 -1
  107. package/src/input/settings-modal.ts +28 -3
  108. package/src/input/worktree-setup-config.ts +1 -1
  109. package/src/main.ts +11 -9
  110. package/src/panels/agent-inspector-shared.ts +2 -2
  111. package/src/panels/base-panel.ts +2 -2
  112. package/src/panels/builtin/operations.ts +6 -8
  113. package/src/panels/builtin/session.ts +17 -0
  114. package/src/panels/builtin/shared.ts +5 -5
  115. package/src/panels/builtin-modals.ts +3 -3
  116. package/src/panels/confirm-state.ts +1 -1
  117. package/src/panels/cost-tracker-panel.ts +4 -4
  118. package/src/panels/diff-panel.ts +2 -2
  119. package/src/panels/diff-review-panel.ts +2 -2
  120. package/src/panels/expandable-list-panel.ts +2 -2
  121. package/src/panels/fleet-acts.ts +1 -1
  122. package/src/panels/fleet-observed-render.ts +1 -1
  123. package/src/panels/fleet-panel-format.ts +1 -1
  124. package/src/panels/fleet-panel.ts +1 -1
  125. package/src/panels/fleet-spawn.ts +1 -1
  126. package/src/panels/fleet-steer.ts +1 -1
  127. package/src/panels/fleet-transcript.ts +1 -1
  128. package/src/panels/git-panel.ts +1 -1
  129. package/src/panels/hosted-session-feed.ts +352 -0
  130. package/src/panels/hosted-session-panel.ts +221 -0
  131. package/src/panels/local-auth-panel.ts +3 -3
  132. package/src/panels/modals/devices-modal.ts +1 -1
  133. package/src/panels/modals/local-auth-modal.ts +1 -1
  134. package/src/panels/modals/pairing-modal.ts +1 -1
  135. package/src/panels/modals/services-modal.ts +1 -1
  136. package/src/panels/modals/settings-sync-modal.ts +1 -1
  137. package/src/panels/modals/subscription-modal.ts +1 -1
  138. package/src/panels/modals/work-plan-modal.ts +1 -1
  139. package/src/panels/notifications-panel.ts +1 -1
  140. package/src/panels/panel-confirm-overlay.ts +1 -1
  141. package/src/panels/plugins-panel.ts +2 -2
  142. package/src/panels/polish-core.ts +2 -2
  143. package/src/panels/polish-tables.ts +1 -1
  144. package/src/panels/polish.ts +2 -2
  145. package/src/panels/scrollable-list-panel.ts +3 -3
  146. package/src/panels/skills-panel.ts +3 -3
  147. package/src/panels/token-budget-panel.ts +5 -5
  148. package/src/panels/types.ts +1 -2
  149. package/src/permissions/prompt.ts +1 -1
  150. package/src/renderer/autocomplete-overlay.ts +1 -1
  151. package/src/renderer/block-actions-overlay.ts +1 -1
  152. package/src/renderer/bookmark-modal.ts +1 -1
  153. package/src/renderer/buffer.ts +1 -1
  154. package/src/renderer/code-block.ts +1 -1
  155. package/src/renderer/composer-fragments.ts +1 -1
  156. package/src/renderer/compositor.ts +2 -2
  157. package/src/renderer/config-modal.ts +1 -1
  158. package/src/renderer/context-inspector.ts +1 -1
  159. package/src/renderer/conversation-layout.ts +2 -2
  160. package/src/renderer/conversation-overlays.ts +1 -1
  161. package/src/renderer/conversation-surface.ts +2 -2
  162. package/src/renderer/diff-view.ts +1 -1
  163. package/src/renderer/diff.ts +1 -1
  164. package/src/renderer/file-picker-overlay.ts +1 -1
  165. package/src/renderer/fleet-tab-strip.ts +1 -1
  166. package/src/renderer/fullscreen-primitives.ts +2 -2
  167. package/src/renderer/fullscreen-workspace.ts +1 -1
  168. package/src/renderer/header-line.ts +1 -1
  169. package/src/renderer/help-overlay.ts +1 -1
  170. package/src/renderer/history-search-overlay.ts +2 -2
  171. package/src/renderer/layout.ts +5 -8
  172. package/src/renderer/markdown-inline.ts +1 -1
  173. package/src/renderer/markdown-table.ts +1 -1
  174. package/src/renderer/markdown.ts +1 -1
  175. package/src/renderer/mcp-workspace.ts +1 -1
  176. package/src/renderer/modal-factory.ts +2 -2
  177. package/src/renderer/model-workspace.ts +2 -2
  178. package/src/renderer/onboarding/onboarding-wizard.ts +1 -1
  179. package/src/renderer/overlay-box.ts +1 -1
  180. package/src/renderer/panel-composite.ts +1 -1
  181. package/src/renderer/panel-workspace-bar.ts +1 -1
  182. package/src/renderer/process-indicator.ts +1 -1
  183. package/src/renderer/profile-picker-modal.ts +1 -1
  184. package/src/renderer/search-overlay.ts +2 -2
  185. package/src/renderer/selection-modal-overlay.ts +1 -1
  186. package/src/renderer/session-picker-modal.ts +124 -3
  187. package/src/renderer/settings-modal-helpers.ts +2 -0
  188. package/src/renderer/settings-modal.ts +2 -2
  189. package/src/renderer/shell-surface.ts +2 -2
  190. package/src/renderer/system-message.ts +1 -1
  191. package/src/renderer/tab-strip.ts +1 -1
  192. package/src/renderer/theme-mode-config.ts +1 -1
  193. package/src/renderer/theme.ts +11 -12
  194. package/src/renderer/thinking.ts +1 -1
  195. package/src/renderer/tool-call.ts +2 -2
  196. package/src/renderer/tool-result-expanded-lines.ts +1 -1
  197. package/src/renderer/turn-injection.ts +7 -8
  198. package/src/renderer/ui-factory.ts +3 -3
  199. package/src/runtime/bootstrap-command-context.ts +4 -4
  200. package/src/runtime/bootstrap-command-parts.ts +5 -5
  201. package/src/runtime/bootstrap-core.ts +30 -30
  202. package/src/runtime/bootstrap-shell.ts +4 -4
  203. package/src/runtime/bootstrap.ts +128 -157
  204. package/src/runtime/client/approval-updates.ts +82 -0
  205. package/src/runtime/client/build-floors.ts +167 -0
  206. package/src/runtime/client/daemon-attach-handshake.ts +115 -0
  207. package/src/runtime/client/fleet-union.ts +147 -0
  208. package/src/runtime/client/host-service-status.ts +71 -0
  209. package/src/runtime/client/hosted-exit.ts +86 -0
  210. package/src/runtime/client/hosted-roster.ts +110 -0
  211. package/src/runtime/client/hosted-session-stream.ts +178 -0
  212. package/src/runtime/client/hosted-sessions.ts +187 -0
  213. package/src/runtime/client/operator-endpoint.ts +213 -0
  214. package/src/runtime/client/spine-adoption.ts +99 -0
  215. package/src/runtime/cloudflare-control-plane.ts +1 -1
  216. package/src/runtime/context-accounting-source.ts +1 -1
  217. package/src/runtime/context.ts +2 -2
  218. package/src/runtime/conversation-rewind-port.ts +42 -14
  219. package/src/runtime/daemon-attach-notices.ts +55 -19
  220. package/src/runtime/diagnostics/panels/ops.ts +1 -1
  221. package/src/runtime/disposal-wiring.ts +4 -24
  222. package/src/runtime/index.ts +119 -105
  223. package/src/runtime/install-self-check-startup.ts +2 -2
  224. package/src/runtime/interaction-seams.ts +1 -1
  225. package/src/runtime/knowledge-services.ts +8 -2
  226. package/src/runtime/legacy-daemon-migration.ts +15 -3
  227. package/src/runtime/legacy-daemon-reconcile.ts +3 -2
  228. package/src/runtime/memory-spine-transport.ts +37 -267
  229. package/src/runtime/notification-dispatch.ts +2 -2
  230. package/src/runtime/onboarding/apply.ts +2 -2
  231. package/src/runtime/onboarding/derivation.ts +2 -2
  232. package/src/runtime/onboarding/markers.ts +2 -2
  233. package/src/runtime/onboarding/progress.ts +2 -2
  234. package/src/runtime/onboarding/snapshot.ts +1 -1
  235. package/src/runtime/onboarding/state.ts +2 -2
  236. package/src/runtime/onboarding/types.ts +2 -2
  237. package/src/runtime/orchestrator-core-services.ts +1 -1
  238. package/src/runtime/path-shadow-startup.ts +28 -221
  239. package/src/runtime/power-keepawake-remote.ts +3 -2
  240. package/src/runtime/process-lifecycle.ts +2 -2
  241. package/src/runtime/provider-fallback.ts +10 -66
  242. package/src/runtime/recovery-autosave.ts +1 -1
  243. package/src/runtime/recovery-offer-wiring.ts +39 -23
  244. package/src/runtime/recovery-prompt.ts +31 -17
  245. package/src/runtime/relay-reachability-bridge.ts +53 -42
  246. package/src/runtime/release-artifacts.ts +2 -1
  247. package/src/runtime/runtime-services-types.ts +82 -35
  248. package/src/runtime/services.ts +305 -300
  249. package/src/runtime/session-resume-liveness-confirm.ts +1 -1
  250. package/src/runtime/ui-services.ts +7 -6
  251. package/src/runtime/update-check.ts +23 -43
  252. package/src/runtime/workspace-checkpointing.ts +1 -1
  253. package/src/runtime/wrfc-persistence.ts +2 -2
  254. package/src/shell/ui-openers.ts +30 -7
  255. package/src/utils/clipboard.ts +1 -1
  256. package/src/verification/verification-ledger.ts +10 -8
  257. package/src/version.ts +1 -1
  258. package/bin/goodvibes-daemon +0 -90
  259. package/src/cli/config-overrides.ts +0 -267
  260. package/src/cli/endpoints.ts +0 -109
  261. package/src/cli/parser.ts +0 -395
  262. package/src/cli/redaction.ts +0 -170
  263. package/src/cli/types.ts +0 -84
  264. package/src/cluster/commands.ts +0 -382
  265. package/src/cluster/remote-daemon-target.ts +0 -225
  266. package/src/cluster/render.ts +0 -268
  267. package/src/config/atomic-write.ts +0 -70
  268. package/src/config/credential-availability.ts +0 -122
  269. package/src/config/goodvibes-home.ts +0 -149
  270. package/src/config/index.ts +0 -88
  271. package/src/config/provider-model.ts +0 -23
  272. package/src/config/read-versioned.ts +0 -228
  273. package/src/core/alert-gating.ts +0 -67
  274. package/src/core/consolidation-receipt.ts +0 -41
  275. package/src/core/focus-tracker.ts +0 -41
  276. package/src/core/history.ts +0 -45
  277. package/src/core/memory-status.ts +0 -118
  278. package/src/core/orchestrator.ts +0 -7
  279. package/src/core/pairing-devices.ts +0 -49
  280. package/src/core/pairing-handoff.ts +0 -101
  281. package/src/core/pairing-offers.ts +0 -83
  282. package/src/core/pairing-origin.ts +0 -78
  283. package/src/core/rewind-turn-anchors.ts +0 -311
  284. package/src/core/session-recovery.ts +0 -210
  285. package/src/core/stable-host.ts +0 -171
  286. package/src/core/transcript-journal.ts +0 -503
  287. package/src/daemon/cli.ts +0 -641
  288. package/src/daemon/handlers/context.ts +0 -29
  289. package/src/daemon/handlers/contracts.ts +0 -77
  290. package/src/daemon/handlers/credentials.ts +0 -139
  291. package/src/daemon/handlers/drafts/draft-store.ts +0 -427
  292. package/src/daemon/handlers/drafts/index.ts +0 -17
  293. package/src/daemon/handlers/drafts/register.ts +0 -331
  294. package/src/daemon/handlers/errors.ts +0 -18
  295. package/src/daemon/handlers/inbox/cursor-store.ts +0 -457
  296. package/src/daemon/handlers/inbox/index.ts +0 -284
  297. package/src/daemon/handlers/inbox/mapping.ts +0 -192
  298. package/src/daemon/handlers/inbox/poller.ts +0 -212
  299. package/src/daemon/handlers/inbox/provider-adapter.ts +0 -156
  300. package/src/daemon/handlers/inbox/providers/discord.ts +0 -252
  301. package/src/daemon/handlers/inbox/providers/email.ts +0 -152
  302. package/src/daemon/handlers/inbox/providers/imap-client.ts +0 -300
  303. package/src/daemon/handlers/inbox/providers/route-util.ts +0 -24
  304. package/src/daemon/handlers/inbox/providers/slack.ts +0 -263
  305. package/src/daemon/handlers/index.ts +0 -117
  306. package/src/daemon/handlers/register.ts +0 -168
  307. package/src/daemon/handlers/remote/backends/cloud-terminal.ts +0 -142
  308. package/src/daemon/handlers/remote/backends/docker.ts +0 -79
  309. package/src/daemon/handlers/remote/backends/index.ts +0 -40
  310. package/src/daemon/handlers/remote/backends/local-process.ts +0 -113
  311. package/src/daemon/handlers/remote/backends/process-runner.ts +0 -127
  312. package/src/daemon/handlers/remote/backends/ssh.ts +0 -125
  313. package/src/daemon/handlers/remote/backends/types.ts +0 -97
  314. package/src/daemon/handlers/remote/dispatcher.ts +0 -181
  315. package/src/daemon/handlers/remote/index.ts +0 -119
  316. package/src/daemon/handlers/remote/peer-registry.ts +0 -357
  317. package/src/daemon/handlers/remote/service.ts +0 -191
  318. package/src/daemon/handlers/routing/inbox-bridge.ts +0 -71
  319. package/src/daemon/handlers/routing/index.ts +0 -261
  320. package/src/daemon/handlers/routing/route-store.ts +0 -319
  321. package/src/daemon/handlers/routing/routing-resolver.ts +0 -75
  322. package/src/daemon/handlers/sqlite-store.ts +0 -278
  323. package/src/daemon/handlers/triage/index.ts +0 -57
  324. package/src/daemon/handlers/triage/integration.ts +0 -213
  325. package/src/daemon/handlers/triage/pipeline.ts +0 -274
  326. package/src/daemon/handlers/triage/scorer.ts +0 -287
  327. package/src/daemon/handlers/triage/tagger/discord.ts +0 -187
  328. package/src/daemon/handlers/triage/tagger/imap.ts +0 -384
  329. package/src/daemon/handlers/triage/tagger/index.ts +0 -184
  330. package/src/daemon/handlers/triage/tagger/shared.ts +0 -70
  331. package/src/daemon/handlers/triage/tagger/slack.ts +0 -69
  332. package/src/daemon/handlers/triage/types.ts +0 -50
  333. package/src/daemon/lifecycle.ts +0 -41
  334. package/src/daemon/provision-wake-model.ts +0 -81
  335. package/src/daemon/safe-serve.ts +0 -61
  336. package/src/daemon/send/channels.ts +0 -200
  337. package/src/daemon/send/command.ts +0 -332
  338. package/src/daemon/send/composition.ts +0 -99
  339. package/src/daemon/send/failure-text.ts +0 -93
  340. package/src/daemon/send/inert-text.ts +0 -225
  341. package/src/daemon/send/stdin.ts +0 -24
  342. package/src/daemon/service-commands.ts +0 -329
  343. package/src/export/cost-utils.ts +0 -319
  344. package/src/input/selection.ts +0 -135
  345. package/src/plugins/loader.ts +0 -15
  346. package/src/renderer/bottom-bar.ts +0 -58
  347. package/src/renderer/conversation-tree.ts +0 -228
  348. package/src/runtime/agent-graph-composition.ts +0 -78
  349. package/src/runtime/channel-composition.ts +0 -46
  350. package/src/runtime/cluster-composition.ts +0 -124
  351. package/src/runtime/cluster-group-composition.ts +0 -284
  352. package/src/runtime/code-index-services.ts +0 -167
  353. package/src/runtime/daemon-handler-composition.ts +0 -76
  354. package/src/runtime/device-posture-composition.ts +0 -110
  355. package/src/runtime/durability-housekeeping.ts +0 -183
  356. package/src/runtime/durability-services.ts +0 -155
  357. package/src/runtime/embedded-service-factories.ts +0 -58
  358. package/src/runtime/feature-settings.ts +0 -154
  359. package/src/runtime/fleet-needs-input-push.ts +0 -70
  360. package/src/runtime/fleet-services.ts +0 -41
  361. package/src/runtime/idle-power-services.ts +0 -85
  362. package/src/runtime/mail-composition.ts +0 -65
  363. package/src/runtime/memory-fold.ts +0 -55
  364. package/src/runtime/operator-token-cleanup.ts +0 -109
  365. package/src/runtime/path-shadow.ts +0 -418
  366. package/src/runtime/perf/panel-health-monitor.ts +0 -18
  367. package/src/runtime/provider-optimizer-wiring.ts +0 -55
  368. package/src/runtime/reachability-notice.ts +0 -100
  369. package/src/runtime/remote-execution-composition.ts +0 -62
  370. package/src/runtime/session-liveness-marker.ts +0 -257
  371. package/src/runtime/session-pointer-surface.ts +0 -22
  372. package/src/runtime/session-storage-services.ts +0 -41
  373. package/src/runtime/surface-feature-flags.ts +0 -96
  374. package/src/runtime/terminal-output-guard.ts +0 -235
  375. package/src/runtime/trust/workspace-registration.ts +0 -192
  376. package/src/runtime/trust/workspace-trust.ts +0 -229
  377. package/src/runtime/ui/index.ts +0 -39
  378. package/src/runtime/ui/model-picker/data-provider.ts +0 -182
  379. package/src/runtime/ui/model-picker/health-enrichment.ts +0 -231
  380. package/src/runtime/ui/model-picker/index.ts +0 -59
  381. package/src/runtime/ui/model-picker/types.ts +0 -153
  382. package/src/runtime/ui/provider-health/data-provider.ts +0 -244
  383. package/src/runtime/ui/provider-health/fallback-visualizer.ts +0 -71
  384. package/src/runtime/ui/provider-health/index.ts +0 -46
  385. package/src/runtime/ui/provider-health/types.ts +0 -146
  386. package/src/runtime/ui-events.ts +0 -1
  387. package/src/runtime/ui-read-model-helpers.ts +0 -1
  388. package/src/runtime/ui-read-models-observability-maintenance.ts +0 -1
  389. package/src/runtime/ui-read-models-observability-options.ts +0 -1
  390. package/src/runtime/ui-read-models-observability-remote.ts +0 -1
  391. package/src/runtime/ui-read-models-observability-security.ts +0 -1
  392. package/src/runtime/ui-read-models-observability-system.ts +0 -1
  393. package/src/runtime/ui-read-models-observability.ts +0 -1
  394. package/src/runtime/ui-service-queries.ts +0 -1
  395. package/src/runtime/voice-setup-services.ts +0 -98
  396. package/src/runtime/workstream-attempts-validation.ts +0 -77
  397. package/src/runtime/workstream-draft-edits.ts +0 -95
  398. package/src/runtime/workstream-draft-store.ts +0 -274
  399. package/src/runtime/workstream-draft-types.ts +0 -42
  400. package/src/runtime/workstream-services.ts +0 -373
  401. package/src/tools/index.ts +0 -1
  402. package/src/types/grid.ts +0 -48
  403. package/src/types/sql-js.d.ts +0 -15
  404. package/src/work-plans/work-plan-store.ts +0 -702
package/src/daemon/cli.ts DELETED
@@ -1,641 +0,0 @@
1
- import { homedir } from 'node:os';
2
- import {
3
- ConfigManager,
4
- deriveControlPlaneBaseUrl,
5
- readControlPlaneBinding,
6
- resolveDaemonEnabled,
7
- } from '@pellux/goodvibes-sdk/platform/config';
8
- import type { ConfigKey } from '@pellux/goodvibes-sdk/platform/config';
9
- import { formatProviderModel, getModelIdFromProviderModel, getProviderIdFromModel } from '../config/provider-model.ts';
10
- import { resolveGoodVibesHomeOwnership } from '../config/goodvibes-home.ts';
11
- import { RuntimeEventBus, GlobalNetworkTransportInstaller, configureRuntimeEventBusDefaults, runtimeEventBusOptionsFrom } from '@/runtime/index.ts';
12
- import { bindFeatureSettingsBridge, createFeatureFlagManager, deriveFeatureStates } from '@/runtime/index.ts';
13
- import { createRuntimeStore } from '../runtime/store/index.ts';
14
- import { createRuntimeServices } from '../runtime/services.ts';
15
- import { startDeviceHousekeeping } from '../runtime/device-posture-composition.ts';
16
- import { DaemonServer, HttpListener } from '@pellux/goodvibes-sdk/platform/daemon';
17
- import { createHostPowerSeam } from '@pellux/goodvibes-sdk/platform/power';
18
- import { flushActivityLogSync, logger } from '@pellux/goodvibes-sdk/platform/utils';
19
- import { summarizeError } from '@pellux/goodvibes-sdk/platform/utils';
20
- import {
21
- getOrCreateCompanionToken,
22
- pruneStaleOperatorTokens,
23
- buildPairingHandoffLink,
24
- describeOriginPosture,
25
- generateQrMatrix,
26
- renderQrToString,
27
- } from '@pellux/goodvibes-sdk/platform/pairing';
28
- import { ensurePublicBaseUrl } from '../core/pairing-origin.ts';
29
- import { availablePairingOffers } from '../core/pairing-handoff.ts';
30
- import { formatPairingOffers, formatPostureCapabilities, pairingPostureNotice } from '../core/pairing-offers.ts';
31
- import { workspaceOperatorTokenCandidates } from '../runtime/operator-token-cleanup.ts';
32
- import {
33
- scan,
34
- loadPersistedProviders,
35
- persistProviders,
36
- } from '@pellux/goodvibes-sdk/platform/discovery';
37
- import { createSafeHostServeFactory } from './safe-serve.ts';
38
- import { runProvisionWakeModelCommand } from './provision-wake-model.ts';
39
- import { runSendCommand } from './send/command.ts';
40
- import { createSendStack } from './send/composition.ts';
41
- import { readAllStdin } from './send/stdin.ts';
42
- import { isDaemonServiceSubcommand, resolveInstalledDaemonBinary, runDaemonServiceCli } from './service-commands.ts';
43
- import { runClusterCommand } from '../cluster/commands.ts';
44
- import { resolveConfiguredServiceName } from '../runtime/legacy-daemon-migration.ts';
45
- import { runDaemonConfigMigration } from '../config/run-daemon-config-migration.ts';
46
- import { reconcileRedundantLegacyUnit } from '../runtime/legacy-daemon-reconcile.ts';
47
- import { resolveRuntimeEndpointBinding } from '../cli/endpoints.ts';
48
- import { resolveDaemonUpdateArtifact } from './lifecycle.ts';
49
- import { VERSION } from '../version.ts';
50
-
51
- import {
52
- parseGoodVibesCli,
53
- renderGoodVibesDaemonHelp,
54
- renderGoodVibesVersion,
55
- renderDaemonStartupBanner,
56
- applyRuntimeConfigOverrides,
57
- applyRuntimeConfigValue,
58
- applyRuntimeFeatureFlagOverrides,
59
- applyRuntimeEndpointFlagOverrides,
60
- } from '../cli/index.ts';
61
- type DaemonCliOwnership = {
62
- readonly workingDirectory: string;
63
- /** The GoodVibes tree root — settings, workspace, and discovery all hang off this. */
64
- readonly homeDirectory: string;
65
- /** The daemon's OWN identity home (auth users, operator tokens, daemon settings). */
66
- readonly daemonHomeDirectory: string;
67
- /** True when GOODVIBES_HOME or GOODVIBES_DAEMON_HOME named an override — see goodvibes-home.ts. */
68
- readonly isOverridden: boolean;
69
- };
70
-
71
- // CLI flag parsing delegated to shared module — see src/cli-flags.ts
72
-
73
- type DaemonCliTokens = {
74
- readonly daemonToken: string | undefined;
75
- readonly httpToken: string | undefined;
76
- };
77
-
78
- /**
79
- * Two different directories that used to be one.
80
- *
81
- * `GOODVIBES_DAEMON_HOME` names the DAEMON's home — the identity directory
82
- * holding auth-users.json, operator-tokens.json, and daemon-settings.json. That
83
- * is what the name says and what the SDK's `resolveDaemonHomeDir()` has always
84
- * meant by it. This function used to read it as the GoodVibes tree ROOT, so
85
- * setting it relocated settings, workspace, and every discovery root as well —
86
- * far more than the daemon's own state.
87
- *
88
- * `GOODVIBES_HOME` is the variable for relocating the tree root. It is what a
89
- * test harness or a service unit should set when it wants an isolated tree; the
90
- * daemon home then falls under it unless separately overridden.
91
- */
92
- function resolveDaemonCliOwnership(): DaemonCliOwnership {
93
- // Both roots come from src/config/goodvibes-home.ts, which the CLIENT entry
94
- // point also uses. They were resolved independently, and the client's copy
95
- // simply did not read GOODVIBES_HOME — so a redirected client wrote into the
96
- // real tree while the daemon honoured the redirect.
97
- const { homeDirectory, daemonHomeDirectory, isOverridden } = resolveGoodVibesHomeOwnership();
98
- return {
99
- workingDirectory: process.env['GOODVIBES_WORKING_DIR'] ?? process.cwd(),
100
- homeDirectory,
101
- daemonHomeDirectory,
102
- isOverridden,
103
- };
104
- }
105
-
106
- function readDaemonCliTokens(env: NodeJS.ProcessEnv): DaemonCliTokens {
107
- const daemonToken = env.GOODVIBES_DAEMON_TOKEN;
108
- return {
109
- daemonToken,
110
- httpToken: env.GOODVIBES_HTTP_TOKEN ?? daemonToken,
111
- };
112
- }
113
-
114
- async function main(): Promise<void> {
115
- // `cluster …` is intercepted before the daemon's own flag parser runs, and
116
- // before any runtime is composed.
117
- //
118
- // Before the parser, because the subcommand has its own flag vocabulary
119
- // (--group, --key, --host, --port, --token) that the daemon parser would
120
- // reject as unknown. Before the runtime, because this command talks to a
121
- // daemon that is ALREADY RUNNING — composing a second runtime here would
122
- // build a competing set of state on a machine that already has one.
123
- //
124
- // See remote-daemon-target.ts for the --host/--port/--token convention that
125
- // every later remote-capable subcommand should follow.
126
- const rawArgs = process.argv.slice(2);
127
- if (rawArgs[0] === 'cluster') {
128
- const ownership = resolveDaemonCliOwnership();
129
- const clusterConfig = new ConfigManager({
130
- workingDir: ownership.workingDirectory,
131
- homeDir: ownership.homeDirectory,
132
- surfaceRoot: 'tui',
133
- });
134
- const result = await runClusterCommand({
135
- argv: rawArgs.slice(1),
136
- configManager: clusterConfig,
137
- daemonHomeDir: ownership.daemonHomeDirectory,
138
- });
139
- if (result.rawOutput) process.stdout.write(`\u001b${result.rawOutput}`);
140
- for (const line of result.lines) {
141
- // eslint-disable-next-line no-console
142
- if (result.exitCode === 0) console.log(line);
143
- else console.error(line);
144
- }
145
- process.exit(result.exitCode);
146
- }
147
-
148
- // `send …` is intercepted here for both of the reasons `cluster` is, plus one
149
- // of its own.
150
- //
151
- // Before the parser, because the message is arbitrary operator text: a
152
- // message beginning with a dash, or one carrying `--port` inside it, must
153
- // reach the channel rather than be eaten as a daemon flag.
154
- //
155
- // Before the runtime, because this composes only the services a delivery
156
- // needs (see send/composition.ts) and must not start a second copy of the
157
- // pollers, cluster election and LAN scan a running daemon already owns.
158
- //
159
- // And it must work when NO daemon is running, which is much of the point: the
160
- // reason to message the owner is usually that something stopped.
161
- if (rawArgs[0] === 'send') {
162
- const ownership = resolveDaemonCliOwnership();
163
- runDaemonConfigMigration(ownership.homeDirectory);
164
- const stack = createSendStack({
165
- workingDirectory: ownership.workingDirectory,
166
- homeDirectory: ownership.homeDirectory,
167
- daemonHomeDirectory: ownership.daemonHomeDirectory,
168
- });
169
- const result = await runSendCommand(rawArgs.slice(1), {
170
- configManager: stack.configManager,
171
- deliver: stack.deliver,
172
- readStdin: readAllStdin,
173
- stdinIsTty: process.stdin.isTTY === true,
174
- });
175
- for (const line of result.lines) {
176
- // eslint-disable-next-line no-console
177
- if (result.exitCode === 0) console.log(line);
178
- else console.error(line);
179
- }
180
- // The activity log holds the OUTBOUND_HTTP record for the send that just
181
- // happened (or did not); a process exiting this promptly would drop it.
182
- flushActivityLogSync();
183
- process.exit(result.exitCode);
184
- }
185
-
186
- // `provision-wake-model …` is intercepted here for the same two reasons, plus
187
- // its own: the curl installer runs it on the binary it has just placed, before
188
- // any daemon exists, and it must reach the SDK's pinned manifest rather than
189
- // have install.sh carry a second copy of the pins. It composes no runtime — it
190
- // resolves a home directory, derives the managed voice root the running daemon
191
- // uses, and calls one SDK function. See provision-wake-model.ts for why it
192
- // exits 0 on a failed download.
193
- if (rawArgs[0] === 'provision-wake-model') {
194
- const ownership = resolveDaemonCliOwnership();
195
- const result = await runProvisionWakeModelCommand(rawArgs.slice(1), {
196
- homeDirectory: ownership.homeDirectory,
197
- });
198
- for (const line of result.lines) {
199
- // eslint-disable-next-line no-console
200
- console.log(line);
201
- }
202
- process.exit(result.exitCode);
203
- }
204
-
205
- // Parse CLI flags first so --daemon-home and --working-dir env vars are set
206
- // before resolveDaemonCliOwnership() reads them.
207
- const cli = parseGoodVibesCli(process.argv.slice(2), 'goodvibes-daemon');
208
- if (cli.errors.length > 0) {
209
- console.error(cli.errors.join('\n'));
210
- console.error('');
211
- console.error(renderGoodVibesDaemonHelp('goodvibes-daemon'));
212
- process.exit(2);
213
- }
214
- if (cli.warnings.length > 0) {
215
- for (const warning of cli.warnings) {
216
- console.warn(`[goodvibes-daemon] warning: ${warning}`);
217
- }
218
- }
219
- if (cli.flags.help || cli.command === 'help') {
220
- console.log(renderGoodVibesDaemonHelp('goodvibes-daemon'));
221
- process.exit(0);
222
- }
223
- if (cli.flags.version || cli.command === 'version') {
224
- console.log(renderGoodVibesVersion('goodvibes-daemon'));
225
- process.exit(0);
226
- }
227
- const cliFlags = cli.flags;
228
- if (cliFlags.daemonHome !== undefined) {
229
- process.env['GOODVIBES_DAEMON_HOME'] = cliFlags.daemonHome;
230
- logger.info('daemon: --daemon-home flag applied', { daemonHome: cliFlags.daemonHome });
231
- }
232
- if (cliFlags.workingDir !== undefined) {
233
- process.env['GOODVIBES_WORKING_DIR'] = cliFlags.workingDir;
234
- logger.info('daemon: --working-dir flag applied', { workingDir: cliFlags.workingDir });
235
- }
236
-
237
- const { workingDirectory: workingDir, homeDirectory, daemonHomeDirectory, isOverridden: hasOverriddenHome } = resolveDaemonCliOwnership();
238
- runDaemonConfigMigration(homeDirectory);
239
- const config = new ConfigManager({ workingDir, homeDir: homeDirectory, surfaceRoot: 'tui' });
240
- new GlobalNetworkTransportInstaller().install(config);
241
-
242
- const overrideErrors = applyRuntimeConfigOverrides(config, cliFlags.configOverrides);
243
- if (overrideErrors.length > 0) {
244
- console.error(overrideErrors.join('\n'));
245
- process.exit(2);
246
- }
247
- applyRuntimeFeatureFlagOverrides(config, {
248
- enableFeatures: cliFlags.enableFeatures,
249
- disableFeatures: cliFlags.disableFeatures,
250
- });
251
-
252
- // Apply remaining CLI flags before the provider registry is constructed.
253
- // These are runtime-only overrides; they must not rewrite settings.json.
254
- if (cliFlags.provider !== undefined || cliFlags.model !== undefined) {
255
- const currentModel = config.get('provider.model');
256
- const provider = cliFlags.provider ?? getProviderIdFromModel(currentModel);
257
- const model = cliFlags.model ?? getModelIdFromProviderModel(currentModel);
258
- const registryKey = formatProviderModel(provider, model);
259
- applyRuntimeConfigValue(config, 'provider.model', registryKey);
260
- logger.info('daemon: provider/model flags applied', { provider, model: registryKey });
261
- }
262
- const endpointOverrideErrors = applyRuntimeEndpointFlagOverrides(config, 'controlPlane', cliFlags);
263
- if (endpointOverrideErrors.length > 0) {
264
- console.error(endpointOverrideErrors.join('\n'));
265
- process.exit(2);
266
- }
267
- if (cliFlags.port !== undefined) logger.info('daemon: --port flag applied', { port: cliFlags.port });
268
- if (cliFlags.hostname !== undefined) {
269
- process.env['GOODVIBES_DAEMON_HOST'] = cliFlags.hostname;
270
- logger.info('daemon: --hostname flag applied', { hostname: cliFlags.hostname });
271
- }
272
-
273
- // Service lifecycle: `install-service` / `uninstall-service` / `service-status` manage
274
- // the systemd USER unit for the shared daemon. They run BEFORE the daemon boots —
275
- // no runtime/services are constructed — and exit with the honest result code.
276
- const serviceSubcommand = cli.positionals[0];
277
- if (isDaemonServiceSubcommand(serviceSubcommand)) {
278
- // The host/port baked into the unit's ExecStart (and displayed) come from
279
- // the SAME hostMode-aware resolution the SDK bind path uses — never from
280
- // the GOODVIBES_DAEMON_HOST env var, which nothing in the bind path reads
281
- // (the --hostname flag already lands in config via
282
- // applyRuntimeEndpointFlagOverrides above, so it is covered here).
283
- const binding = resolveRuntimeEndpointBinding(config, 'controlPlane');
284
- if (!binding.recognized) {
285
- // The SDK bind path has no default case for an unrecognized hostMode —
286
- // a daemon launched with this config throws before binding. Say so
287
- // instead of presenting the fallback values as a real binding.
288
- console.warn(
289
- `[goodvibes-daemon] warning: controlPlane.hostMode '${binding.hostMode}' is not a recognized mode ` +
290
- "(local|network|custom) — the daemon will fail to start until it is corrected.",
291
- );
292
- }
293
- const binaryPath = resolveInstalledDaemonBinary({ moduleUrl: import.meta.url });
294
- const result = await runDaemonServiceCli({
295
- subcommand: serviceSubcommand,
296
- binaryPath,
297
- homeDir: homeDirectory,
298
- host: binding.host,
299
- port: binding.port,
300
- // migrate-service only: never auto-migrate — requires the same explicit
301
- // consent as any other non-interactive destructive confirmation.
302
- confirmMigration: cliFlags.yes,
303
- });
304
- for (const line of result.lines) {
305
- // eslint-disable-next-line no-console
306
- console.log(line);
307
- }
308
- process.exit(result.exitCode);
309
- }
310
-
311
- // Honest startup identity, printed for EVERY launch shape — including a bare
312
- // (no-arg) systemd launch, and BEFORE any runtime construction so it still
313
- // reaches the journal when a broken config makes the daemon throw during
314
- // composition. It states the resolved version (never a placeholder) and the
315
- // home/host/port the daemon will actually bind: the binding comes from the
316
- // SAME hostMode-aware resolution the SDK bind path uses (resolveHostBinding:
317
- // 'local' forces 127.0.0.1, 'network' forces 0.0.0.0, port 0/non-numeric
318
- // falls back to the default) — never from controlPlane.host alone, and never
319
- // from the GOODVIBES_DAEMON_HOST env var, which the bind path does not read.
320
- const bannerBinding = resolveRuntimeEndpointBinding(config, 'controlPlane');
321
- // eslint-disable-next-line no-console
322
- console.log(renderDaemonStartupBanner(VERSION, { homeDir: homeDirectory, host: bannerBinding.host, port: bannerBinding.port }));
323
- if (!bannerBinding.recognized) {
324
- // An unrecognized hostMode has NO binding the SDK can produce (its
325
- // resolver has no default case and the daemon will throw below, before
326
- // serving). Warn here so the journaled crash is explained.
327
- console.warn(
328
- `[goodvibes-daemon] warning: controlPlane.hostMode '${bannerBinding.hostMode}' is not a recognized mode ` +
329
- "(local|network|custom) — the daemon cannot bind until it is corrected; the host/port above are fallback values, not a real binding.",
330
- );
331
- }
332
-
333
- // Boot-time reconciliation. The banner above states the real bind; this
334
- // compares it to the URL clients are actually handed. Those are produced by
335
- // two different resolvers, and when they disagree the daemon is advertising an
336
- // address it does not answer on — the state that produced two different click
337
- // hosts from one daemon. Only reported when it is genuinely wrong: a wildcard
338
- // bind against a loopback dial target is a deliberate substitution, and a
339
- // declared controlPlane.publicBaseUrl is supposed to differ from the bind.
340
- // The derivation itself is the SDK's (deriveControlPlaneBaseUrl); only the
341
- // comparison is local. The SDK also carries describeDerivedBindMismatch,
342
- // which does exactly this — swap to it once a release ships it, since the
343
- // published 1.14.0 this build consumes predates it.
344
- if (bannerBinding.recognized) {
345
- const derived = new URL(
346
- deriveControlPlaneBaseUrl(readControlPlaneBinding((key) => config.get(key as ConfigKey)), 'loopback'),
347
- );
348
- // A wildcard bind is REPORTED as 0.0.0.0 while the dial target is loopback;
349
- // that substitution is deliberate, not drift.
350
- const wildcardBind = bannerBinding.host === '0.0.0.0' || bannerBinding.host === '::';
351
- const hostAgrees = derived.hostname === bannerBinding.host
352
- || (wildcardBind && derived.hostname === '127.0.0.1');
353
- if (!hostAgrees || Number(derived.port) !== bannerBinding.port) {
354
- console.warn(
355
- `[goodvibes-daemon] warning: control-plane clients are handed ${derived.origin}, but the daemon `
356
- + `actually bound ${bannerBinding.host}:${bannerBinding.port}. One of these is wrong, and anything `
357
- + 'given the first will dial a place this daemon does not answer.',
358
- );
359
- logger.warn('daemon: control-plane base URL disagrees with the real bind', {
360
- handedToClients: derived.origin,
361
- boundHost: bannerBinding.host,
362
- boundPort: bannerBinding.port,
363
- });
364
- }
365
- }
366
-
367
- // Point the bus listener cap at runtime.eventBus.maxListeners before the
368
- // first bus exists, so every bus this process builds later uses it.
369
- configureRuntimeEventBusDefaults(runtimeEventBusOptionsFrom((key) => config.get(key)));
370
- const runtimeBus = new RuntimeEventBus();
371
- const runtimeStore = createRuntimeStore();
372
- // Gate states derive from the domain settings keys; the bridge keeps live
373
- // config changes flowing into the manager for the daemon's lifetime.
374
- const featureFlags = createFeatureFlagManager();
375
- featureFlags.loadFromConfig({ flags: deriveFeatureStates(config) });
376
- bindFeatureSettingsBridge(config, featureFlags);
377
- const runtimeServices = createRuntimeServices({
378
- configManager: config,
379
- featureFlags,
380
- runtimeBus,
381
- runtimeStore,
382
- getConversationTitle: () => 'goodvibes daemon',
383
- workingDir,
384
- homeDirectory,
385
- // Honour --daemon-home / GOODVIBES_DAEMON_HOME on the CREDENTIAL store, not
386
- // just the identity directory. Without this a daemon told to run out of a
387
- // temp tree still read the real home's daemon secrets, so "isolating" a
388
- // test daemon left it holding the owner's live credentials.
389
- daemonHomeDirectory,
390
- // The standalone daemon observes externally-launched coding-agent sessions
391
- // on the host read-only (fleet visibility + steer; never counted, never
392
- // stopped). Daemon-side only — the interactive process reads this snapshot
393
- // rather than double-detecting. Mirrors the SDK daemon cli.
394
- observeExternalAgents: true,
395
- // Opt into the REAL host power seam (Linux logind: systemd-inhibit children
396
- // + the dbus-monitor sleep-edge watcher) so the standalone daemon holds live
397
- // keep-awake/idle-inhibit. SDK 1.9.0's runtime-services factory defaults to
398
- // the non-spawning unavailable seam; only daemon compositions opt in. Mirrors
399
- // the SDK daemon cli's createHostPowerSeam() (sdk commit 3a5ea26d).
400
- powerSeam: createHostPowerSeam(),
401
- // The wake-word model ships with the installation, and this is the retry: at
402
- // every daemon start, sweep the managed wake tree and fetch whatever the
403
- // install could not (an offline install, a killed download, a changed pin).
404
- // It never blocks startup and never fails it — see the SDK's
405
- // voice/wake/install-provision.ts. The webui reads the model bytes from THIS
406
- // process, so a provisioned daemon is what makes the browser path work too.
407
- provisionWakeModelsAtBoot: true,
408
- });
409
-
410
- // Load persisted providers from disk so the provider registry is pre-populated
411
- // on standalone daemon startup (same machinery the TUI uses after /scan).
412
- const discoveryRoots = { homeDirectory, surfaceRoot: 'tui' };
413
- const persistedProviders = loadPersistedProviders(discoveryRoots);
414
- if (persistedProviders.length > 0) {
415
- runtimeServices.providerRegistry.registerDiscoveredProviders(persistedProviders);
416
- logger.info('daemon: loaded persisted providers', { count: persistedProviders.length });
417
- }
418
-
419
- // Run a background LAN scan (non-blocking). Discovered servers are registered
420
- // and persisted so subsequent daemon restarts benefit from the warm cache.
421
- void scan().then((result) => {
422
- if (result.servers.length > 0) {
423
- runtimeServices.providerRegistry.registerDiscoveredProviders(result.servers);
424
- persistProviders(discoveryRoots, result.servers);
425
- logger.info('daemon: LAN scan complete', { found: result.servers.length });
426
- } else {
427
- logger.info('daemon: LAN scan found no servers');
428
- }
429
- }).catch((err: unknown) => {
430
- logger.warn('daemon: LAN scan failed', { error: summarizeError(err) });
431
- });
432
-
433
- const userAuth = runtimeServices.localUserAuthManager;
434
- const daemon = new DaemonServer({
435
- runtimeBus,
436
- userAuth,
437
- runtimeServices,
438
- serveFactory: createSafeHostServeFactory('Standalone daemon'),
439
- // Hand the facade this binary's real version + exec path so its self-update
440
- // lifecycle compares the HOST's version (not the sdk package's). Resolves to
441
- // undefined for a non-binary install, so a dev run stays host-managed and
442
- // never swaps the interpreter. See lifecycle.ts.
443
- updateArtifact: resolveDaemonUpdateArtifact({ version: VERSION }),
444
- // A daemon running out of an overridden home must never adopt the machine's
445
- // service unit. One did: started from a scratchpad with --daemon-home, it
446
- // found the unit not running, wrote its own scratchpad ExecStart into
447
- // ~/.config/systemd/user/goodvibes.service and exited, and systemd then
448
- // supervised the throwaway as the machine's daemon for five hours.
449
- hasOverriddenHome,
450
- // The SAME coordinator this repository's inbox poller registered with (see
451
- // runtime/cluster-composition.ts). The facade must reuse it rather than
452
- // compose its own: two coordinators in one process are two nodes in the
453
- // election, and whichever lost would silence consumers the other owns.
454
- clusterCoordinator: runtimeServices.clusterCoordinator,
455
- // The `cluster` verbs, served on /api/cluster/*. The CLI subcommands, the
456
- // TUI's /cluster command and any web UI all call these, so a command run
457
- // against a REMOTE daemon behaves exactly like one run on that machine.
458
- clusterGroupVerbs: runtimeServices.clusterGroup.verbs,
459
- });
460
- const listener = new HttpListener({
461
- hookDispatcher: runtimeServices.hookDispatcher,
462
- userAuth,
463
- configManager: config,
464
- serveFactory: createSafeHostServeFactory('Standalone HTTP listener'),
465
- });
466
- const { daemonToken, httpToken } = readDaemonCliTokens(process.env);
467
-
468
- // If no explicit daemon token is set, use the companion token so mobile apps can connect.
469
- const daemonHomeDir = daemonHomeDirectory;
470
- const companionTokenRecord = getOrCreateCompanionToken('tui', { daemonHomeDir });
471
- // Fix (TUI 0.19.20): remove stale pre-0.21.28 workspace-scoped operator
472
- // token files so only the canonical <daemonHomeDir>/operator-tokens.json survives.
473
- const prune = pruneStaleOperatorTokens({
474
- daemonHomeDir,
475
- candidatePaths: workspaceOperatorTokenCandidates(workingDir),
476
- });
477
- if (prune.prunedPaths.length > 0) {
478
- logger.info('daemon: pruned stale operator-token files', { count: prune.prunedPaths.length, paths: prune.prunedPaths });
479
- }
480
- if (prune.failedPaths.length > 0) {
481
- logger.warn('daemon: failed to prune stale operator-token files (permission/race)', { count: prune.failedPaths.length, paths: prune.failedPaths });
482
- }
483
- const effectiveDaemonToken = daemonToken ?? companionTokenRecord.token;
484
- const effectiveHttpToken = httpToken ?? effectiveDaemonToken;
485
-
486
- daemon.enable({ daemon: true }, effectiveDaemonToken);
487
- listener.enable({ httpListener: true }, effectiveHttpToken);
488
-
489
- // Before the daemon: the group layer owns the socket the leader election
490
- // coordinates over, and it announces this machine's return to the group so a
491
- // box that has been off for months re-keys itself with no operator action.
492
- await runtimeServices.startCluster();
493
-
494
- // Grants and captures from paired phones outlive a restart, so the recovery
495
- // sweep runs before this daemon serves its first request and the periodic one
496
- // keeps a long-running daemon from going days without a sweep. The stop is
497
- // registered with the disposal scope, so shutdown clears the timer.
498
- startDeviceHousekeeping(runtimeServices.devicePosture);
499
-
500
- await Promise.all([
501
- daemon.start(),
502
- config.get('danger.httpListener') ? listener.start() : Promise.resolve(),
503
- ]);
504
-
505
- // The DaemonServer facade owns the hourly self-update loop now (fed this
506
- // binary's version + exec path via updateArtifact above); it checks hourly,
507
- // swaps only at an idle moment, keeps the outgoing binary at `<path>.previous`,
508
- // and stops with the daemon. No separate host loop.
509
-
510
- const abortController = new AbortController();
511
-
512
- const shutdown = async (): Promise<void> => {
513
- abortController.abort();
514
- const SHUTDOWN_DEADLINE_MS = 15_000;
515
- const timeout = new Promise<'timeout'>((resolve) =>
516
- setTimeout(() => resolve('timeout'), SHUTDOWN_DEADLINE_MS)
517
- );
518
- // daemon.stop() drives the ordered inbound teardown: it stops every gated
519
- // consumer and only then broadcasts the resignation, so another node on
520
- // this network takes over in about a second instead of waiting out the
521
- // crash timeout.
522
- //
523
- // This process built the runtime graph and handed it to DaemonServer, so by
524
- // the SDK's ownership rule the facade leaves it alone — nothing else stops
525
- // these pollers. Without dispose() the config watch, fleet tick, memory
526
- // governor, watcher registry and six more kept ticking until process exit.
527
- // The handler surfaces (inbox store + its poll timers, catalog handlers) are
528
- // the FIRST thing dispose() unwinds — they are on the disposal owner list
529
- // now rather than sequenced by hand here, which is what makes every other
530
- // shutdown path stop them too instead of only this one.
531
- const stop = Promise.allSettled([listener.stop(), daemon.stop()])
532
- .then(() => { runtimeServices.dispose(); })
533
- .then(() => 'done' as const);
534
- const result = await Promise.race([stop, timeout]);
535
- if (result === 'timeout') {
536
- logger.warn('shutdown deadline exceeded — forcing exit');
537
- // A forced exit is exactly the case where the log matters most and is
538
- // least likely to have drained on its own.
539
- flushActivityLogSync();
540
- process.exit(1);
541
- }
542
- flushActivityLogSync();
543
- process.exit(0);
544
- };
545
-
546
- let shutdownInFlight = false;
547
- const handleSignal = (): void => {
548
- if (shutdownInFlight) return;
549
- shutdownInFlight = true;
550
- void shutdown();
551
- };
552
-
553
- process.on('SIGINT', handleSignal);
554
- process.on('SIGTERM', handleSignal);
555
-
556
- logger.info('goodvibes daemon host started', {
557
- daemon: resolveDaemonEnabled(config),
558
- httpListener: config.get('danger.httpListener'),
559
- });
560
-
561
- // Cheap unattended reconcile: if this (canonical) daemon unit is confirmed
562
- // serving AND a redundant installer-managed goodvibes-daemon.service (the
563
- // retired unit name) sits enabled-but-NOT-running beside it — the exact
564
- // production-incident state — auto-disable and remove it, printing a
565
- // receipt. A RUNNING legacy daemon, a hand-written unit, or an unanswered
566
- // configured endpoint all refuse with a notice instead. Best-effort: never
567
- // let this block or crash daemon boot (per-call systemctl timeouts plus one
568
- // cumulative pass deadline). The unit search root is the LOGIN user's home
569
- // (where systemd user units live), never the daemon data home
570
- // (GOODVIBES_DAEMON_HOME); the tracked name honors service.serviceName. The
571
- // endpoint requirement uses the CLIENT view of the config — a fresh read of
572
- // settings.json with none of this process's runtime flag overrides — because
573
- // that is what clients resolve when they look for the daemon.
574
- try {
575
- runDaemonConfigMigration(homeDirectory);
576
- const clientViewConfig = new ConfigManager({ workingDir, homeDir: homeDirectory, surfaceRoot: 'tui' });
577
- const clientEndpoint = resolveRuntimeEndpointBinding(clientViewConfig, 'controlPlane');
578
- const reconcile = await reconcileRedundantLegacyUnit({
579
- homeDir: homedir(),
580
- trackedServiceName: resolveConfiguredServiceName(clientViewConfig),
581
- configuredEndpoint: { host: clientEndpoint.host, port: clientEndpoint.port },
582
- });
583
- if (reconcile.action !== 'noop') {
584
- for (const line of reconcile.lines) {
585
- // eslint-disable-next-line no-console
586
- console.log(line);
587
- }
588
- }
589
- if (reconcile.reason !== 'no-legacy-unit') {
590
- // Breadcrumb for EVERY outcome where a legacy unit file exists —
591
- // including guard refusals — so a persisting two-unit state is never
592
- // silent about why nothing was reconciled.
593
- logger.info('daemon: legacy-unit reconcile', { action: reconcile.action, reason: reconcile.reason });
594
- }
595
- } catch (error) {
596
- logger.warn('daemon: legacy-unit reconcile failed (non-fatal)', { error: summarizeError(error) });
597
- }
598
-
599
- // Print a device-pairing QR to stdout. The QR encodes the canonical
600
- // `#pair=<token>` deep link the web app consumes — a camera scan opens it
601
- // already signed in. No raw JSON connection blob is printed. This is also the
602
- // one place web.publicBaseUrl is frozen from the stable-name resolution (never
603
- // clobbering a user-set value), so the printed origin survives a DHCP change.
604
- // The daemon's shared companion token rides as the pairing token; a device can
605
- // later migrate to its own per-device token from /settings → security → devices.
606
- const webOrigin = ensurePublicBaseUrl(config);
607
- const offers = availablePairingOffers({
608
- relayEnabled: config.get('relay.enabled') === true,
609
- stepUpAvailable: true,
610
- });
611
- const deepLink = buildPairingHandoffLink({ webOrigin: webOrigin.origin, token: companionTokenRecord.token, offers });
612
- const qrString = renderQrToString(generateQrMatrix(deepLink));
613
- // The banner renders the SAME SDK posture the pairing verb carries: the labeled
614
- // capability list, and the one honest LAN line only when the posture holds it.
615
- const posture = describeOriginPosture(webOrigin.origin);
616
- const capabilities = formatPostureCapabilities(posture);
617
- const notice = pairingPostureNotice(posture);
618
- const bannerLines = [
619
- `GoodVibes daemon ${VERSION} — scan to pair a device (opens the web app signed in):`,
620
- '',
621
- ` ${webOrigin.origin}`,
622
- '',
623
- ...(offers.length > 0 ? ['Offers (each declinable in the web app):', ...formatPairingOffers(offers), ''] : []),
624
- ...(capabilities.length > 0 ? ['This device will get:', ...capabilities, ''] : []),
625
- ...(notice ? [notice, ''] : []),
626
- qrString,
627
- ];
628
- // eslint-disable-next-line no-console
629
- console.log(bannerLines.join('\n'));
630
- }
631
-
632
- void main().catch(async (error) => {
633
- logger.error('goodvibes daemon host failed', {
634
- error: summarizeError(error),
635
- });
636
- // The daemon never came up. Without this the reason is buffered in a process
637
- // that is about to stop existing, and the failure reads as a daemon that
638
- // never logged anything at all.
639
- flushActivityLogSync();
640
- process.exit(1);
641
- });
@@ -1,29 +0,0 @@
1
- /**
2
- * Host-facing context passed to every surface register function. Replaces the
3
- * former OperatorContext. It carries the SDK gateway catalog (handlers attach
4
- * to it), the daemon credential store, a read-only slice of the config manager,
5
- * resolved directories, and a logger. No SDK descriptor or schema is declared
6
- * here — the catalog type is re-exported through the contracts seam.
7
- */
8
- import type { ConfigManager } from '@pellux/goodvibes-sdk/platform/config';
9
- import type { GatewayMethodCatalog } from './contracts.ts';
10
- import type { DaemonCredentialStore } from './credentials.ts';
11
- import type { Unregister } from './register.ts';
12
-
13
- export interface HandlerLogger {
14
- info(message: string, meta?: unknown): void;
15
- warn(message: string, meta?: unknown): void;
16
- error(message: string, meta?: unknown): void;
17
- }
18
-
19
- export interface HandlerContext {
20
- readonly catalog: GatewayMethodCatalog;
21
- readonly credentials: DaemonCredentialStore;
22
- readonly configManager: Pick<ConfigManager, 'get' | 'getCategory'>;
23
- readonly workingDirectory: string;
24
- readonly homeDirectory: string;
25
- readonly logger: HandlerLogger;
26
- }
27
-
28
- /** Every surface module exports a register function of this shape. */
29
- export type SurfaceRegister = (ctx: HandlerContext) => Unregister;