@pellux/goodvibes-tui 1.27.1 → 2.0.1

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