@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
@@ -0,0 +1,167 @@
1
+ /**
2
+ * build-floors.ts — the two build floors this terminal and the daemon hold
3
+ * against each other.
4
+ *
5
+ * ── Forward: the daemon's floor on this client ────────────────────────────
6
+ *
7
+ * A daemon update swaps the daemon binary and nothing else. This terminal keeps
8
+ * running the build it started with: same rules, same bugs, still registered in
9
+ * the shared session store, still able to execute shared-session work. That is
10
+ * how a behavioral fix can land in the daemon, be verified present in the
11
+ * installed binaries, and still not change what the owner observes — an older
12
+ * terminal beside the new daemon simply keeps doing the old thing.
13
+ *
14
+ * So the daemon publishes the minimum client build it will let participate, as
15
+ * an `X-Goodvibes-Client-Floor` response header on `/status`. Below that floor
16
+ * this terminal says so plainly and stops taking shared-session work, rather
17
+ * than quietly executing under superseded rules until someone notices.
18
+ *
19
+ * ── Reverse: this client's floor on the daemon ────────────────────────────
20
+ *
21
+ * The exposure runs the other way too. This terminal is a pure client: it talks
22
+ * to a daemon it did not start, over a base URL that may point at another
23
+ * machine, and every capability it has is something the daemon performs on its
24
+ * behalf. A verb it depends on may simply not exist in the build that answers,
25
+ * and what the owner sees then is a 400 or a 404 on one call — which reads as a
26
+ * broken feature rather than as an old daemon. So this terminal declares the
27
+ * oldest daemon build it can work against and checks it on attach against the
28
+ * `version` `/status` already returns, refusing to adopt a daemon below it.
29
+ *
30
+ * ── Where these rules live ────────────────────────────────────────────────
31
+ *
32
+ * Both comparisons — the header name, the verdict shapes, the version ordering
33
+ * — are owned by the SDK (platform/control-plane/client-compatibility.ts and
34
+ * daemon-compatibility.ts) and imported below rather than duplicated. What
35
+ * stays here is this process's own state: the forward latch, the reverse
36
+ * announce-once, and the daemon floor this product declares. The agent holds
37
+ * the same forward latch shape for its own process lifetime, which is why the
38
+ * latch is not a value the SDK ships.
39
+ */
40
+
41
+ import {
42
+ evaluateClientCompatibility,
43
+ evaluateDaemonStatusCompatibility,
44
+ type ClientCompatibilityVerdict,
45
+ type DaemonCompatibilityVerdict,
46
+ } from '@pellux/goodvibes-sdk/platform/control-plane';
47
+
48
+ /**
49
+ * The oldest daemon build this terminal will adopt.
50
+ *
51
+ * 1.28.0 is the daemon release in which the daemon became its own product and
52
+ * this terminal became a pure client of it: the session register, the inbound
53
+ * steer queue, the fleet needs-input push and the credential writes this build
54
+ * calls are all served there. An older daemon answers some of those and not
55
+ * others, which is the half-working state this floor exists to stop.
56
+ *
57
+ * Raise this only for a daemon behavior this terminal MUST have, and say in the
58
+ * release notes what it was raised for. It costs a daemon update for anyone
59
+ * running an older one.
60
+ */
61
+ export const TUI_DAEMON_BUILD_FLOOR = '1.28.0';
62
+
63
+ export interface ClientBuildGuardOptions {
64
+ readonly clientVersion: string;
65
+ }
66
+
67
+ /**
68
+ * The live forward verdict for this process, updated by whatever reads the
69
+ * daemon's floor (the attach handshake) and consulted by whatever would execute
70
+ * shared-session work (the continuation runner).
71
+ *
72
+ * Latching is deliberate: once a daemon has told this process it is too old, a
73
+ * later read that fails to see the header (a restart, a truncated response)
74
+ * must not silently re-enable work.
75
+ *
76
+ * The notice sink is attached after construction — this guard is built in the
77
+ * services composition root, and the surface that renders its one line is wired
78
+ * in the bootstrap tail. A verdict reached before the sink attaches is held and
79
+ * delivered on attach, the same buffered-until-attach idiom the daemon receipt
80
+ * notices use, so a refusal is never the message nobody saw.
81
+ */
82
+ export class ClientBuildGuard {
83
+ private verdict: ClientCompatibilityVerdict;
84
+ private announced = false;
85
+ private notify: ((verdict: ClientCompatibilityVerdict) => void) | null = null;
86
+ private pending: ClientCompatibilityVerdict | null = null;
87
+
88
+ constructor(private readonly options: ClientBuildGuardOptions) {
89
+ this.verdict = {
90
+ status: 'ok',
91
+ message: 'No daemon floor observed yet.',
92
+ clientVersion: options.clientVersion,
93
+ floor: undefined,
94
+ };
95
+ }
96
+
97
+ /**
98
+ * Attach the surface that renders the one line telling the owner to restart.
99
+ * Flushes a verdict already reached before this call.
100
+ */
101
+ onRestartRequired(notify: (verdict: ClientCompatibilityVerdict) => void): void {
102
+ this.notify = notify;
103
+ const held = this.pending;
104
+ this.pending = null;
105
+ if (held) notify(held);
106
+ }
107
+
108
+ /** Feed a floor read from a daemon `/status` response. */
109
+ observeFloor(floor: string | undefined): ClientCompatibilityVerdict {
110
+ if (this.verdict.status === 'restart-required') return this.verdict;
111
+ const next = evaluateClientCompatibility({ clientVersion: this.options.clientVersion, floor });
112
+ this.verdict = next;
113
+ if (next.status === 'restart-required' && !this.announced) {
114
+ this.announced = true;
115
+ if (this.notify) this.notify(next);
116
+ else this.pending = next;
117
+ }
118
+ return next;
119
+ }
120
+
121
+ /** False once this build has been judged too old for the live daemon. */
122
+ maySharedSessionWork(): boolean {
123
+ return this.verdict.status !== 'restart-required';
124
+ }
125
+
126
+ current(): ClientCompatibilityVerdict {
127
+ return this.verdict;
128
+ }
129
+ }
130
+
131
+ /**
132
+ * The reverse half: this terminal's verdict on the daemon answering at a base
133
+ * URL, held across attaches so a reconnect loop against one old daemon states
134
+ * the problem once instead of on every poll.
135
+ *
136
+ * Unlatched, unlike the forward guard: a daemon CAN become new enough while
137
+ * this terminal runs — that is exactly what a daemon update does — and the
138
+ * attach that sees the newer build must adopt it. What is remembered is only
139
+ * which sentence has already been said.
140
+ */
141
+ export class DaemonBuildFloor {
142
+ private announced: string | null = null;
143
+
144
+ constructor(private readonly floor: string = TUI_DAEMON_BUILD_FLOOR) {}
145
+
146
+ /**
147
+ * Judge a parsed `/status` body. `daemonLabel` names the peer in the sentence
148
+ * so an operator with two daemons on the LAN learns WHICH one is old.
149
+ */
150
+ evaluate(statusPayload: unknown, daemonLabel: string): DaemonCompatibilityVerdict {
151
+ return evaluateDaemonStatusCompatibility(statusPayload, this.floor, daemonLabel);
152
+ }
153
+
154
+ /**
155
+ * The line to show for a verdict, or null when this exact sentence has
156
+ * already been shown (or there is nothing to say).
157
+ */
158
+ noticeFor(verdict: DaemonCompatibilityVerdict): string | null {
159
+ if (verdict.status === 'ok') {
160
+ this.announced = null;
161
+ return null;
162
+ }
163
+ if (this.announced === verdict.message) return null;
164
+ this.announced = verdict.message;
165
+ return verdict.message;
166
+ }
167
+ }
@@ -0,0 +1,115 @@
1
+ /**
2
+ * daemon-attach-handshake.ts — attaching to a daemon is a handshake, not a
3
+ * pointer swap.
4
+ *
5
+ * ONE `/status` read carries the three things this terminal has to settle
6
+ * before it mirrors anything to a daemon, and as a pure client there is no
7
+ * in-process daemon handle to fold any of them from:
8
+ *
9
+ * - The DAEMON's own build. Every capability here is something the daemon
10
+ * performs on this terminal's behalf, so a daemon below this build's floor
11
+ * is refused rather than adopted — otherwise a verb it does not serve
12
+ * surfaces as one broken feature instead of as an old daemon, and the
13
+ * terminal keeps running half-working against a peer it has no reason to
14
+ * suspect. Refused means local-only, which is a state this app already
15
+ * renders honestly.
16
+ * - The minimum CLIENT build the daemon accepts. Below it the guard latches
17
+ * and the continuation runner stops taking shared-session work.
18
+ * - Its undelivered receipts (update applied, restarted after a crash,
19
+ * settings migrated), read from `/status?receipts=consume` where delivery is
20
+ * destructive — so the read that consumed them is the read that renders
21
+ * them.
22
+ *
23
+ * Every one of those is decided from the same response, which is why this is
24
+ * one function and not three callers each making their own read.
25
+ *
26
+ * A daemon that goes away and comes back gets the handshake again, off the
27
+ * liveness flip a caller already observes rather than a timer of this module's
28
+ * own: for an already-adopted daemon, coming back up is what its hourly
29
+ * self-update looks like from here, and the build that came back may not be the
30
+ * build that left. The flip an adoption's own first refresh causes runs the
31
+ * handshake once more too; that costs one /status GET and is the price of never
32
+ * having to decide which online flip is "really" a reconnect.
33
+ */
34
+
35
+ import type { HostServiceStatus } from '@/runtime/index.ts';
36
+ import { readExternalDaemonAttach } from '../daemon-attach-notices.ts';
37
+ import { ClientBuildGuard, DaemonBuildFloor } from './build-floors.ts';
38
+
39
+ export interface DaemonAttachHandshakeDeps {
40
+ /** The forward floor's latch — fed the floor this daemon announces. */
41
+ readonly clientBuildGuard: ClientBuildGuard;
42
+ /** The daemon this terminal is currently pointed at. Re-read on every attach. */
43
+ readonly readDaemonStatus: () => HostServiceStatus;
44
+ /**
45
+ * Record a refusal on the status every reader already consults, so the footer
46
+ * and `/status` report "a daemon is there and this build will not adopt it"
47
+ * rather than claiming a working mirror.
48
+ */
49
+ readonly recordRefusal: (status: HostServiceStatus) => void;
50
+ /** Bring the session and memory spines up against this daemon (or take them down). */
51
+ readonly adopt: (status: HostServiceStatus, daemonToken: string) => void;
52
+ /** Where a daemon notice is shown to the owner. */
53
+ readonly notify: (text: string) => void;
54
+ /** This terminal's own floor on the daemon; defaults to the shipped one. */
55
+ readonly daemonFloor?: string | undefined;
56
+ /** Injectable `/status` read for tests. */
57
+ readonly read?: typeof readExternalDaemonAttach | undefined;
58
+ }
59
+
60
+ export interface DaemonAttachHandshake {
61
+ /** Run the handshake against the current daemon status, then adopt or refuse. */
62
+ attach(daemonToken: string): Promise<void>;
63
+ /** Feed the adopted daemon's liveness flips; a flip to online re-handshakes. */
64
+ onLivenessTransition(online: boolean): void;
65
+ }
66
+
67
+ export function createDaemonAttachHandshake(deps: DaemonAttachHandshakeDeps): DaemonAttachHandshake {
68
+ const daemonBuildFloor = new DaemonBuildFloor(deps.daemonFloor);
69
+ const read = deps.read ?? readExternalDaemonAttach;
70
+ let lastDaemonToken: string | null = null;
71
+ // The forward floor's one line. The guard latches in the services graph,
72
+ // where the continuation runner reads it; this is the surface that tells the
73
+ // owner, and a verdict reached before now is held and delivered here.
74
+ deps.clientBuildGuard.onRestartRequired((verdict) => deps.notify(verdict.message));
75
+
76
+ const handshake: DaemonAttachHandshake = {
77
+ onLivenessTransition(online) {
78
+ if (online && lastDaemonToken !== null) void handshake.attach(lastDaemonToken);
79
+ },
80
+ async attach(daemonToken) {
81
+ lastDaemonToken = daemonToken;
82
+ const daemonStatus = deps.readDaemonStatus();
83
+ if (daemonStatus.mode !== 'external' || !daemonStatus.baseUrl) {
84
+ deps.adopt(daemonStatus, daemonToken);
85
+ return;
86
+ }
87
+ const answer = await read({
88
+ baseUrl: daemonStatus.baseUrl,
89
+ authToken: daemonToken,
90
+ consumeReceipts: true,
91
+ });
92
+ // Nothing was read, so nothing is known — adopt as before and leave a
93
+ // daemon that is not answering to the spine's own reachability handling.
94
+ // Refusing on a failed read would turn one dropped request into a lost
95
+ // mirror.
96
+ if (!answer.answered) {
97
+ deps.adopt(daemonStatus, daemonToken);
98
+ return;
99
+ }
100
+ const verdict = daemonBuildFloor.evaluate(answer.statusPayload, daemonStatus.baseUrl);
101
+ const notice = daemonBuildFloor.noticeFor(verdict);
102
+ if (notice) deps.notify(notice);
103
+ if (verdict.status === 'daemon-update-required') {
104
+ const refused: HostServiceStatus = { ...daemonStatus, mode: 'incompatible', reason: verdict.message };
105
+ deps.recordRefusal(refused);
106
+ deps.adopt(refused, daemonToken);
107
+ return;
108
+ }
109
+ deps.clientBuildGuard.observeFloor(answer.clientFloor);
110
+ deps.adopt(daemonStatus, daemonToken);
111
+ for (const receipt of answer.notices) deps.notify(receipt);
112
+ },
113
+ };
114
+ return handshake;
115
+ }
@@ -0,0 +1,147 @@
1
+ /**
2
+ * fleet-union.ts — the Fleet panel shows everything running, not just what this
3
+ * terminal started.
4
+ *
5
+ * ── What moved to the SDK, and what stays here ────────────────────────────
6
+ *
7
+ * The SDK's `@pellux/goodvibes-sdk/platform/runtime/client` now owns the
8
+ * policy every surface needs for this: `createDaemonFleetRowsPoller` (the poll
9
+ * cadence and last-known-rows retention when a poll fails), `mergeFleetNodes`
10
+ * (the local-wins dedupe by node id) and `daemonOnlyFleetActRefusal` (the
11
+ * reason string a steer/archive against a daemon-only row gets, rather than a
12
+ * bare false). This module is now a thin wrapper over that policy.
13
+ *
14
+ * What stays here is the panel binding: the TUI's own `FleetReadModel`
15
+ * interface (interrupt/resume/kill/steer/archive, subscribe, snapshots) and
16
+ * `buildFleetSnapshot`, the tree-builder + honest cost/token aggregator this
17
+ * repo's Fleet panel renders from. Rebuilding through the SAME builder the
18
+ * local view uses — rather than summing two halves — is what keeps the
19
+ * rollups, the cost/token totals and the blocked-on-user ordering computed
20
+ * once, over the whole fleet.
21
+ *
22
+ * ── Who wins ──────────────────────────────────────────────────────────────
23
+ *
24
+ * Local rows are AUTHORITATIVE for processes this terminal spawned. They are
25
+ * live — the registry pushes on every state change, with sub-second latency —
26
+ * and they carry the capabilities that make a row actionable here (interrupt,
27
+ * resume, kill, steer all reach a real child process). The daemon's copy of
28
+ * the same row, arriving over a poll, is necessarily staler; where both
29
+ * describe the same node id, the local one is kept (`mergeFleetNodes`).
30
+ *
31
+ * ── Acting on a row you do not own ────────────────────────────────────────
32
+ *
33
+ * `interrupt`/`resume`/`kill`/`steer` reach this process's own children. A
34
+ * daemon row has no child here to signal, so those refuse — and `steer`,
35
+ * which has a reason channel, says why rather than returning a bare false
36
+ * that reads as "the agent ignored you". The panel's own act surface
37
+ * (fleet-gateway.ts) already drives the daemon's verbs for the acts the
38
+ * daemon serves.
39
+ */
40
+ import {
41
+ createDaemonFleetRowsPoller,
42
+ daemonOnlyFleetActRefusal,
43
+ DEFAULT_FLEET_REFRESH_MS,
44
+ mergeFleetNodes,
45
+ readDaemonFleetRows,
46
+ type DaemonFleetRows,
47
+ type DaemonFleetRowsPoller,
48
+ type DaemonFleetRowsPollerOptions,
49
+ type DaemonVerbCaller,
50
+ } from '@pellux/goodvibes-sdk/platform/runtime/client';
51
+ import { logger } from '@pellux/goodvibes-sdk/platform/utils';
52
+ import { buildFleetSnapshot, type FleetReadModel, type FleetSnapshot } from '../../panels/fleet-read-model.ts';
53
+
54
+ /** Re-exported so an importer that only needs the SDK's policy has one import site. */
55
+ export { DEFAULT_FLEET_REFRESH_MS, daemonOnlyFleetActRefusal, mergeFleetNodes, readDaemonFleetRows };
56
+ export type { DaemonFleetRows, DaemonFleetRowsPoller, DaemonFleetRowsPollerOptions };
57
+
58
+ export interface FleetUnionOptions {
59
+ /** This surface's own live registry view — authoritative for what it spawned. */
60
+ readonly local: FleetReadModel;
61
+ readonly verbs: DaemonVerbCaller;
62
+ readonly refreshIntervalMs?: number;
63
+ readonly log?: Pick<typeof logger, 'debug'>;
64
+ }
65
+
66
+ export interface FleetUnionReadModel extends FleetReadModel {
67
+ /** Stop the refresh timer. Idempotent. */
68
+ stop(): void;
69
+ /** Re-read the daemon's rows now. Never throws. */
70
+ refresh(): Promise<void>;
71
+ }
72
+
73
+ /** How this terminal names itself in a daemon-only-row act refusal. */
74
+ const SURFACE_LABEL = 'this terminal';
75
+
76
+ /**
77
+ * Wrap a local fleet read model so its snapshot also carries the adopted
78
+ * daemon's rows, over the SDK's poll/merge/refusal policy.
79
+ *
80
+ * Inert until the first poll lands: before then, and whenever the daemon
81
+ * cannot answer, the snapshot is exactly the local one — which is the honest
82
+ * answer, not a degraded one. A daemon that stops answering keeps its LAST
83
+ * known rows rather than dropping them (the poller's own retention), so a
84
+ * momentary blip does not make half the fleet blink out and back.
85
+ */
86
+ export function createFleetUnionReadModel(options: FleetUnionOptions): FleetUnionReadModel {
87
+ const local = options.local;
88
+ const poller = createDaemonFleetRowsPoller({
89
+ verbs: options.verbs,
90
+ ...(options.refreshIntervalMs === undefined ? {} : { refreshIntervalMs: options.refreshIntervalMs }),
91
+ ...(options.log === undefined ? {} : { log: options.log }),
92
+ });
93
+
94
+ const listeners = new Set<() => void>();
95
+ const unsubscribePoller = poller.subscribe(() => {
96
+ for (const listener of listeners) listener();
97
+ });
98
+
99
+ /** Ids this surface owns right now — the set the daemon's copy defers to. */
100
+ const localNodeIds = (snapshot: FleetSnapshot): Set<string> =>
101
+ new Set(snapshot.rows.map((row) => row.node.id));
102
+ const isLocalId = (id: string): boolean => localNodeIds(local.getSnapshot()).has(id);
103
+
104
+ return {
105
+ getSnapshot(): FleetSnapshot {
106
+ const localSnapshot = local.getSnapshot();
107
+ const daemonRows = poller.rows();
108
+ if (!daemonRows || daemonRows.nodes.length === 0) return localSnapshot;
109
+ const merged = mergeFleetNodes(localSnapshot.rows.map((row) => row.node), daemonRows.nodes);
110
+ // Rebuilt through the SAME builder the local view uses, so the rollups,
111
+ // the cost/token totals and the blocked-on-user ordering are computed
112
+ // once, over the whole fleet, rather than summed from two halves.
113
+ return buildFleetSnapshot(merged, Math.max(localSnapshot.capturedAt, daemonRows.capturedAt));
114
+ },
115
+
116
+ subscribe(listener: () => void): () => void {
117
+ listeners.add(listener);
118
+ const unsubscribeLocal = local.subscribe(listener);
119
+ return () => {
120
+ listeners.delete(listener);
121
+ unsubscribeLocal();
122
+ };
123
+ },
124
+
125
+ interrupt: (id) => local.interrupt(id),
126
+ resume: (id) => local.resume(id),
127
+ kill: (id, opts) => local.kill(id, opts),
128
+ steer: (id, text) => (isLocalId(id)
129
+ ? local.steer(id, text)
130
+ : { queued: false, reason: daemonOnlyFleetActRefusal(id, SURFACE_LABEL) }),
131
+ subscribeConsumed: (listener) => local.subscribeConsumed(listener),
132
+ // The archive is this surface's own view state; the daemon keeps its own.
133
+ getArchivedSnapshot: () => local.getArchivedSnapshot(),
134
+ archive: (id) => (isLocalId(id)
135
+ ? local.archive(id)
136
+ : { archived: false, count: 0, reason: daemonOnlyFleetActRefusal(id, SURFACE_LABEL) }),
137
+ archiveFinished: () => local.archiveFinished(),
138
+ unarchive: (id) => local.unarchive(id),
139
+
140
+ refresh: () => poller.refresh(),
141
+ stop(): void {
142
+ poller.stop();
143
+ unsubscribePoller();
144
+ listeners.clear();
145
+ },
146
+ };
147
+ }
@@ -0,0 +1,71 @@
1
+ /**
2
+ * host-service-status.ts — reading a background service's posture.
3
+ *
4
+ * Three questions the composition root asks about the daemon and the webhook
5
+ * listener, none of which need anything from the composition itself: what URL
6
+ * a configured host/port is reachable at, what to report before the discovery
7
+ * probe has finished, and whether a given verdict means "usable" or "the port
8
+ * is held and this terminal cannot have it".
9
+ */
10
+
11
+ import type { HostServiceStatus } from '@/runtime/index.ts';
12
+ import type { ConfigManager } from '@pellux/goodvibes-sdk/platform/config';
13
+
14
+ /** Which of the two background services a question is about. */
15
+ export type HostServiceName = 'daemon' | 'httpListener';
16
+
17
+ /**
18
+ * The URL a configured host/port is actually reachable at. A wildcard bind is
19
+ * not an address anything can connect to, so `0.0.0.0` and `::` resolve to the
20
+ * loopback address of their family; a bare IPv6 literal is bracketed.
21
+ */
22
+ export function formatHostServiceBaseUrl(host: string, port: number): string {
23
+ const normalized = host.trim().toLowerCase();
24
+ const probeHost = normalized === '0.0.0.0'
25
+ ? '127.0.0.1'
26
+ : normalized === '::' || normalized === '[::]'
27
+ ? '::1'
28
+ : host;
29
+ const urlHost = probeHost.includes(':') && !probeHost.startsWith('[') ? `[${probeHost}]` : probeHost;
30
+ return `http://${urlHost}:${port}`;
31
+ }
32
+
33
+ /**
34
+ * What to report about a service before the discovery probe has answered:
35
+ * where it WOULD be, and the honest reason nothing is known about it yet.
36
+ */
37
+ export function createPendingServiceStatus(
38
+ configManager: Pick<ConfigManager, 'get'>,
39
+ service: HostServiceName,
40
+ ): HostServiceStatus {
41
+ const host = String(configManager.get(service === 'daemon' ? 'controlPlane.host' : 'httpListener.host') ?? '127.0.0.1');
42
+ const port = Number(configManager.get(service === 'daemon' ? 'controlPlane.port' : 'httpListener.port') ?? (service === 'daemon' ? 3421 : 3422));
43
+ return {
44
+ mode: 'unavailable',
45
+ host,
46
+ port,
47
+ baseUrl: formatHostServiceBaseUrl(host, port),
48
+ reason: 'Background service startup has not completed yet',
49
+ };
50
+ }
51
+
52
+ /**
53
+ * A service this terminal can actually use. Called for both the daemon and the
54
+ * HTTP listener (see bootstrap.ts), so the 'embedded' arm stays live: the SDK
55
+ * always reports its in-process HTTP listener as 'embedded', even though this
56
+ * product's daemon status can never carry that mode (adoptOnly is hardcoded on
57
+ * every host-services call this app makes).
58
+ */
59
+ export function hostServiceIsActive(status: HostServiceStatus): boolean {
60
+ return status.mode === 'embedded' || status.mode === 'external';
61
+ }
62
+
63
+ /**
64
+ * The configured port is held and unusable by this terminal — either by an
65
+ * unverified process ('blocked') or by a GoodVibes daemon this build refused to
66
+ * adopt ('incompatible': a wire-version mismatch, or a daemon below this
67
+ * build's own floor).
68
+ */
69
+ export function hostServiceIsBlocked(status: HostServiceStatus): boolean {
70
+ return status.mode === 'blocked' || status.mode === 'incompatible';
71
+ }
@@ -0,0 +1,86 @@
1
+ /**
2
+ * hosted-exit.ts — leaving a hosted session because the terminal is closing.
3
+ *
4
+ * ── Why this exists at all ────────────────────────────────────────────────
5
+ *
6
+ * The detach policy is applied when the LAST attached client detaches. A
7
+ * terminal that simply exits never detaches: the daemon keeps this window in
8
+ * `attachedClients` forever, so a `kill`-policy session — the shipped default,
9
+ * and the behavior closing a client has always had — would quietly NOT be
10
+ * killed, and would sit there attached to a process that no longer exists.
11
+ *
12
+ * That is not a cosmetic gap. The owner's ruling is that the capability lands
13
+ * while the familiar default is preserved: quitting ends the work unless the
14
+ * user asked for `survive`. Preserving it requires the quit to actually say so.
15
+ *
16
+ * ── Bounded, and never able to hold the exit open ─────────────────────────
17
+ *
18
+ * The app's shutdown races a hard timeout; a detach is one small round trip to
19
+ * a daemon on loopback, but a daemon that has stopped answering must not add a
20
+ * hang to an exit that is already restoring the user's terminal. So this is
21
+ * bounded on its own and every failure is swallowed after being logged: the
22
+ * worst case is the session keeping a stale attachment, which the daemon's own
23
+ * housekeeping is what resolves, not this process.
24
+ */
25
+ import { logger, summarizeError } from '@pellux/goodvibes-sdk/platform/utils';
26
+ import type { ConfigManager } from '@pellux/goodvibes-sdk/platform/config';
27
+ import { createDaemonVerbCaller } from './operator-endpoint.ts';
28
+ import { createHostedSessionsClient } from './hosted-sessions.ts';
29
+ import { getSharedHostedSessionFeed, type HostedSessionFeed } from '../../panels/hosted-session-feed.ts';
30
+
31
+ /** How long the exit will wait for the detach before giving up on it. */
32
+ export const HOSTED_DETACH_ON_EXIT_TIMEOUT_MS = 1_500;
33
+
34
+ export interface LeaveHostedSessionOnExitOptions {
35
+ readonly configManager: ConfigManager;
36
+ readonly homeDirectory: string | (() => string);
37
+ /** Defaults to the shared feed the `/hosted` command and the panel use. */
38
+ readonly feed?: HostedSessionFeed | undefined;
39
+ readonly timeoutMs?: number | undefined;
40
+ }
41
+
42
+ /**
43
+ * Detach from whatever hosted session this terminal is attached to, so the
44
+ * daemon applies the policy the record has been advertising all along.
45
+ *
46
+ * Resolves to what happened, for a caller that wants to say so: `'none'` when
47
+ * nothing was attached, `'detached'` when the daemon answered, `'failed'` when
48
+ * it did not. Never throws and never runs past its own budget.
49
+ */
50
+ export async function leaveHostedSessionOnExit(
51
+ options: LeaveHostedSessionOnExitOptions,
52
+ ): Promise<'none' | 'detached' | 'failed'> {
53
+ const feed = options.feed ?? getSharedHostedSessionFeed();
54
+ const record = feed.getState().record;
55
+ // Closing the socket is this process's own business and is done either way —
56
+ // an already-terminated session has nothing to detach from, but the stream
57
+ // watching it is still open.
58
+ feed.closeStream();
59
+ if (!record || record.status === 'terminated') return 'none';
60
+
61
+ const client = createHostedSessionsClient(createDaemonVerbCaller({
62
+ configManager: options.configManager,
63
+ homeDirectory: options.homeDirectory,
64
+ }));
65
+ const timeoutMs = options.timeoutMs ?? HOSTED_DETACH_ON_EXIT_TIMEOUT_MS;
66
+ try {
67
+ const outcome = await Promise.race([
68
+ client.detach(record.id).then(() => 'detached' as const),
69
+ new Promise<'failed'>((resolve) => {
70
+ const timer = setTimeout(() => resolve('failed'), timeoutMs);
71
+ timer.unref?.();
72
+ }),
73
+ ]);
74
+ if (outcome === 'failed') {
75
+ logger.debug('[hosted-sessions] the detach-on-exit did not complete in time', {
76
+ sessionId: record.id, timeoutMs,
77
+ });
78
+ }
79
+ return outcome;
80
+ } catch (error) {
81
+ logger.debug('[hosted-sessions] the detach-on-exit was refused', {
82
+ sessionId: record.id, error: summarizeError(error),
83
+ });
84
+ return 'failed';
85
+ }
86
+ }