@pellux/goodvibes-tui 1.27.1 → 2.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (405) hide show
  1. package/CHANGELOG.md +127 -2
  2. package/README.md +7 -9
  3. package/bin/goodvibes +2 -2
  4. package/bin/launcher-support.js +8 -4
  5. package/docs/README.md +11 -2
  6. package/docs/foundation-artifacts/operator-contract.json +6403 -2347
  7. package/package.json +14 -25
  8. package/scripts/postinstall.js +35 -35
  9. package/src/audio/spoken-turn-model-routing.ts +1 -1
  10. package/src/cli/bundle-command.ts +5 -5
  11. package/src/cli/completions/generate.ts +1 -1
  12. package/src/cli/doctor.ts +3 -3
  13. package/src/cli/entrypoint.ts +12 -7
  14. package/src/cli/help.ts +4 -5
  15. package/src/cli/hooks-command.ts +1 -1
  16. package/src/cli/index.ts +1 -4
  17. package/src/cli/launch-auto-update.ts +59 -7
  18. package/src/cli/management-commands.ts +6 -6
  19. package/src/cli/management-utils.ts +5 -5
  20. package/src/cli/management.ts +7 -7
  21. package/src/cli/network-posture.ts +1 -1
  22. package/src/cli/package-verification.ts +7 -6
  23. package/src/cli/plugin-bundles-command.ts +1 -1
  24. package/src/cli/plugin-command.ts +2 -2
  25. package/src/cli/service-command.ts +2 -2
  26. package/src/cli/service-posture.ts +4 -4
  27. package/src/cli/status.ts +9 -8
  28. package/src/cli/surface-command.ts +4 -4
  29. package/src/cli/tui-startup.ts +3 -3
  30. package/src/cli-flags.ts +3 -3
  31. package/src/config/goodvibes-home-audit.ts +0 -2
  32. package/src/config/secret-config.ts +37 -5
  33. package/src/config/tui-extension-settings.ts +1 -1
  34. package/src/core/approval-alert.ts +2 -2
  35. package/src/core/budget-breach-notifier.ts +3 -3
  36. package/src/core/conversation-compaction-render.ts +1 -1
  37. package/src/core/conversation-line-cache.ts +1 -1
  38. package/src/core/conversation-render-context.ts +1 -1
  39. package/src/core/conversation-rendering.ts +2 -2
  40. package/src/core/conversation.ts +10 -10
  41. package/src/core/long-task-notifier.ts +2 -2
  42. package/src/core/memory-diagnostics-gateway.ts +1 -1
  43. package/src/core/scriptable-statusline.ts +1 -1
  44. package/src/core/session-auto-titler.ts +1 -1
  45. package/src/core/session-resume-core.ts +2 -2
  46. package/src/core/stream-event-wiring.ts +24 -44
  47. package/src/core/terminal-notifier.ts +3 -3
  48. package/src/core/turn-cancellation.ts +1 -1
  49. package/src/core/turn-event-wiring.ts +5 -5
  50. package/src/input/command-registry.ts +6 -6
  51. package/src/input/commands/channel-pairing.ts +1 -1
  52. package/src/input/commands/cluster-runtime.ts +1 -1
  53. package/src/input/commands/codebase-runtime.ts +1 -1
  54. package/src/input/commands/devices-runtime.ts +1 -1
  55. package/src/input/commands/hosted-runtime.ts +330 -0
  56. package/src/input/commands/local-setup-review.ts +1 -1
  57. package/src/input/commands/local-setup-transfer.ts +2 -2
  58. package/src/input/commands/local-setup.ts +3 -3
  59. package/src/input/commands/managed-runtime.ts +2 -2
  60. package/src/input/commands/operator-rpc.ts +24 -110
  61. package/src/input/commands/payment-card-intake.ts +1 -1
  62. package/src/input/commands/permissions-provenance.ts +2 -2
  63. package/src/input/commands/plugin-runtime.ts +1 -1
  64. package/src/input/commands/provider.ts +1 -1
  65. package/src/input/commands/relay-runtime.ts +18 -9
  66. package/src/input/commands/rewind-runtime.ts +1 -1
  67. package/src/input/commands/runtime-services.ts +1 -1
  68. package/src/input/commands/services-runtime.ts +1 -1
  69. package/src/input/commands/session-workflow.ts +1 -1
  70. package/src/input/commands/settings-sync-runtime.ts +2 -2
  71. package/src/input/commands/share-runtime.ts +1 -1
  72. package/src/input/commands/tasks-runtime.ts +145 -55
  73. package/src/input/commands/update-runtime.ts +31 -28
  74. package/src/input/commands/work-plan-runtime.ts +2 -2
  75. package/src/input/commands/workstream-attempts.ts +1 -1
  76. package/src/input/commands/workstream-runtime.ts +2 -2
  77. package/src/input/commands.ts +2 -0
  78. package/src/input/feature-unit-layout.ts +1 -1
  79. package/src/input/feed-context-factory.ts +3 -3
  80. package/src/input/handler-content-actions.ts +2 -2
  81. package/src/input/handler-feed-routes.ts +1 -1
  82. package/src/input/handler-feed.ts +3 -3
  83. package/src/input/handler-modal-token-routes.ts +1 -1
  84. package/src/input/handler-onboarding-daemon-adopt.ts +3 -2
  85. package/src/input/handler-onboarding.ts +1 -1
  86. package/src/input/handler-types.ts +2 -2
  87. package/src/input/handler-ui-state.ts +1 -1
  88. package/src/input/handler.ts +4 -4
  89. package/src/input/input-history.ts +1 -1
  90. package/src/input/onboarding/onboarding-feature-units.ts +2 -2
  91. package/src/input/onboarding/onboarding-runtime-status.ts +15 -1
  92. package/src/input/onboarding/onboarding-wizard-apply.ts +17 -8
  93. package/src/input/onboarding/onboarding-wizard-cloudflare-step.ts +5 -3
  94. package/src/input/onboarding/onboarding-wizard-external-surfaces.ts +1 -1
  95. package/src/input/onboarding/onboarding-wizard-types.ts +1 -1
  96. package/src/input/payments-config.ts +6 -6
  97. package/src/input/search.ts +1 -1
  98. package/src/input/session-picker-modal.ts +46 -0
  99. package/src/input/settings-modal-activation.ts +1 -1
  100. package/src/input/settings-modal-behavior.ts +4 -4
  101. package/src/input/settings-modal-commit.ts +11 -0
  102. package/src/input/settings-modal-data.ts +9 -122
  103. package/src/input/settings-modal-mutations.ts +33 -0
  104. package/src/input/settings-modal-secrets.ts +34 -0
  105. package/src/input/settings-modal-subscriptions.ts +1 -1
  106. package/src/input/settings-modal-types.ts +15 -1
  107. package/src/input/settings-modal.ts +28 -3
  108. package/src/input/worktree-setup-config.ts +1 -1
  109. package/src/main.ts +11 -9
  110. package/src/panels/agent-inspector-shared.ts +2 -2
  111. package/src/panels/base-panel.ts +2 -2
  112. package/src/panels/builtin/operations.ts +6 -8
  113. package/src/panels/builtin/session.ts +17 -0
  114. package/src/panels/builtin/shared.ts +5 -5
  115. package/src/panels/builtin-modals.ts +3 -3
  116. package/src/panels/confirm-state.ts +1 -1
  117. package/src/panels/cost-tracker-panel.ts +4 -4
  118. package/src/panels/diff-panel.ts +2 -2
  119. package/src/panels/diff-review-panel.ts +2 -2
  120. package/src/panels/expandable-list-panel.ts +2 -2
  121. package/src/panels/fleet-acts.ts +1 -1
  122. package/src/panels/fleet-observed-render.ts +1 -1
  123. package/src/panels/fleet-panel-format.ts +1 -1
  124. package/src/panels/fleet-panel.ts +1 -1
  125. package/src/panels/fleet-spawn.ts +1 -1
  126. package/src/panels/fleet-steer.ts +1 -1
  127. package/src/panels/fleet-transcript.ts +1 -1
  128. package/src/panels/git-panel.ts +1 -1
  129. package/src/panels/hosted-session-feed.ts +352 -0
  130. package/src/panels/hosted-session-panel.ts +221 -0
  131. package/src/panels/local-auth-panel.ts +3 -3
  132. package/src/panels/modals/devices-modal.ts +1 -1
  133. package/src/panels/modals/local-auth-modal.ts +1 -1
  134. package/src/panels/modals/pairing-modal.ts +1 -1
  135. package/src/panels/modals/services-modal.ts +1 -1
  136. package/src/panels/modals/settings-sync-modal.ts +1 -1
  137. package/src/panels/modals/subscription-modal.ts +1 -1
  138. package/src/panels/modals/work-plan-modal.ts +1 -1
  139. package/src/panels/notifications-panel.ts +1 -1
  140. package/src/panels/panel-confirm-overlay.ts +1 -1
  141. package/src/panels/plugins-panel.ts +2 -2
  142. package/src/panels/polish-core.ts +2 -2
  143. package/src/panels/polish-tables.ts +1 -1
  144. package/src/panels/polish.ts +2 -2
  145. package/src/panels/scrollable-list-panel.ts +3 -3
  146. package/src/panels/skills-panel.ts +3 -3
  147. package/src/panels/token-budget-panel.ts +5 -5
  148. package/src/panels/types.ts +1 -2
  149. package/src/permissions/prompt.ts +1 -1
  150. package/src/renderer/autocomplete-overlay.ts +1 -1
  151. package/src/renderer/block-actions-overlay.ts +1 -1
  152. package/src/renderer/bookmark-modal.ts +1 -1
  153. package/src/renderer/buffer.ts +1 -1
  154. package/src/renderer/code-block.ts +1 -1
  155. package/src/renderer/composer-fragments.ts +1 -1
  156. package/src/renderer/compositor.ts +2 -2
  157. package/src/renderer/config-modal.ts +1 -1
  158. package/src/renderer/context-inspector.ts +1 -1
  159. package/src/renderer/conversation-layout.ts +2 -2
  160. package/src/renderer/conversation-overlays.ts +1 -1
  161. package/src/renderer/conversation-surface.ts +2 -2
  162. package/src/renderer/diff-view.ts +1 -1
  163. package/src/renderer/diff.ts +1 -1
  164. package/src/renderer/file-picker-overlay.ts +1 -1
  165. package/src/renderer/fleet-tab-strip.ts +1 -1
  166. package/src/renderer/fullscreen-primitives.ts +2 -2
  167. package/src/renderer/fullscreen-workspace.ts +1 -1
  168. package/src/renderer/header-line.ts +1 -1
  169. package/src/renderer/help-overlay.ts +1 -1
  170. package/src/renderer/history-search-overlay.ts +2 -2
  171. package/src/renderer/layout.ts +5 -8
  172. package/src/renderer/markdown-inline.ts +1 -1
  173. package/src/renderer/markdown-table.ts +1 -1
  174. package/src/renderer/markdown.ts +1 -1
  175. package/src/renderer/mcp-workspace.ts +1 -1
  176. package/src/renderer/modal-factory.ts +2 -2
  177. package/src/renderer/model-workspace.ts +2 -2
  178. package/src/renderer/onboarding/onboarding-wizard.ts +1 -1
  179. package/src/renderer/overlay-box.ts +1 -1
  180. package/src/renderer/panel-composite.ts +1 -1
  181. package/src/renderer/panel-workspace-bar.ts +1 -1
  182. package/src/renderer/process-indicator.ts +1 -1
  183. package/src/renderer/profile-picker-modal.ts +1 -1
  184. package/src/renderer/search-overlay.ts +2 -2
  185. package/src/renderer/selection-modal-overlay.ts +1 -1
  186. package/src/renderer/session-picker-modal.ts +124 -3
  187. package/src/renderer/settings-modal-helpers.ts +2 -0
  188. package/src/renderer/settings-modal.ts +2 -2
  189. package/src/renderer/shell-surface.ts +2 -2
  190. package/src/renderer/system-message.ts +1 -1
  191. package/src/renderer/tab-strip.ts +1 -1
  192. package/src/renderer/theme-mode-config.ts +21 -33
  193. package/src/renderer/theme.ts +11 -12
  194. package/src/renderer/thinking.ts +1 -1
  195. package/src/renderer/tool-call.ts +2 -2
  196. package/src/renderer/tool-result-expanded-lines.ts +1 -1
  197. package/src/renderer/turn-injection.ts +7 -8
  198. package/src/renderer/ui-factory.ts +3 -3
  199. package/src/runtime/bootstrap-command-context.ts +4 -4
  200. package/src/runtime/bootstrap-command-parts.ts +5 -5
  201. package/src/runtime/bootstrap-core.ts +30 -30
  202. package/src/runtime/bootstrap-shell.ts +4 -4
  203. package/src/runtime/bootstrap.ts +128 -157
  204. package/src/runtime/client/approval-updates.ts +82 -0
  205. package/src/runtime/client/build-floors.ts +167 -0
  206. package/src/runtime/client/daemon-attach-handshake.ts +115 -0
  207. package/src/runtime/client/fleet-union.ts +147 -0
  208. package/src/runtime/client/host-service-status.ts +71 -0
  209. package/src/runtime/client/hosted-exit.ts +86 -0
  210. package/src/runtime/client/hosted-roster.ts +110 -0
  211. package/src/runtime/client/hosted-session-stream.ts +178 -0
  212. package/src/runtime/client/hosted-sessions.ts +187 -0
  213. package/src/runtime/client/operator-endpoint.ts +213 -0
  214. package/src/runtime/client/spine-adoption.ts +99 -0
  215. package/src/runtime/cloudflare-control-plane.ts +1 -1
  216. package/src/runtime/context-accounting-source.ts +1 -1
  217. package/src/runtime/context.ts +2 -2
  218. package/src/runtime/conversation-rewind-port.ts +42 -14
  219. package/src/runtime/daemon-attach-notices.ts +55 -19
  220. package/src/runtime/diagnostics/panels/ops.ts +1 -1
  221. package/src/runtime/disposal-wiring.ts +4 -24
  222. package/src/runtime/index.ts +119 -105
  223. package/src/runtime/install-self-check-startup.ts +2 -2
  224. package/src/runtime/interaction-seams.ts +1 -1
  225. package/src/runtime/knowledge-services.ts +8 -2
  226. package/src/runtime/legacy-daemon-migration.ts +4 -3
  227. package/src/runtime/legacy-daemon-reconcile.ts +3 -2
  228. package/src/runtime/memory-spine-transport.ts +37 -267
  229. package/src/runtime/notification-dispatch.ts +2 -2
  230. package/src/runtime/onboarding/apply.ts +2 -2
  231. package/src/runtime/onboarding/derivation.ts +2 -2
  232. package/src/runtime/onboarding/markers.ts +2 -2
  233. package/src/runtime/onboarding/progress.ts +2 -2
  234. package/src/runtime/onboarding/snapshot.ts +1 -1
  235. package/src/runtime/onboarding/state.ts +2 -2
  236. package/src/runtime/onboarding/types.ts +2 -2
  237. package/src/runtime/orchestrator-core-services.ts +1 -1
  238. package/src/runtime/path-shadow-startup.ts +28 -221
  239. package/src/runtime/power-keepawake-remote.ts +3 -2
  240. package/src/runtime/process-lifecycle.ts +2 -2
  241. package/src/runtime/provider-fallback.ts +10 -66
  242. package/src/runtime/recovery-autosave.ts +1 -1
  243. package/src/runtime/recovery-offer-wiring.ts +39 -23
  244. package/src/runtime/recovery-prompt.ts +31 -17
  245. package/src/runtime/relay-reachability-bridge.ts +53 -42
  246. package/src/runtime/release-artifacts.ts +2 -1
  247. package/src/runtime/runtime-services-types.ts +82 -35
  248. package/src/runtime/services.ts +305 -300
  249. package/src/runtime/session-resume-liveness-confirm.ts +1 -1
  250. package/src/runtime/ui-services.ts +7 -6
  251. package/src/runtime/update-check.ts +23 -43
  252. package/src/runtime/workspace-checkpointing.ts +1 -1
  253. package/src/runtime/wrfc-persistence.ts +2 -2
  254. package/src/shell/ui-openers.ts +30 -7
  255. package/src/utils/clipboard.ts +1 -1
  256. package/src/verification/verification-ledger.ts +10 -8
  257. package/src/version.ts +1 -1
  258. package/bin/goodvibes-daemon +0 -90
  259. package/src/cli/config-overrides.ts +0 -267
  260. package/src/cli/endpoints.ts +0 -109
  261. package/src/cli/parser.ts +0 -395
  262. package/src/cli/redaction.ts +0 -170
  263. package/src/cli/types.ts +0 -84
  264. package/src/cluster/commands.ts +0 -382
  265. package/src/cluster/remote-daemon-target.ts +0 -225
  266. package/src/cluster/render.ts +0 -268
  267. package/src/config/atomic-write.ts +0 -70
  268. package/src/config/credential-availability.ts +0 -122
  269. package/src/config/goodvibes-home.ts +0 -149
  270. package/src/config/index.ts +0 -88
  271. package/src/config/provider-model.ts +0 -23
  272. package/src/config/read-versioned.ts +0 -228
  273. package/src/core/alert-gating.ts +0 -67
  274. package/src/core/consolidation-receipt.ts +0 -41
  275. package/src/core/focus-tracker.ts +0 -41
  276. package/src/core/history.ts +0 -45
  277. package/src/core/memory-status.ts +0 -118
  278. package/src/core/orchestrator.ts +0 -7
  279. package/src/core/pairing-devices.ts +0 -49
  280. package/src/core/pairing-handoff.ts +0 -101
  281. package/src/core/pairing-offers.ts +0 -83
  282. package/src/core/pairing-origin.ts +0 -78
  283. package/src/core/rewind-turn-anchors.ts +0 -311
  284. package/src/core/session-recovery.ts +0 -210
  285. package/src/core/stable-host.ts +0 -171
  286. package/src/core/transcript-journal.ts +0 -503
  287. package/src/daemon/cli.ts +0 -693
  288. package/src/daemon/fatal-boot-report.ts +0 -111
  289. package/src/daemon/handlers/context.ts +0 -29
  290. package/src/daemon/handlers/contracts.ts +0 -77
  291. package/src/daemon/handlers/credentials.ts +0 -139
  292. package/src/daemon/handlers/drafts/draft-store.ts +0 -427
  293. package/src/daemon/handlers/drafts/index.ts +0 -17
  294. package/src/daemon/handlers/drafts/register.ts +0 -331
  295. package/src/daemon/handlers/errors.ts +0 -18
  296. package/src/daemon/handlers/inbox/cursor-store.ts +0 -457
  297. package/src/daemon/handlers/inbox/index.ts +0 -284
  298. package/src/daemon/handlers/inbox/mapping.ts +0 -192
  299. package/src/daemon/handlers/inbox/poller.ts +0 -212
  300. package/src/daemon/handlers/inbox/provider-adapter.ts +0 -156
  301. package/src/daemon/handlers/inbox/providers/discord.ts +0 -252
  302. package/src/daemon/handlers/inbox/providers/email.ts +0 -152
  303. package/src/daemon/handlers/inbox/providers/imap-client.ts +0 -300
  304. package/src/daemon/handlers/inbox/providers/route-util.ts +0 -24
  305. package/src/daemon/handlers/inbox/providers/slack.ts +0 -263
  306. package/src/daemon/handlers/index.ts +0 -117
  307. package/src/daemon/handlers/register.ts +0 -168
  308. package/src/daemon/handlers/remote/backends/cloud-terminal.ts +0 -142
  309. package/src/daemon/handlers/remote/backends/docker.ts +0 -79
  310. package/src/daemon/handlers/remote/backends/index.ts +0 -40
  311. package/src/daemon/handlers/remote/backends/local-process.ts +0 -113
  312. package/src/daemon/handlers/remote/backends/process-runner.ts +0 -127
  313. package/src/daemon/handlers/remote/backends/ssh.ts +0 -125
  314. package/src/daemon/handlers/remote/backends/types.ts +0 -97
  315. package/src/daemon/handlers/remote/dispatcher.ts +0 -181
  316. package/src/daemon/handlers/remote/index.ts +0 -119
  317. package/src/daemon/handlers/remote/peer-registry.ts +0 -357
  318. package/src/daemon/handlers/remote/service.ts +0 -191
  319. package/src/daemon/handlers/routing/inbox-bridge.ts +0 -71
  320. package/src/daemon/handlers/routing/index.ts +0 -261
  321. package/src/daemon/handlers/routing/route-store.ts +0 -319
  322. package/src/daemon/handlers/routing/routing-resolver.ts +0 -75
  323. package/src/daemon/handlers/sqlite-store.ts +0 -278
  324. package/src/daemon/handlers/triage/index.ts +0 -57
  325. package/src/daemon/handlers/triage/integration.ts +0 -213
  326. package/src/daemon/handlers/triage/pipeline.ts +0 -274
  327. package/src/daemon/handlers/triage/scorer.ts +0 -287
  328. package/src/daemon/handlers/triage/tagger/discord.ts +0 -187
  329. package/src/daemon/handlers/triage/tagger/imap.ts +0 -384
  330. package/src/daemon/handlers/triage/tagger/index.ts +0 -184
  331. package/src/daemon/handlers/triage/tagger/shared.ts +0 -70
  332. package/src/daemon/handlers/triage/tagger/slack.ts +0 -69
  333. package/src/daemon/handlers/triage/types.ts +0 -50
  334. package/src/daemon/lifecycle.ts +0 -41
  335. package/src/daemon/provision-wake-model.ts +0 -81
  336. package/src/daemon/safe-serve.ts +0 -61
  337. package/src/daemon/send/channels.ts +0 -200
  338. package/src/daemon/send/command.ts +0 -332
  339. package/src/daemon/send/composition.ts +0 -99
  340. package/src/daemon/send/failure-text.ts +0 -93
  341. package/src/daemon/send/inert-text.ts +0 -225
  342. package/src/daemon/send/stdin.ts +0 -24
  343. package/src/daemon/service-commands.ts +0 -329
  344. package/src/export/cost-utils.ts +0 -319
  345. package/src/input/selection.ts +0 -135
  346. package/src/plugins/loader.ts +0 -15
  347. package/src/renderer/bottom-bar.ts +0 -58
  348. package/src/renderer/conversation-tree.ts +0 -228
  349. package/src/runtime/agent-graph-composition.ts +0 -78
  350. package/src/runtime/channel-composition.ts +0 -46
  351. package/src/runtime/cluster-composition.ts +0 -124
  352. package/src/runtime/cluster-group-composition.ts +0 -284
  353. package/src/runtime/code-index-services.ts +0 -167
  354. package/src/runtime/daemon-handler-composition.ts +0 -76
  355. package/src/runtime/device-posture-composition.ts +0 -110
  356. package/src/runtime/durability-housekeeping.ts +0 -183
  357. package/src/runtime/durability-services.ts +0 -155
  358. package/src/runtime/embedded-service-factories.ts +0 -74
  359. package/src/runtime/feature-settings.ts +0 -154
  360. package/src/runtime/fleet-needs-input-push.ts +0 -70
  361. package/src/runtime/fleet-services.ts +0 -41
  362. package/src/runtime/idle-power-services.ts +0 -85
  363. package/src/runtime/mail-composition.ts +0 -65
  364. package/src/runtime/memory-fold.ts +0 -55
  365. package/src/runtime/operator-token-cleanup.ts +0 -109
  366. package/src/runtime/path-shadow.ts +0 -418
  367. package/src/runtime/perf/panel-health-monitor.ts +0 -18
  368. package/src/runtime/provider-optimizer-wiring.ts +0 -55
  369. package/src/runtime/reachability-notice.ts +0 -100
  370. package/src/runtime/remote-execution-composition.ts +0 -62
  371. package/src/runtime/session-liveness-marker.ts +0 -257
  372. package/src/runtime/session-pointer-surface.ts +0 -22
  373. package/src/runtime/session-storage-services.ts +0 -41
  374. package/src/runtime/surface-feature-flags.ts +0 -96
  375. package/src/runtime/terminal-output-guard.ts +0 -235
  376. package/src/runtime/trust/workspace-registration.ts +0 -192
  377. package/src/runtime/trust/workspace-trust.ts +0 -229
  378. package/src/runtime/ui/index.ts +0 -39
  379. package/src/runtime/ui/model-picker/data-provider.ts +0 -182
  380. package/src/runtime/ui/model-picker/health-enrichment.ts +0 -231
  381. package/src/runtime/ui/model-picker/index.ts +0 -59
  382. package/src/runtime/ui/model-picker/types.ts +0 -153
  383. package/src/runtime/ui/provider-health/data-provider.ts +0 -244
  384. package/src/runtime/ui/provider-health/fallback-visualizer.ts +0 -71
  385. package/src/runtime/ui/provider-health/index.ts +0 -46
  386. package/src/runtime/ui/provider-health/types.ts +0 -146
  387. package/src/runtime/ui-events.ts +0 -1
  388. package/src/runtime/ui-read-model-helpers.ts +0 -1
  389. package/src/runtime/ui-read-models-observability-maintenance.ts +0 -1
  390. package/src/runtime/ui-read-models-observability-options.ts +0 -1
  391. package/src/runtime/ui-read-models-observability-remote.ts +0 -1
  392. package/src/runtime/ui-read-models-observability-security.ts +0 -1
  393. package/src/runtime/ui-read-models-observability-system.ts +0 -1
  394. package/src/runtime/ui-read-models-observability.ts +0 -1
  395. package/src/runtime/ui-service-queries.ts +0 -1
  396. package/src/runtime/voice-setup-services.ts +0 -98
  397. package/src/runtime/workstream-attempts-validation.ts +0 -77
  398. package/src/runtime/workstream-draft-edits.ts +0 -95
  399. package/src/runtime/workstream-draft-store.ts +0 -274
  400. package/src/runtime/workstream-draft-types.ts +0 -42
  401. package/src/runtime/workstream-services.ts +0 -373
  402. package/src/tools/index.ts +0 -1
  403. package/src/types/grid.ts +0 -48
  404. package/src/types/sql-js.d.ts +0 -15
  405. package/src/work-plans/work-plan-store.ts +0 -702
@@ -0,0 +1,213 @@
1
+ /**
2
+ * operator-endpoint.ts — the one place this product resolves "the daemon I talk
3
+ * to", for every seam that talks to it.
4
+ *
5
+ * The terminal app is a pure client: it runs its own conversation loop and asks
6
+ * a separately-running daemon for everything else. Every one of those asks is
7
+ * an operator verb over HTTP, and every one of them needs the same three
8
+ * things — is the daemon enabled, what base URL does it answer on, and what
9
+ * bearer token proves this surface may call it. Resolving that in each seam is
10
+ * how the old composition ended up with a DirectTransport in one place and a
11
+ * real fetch in another.
12
+ *
13
+ * `resolveOperatorRpc` was previously the command layer's private helper
14
+ * (input/commands/operator-rpc.ts). It moved here unchanged in behaviour so the
15
+ * runtime seams — approvals, config, credentials, sessions, fleet, tasks,
16
+ * devices, checkpoints — reach the daemon through the SAME resolution the
17
+ * commands already used, and the command module now re-exports it.
18
+ *
19
+ * Refusals are values, never throws: a disabled daemon or an underivable base
20
+ * URL comes back as `{ available: false, reason }` so a caller prints an honest
21
+ * line instead of guessing. Once a call is made, a non-2xx (including the 404 a
22
+ * daemon that has not wired a verb returns) throws `GoodVibesSdkError` and
23
+ * `describeOperatorRpcError` renders it.
24
+ *
25
+ * Auth is the loopback file-token bootstrap: the token
26
+ * is read (or minted) from the daemon's own state directory. A daemon on
27
+ * another machine is reached by the network-adopt path, which writes that
28
+ * daemon's bearer into the same file — so this resolution covers both.
29
+ */
30
+ import { join } from 'node:path';
31
+ import { createGoodVibesSdk } from '@pellux/goodvibes-sdk';
32
+ import type { GoodVibesSdk } from '@pellux/goodvibes-sdk';
33
+ import { GoodVibesSdkError } from '@pellux/goodvibes-sdk';
34
+ import type { ConfigManager } from '@pellux/goodvibes-sdk/platform/config';
35
+ import { resolveDaemonEnabled } from '@pellux/goodvibes-sdk/platform/config';
36
+ import { getOrCreateCompanionToken } from '@pellux/goodvibes-sdk/platform/pairing';
37
+ import type { DaemonVerbCaller } from '@pellux/goodvibes-sdk/platform/runtime/client';
38
+
39
+ /** Re-exported so this repo's importers keep one import site for the shape
40
+ * every client seam takes. See the doc comment further down for why
41
+ * `OperatorRpc`/`OperatorRpcAvailable` (declared below, carrying `sdk`)
42
+ * structurally satisfy the SDK's `DaemonReachability`, and so `DaemonVerbCaller`
43
+ * built here still type-checks as the SDK's own. */
44
+ export type { DaemonVerbCaller };
45
+
46
+ export interface OperatorRpcUnavailable {
47
+ readonly available: false;
48
+ readonly reason: string;
49
+ }
50
+
51
+ export interface OperatorRpcAvailable {
52
+ readonly available: true;
53
+ readonly sdk: GoodVibesSdk;
54
+ }
55
+
56
+ export type OperatorRpc = OperatorRpcUnavailable | OperatorRpcAvailable;
57
+
58
+ /**
59
+ * Derive the dial address of the configured control-plane daemon.
60
+ *
61
+ * An explicitly declared external address wins; otherwise the URL is DERIVED
62
+ * from hostMode/host/port/tls.mode. This deliberately does not prefer a stored
63
+ * `controlPlane.baseUrl`: that key had no writers and so drifted from the bind
64
+ * on port, scheme, and host at once.
65
+ */
66
+ export function resolveControlPlaneBaseUrl(configManager: ConfigManager): string | null {
67
+ const explicit = String(configManager.get('controlPlane.publicBaseUrl' as never) ?? '').trim();
68
+ if (explicit) return explicit.replace(/\/+$/, '');
69
+ const host = String(configManager.get('controlPlane.host') ?? '').trim();
70
+ const port = configManager.get('controlPlane.port');
71
+ if (host && typeof port === 'number' && port > 0) {
72
+ const scheme = String(configManager.get('controlPlane.tls.mode') ?? 'off') !== 'off' ? 'https' : 'http';
73
+ // A wildcard bind is not a dial target; loopback is the interface it answers on.
74
+ const dialHost = host === '0.0.0.0' || host === '::' ? '127.0.0.1' : host;
75
+ return `${scheme}://${dialHost.includes(':') && !dialHost.startsWith('[') ? `[${dialHost}]` : dialHost}:${port}`;
76
+ }
77
+ return null;
78
+ }
79
+
80
+ /** The daemon's own state directory — where the shared bearer token lives. */
81
+ export function resolveDaemonStateDirectory(homeDirectory: string): string {
82
+ return join(homeDirectory, '.goodvibes', 'daemon');
83
+ }
84
+
85
+ /**
86
+ * Resolve (or honestly refuse to resolve) an operator SDK client wired to this
87
+ * workspace's control-plane daemon, from just the two things the resolution
88
+ * actually needs — the config manager and the home directory.
89
+ */
90
+ export function resolveOperatorRpc(deps: {
91
+ readonly configManager: ConfigManager;
92
+ readonly homeDirectory: string | (() => string);
93
+ }): OperatorRpc {
94
+ const { configManager } = deps;
95
+ // A refusal is a VALUE here, never a throw — that is the whole contract of
96
+ // this function, and callers rely on it to print an honest line rather than
97
+ // crash a keystroke. A context whose config manager cannot answer (a narrow
98
+ // embed, a partially-wired test double) is one more case of "no daemon can be
99
+ // resolved", not an exception to raise from a probe.
100
+ if (typeof (configManager as { get?: unknown } | null)?.get !== 'function') {
101
+ return { available: false, reason: 'no config manager is wired here, so no control-plane daemon can be resolved.' };
102
+ }
103
+ if (!resolveDaemonEnabled(configManager)) {
104
+ return { available: false, reason: 'the daemon is disabled (daemon.enabled=false) — no operator surface to reach. Enable it in /settings, then retry.' };
105
+ }
106
+ const baseUrl = resolveControlPlaneBaseUrl(configManager);
107
+ if (!baseUrl) {
108
+ return { available: false, reason: 'no control-plane base URL is configured (controlPlane.publicBaseUrl / controlPlane.host+port) — cannot reach the operator surface.' };
109
+ }
110
+ // Resolve the home directory only AFTER the static refusals — a caller whose
111
+ // shell paths are not wired (a disabled-daemon path) must still get the honest
112
+ // unavailable reason above, never a "shell paths not wired" throw.
113
+ const homeDirectory = typeof deps.homeDirectory === 'function' ? deps.homeDirectory() : deps.homeDirectory;
114
+ const token = getOrCreateCompanionToken('tui', { daemonHomeDir: resolveDaemonStateDirectory(homeDirectory) }).token;
115
+ const sdk = createGoodVibesSdk({ baseUrl, authToken: token });
116
+ return { available: true, sdk };
117
+ }
118
+
119
+ /**
120
+ * Render a thrown operator-rpc error honestly: a 404 means this daemon has not
121
+ * wired a handler for the verb (a live daemon that simply doesn't implement it
122
+ * yet), never "no result" or a fabricated empty state.
123
+ */
124
+ export function describeOperatorRpcError(error: unknown): string {
125
+ if (error instanceof GoodVibesSdkError) {
126
+ if (error.status === 404) {
127
+ return 'the connected daemon returned 404 — this operator verb is not wired up on that daemon yet.';
128
+ }
129
+ if (error.status === 401 || error.status === 403) {
130
+ return `the connected daemon rejected the request (${error.status}): ${error.message}`;
131
+ }
132
+ return `operator request failed${error.status ? ` (${error.status})` : ''}: ${error.message}`;
133
+ }
134
+ return error instanceof Error ? error.message : String(error);
135
+ }
136
+
137
+ /**
138
+ * The generic gateway-method route, for a verb the contract carries with a
139
+ * WEBSOCKET binding only.
140
+ *
141
+ * This matters more than it looks. `sdk.operator.invoke` resolves a method's
142
+ * declared `http` route and refuses — with a contract error, before any request
143
+ * is made — when the method has none. A large part of what this client needs is
144
+ * exactly that class: `approvals.raise`, `credentials.set`/`delete`,
145
+ * `checkpoints.*`, `rewind.*` and the `fleet.*` reads are all `transport: ws`
146
+ * in the operator contract, with no REST path of their own.
147
+ *
148
+ * The daemon serves every catalogued verb over one generic route regardless of
149
+ * its declared transport, which is what a client that is not holding a
150
+ * websocket uses. Calling that route directly is not a workaround for a missing
151
+ * binding: it IS the binding for a ws-declared verb reached over HTTP.
152
+ */
153
+ async function invokeGatewayMethodOverHttp<T>(
154
+ rpc: OperatorRpcAvailable,
155
+ baseUrl: string,
156
+ authToken: string,
157
+ methodId: string,
158
+ input: unknown,
159
+ ): Promise<T> {
160
+ void rpc;
161
+ const response = await fetch(`${baseUrl}/api/control-plane/methods/${encodeURIComponent(methodId)}/invoke`, {
162
+ method: 'POST',
163
+ headers: { Authorization: `Bearer ${authToken}`, 'Content-Type': 'application/json' },
164
+ // The envelope the route requires: `body` must be PRESENT even when empty,
165
+ // or the route refuses with a 400 naming the shape it wanted.
166
+ body: JSON.stringify({ body: input ?? {} }),
167
+ });
168
+ const payload = await response.json().catch(() => null) as unknown;
169
+ if (!response.ok) {
170
+ const described = payload && typeof payload === 'object' && 'error' in payload
171
+ ? String((payload as { error: unknown }).error)
172
+ : `HTTP ${response.status}`;
173
+ throw new GoodVibesSdkError(`'${methodId}' failed: ${described}`, {
174
+ // Mapped from the status rather than fixed, so `describeOperatorRpcError`
175
+ // and every caller that branches on category sees the same classification
176
+ // it would have seen through the typed client path.
177
+ category: response.status === 401 || response.status === 403
178
+ ? 'authorization'
179
+ : response.status === 404 ? 'not_found' : response.status >= 500 ? 'service' : 'bad_request',
180
+ source: 'runtime',
181
+ recoverable: response.status >= 500,
182
+ status: response.status,
183
+ });
184
+ }
185
+ return payload as T;
186
+ }
187
+
188
+ export function createDaemonVerbCaller(deps: {
189
+ readonly configManager: ConfigManager;
190
+ readonly homeDirectory: string | (() => string);
191
+ }): DaemonVerbCaller {
192
+ return {
193
+ probe: () => resolveOperatorRpc(deps),
194
+ invoke: async <T,>(methodId: string, input?: unknown): Promise<T> => {
195
+ const rpc = resolveOperatorRpc(deps);
196
+ if (!rpc.available) throw new Error(`cannot invoke '${methodId}': ${rpc.reason}`);
197
+ try {
198
+ return await rpc.sdk.operator.invoke(methodId as never, (input ?? {}) as never) as T;
199
+ } catch (error) {
200
+ // A CONTRACT_MISMATCH here means exactly one thing: this verb declares
201
+ // no HTTP route. That is a routing fact, not a failure of the call, so
202
+ // it falls through to the generic gateway route rather than surfacing
203
+ // as "the daemon refused" to a user who did nothing wrong.
204
+ if (!(error instanceof GoodVibesSdkError) || error.code !== 'CONTRACT_MISMATCH') throw error;
205
+ const baseUrl = resolveControlPlaneBaseUrl(deps.configManager);
206
+ if (!baseUrl) throw error;
207
+ const homeDirectory = typeof deps.homeDirectory === 'function' ? deps.homeDirectory() : deps.homeDirectory;
208
+ const token = getOrCreateCompanionToken('tui', { daemonHomeDir: resolveDaemonStateDirectory(homeDirectory) }).token;
209
+ return await invokeGatewayMethodOverHttp<T>(rpc, baseUrl, token, methodId, input);
210
+ }
211
+ },
212
+ };
213
+ }
@@ -0,0 +1,99 @@
1
+ /**
2
+ * spine-adoption.ts — wiring this surface's session and memory spines to the
3
+ * daemon it adopted, over the SDK's adoption policy.
4
+ *
5
+ * ── What moved to the SDK, and what stays here ────────────────────────────
6
+ *
7
+ * The SDK's `@pellux/goodvibes-sdk/platform/runtime/client` now owns WHEN the
8
+ * wire comes up and goes down: idempotent per base URL, torn down on a
9
+ * change, the one-time legacy-store fold. This module owns WHAT the wire is —
10
+ * this terminal's own `createHttpTransport` + `createTuiSpineTransport` +
11
+ * `createTuiMemorySpineTransport`, injected as the SDK's `connect(baseUrl,
12
+ * authToken)` callback — because building the actual HTTP client is the same
13
+ * connection-resolution concern the verb caller already keeps product-side.
14
+ *
15
+ * `activation: 'adopt-on-status'` is passed explicitly: this terminal gates
16
+ * adoption on its boot discovery probe's `external` verdict (it can render
17
+ * "no daemon" honestly), rather than wiring live-immediately the moment it is
18
+ * handed a base URL.
19
+ *
20
+ * ── What crosses the wire ──────────────────────────────────────────────────
21
+ *
22
+ * Session IDENTITY, not session execution. The conversation itself runs here;
23
+ * what the daemon holds is the register —
24
+ * which sessions exist, which surface is live on each, and the inputs queued
25
+ * against them. Concretely, on adoption this wires:
26
+ *
27
+ * - `sessions.register` / `sessions.close` — the identity mirror, deliberately
28
+ * fire-and-forget so a slow daemon never shows up in a keystroke.
29
+ * - `sessions.inputs.list` / `sessions.inputs.deliver` — the inbound steer
30
+ * path, so a message another surface queued for THIS session lands in the
31
+ * turn machinery here and is acknowledged on the wire.
32
+ * - `sessions.list` — the cross-surface union the panels read, interval-
33
+ * refreshed and served synchronously.
34
+ * - the memory spine's wire transport, folded into the same handler by the
35
+ * SDK — this module's own `syncMemorySpineToHostStatus` call is retired;
36
+ * supplying `memoryTransport` in the bundle and `memorySpine` in the
37
+ * options is what activates it now.
38
+ *
39
+ * Plus a one-time, marker-guarded fold of this project's own pre-spine
40
+ * `control-plane/sessions.json` into the adopted daemon, done by the SDK.
41
+ *
42
+ * ── Why this module's exported shape does not change ──────────────────────
43
+ *
44
+ * `bootstrap.ts` gates on `HostServiceStatus` and hands a bare
45
+ * `(daemonStatus, sharedDaemonToken)` pair to whatever this returns. The SDK's
46
+ * handler takes a narrower `{ mode, baseUrl }` signal instead, so the adapting
47
+ * happens inside this module rather than at the call site.
48
+ */
49
+ import { createHttpTransport, type HostServiceStatus } from '@/runtime/index.ts';
50
+ import { createSpineAdoptionSync as createSdkSpineAdoptionSync } from '@pellux/goodvibes-sdk/platform/runtime/client';
51
+ import type { SpineAdoptionOptions as SdkSpineAdoptionOptions } from '@pellux/goodvibes-sdk/platform/runtime/client';
52
+
53
+ import { createTuiSpineTransport, type SpineSessionsClient } from '../session-spine-transport.ts';
54
+ import { createTuiMemorySpineTransport } from '../memory-spine-transport.ts';
55
+
56
+ /** This module's own options: the SDK's shape minus the two fields it builds itself. */
57
+ export type SpineAdoptionOptions = Omit<SdkSpineAdoptionOptions, 'connect' | 'activation'>;
58
+
59
+ /**
60
+ * Build the "the adopted daemon changed" handler.
61
+ *
62
+ * Idempotent per base URL (the SDK's own guarantee): re-running it against the
63
+ * same adopted daemon is a no-op, so a re-probe after an autostart does not
64
+ * tear a live mirror down and put it back up.
65
+ */
66
+ export function createSpineAdoptionSync(
67
+ options: SpineAdoptionOptions,
68
+ ): (daemonStatus: HostServiceStatus, sharedDaemonToken: string) => void {
69
+ const sync = createSdkSpineAdoptionSync({
70
+ ...options,
71
+ // This terminal has a boot discovery probe and can render "no daemon"
72
+ // honestly, so it gates on the probe's verdict rather than wiring the
73
+ // moment it is handed a base URL.
74
+ activation: 'adopt-on-status',
75
+ connect: (baseUrl, authToken) => {
76
+ const httpTransport = createHttpTransport({ baseUrl, authToken });
77
+ const sessionsClient: SpineSessionsClient = {
78
+ register: (input) => httpTransport.operator.sessions.register(input),
79
+ close: (sessionId) => httpTransport.operator.sessions.close(sessionId),
80
+ };
81
+ return {
82
+ sessionTransport: createTuiSpineTransport(sessionsClient),
83
+ inboundInputs: {
84
+ listInputs: async (sessionId, opts) => ({
85
+ inputs: await httpTransport.operator.sessions.inputs(sessionId, opts.limit, { state: opts.state, since: opts.since }),
86
+ }),
87
+ deliverInput: (sessionId, inputId, opts) =>
88
+ httpTransport.operator.sessions.deliverInput(sessionId, inputId, opts),
89
+ },
90
+ sessionList: { list: (limit: number) => httpTransport.operator.sessions.list(limit) },
91
+ memoryTransport: createTuiMemorySpineTransport({ baseUrl, authToken }),
92
+ };
93
+ },
94
+ });
95
+
96
+ return (daemonStatus, sharedDaemonToken) => {
97
+ sync({ mode: daemonStatus.mode, baseUrl: daemonStatus.baseUrl }, sharedDaemonToken);
98
+ };
99
+ }
@@ -1,6 +1,6 @@
1
1
  import { join } from 'node:path';
2
2
  import { getOrCreateCompanionToken } from '@pellux/goodvibes-sdk/platform/pairing';
3
- import type { ConfigManager } from '../config/index.ts';
3
+ import type { ConfigManager } from '@pellux/goodvibes-sdk/platform/config';
4
4
 
5
5
  export const CLOUDFLARE_COMPONENT_IDS = [
6
6
  'workers',
@@ -1,4 +1,4 @@
1
- import type { Orchestrator } from '../core/orchestrator.ts';
1
+ import type { Orchestrator } from '@pellux/goodvibes-sdk/platform/core';
2
2
  import type { ProviderRegistry } from '@pellux/goodvibes-sdk/platform/providers';
3
3
  import type { SessionLineageTracker } from '@pellux/goodvibes-sdk/platform/core';
4
4
  import type { ContextAccountingSource } from '@pellux/goodvibes-sdk/platform/tools';
@@ -6,7 +6,7 @@
6
6
  * the runtime loop.
7
7
  */
8
8
  import type { ConversationManager } from '../core/conversation';
9
- import type { Orchestrator } from '../core/orchestrator';
9
+ import type { Orchestrator } from '@pellux/goodvibes-sdk/platform/core';
10
10
  import type { ToolRegistry } from '@pellux/goodvibes-sdk/platform/tools';
11
11
  import type { PermissionManager } from '@pellux/goodvibes-sdk/platform/permissions';
12
12
  import type { HookDispatcher } from '@pellux/goodvibes-sdk/platform/hooks';
@@ -19,7 +19,7 @@ import type { FeatureFlagManager } from '@/runtime/index.ts';
19
19
  import type { MutableRuntimeState } from '@/runtime/index.ts';
20
20
  import type { SessionSnapshot } from '@/runtime/index.ts';
21
21
  import type { RuntimeServices } from './services.ts';
22
- import type { ComponentHealthMonitor } from './perf/panel-health-monitor.ts';
22
+ import type { ComponentHealthMonitor } from '@pellux/goodvibes-sdk/platform/runtime/observability';
23
23
  import type { WorktreeRegistry } from '@/runtime/index.ts';
24
24
  import type { SandboxSessionRegistry } from '@/runtime/index.ts';
25
25
 
@@ -11,11 +11,22 @@
11
11
  // for the anchor's turnId at TURN_COMPLETED (rewind-turn-anchors.ts) — the same
12
12
  // join key files rewind uses against the workspace checkpoint.
13
13
  //
14
- // This module is the single implementation of that port for the TUI, consumed by
15
- // BOTH the in-process /rewind command (bound to one session's conversation) and
16
- // the composed daemon's rewind.plan/apply verbs (resolving the live conversation
17
- // per anchor.sessionId from the registry below), so the truncation semantics
18
- // cannot drift between the two surfaces.
14
+ // This module is the single implementation of that port for the TUI, and it has
15
+ // two consumers again — but the second one reaches it differently now.
16
+ //
17
+ // It used to be the composed daemon's own rewind.plan/apply verbs, resolving a
18
+ // conversation out of the registry below because the daemon was this process.
19
+ // It is not any more. FILES rewind still works from anywhere (the workspace
20
+ // checkpoint store is the daemon's), but the messages live in whichever process
21
+ // runs the loop, and for a while a rewind driven from another surface got a
22
+ // confident "0 messages to drop" from a daemon registry nothing could populate.
23
+ //
24
+ // The daemon now ASKS. This surface offers the conversation it is holding
25
+ // (rewind.conversation.host.register), takes the questions the daemon puts to it
26
+ // and answers them (the SDK's client/conversation-rewind-host.ts); this port is what
27
+ // produces those answers. So conversation rewind works from any surface, and a
28
+ // session nobody has offered is reported unavailable with the reason rather
29
+ // than as a zero that cannot be told apart from a real one.
19
30
  // ---------------------------------------------------------------------------
20
31
 
21
32
  import type {
@@ -25,7 +36,7 @@ import type {
25
36
  RewindConversationPreview,
26
37
  } from '@pellux/goodvibes-sdk/platform/rewind';
27
38
  import type { ConversationManager } from '../core/conversation.ts';
28
- import { resolveTurnAnchor } from '../core/rewind-turn-anchors.ts';
39
+ import { resolveTurnAnchor } from '@pellux/goodvibes-sdk/platform/rewind';
29
40
 
30
41
  type ConversationJson = Parameters<ConversationManager['fromJSON']>[0];
31
42
 
@@ -101,10 +112,13 @@ export function createConversationRewindPort(
101
112
  }
102
113
 
103
114
  // ---------------------------------------------------------------------------
104
- // Live per-session conversation registry — the daemon-hosted mutable store the
105
- // composed daemon's rewind.plan/apply verbs resolve conversations from. The TUI
106
- // registers its active session's ConversationManager at bootstrap; a session
107
- // with no registration reports conversation rewind as "nothing to drop".
115
+ // Live per-session conversation registry.
116
+ //
117
+ // This process's conversations, by session id. Two readers now: the local
118
+ // /rewind command, and the host loop that answers the DAEMON's questions about
119
+ // a session this surface is running (the SDK's client/conversation-rewind-host.ts) —
120
+ // which is what makes conversation rewind work from any surface again, since
121
+ // only the process holding the messages can count or drop them.
108
122
  // ---------------------------------------------------------------------------
109
123
 
110
124
  const liveConversations = new Map<string, ConversationManager>();
@@ -120,10 +134,24 @@ export function unregisterSessionConversation(sessionId: string): void {
120
134
  }
121
135
 
122
136
  /**
123
- * The conversation rewind port the composed daemon threads into
124
- * registerGatewayVerbGroups — it resolves each anchor's live conversation from
125
- * the registry above, so the daemon's own rewind verbs serve conversation scope
126
- * live in this process.
137
+ * Whether this process is actually holding a session's conversation right now.
138
+ *
139
+ * The port below answers "nothing to drop" for a session it cannot resolve,
140
+ * which is the right degrade for a LOCAL /rewind (there is nothing to truncate
141
+ * here). It is the wrong answer for a rewind driven from another surface: a
142
+ * confident zero is indistinguishable from a real zero, and that is exactly the
143
+ * failure the surface-hosted rewind contract exists to end. The host loop
144
+ * (the SDK's client/conversation-rewind-host.ts) checks this first and answers
145
+ * `unavailable` with a reason instead.
146
+ */
147
+ export function hasSessionConversation(sessionId: string): boolean {
148
+ return liveConversations.has(sessionId);
149
+ }
150
+
151
+ /**
152
+ * The port that answers for whichever of this process's conversations an anchor
153
+ * names. Used by the local /rewind command and, through the host loop, by the
154
+ * daemon when a rewind driven from anywhere touches a session running here.
127
155
  */
128
156
  export function createSessionConversationRewindPort(): ConversationRewindPort {
129
157
  return createConversationRewindPort((sessionId) => liveConversations.get(sessionId) ?? null);
@@ -11,7 +11,8 @@
11
11
  * for the embedded daemon.
12
12
  */
13
13
  import { FeatureAnnouncementStore, featureAnnouncementsPath, createSandboxContainmentAnnouncer } from '@pellux/goodvibes-sdk/platform/runtime/feature-announcements';
14
- import type { ConfigManager } from '../config/index.ts';
14
+ import { readClientCompatibilityFloor } from '@pellux/goodvibes-sdk/platform/control-plane';
15
+ import type { ConfigManager } from '@pellux/goodvibes-sdk/platform/config';
15
16
 
16
17
  /** A daemon receipt line (already a human one-liner, e.g. "restarted after a crash at 14:32"). */
17
18
  export interface DaemonReceiptLike {
@@ -51,43 +52,73 @@ export function consumeDaemonAttachNotices(deps: DaemonAttachNoticesDeps): strin
51
52
  return notices;
52
53
  }
53
54
 
54
- /** Milliseconds an external-daemon receipts read waits before giving up. */
55
+ /** Milliseconds an external-daemon `/status` read waits before giving up. */
55
56
  const EXTERNAL_RECEIPTS_TIMEOUT_MS = 1500;
56
57
 
57
- export interface ExternalDaemonAttachNoticesDeps {
58
+ export interface ExternalDaemonAttachReadDeps {
58
59
  /** The adopted daemon's base URL, e.g. "http://127.0.0.1:3421". */
59
60
  readonly baseUrl: string;
60
61
  /** The shared bearer the adopted daemon authenticates (the companion-pairing token). */
61
62
  readonly authToken: string;
63
+ /**
64
+ * Ask for the daemon's undelivered receipts on this read. Delivery at the
65
+ * daemon is destructive — served once, to the consuming reader — so only the
66
+ * read that RENDERS them asks. A reconnect read that just refreshes the two
67
+ * build floors leaves this off, and no receipt is eaten before a surface can
68
+ * show it.
69
+ */
70
+ readonly consumeReceipts?: boolean;
62
71
  /** Injectable fetch for tests; defaults to the global fetch. */
63
72
  readonly fetchImpl?: typeof fetch;
64
73
  /** Injectable timeout (ms); defaults to EXTERNAL_RECEIPTS_TIMEOUT_MS. */
65
74
  readonly timeoutMs?: number;
66
75
  }
67
76
 
77
+ /** Everything one `/status` read of an adopted daemon tells this terminal. */
78
+ export interface ExternalDaemonAttachRead {
79
+ /** True only when the daemon answered with a usable 2xx body. */
80
+ readonly answered: boolean;
81
+ /** Receipt lines to render; empty unless `consumeReceipts` was asked for. */
82
+ readonly notices: string[];
83
+ /**
84
+ * The minimum CLIENT build the daemon announced on the
85
+ * `X-Goodvibes-Client-Floor` response header, or undefined when it announced
86
+ * none (a daemon too old to publish one is not asking for anything).
87
+ */
88
+ readonly clientFloor: string | undefined;
89
+ /** The parsed body, which carries the daemon's own `version`. */
90
+ readonly statusPayload: unknown;
91
+ }
92
+
68
93
  /**
69
- * Consume — exactly once — a separately-hosted (external/systemd) daemon's
70
- * undelivered receipts AND drained announcements over HTTP, returning the
71
- * one-line notices to render. The default deployment adopts such a daemon with
72
- * no in-process handle (daemonServer is null), so the embedded in-process fold
73
- * sees nothing; this hits the daemon's own consuming endpoint instead —
74
- * GET <baseUrl>/status?receipts=consume with the shared bearer. The daemon
75
- * marks the served receipts delivered as it responds (its own exactly-once
76
- * store), so a second attach with nothing new returns []. Any transport, auth,
77
- * or parse failure yields no notices rather than throwing into the attach path.
94
+ * One `/status` read of a separately-hosted (external/systemd) daemon, carrying
95
+ * the three things attaching to it decides on: its undelivered receipts, the
96
+ * client build floor it publishes, and its own build.
97
+ *
98
+ * The default deployment adopts such a daemon with no in-process handle
99
+ * (daemonServer is null), so the embedded in-process fold sees nothing; this
100
+ * hits the daemon's own endpoint instead — GET <baseUrl>/status with the shared
101
+ * bearer, plus `?receipts=consume` when the caller is going to render them. The
102
+ * daemon marks the served receipts delivered as it responds (its own
103
+ * exactly-once store), so a second attach with nothing new returns no notices.
104
+ * Any transport, auth, or parse failure reads as unanswered rather than
105
+ * throwing into the attach path.
78
106
  */
79
- export async function consumeExternalDaemonAttachNotices(
80
- deps: ExternalDaemonAttachNoticesDeps,
81
- ): Promise<string[]> {
107
+ export async function readExternalDaemonAttach(
108
+ deps: ExternalDaemonAttachReadDeps,
109
+ ): Promise<ExternalDaemonAttachRead> {
110
+ const unanswered: ExternalDaemonAttachRead = {
111
+ answered: false, notices: [], clientFloor: undefined, statusPayload: null,
112
+ };
82
113
  const fetchImpl = deps.fetchImpl ?? fetch;
83
114
  const controller = new AbortController();
84
115
  const timer = setTimeout(() => controller.abort(), deps.timeoutMs ?? EXTERNAL_RECEIPTS_TIMEOUT_MS);
85
116
  try {
86
117
  const headers = new Headers();
87
118
  if (deps.authToken.trim()) headers.set('Authorization', `Bearer ${deps.authToken.trim()}`);
88
- const url = `${deps.baseUrl.replace(/\/+$/, '')}/status?receipts=consume`;
119
+ const url = `${deps.baseUrl.replace(/\/+$/, '')}/status${deps.consumeReceipts ? '?receipts=consume' : ''}`;
89
120
  const response = await fetchImpl(url, { headers, signal: controller.signal });
90
- if (!response.ok) return [];
121
+ if (!response.ok) return unanswered;
91
122
  const body = (await response.json()) as { receipts?: unknown } | null;
92
123
  const receipts = Array.isArray(body?.receipts) ? body.receipts : [];
93
124
  const notices: string[] = [];
@@ -97,9 +128,14 @@ export async function consumeExternalDaemonAttachNotices(
97
128
  if (text.trim().length > 0) notices.push(text);
98
129
  }
99
130
  }
100
- return notices;
131
+ return {
132
+ answered: true,
133
+ notices,
134
+ clientFloor: readClientCompatibilityFloor(response.headers),
135
+ statusPayload: body,
136
+ };
101
137
  } catch {
102
- return [];
138
+ return unanswered;
103
139
  } finally {
104
140
  clearTimeout(timer);
105
141
  }
@@ -13,7 +13,7 @@ import type { PanelConfig } from '@/runtime/index.ts';
13
13
  import { DEFAULT_PANEL_CONFIG, appendBounded, applyFilter } from '@/runtime/index.ts';
14
14
  import type { DiagnosticFilter } from '@/runtime/index.ts';
15
15
  import type { OpsInterventionReason, OpsEvent } from '@/runtime/index.ts';
16
- import type { UiEventFeed } from '../../ui-events.ts';
16
+ import type { UiEventFeed } from '@/runtime/index.ts';
17
17
 
18
18
  // ---------------------------------------------------------------------------
19
19
  // Audit entry
@@ -46,21 +46,6 @@ export interface SurfaceRuntimePollerOwners extends Omit<RuntimePollerOwners, 's
46
46
  * teardown path to have no way to stop it.
47
47
  */
48
48
  readonly stopWakeHousekeeping: () => void;
49
- /**
50
- * Fork-only: the daemon handler surfaces (daemon-handler-composition.ts).
51
- *
52
- * `unregister()` detaches the gateway handlers AND stops the two pollers this
53
- * product's inbox surface owns — the retention sweep inside `InboxCursorStore`
54
- * and the per-provider `InboundPoller` intervals. The SDK does not know either
55
- * exists, so if this surface does not stop them nothing does.
56
- */
57
- readonly daemonHandlers: { readonly unregister: () => void };
58
- /**
59
- * Fork-only: the paired-phone feature's housekeeping timer
60
- * (device-posture-composition.ts). Started by whichever entry point boots the
61
- * host, so the stop belongs on this list rather than in one shutdown path.
62
- */
63
- readonly devicePosture: { readonly stopHousekeeping: () => void };
64
49
  }
65
50
 
66
51
  /**
@@ -86,14 +71,9 @@ export function registerSurfaceRuntimePollers(
86
71
  ): void {
87
72
  registerRuntimePollers(registry, { ...services, stopConfigWatch: extras.stopConfigWatch });
88
73
  registry.add('durability housekeeping', services.stopDurabilityHousekeeping);
89
- registry.add('device housekeeping', () => services.devicePosture.stopHousekeeping());
90
74
  registry.add('wake-word housekeeping', services.stopWakeHousekeeping);
91
- // Registered LAST so it tears down FIRST (the scope unwinds in reverse), which
92
- // is the order daemon/cli.ts already used by hand: release the handler surfaces
93
- // — closing the inbox store and stopping its poll timers — before the pollers
94
- // the rest of the graph owns. Being on this list is what makes a plain
95
- // `dispose()` total: every shutdown path (daemon/cli.ts, main.ts's teardown
96
- // registry, the one-shot CLI commands) now stops these two, not just the one
97
- // that remembered to call `unregister()` itself.
98
- registry.add('daemon handler surfaces', () => services.daemonHandlers.unregister());
75
+ // The inbox surfaces and the device-posture housekeeping used to be stopped
76
+ // here too. Both moved to the daemon product with the runtimes that started
77
+ // them: nothing in this process polls a mailbox or reaps a device grant any
78
+ // more, so there is nothing left here to stop.
99
79
  }