@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
@@ -1,257 +0,0 @@
1
- /**
2
- * session-liveness-marker.ts — best-effort "this session is open in another
3
- * terminal" signal for multi-instance safety.
4
- *
5
- * Two independent TUI instances resuming or writing recovery snapshots for
6
- * the SAME session can silently step on each other: one instance's periodic
7
- * recovery write looks, to a second instance, indistinguishable from a
8
- * genuine crash snapshot, and resuming a session another instance still has
9
- * open forks its live state without warning.
10
- *
11
- * This module writes a small marker file (session id + pid), refreshed on
12
- * the SAME 60s cadence as the existing recovery-file write (main.ts), so a
13
- * second instance can do a best-effort liveness check before either warning
14
- * about a stale-looking recovery snapshot or resuming a live session out
15
- * from under its owner.
16
- *
17
- * Every check here is best-effort by design: a missing or stale marker never
18
- * blocks anything — it just means "we can't tell, proceed as before". This is
19
- * a convenience signal, not a lock.
20
- *
21
- * Location: <homeDirectory>/.goodvibes/<surfaceRoot>/liveness/<sessionId>.json.
22
- * Both halves come off the caller's SessionSurface rather than being spelled
23
- * out here, so the markers land under the same scope as the sessions and
24
- * recovery snapshots they describe — mirrors the transcript-journal.ts
25
- * convention (homeDirectory-scoped, under the surface's own directory).
26
- */
27
- import { existsSync, mkdirSync, readFileSync, readdirSync, unlinkSync, writeFileSync } from 'node:fs';
28
- import { dirname, join } from 'node:path';
29
- import type { SessionSurface } from '@/runtime/index.ts';
30
-
31
- export interface LivenessMarker {
32
- readonly sessionId: string;
33
- readonly pid: number;
34
- readonly updatedAt: number;
35
- }
36
-
37
- /**
38
- * A marker older than this is treated as stale even if its pid happens to
39
- * still resolve to a running process (pid reuse after a crash) — the marker
40
- * is refreshed every 60s (main.ts's recovery cadence), so anything past
41
- * ~2.5x that cadence means the writer stopped refreshing it.
42
- */
43
- export const LIVENESS_STALE_AFTER_MS = 150_000;
44
-
45
- /** Absolute path to the directory holding every session's liveness marker. */
46
- export function livenessMarkerDirFor(surface: SessionSurface): string {
47
- return join(surface.homeDirectory, '.goodvibes', surface.surfaceRoot, 'liveness');
48
- }
49
-
50
- /** Absolute path to a session's liveness marker file. */
51
- export function livenessMarkerPathFor(surface: SessionSurface, sessionId: string): string {
52
- return join(livenessMarkerDirFor(surface), `${sessionId}.json`);
53
- }
54
-
55
- /** Refresh (creating if needed) the liveness marker for this session/pid. Best-effort — never throws. */
56
- export function writeLivenessMarker(surface: SessionSurface, sessionId: string, pid: number = process.pid): void {
57
- try {
58
- const path = livenessMarkerPathFor(surface, sessionId);
59
- mkdirSync(dirname(path), { recursive: true });
60
- const marker: LivenessMarker = { sessionId, pid, updatedAt: Date.now() };
61
- writeFileSync(path, JSON.stringify(marker), { mode: 0o600 });
62
- } catch {
63
- // Best-effort — a missed liveness refresh never blocks anything.
64
- }
65
- }
66
-
67
- /** Best-effort removal on exit. Never throws. */
68
- export function removeLivenessMarker(surface: SessionSurface, sessionId: string): void {
69
- try {
70
- const path = livenessMarkerPathFor(surface, sessionId);
71
- if (existsSync(path)) unlinkSync(path);
72
- } catch {
73
- // Best-effort.
74
- }
75
- }
76
-
77
- /** Parse one marker file by absolute path. Returns null for missing, empty, torn, or wrong-shaped content. */
78
- function parseMarkerFile(path: string): LivenessMarker | null {
79
- try {
80
- if (!existsSync(path)) return null;
81
- const text = readFileSync(path, 'utf-8');
82
- // A crash between create and write leaves a zero-byte file. Content, not
83
- // existence, decides whether this marker says anything at all.
84
- if (text.trim().length === 0) return null;
85
- const parsed = JSON.parse(text) as Partial<LivenessMarker>;
86
- if (typeof parsed.sessionId !== 'string' || typeof parsed.pid !== 'number' || typeof parsed.updatedAt !== 'number') return null;
87
- if (!Number.isFinite(parsed.pid) || !Number.isFinite(parsed.updatedAt)) return null;
88
- return { sessionId: parsed.sessionId, pid: parsed.pid, updatedAt: parsed.updatedAt };
89
- } catch {
90
- return null;
91
- }
92
- }
93
-
94
- function readLivenessMarker(surface: SessionSurface, sessionId: string): LivenessMarker | null {
95
- return parseMarkerFile(livenessMarkerPathFor(surface, sessionId));
96
- }
97
-
98
- /** Best-effort check that a pid still refers to a running process. Injectable for tests. */
99
- export function isPidAlive(pid: number, kill: (pid: number, signal: 0) => void = process.kill.bind(process)): boolean {
100
- try {
101
- kill(pid, 0);
102
- return true;
103
- } catch (err) {
104
- // ESRCH = no such process. Any other error (e.g. EPERM — exists but this
105
- // process lacks permission to signal it) is treated as "still alive" —
106
- // best-effort here means erring toward not silently dropping a real warning.
107
- return (err as NodeJS.ErrnoException | undefined)?.code === 'EPERM';
108
- }
109
- }
110
-
111
- export interface SessionLivenessCheck {
112
- readonly live: boolean;
113
- readonly pid: number | null;
114
- }
115
-
116
- /**
117
- * Best-effort: is `sessionId` apparently open in another still-running
118
- * process right now? A missing, stale (older than LIVENESS_STALE_AFTER_MS),
119
- * or unreadable marker all resolve to `{ live: false, pid: null }` — never a
120
- * throw, never a block on the caller.
121
- */
122
- export function checkSessionLiveness(
123
- surface: SessionSurface,
124
- sessionId: string,
125
- opts: { readonly now?: () => number; readonly isPidAliveFn?: typeof isPidAlive } = {},
126
- ): SessionLivenessCheck {
127
- const marker = readLivenessMarker(surface, sessionId);
128
- if (!marker) return { live: false, pid: null };
129
- const now = opts.now?.() ?? Date.now();
130
- if (now - marker.updatedAt > LIVENESS_STALE_AFTER_MS) return { live: false, pid: null };
131
- const aliveCheck = opts.isPidAliveFn ?? isPidAlive;
132
- if (!aliveCheck(marker.pid)) return { live: false, pid: null };
133
- return { live: true, pid: marker.pid };
134
- }
135
-
136
- // ─── Reaping ────────────────────────────────────────────────────────────────
137
- //
138
- // `removeLivenessMarker` only runs on a CLEAN exit (process-lifecycle.ts). A
139
- // SIGKILL, a panic, or a pulled power cord leaves the marker behind forever,
140
- // and `checkSessionLiveness` treating it as not-live does not remove the file
141
- // — so the liveness directory grows one file per session id ever opened. The
142
- // sweep below is the reclaim half of that lifecycle.
143
-
144
- /**
145
- * Hard ceiling on marker files kept after a sweep, oldest dropped first.
146
- *
147
- * The liveness rule below already reclaims ordinary crash residue, but it can
148
- * be defeated: `isPidAlive` deliberately reports EPERM (a pid this user cannot
149
- * signal) as alive, so on a busy host a reused pid can keep a dead session's
150
- * marker "live" indefinitely. This cap is the second, unconditional bound so
151
- * the directory can never grow without limit whatever the pid probe says.
152
- */
153
- export const LIVENESS_MARKER_MAX_FILES = 200;
154
-
155
- export interface LivenessReapResult {
156
- /** Marker files examined this sweep. */
157
- readonly scanned: number;
158
- /** Marker files deleted this sweep. */
159
- readonly reaped: number;
160
- }
161
-
162
- export interface LivenessReapOptions {
163
- readonly now?: () => number;
164
- readonly isPidAliveFn?: typeof isPidAlive;
165
- /**
166
- * Session ids that must never be reaped regardless of what the marker says
167
- * — normally just the current session's id. Belt-and-braces: the current
168
- * session refreshes its own marker every 60s, so the liveness rule already
169
- * keeps it.
170
- */
171
- readonly keepSessionIds?: readonly string[];
172
- /** Override the count cap (tests). */
173
- readonly maxFiles?: number;
174
- }
175
-
176
- /**
177
- * Delete liveness markers that no longer describe a running session.
178
- *
179
- * A marker is reaped when it is either:
180
- * - unreadable, empty, or not shaped like a marker (crash residue that can
181
- * never resolve to a live session again — validated by parsing the
182
- * content, not by the file merely existing); or
183
- * - definitively not live: older than `LIVENESS_STALE_AFTER_MS` AND its pid
184
- * no longer resolves to a running process.
185
- *
186
- * Both halves of that second rule are required on purpose. Deleting is
187
- * destructive while a liveness answer is only advisory, so a marker that is
188
- * stale-but-pid-alive (a wedged owner still holding the session) or
189
- * fresh-but-pid-dead (a process that died seconds ago, or a pid probe that
190
- * failed transiently) is left for a later sweep. A genuinely crashed session
191
- * satisfies both conditions within `LIVENESS_STALE_AFTER_MS` and is reclaimed
192
- * on the next sweep after that.
193
- *
194
- * Idempotent and safe to run concurrently from several processes: a marker
195
- * another sweeper unlinked between the directory listing and this unlink
196
- * (ENOENT) counts as reaped, never as an error.
197
- */
198
- export function reapStaleLivenessMarkers(surface: SessionSurface, opts: LivenessReapOptions = {}): LivenessReapResult {
199
- const dir = livenessMarkerDirFor(surface);
200
- let names: string[];
201
- try {
202
- names = readdirSync(dir).filter((name) => name.endsWith('.json'));
203
- } catch {
204
- // No liveness directory yet, or it is unreadable — nothing to reclaim.
205
- return { scanned: 0, reaped: 0 };
206
- }
207
-
208
- const now = opts.now?.() ?? Date.now();
209
- const aliveCheck = opts.isPidAliveFn ?? isPidAlive;
210
- const keep = new Set(opts.keepSessionIds ?? []);
211
- const maxFiles = opts.maxFiles ?? LIVENESS_MARKER_MAX_FILES;
212
-
213
- let reaped = 0;
214
- /** Survivors, so the count cap below can drop the oldest of them. */
215
- const survivors: { readonly path: string; readonly updatedAt: number }[] = [];
216
-
217
- for (const name of names) {
218
- const sessionId = name.slice(0, -'.json'.length);
219
- if (keep.has(sessionId)) continue;
220
- const path = join(dir, name);
221
- const marker = parseMarkerFile(path);
222
- if (marker === null) {
223
- if (unlinkIfPresent(path)) reaped++;
224
- continue;
225
- }
226
- const stale = now - marker.updatedAt > LIVENESS_STALE_AFTER_MS;
227
- if (stale && !aliveCheck(marker.pid)) {
228
- if (unlinkIfPresent(path)) reaped++;
229
- continue;
230
- }
231
- survivors.push({ path, updatedAt: marker.updatedAt });
232
- }
233
-
234
- if (survivors.length > maxFiles) {
235
- survivors.sort((a, b) => a.updatedAt - b.updatedAt); // oldest first
236
- for (const victim of survivors.slice(0, survivors.length - maxFiles)) {
237
- if (unlinkIfPresent(victim.path)) reaped++;
238
- }
239
- }
240
-
241
- return { scanned: names.length, reaped };
242
- }
243
-
244
- /**
245
- * Best-effort unlink shared by the sweep. Returns true when the file is gone
246
- * because of (or by the time of) this call. A concurrent sweeper winning the
247
- * race (ENOENT) is success, not failure; any other error leaves the file for
248
- * the next sweep.
249
- */
250
- function unlinkIfPresent(path: string): boolean {
251
- try {
252
- unlinkSync(path);
253
- return true;
254
- } catch (err) {
255
- return (err as NodeJS.ErrnoException | undefined)?.code === 'ENOENT';
256
- }
257
- }
@@ -1,22 +0,0 @@
1
- import { writeLastSessionPointer } from '@/runtime/index.ts';
2
- import type { SessionSurface } from '@/runtime/index.ts';
3
-
4
- /**
5
- * Binds the SDK's two-argument `writeLastSessionPointer(sessionId, options?)`
6
- * to one `SessionSurface`, returning a plain `(sessionId: string) => void`.
7
- *
8
- * Never pass the raw SDK function reference into a `(sessionId: string) =>
9
- * void` slot. A function with an extra optional parameter is structurally
10
- * assignable there, so it type-checks, but every downstream caller
11
- * (bootstrap-shell.ts -> bootstrap-hook-bridge.ts's resume handler) invokes
12
- * it with exactly one argument: `options` arrives as `undefined`, the legacy
13
- * compat path's `requireWorkingDirectory(undefined)` throws, and
14
- * `writeLastSessionPointer`'s own try/catch swallows that into a logged
15
- * warning. The pointer file then silently never gets written after a resume,
16
- * and the next launch's `--continue` / boot notice sees nothing. That was the
17
- * live defect at bootstrap.ts's `createBootstrapShell({ writeLastSessionPointer })`
18
- * call. See session-surface-pointer-journey.test.ts for the on-disk proof.
19
- */
20
- export function bindWriteLastSessionPointerToSurface(surface: SessionSurface): (sessionId: string) => void {
21
- return (sessionId: string): void => writeLastSessionPointer(sessionId, { surface });
22
- }
@@ -1,41 +0,0 @@
1
- /**
2
- * session-storage-services.ts — the declare-once storage handle and the
3
- * session store built on it.
4
- *
5
- * Every path the app uses for session state (the sessions directory, the
6
- * last-session pointer, crash-recovery snapshots, the checkpoint store, the
7
- * transcript journal, the multi-instance liveness markers) derives from ONE
8
- * `SessionSurface`, built here, once, at composition time.
9
- *
10
- * The reason it is one object rather than a scope argument repeated at each
11
- * call site: when each site derived its own scope, they drifted. A resume
12
- * wrote the last-session pointer with no scope at all — into the shared,
13
- * unscoped `.goodvibes/` directory — while `--continue` and the boot notice
14
- * read a scoped path under `.goodvibes/tui/`. Nothing errored; the pointer
15
- * simply never existed where anyone looked. `createSessionSurface` also
16
- * throws synchronously on a bad scope instead of silently resolving to the
17
- * unscoped fallback on first use, so a mistake here fails at boot rather than
18
- * six months later as missing state.
19
- */
20
- import { SessionManager } from '@pellux/goodvibes-sdk/platform/sessions';
21
- import { createSessionSurface, type SessionSurface } from '@/runtime/index.ts';
22
- import { GOODVIBES_TUI_SURFACE_ROOT } from '../config/surface.ts';
23
-
24
- export interface SessionStorageServices {
25
- readonly surface: SessionSurface;
26
- readonly sessionManager: SessionManager;
27
- }
28
-
29
- export function createSessionStorageServices(input: {
30
- readonly workingDirectory: string;
31
- readonly homeDirectory: string;
32
- }): SessionStorageServices {
33
- // Also performs the SDK's one-time migration of pre-surface state (pointer
34
- // copy-forward, journal moves) into the scoped location.
35
- const surface = createSessionSurface({
36
- surfaceRoot: GOODVIBES_TUI_SURFACE_ROOT,
37
- workingDirectory: input.workingDirectory,
38
- homeDirectory: input.homeDirectory,
39
- });
40
- return { surface, sessionManager: new SessionManager(input.workingDirectory, { surface }) };
41
- }
@@ -1,96 +0,0 @@
1
- /**
2
- * surface-feature-flags — which platform capabilities a surface/server setup
3
- * needs, and how to switch them on through their real domain settings keys.
4
- *
5
- * Capabilities are configured through first-class domain settings (see
6
- * feature-settings.ts); enabling a surface writes keys like
7
- * `controlPlane.gateway` or `surfaces.slack.enabled`, never a separate
8
- * enablement namespace.
9
- */
10
-
11
- import type { ConfigManager } from '../config/index.ts';
12
- import { surfaceFeatureGateId } from '@/runtime/index.ts';
13
- import { featureEnablementWrite, getFeatureSetting, isFeatureConfigEnabled } from './feature-settings.ts';
14
-
15
- export const CONTROL_PLANE_FEATURE_FLAG = 'control-plane-gateway';
16
- export const ROUTE_BINDING_FEATURE_FLAG = 'route-binding';
17
- export const DELIVERY_ENGINE_FEATURE_FLAG = 'delivery-engine';
18
- export const SERVICE_MANAGEMENT_FEATURE_FLAG = 'service-management';
19
-
20
- const CORE_CHANNEL_FEATURE_FLAGS = [
21
- CONTROL_PLANE_FEATURE_FLAG,
22
- ROUTE_BINDING_FEATURE_FLAG,
23
- DELIVERY_ENGINE_FEATURE_FLAG,
24
- ] as const;
25
-
26
- export function getSurfaceFeatureFlag(surfaceId: string): string | null {
27
- // Onboarding surface ids may be camelCase ('googleChat'); the SDK's gate
28
- // map is keyed by the kebab-case ChannelSurface id ('google-chat'). Try the
29
- // literal id first, then the kebab-case form, so choosing Google Chat in
30
- // onboarding really flips google-chat-surface.
31
- const literal = surfaceFeatureGateId(surfaceId);
32
- if (literal) return literal;
33
- const kebab = surfaceId.replace(/[A-Z]/g, (ch) => `-${ch.toLowerCase()}`);
34
- return kebab === surfaceId ? null : surfaceFeatureGateId(kebab);
35
- }
36
-
37
- export function getServerSurfaceFeatureFlags(options: {
38
- readonly serverBacked?: boolean;
39
- readonly web?: boolean;
40
- readonly externalSurfaces?: readonly string[];
41
- }): readonly string[] {
42
- const flags = new Set<string>();
43
- const hasExternalSurfaces = (options.externalSurfaces?.length ?? 0) > 0;
44
-
45
- if (options.serverBacked || options.web || hasExternalSurfaces) {
46
- flags.add(CONTROL_PLANE_FEATURE_FLAG);
47
- flags.add(SERVICE_MANAGEMENT_FEATURE_FLAG);
48
- }
49
- if (options.web) {
50
- const webFlag = getSurfaceFeatureFlag('web');
51
- if (webFlag) flags.add(webFlag);
52
- }
53
-
54
- if (hasExternalSurfaces) {
55
- for (const flag of CORE_CHANNEL_FEATURE_FLAGS) flags.add(flag);
56
- for (const surfaceId of options.externalSurfaces ?? []) {
57
- const surfaceFlag = getSurfaceFeatureFlag(surfaceId);
58
- if (surfaceFlag) flags.add(surfaceFlag);
59
- }
60
- }
61
-
62
- return [...flags].sort((left, right) => left.localeCompare(right));
63
- }
64
-
65
- /** Whether the live config has the capability on, derived from its domain settings key. */
66
- export function isFeatureFlagEnabled(config: Pick<ConfigManager, 'get'>, flagId: string): boolean {
67
- return isFeatureConfigEnabled(config, flagId);
68
- }
69
-
70
- /**
71
- * Translate internal feature-gate ids (e.g. 'control-plane-gateway') to the
72
- * canonical domain settings keys the user actually sets (e.g.
73
- * 'controlPlane.gateway') — matching the SDK's own gate errors, which name the
74
- * settings key. A gate with no mapped setting falls back to its id. Deduped and
75
- * sorted for stable output.
76
- */
77
- export function surfaceFeatureGateSettingsKeys(flagIds: readonly string[]): string[] {
78
- const keys = new Set<string>();
79
- for (const flagId of flagIds) keys.add(getFeatureSetting(flagId)?.enablement.key ?? flagId);
80
- return [...keys].sort((left, right) => left.localeCompare(right));
81
- }
82
-
83
- export function getMissingSurfaceFeatureFlags(config: Pick<ConfigManager, 'get'>, surfaceId: string): readonly string[] {
84
- const required = surfaceId === 'web'
85
- ? getServerSurfaceFeatureFlags({ web: true })
86
- : getServerSurfaceFeatureFlags({ externalSurfaces: [surfaceId] });
87
- return required.filter((flagId) => !isFeatureFlagEnabled(config, flagId));
88
- }
89
-
90
- /** Switch each capability on by writing its real enablement settings key. */
91
- export function enableFeatureFlags(config: Pick<ConfigManager, 'setDynamic'>, flagIds: readonly string[]): void {
92
- for (const flagId of flagIds) {
93
- const write = featureEnablementWrite(flagId, true);
94
- if (write) config.setDynamic(write.key, write.value);
95
- }
96
- }
@@ -1,235 +0,0 @@
1
- import { format } from 'node:util';
2
- import { logger } from '@pellux/goodvibes-sdk/platform/utils';
3
-
4
- type WritableStreamLike = {
5
- write: {
6
- (buffer: string | Uint8Array, cb?: (error?: Error | null) => void): boolean;
7
- (buffer: string | Uint8Array, encoding?: BufferEncoding, cb?: (error?: Error | null) => void): boolean;
8
- };
9
- };
10
-
11
- export type TerminalOutputInterceptSource =
12
- | 'stdout'
13
- | 'stderr'
14
- | 'console.debug'
15
- | 'console.error'
16
- | 'console.info'
17
- | 'console.log'
18
- | 'console.warn';
19
-
20
- export type TerminalOutputIntercept = {
21
- readonly source: TerminalOutputInterceptSource;
22
- readonly text: string;
23
- readonly preview: string;
24
- };
25
-
26
- export type TerminalOutputGuard = {
27
- setActive(active: boolean): void;
28
- allowTerminalWrite<T>(fn: () => T): T;
29
- dispose(): void;
30
- };
31
-
32
- export type TerminalOutputGuardOptions = {
33
- readonly stdout: WritableStreamLike;
34
- readonly stderr?: WritableStreamLike;
35
- readonly active?: boolean;
36
- readonly onIntercept?: (event: TerminalOutputIntercept) => void;
37
- };
38
-
39
- export type TuiTerminalOutputGuardOptions = {
40
- readonly stdout: WritableStreamLike;
41
- readonly stderr?: WritableStreamLike;
42
- readonly active?: boolean;
43
- /**
44
- * Called (rate-limited) after intercepts with the cumulative count of direct
45
- * writes captured this session, so an honest quiet counter (e.g. /debug) can
46
- * refresh. The per-write detail is already recorded to the activity log via
47
- * logger.warn — this callback intentionally does NOT push transcript lines.
48
- * (item 1a.)
49
- */
50
- readonly onCapture?: (total: number) => void;
51
- };
52
-
53
- const MAX_LOG_TEXT = 4_000;
54
- const MAX_PREVIEW_TEXT = 180;
55
- const ANSI_RE = /\x1b\[[0-?]*[ -/]*[@-~]/g;
56
-
57
- let currentGuard: TerminalOutputGuard | null = null;
58
-
59
- function writeCallback(args: unknown[]): ((error?: Error | null) => void) | undefined {
60
- const maybeCallback = args[args.length - 1];
61
- return typeof maybeCallback === 'function'
62
- ? maybeCallback as (error?: Error | null) => void
63
- : undefined;
64
- }
65
-
66
- function chunkToText(chunk: unknown): string {
67
- if (typeof chunk === 'string') return chunk;
68
- if (chunk instanceof Uint8Array) return Buffer.from(chunk).toString('utf8');
69
- return String(chunk);
70
- }
71
-
72
- function normalizeText(text: string): string {
73
- return text.replace(ANSI_RE, '').replace(/\r/g, '').trim();
74
- }
75
-
76
- function previewText(text: string): string {
77
- const singleLine = normalizeText(text).replace(/\s+/g, ' ');
78
- if (singleLine.length <= MAX_PREVIEW_TEXT) return singleLine;
79
- return `${singleLine.slice(0, MAX_PREVIEW_TEXT - 1)}...`;
80
- }
81
-
82
- function truncateForLog(text: string): string {
83
- if (text.length <= MAX_LOG_TEXT) return text;
84
- return `${text.slice(0, MAX_LOG_TEXT)}\n[truncated ${text.length - MAX_LOG_TEXT} byte(s)]`;
85
- }
86
-
87
- function invokeSuppressedCallback(args: unknown[]): void {
88
- const callback = writeCallback(args);
89
- if (callback) {
90
- queueMicrotask(() => callback(null));
91
- }
92
- }
93
-
94
- export function allowTerminalWrite<T>(fn: () => T): T {
95
- return currentGuard ? currentGuard.allowTerminalWrite(fn) : fn();
96
- }
97
-
98
- export function installTerminalOutputGuard(options: TerminalOutputGuardOptions): TerminalOutputGuard {
99
- const stdout = options.stdout;
100
- const stderr = options.stderr ?? process.stderr;
101
- const originalStdoutWriteMethod = stdout.write;
102
- const originalStderrWriteMethod = stderr.write;
103
- const originalStdoutWrite = (...args: unknown[]): boolean =>
104
- Reflect.apply(originalStdoutWriteMethod, stdout, args) as boolean;
105
- const originalStderrWrite = (...args: unknown[]): boolean =>
106
- Reflect.apply(originalStderrWriteMethod, stderr, args) as boolean;
107
- const originalConsole = {
108
- debug: console.debug.bind(console),
109
- error: console.error.bind(console),
110
- info: console.info.bind(console),
111
- log: console.log.bind(console),
112
- warn: console.warn.bind(console),
113
- };
114
-
115
- let active = options.active ?? true;
116
- let disposed = false;
117
- let allowDepth = 0;
118
- let captureDepth = 0;
119
-
120
- const record = (source: TerminalOutputInterceptSource, text: string): void => {
121
- if (disposed || !active) return;
122
- const normalized = normalizeText(text);
123
- if (!normalized) return;
124
- if (normalized.startsWith('[ActivityLogger]')) return;
125
- if (captureDepth > 0) return;
126
-
127
- captureDepth++;
128
- try {
129
- const event: TerminalOutputIntercept = {
130
- source,
131
- text: truncateForLog(normalized),
132
- preview: previewText(normalized),
133
- };
134
- logger.warn('Intercepted terminal output while TUI renderer was active', {
135
- source: event.source,
136
- text: event.text,
137
- });
138
- options.onIntercept?.(event);
139
- } finally {
140
- captureDepth--;
141
- }
142
- };
143
-
144
- const shouldPassThrough = (): boolean => !active || allowDepth > 0 || disposed;
145
-
146
- stdout.write = ((...args: unknown[]) => {
147
- if (shouldPassThrough()) {
148
- return originalStdoutWrite(...args);
149
- }
150
- record('stdout', chunkToText(args[0]));
151
- invokeSuppressedCallback(args);
152
- return true;
153
- }) as WritableStreamLike['write'];
154
-
155
- stderr.write = ((...args: unknown[]) => {
156
- if (shouldPassThrough()) {
157
- return originalStderrWrite(...args);
158
- }
159
- record('stderr', chunkToText(args[0]));
160
- invokeSuppressedCallback(args);
161
- return true;
162
- }) as WritableStreamLike['write'];
163
-
164
- console.debug = (...args: unknown[]) => {
165
- if (!active || disposed) return originalConsole.debug(...args);
166
- record('console.debug', format(...args));
167
- };
168
- console.error = (...args: unknown[]) => {
169
- if (!active || disposed) return originalConsole.error(...args);
170
- record('console.error', format(...args));
171
- };
172
- console.info = (...args: unknown[]) => {
173
- if (!active || disposed) return originalConsole.info(...args);
174
- record('console.info', format(...args));
175
- };
176
- console.log = (...args: unknown[]) => {
177
- if (!active || disposed) return originalConsole.log(...args);
178
- record('console.log', format(...args));
179
- };
180
- console.warn = (...args: unknown[]) => {
181
- if (!active || disposed) return originalConsole.warn(...args);
182
- record('console.warn', format(...args));
183
- };
184
-
185
- const guard: TerminalOutputGuard = {
186
- setActive(nextActive) {
187
- active = nextActive;
188
- },
189
- allowTerminalWrite<T>(fn: () => T): T {
190
- allowDepth++;
191
- try {
192
- return fn();
193
- } finally {
194
- allowDepth--;
195
- }
196
- },
197
- dispose() {
198
- if (disposed) return;
199
- disposed = true;
200
- stdout.write = originalStdoutWriteMethod;
201
- stderr.write = originalStderrWriteMethod;
202
- console.debug = originalConsole.debug;
203
- console.error = originalConsole.error;
204
- console.info = originalConsole.info;
205
- console.log = originalConsole.log;
206
- console.warn = originalConsole.warn;
207
- if (currentGuard === guard) {
208
- currentGuard = null;
209
- }
210
- },
211
- };
212
-
213
- currentGuard = guard;
214
- return guard;
215
- }
216
-
217
- export function installTuiTerminalOutputGuard(options: TuiTerminalOutputGuardOptions): TerminalOutputGuard {
218
- let totalInterceptedWrites = 0;
219
- let lastNoticeAt = 0;
220
- return installTerminalOutputGuard({
221
- stdout: options.stdout,
222
- stderr: options.stderr,
223
- active: options.active,
224
- onIntercept: () => {
225
- // Each intercept is already logged (logger.warn in record()). Here we only
226
- // maintain a cumulative counter and, rate-limited, refresh a quiet counter
227
- // surface — no repeated transcript lines. (item 1a.)
228
- totalInterceptedWrites++;
229
- const now = Date.now();
230
- if (now - lastNoticeAt < 5_000) return;
231
- lastNoticeAt = now;
232
- options.onCapture?.(totalInterceptedWrites);
233
- },
234
- });
235
- }