@pellux/goodvibes-tui 1.1.0 → 1.9.0

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 (267) hide show
  1. package/CHANGELOG.md +85 -21
  2. package/README.md +28 -18
  3. package/docs/foundation-artifacts/operator-contract.json +4045 -1763
  4. package/package.json +2 -2
  5. package/src/audio/spoken-turn-controller.ts +12 -2
  6. package/src/audio/spoken-turn-wiring.ts +2 -1
  7. package/src/cli/help.ts +8 -1
  8. package/src/cli/service-posture.ts +2 -1
  9. package/src/cli/status.ts +2 -1
  10. package/src/cli/surface-command.ts +2 -2
  11. package/src/core/alert-gating.ts +67 -0
  12. package/src/core/approval-alert.ts +72 -0
  13. package/src/core/budget-breach-notifier.ts +106 -0
  14. package/src/core/composer-state.ts +11 -3
  15. package/src/core/conversation-line-cache.ts +27 -3
  16. package/src/core/conversation-rendering.ts +90 -14
  17. package/src/core/conversation.ts +18 -0
  18. package/src/core/focus-tracker.ts +41 -0
  19. package/src/core/long-task-notifier.ts +33 -6
  20. package/src/core/stream-event-wiring.ts +20 -1
  21. package/src/core/system-message-noise.ts +87 -0
  22. package/src/core/system-message-router.ts +104 -51
  23. package/src/core/turn-cancellation.ts +25 -0
  24. package/src/core/turn-event-wiring.ts +72 -3
  25. package/src/daemon/cli.ts +29 -2
  26. package/src/daemon/handlers/register.ts +8 -1
  27. package/src/daemon/service-commands.ts +329 -0
  28. package/src/export/cost-utils.ts +36 -0
  29. package/src/input/autocomplete.ts +27 -1
  30. package/src/input/command-registry.ts +84 -5
  31. package/src/input/commands/checkpoint-runtime.ts +280 -0
  32. package/src/input/commands/codebase-runtime.ts +232 -0
  33. package/src/input/commands/config.ts +43 -3
  34. package/src/input/commands/eval.ts +1 -1
  35. package/src/input/commands/health-runtime.ts +10 -2
  36. package/src/input/commands/image-runtime.ts +112 -0
  37. package/src/input/commands/intelligence-runtime.ts +11 -1
  38. package/src/input/commands/local-auth-runtime.ts +4 -1
  39. package/src/input/commands/local-runtime.ts +9 -3
  40. package/src/input/commands/marketplace-runtime.ts +2 -2
  41. package/src/input/commands/memory.ts +73 -35
  42. package/src/input/commands/operator-panel-runtime.ts +71 -31
  43. package/src/input/commands/planning-runtime.ts +116 -4
  44. package/src/input/commands/platform-sandbox-runtime.ts +1 -6
  45. package/src/input/commands/plugin-runtime.ts +2 -2
  46. package/src/input/commands/policy-dispatch.ts +21 -0
  47. package/src/input/commands/provider-accounts-runtime.ts +1 -1
  48. package/src/input/commands/qrcode-runtime.ts +26 -6
  49. package/src/input/commands/recall-review.ts +35 -0
  50. package/src/input/commands/remote-runtime-setup.ts +5 -3
  51. package/src/input/commands/remote-runtime.ts +3 -3
  52. package/src/input/commands/runtime-services.ts +54 -13
  53. package/src/input/commands/services-runtime.ts +1 -1
  54. package/src/input/commands/session-content.ts +20 -9
  55. package/src/input/commands/session-workflow.ts +16 -1
  56. package/src/input/commands/settings-sync-runtime.ts +16 -4
  57. package/src/input/commands/shell-core.ts +25 -8
  58. package/src/input/commands/skills-runtime.ts +2 -8
  59. package/src/input/commands/subscription-runtime.ts +2 -2
  60. package/src/input/commands/test-runtime.ts +277 -0
  61. package/src/input/commands/websearch-runtime.ts +101 -0
  62. package/src/input/commands/work-plan-runtime.ts +36 -10
  63. package/src/input/commands/workstream-runtime.ts +488 -0
  64. package/src/input/commands.ts +12 -0
  65. package/src/input/config-modal-types.ts +175 -0
  66. package/src/input/config-modal.ts +592 -0
  67. package/src/input/feed-context-factory.ts +10 -8
  68. package/src/input/handler-command-route.ts +37 -20
  69. package/src/input/handler-content-actions.ts +17 -2
  70. package/src/input/handler-feed-routes.ts +74 -114
  71. package/src/input/handler-feed.ts +79 -27
  72. package/src/input/handler-interactions.ts +1 -3
  73. package/src/input/handler-modal-routes.ts +136 -6
  74. package/src/input/handler-modal-stack.ts +14 -7
  75. package/src/input/handler-modal-token-routes.ts +16 -49
  76. package/src/input/handler-onboarding-daemon-adopt.ts +149 -0
  77. package/src/input/handler-onboarding.ts +71 -59
  78. package/src/input/handler-picker-routes.ts +26 -102
  79. package/src/input/handler-shortcuts.ts +74 -14
  80. package/src/input/handler-types.ts +2 -6
  81. package/src/input/handler-ui-state.ts +10 -22
  82. package/src/input/handler.ts +12 -29
  83. package/src/input/keybindings.ts +23 -8
  84. package/src/input/model-picker-types.ts +24 -6
  85. package/src/input/model-picker.ts +77 -2
  86. package/src/input/onboarding/onboarding-runtime-status.ts +10 -1
  87. package/src/input/onboarding/onboarding-wizard-apply.ts +8 -1
  88. package/src/input/onboarding/onboarding-wizard-constants.ts +4 -1
  89. package/src/input/onboarding/onboarding-wizard-network-adopt.ts +136 -0
  90. package/src/input/onboarding/onboarding-wizard-steps.ts +9 -6
  91. package/src/input/onboarding/onboarding-wizard-types.ts +3 -1
  92. package/src/input/panel-integration-actions.ts +9 -170
  93. package/src/input/panel-mouse-geometry.ts +97 -0
  94. package/src/input/panel-paste-flood-guard.ts +86 -0
  95. package/src/input/selection-modal.ts +11 -0
  96. package/src/input/session-picker-modal.ts +44 -4
  97. package/src/input/settings-modal-data.ts +146 -2
  98. package/src/input/settings-modal-mutations.ts +6 -4
  99. package/src/input/settings-modal-types.ts +4 -2
  100. package/src/main.ts +49 -51
  101. package/src/panels/agent-inspector-shared.ts +2 -1
  102. package/src/panels/base-panel.ts +22 -1
  103. package/src/panels/builtin/agent.ts +21 -107
  104. package/src/panels/builtin/development.ts +15 -61
  105. package/src/panels/builtin/knowledge.ts +8 -32
  106. package/src/panels/builtin/operations.ts +84 -428
  107. package/src/panels/builtin/session.ts +21 -112
  108. package/src/panels/builtin/shared.ts +18 -46
  109. package/src/panels/builtin-modals.ts +218 -0
  110. package/src/panels/builtin-panels.ts +5 -0
  111. package/src/panels/cost-tracker-panel.ts +36 -10
  112. package/src/panels/diff-panel.ts +12 -43
  113. package/src/panels/eval-registry.ts +60 -0
  114. package/src/panels/fleet-deep-link.ts +31 -0
  115. package/src/panels/fleet-panel-format.ts +62 -0
  116. package/src/panels/fleet-panel-worktree-detail.ts +48 -0
  117. package/src/panels/fleet-panel.ts +758 -0
  118. package/src/panels/fleet-read-model.ts +520 -0
  119. package/src/panels/fleet-steer.ts +125 -0
  120. package/src/panels/fleet-stop.ts +153 -0
  121. package/src/panels/fleet-tabs.ts +230 -0
  122. package/src/panels/fleet-transcript.ts +356 -0
  123. package/src/panels/index.ts +7 -31
  124. package/src/panels/modals/hooks-modal.ts +187 -0
  125. package/src/panels/modals/keybindings-modal.ts +166 -0
  126. package/src/panels/modals/knowledge-modal.ts +158 -0
  127. package/src/panels/modals/local-auth-modal.ts +132 -0
  128. package/src/panels/modals/marketplace-modal.ts +218 -0
  129. package/src/panels/modals/memory-modal.ts +180 -0
  130. package/src/panels/modals/modal-surface-helpers.ts +54 -0
  131. package/src/panels/modals/modal-theme.ts +42 -0
  132. package/src/panels/modals/pairing-modal.ts +163 -0
  133. package/src/panels/modals/planning-modal.ts +304 -0
  134. package/src/panels/modals/plugins-modal.ts +174 -0
  135. package/src/panels/modals/policy-modal.ts +256 -0
  136. package/src/panels/modals/provider-health-modal.ts +136 -0
  137. package/src/panels/modals/remote-modal.ts +115 -0
  138. package/src/panels/modals/sandbox-modal.ts +150 -0
  139. package/src/panels/modals/security-modal.ts +217 -0
  140. package/src/panels/modals/services-modal.ts +179 -0
  141. package/src/panels/modals/settings-sync-modal.ts +142 -0
  142. package/src/panels/modals/skills-modal.ts +189 -0
  143. package/src/panels/modals/subscription-modal.ts +172 -0
  144. package/src/panels/modals/work-plan-modal.ts +177 -0
  145. package/src/panels/panel-confirm-overlay.ts +72 -0
  146. package/src/panels/panel-manager.ts +123 -9
  147. package/src/panels/polish-core.ts +38 -25
  148. package/src/panels/project-planning-answer-actions.ts +8 -11
  149. package/src/panels/skills-panel.ts +7 -51
  150. package/src/panels/types.ts +37 -0
  151. package/src/permissions/hunk-selection.ts +179 -0
  152. package/src/permissions/prompt.ts +216 -13
  153. package/src/renderer/autocomplete-overlay.ts +28 -2
  154. package/src/renderer/compaction-history-modal.ts +19 -3
  155. package/src/renderer/compaction-preview.ts +13 -2
  156. package/src/renderer/compaction-quality.ts +100 -0
  157. package/src/renderer/compositor.ts +2 -3
  158. package/src/renderer/config-modal.ts +95 -0
  159. package/src/renderer/conversation-overlays.ts +10 -17
  160. package/src/renderer/fleet-tab-strip.ts +56 -0
  161. package/src/renderer/footer-tips.ts +15 -3
  162. package/src/renderer/fullscreen-primitives.ts +32 -22
  163. package/src/renderer/git-status.ts +43 -1
  164. package/src/renderer/help-overlay.ts +2 -2
  165. package/src/renderer/layout.ts +0 -4
  166. package/src/renderer/markdown.ts +7 -3
  167. package/src/renderer/modal-factory.ts +25 -20
  168. package/src/renderer/model-workspace.ts +62 -4
  169. package/src/renderer/overlay-box.ts +21 -17
  170. package/src/renderer/panel-workspace-bar.ts +8 -2
  171. package/src/renderer/process-indicator.ts +14 -3
  172. package/src/renderer/selection-modal-overlay.ts +6 -1
  173. package/src/renderer/session-picker-modal.ts +196 -3
  174. package/src/renderer/settings-modal-helpers.ts +2 -0
  175. package/src/renderer/settings-modal.ts +7 -0
  176. package/src/renderer/shell-surface.ts +8 -1
  177. package/src/renderer/status-glyphs.ts +14 -15
  178. package/src/renderer/system-message.ts +15 -3
  179. package/src/renderer/terminal-bg-probe.ts +339 -0
  180. package/src/renderer/terminal-escapes.ts +20 -0
  181. package/src/renderer/theme-mode-config.ts +67 -0
  182. package/src/renderer/theme.ts +91 -1
  183. package/src/renderer/thinking.ts +11 -3
  184. package/src/renderer/tool-call.ts +15 -9
  185. package/src/renderer/tool-result-summary.ts +148 -0
  186. package/src/renderer/turn-injection.ts +133 -0
  187. package/src/renderer/ui-factory.ts +154 -85
  188. package/src/renderer/ui-primitives.ts +30 -129
  189. package/src/runtime/bootstrap-command-context.ts +27 -1
  190. package/src/runtime/bootstrap-command-parts.ts +41 -10
  191. package/src/runtime/bootstrap-core.ts +47 -15
  192. package/src/runtime/bootstrap-hook-bridge.ts +7 -0
  193. package/src/runtime/bootstrap-shell.ts +48 -17
  194. package/src/runtime/bootstrap.ts +129 -22
  195. package/src/runtime/code-index-services.ts +135 -0
  196. package/src/runtime/legacy-daemon-migration.ts +516 -0
  197. package/src/runtime/memory-fold.ts +26 -0
  198. package/src/runtime/onboarding/derivation.ts +7 -2
  199. package/src/runtime/onboarding/snapshot.ts +27 -1
  200. package/src/runtime/onboarding/types.ts +31 -1
  201. package/src/runtime/operator-token-cleanup.ts +82 -1
  202. package/src/runtime/orchestrator-core-services.ts +59 -0
  203. package/src/runtime/process-lifecycle.ts +3 -1
  204. package/src/runtime/resume-notice.ts +209 -0
  205. package/src/runtime/services.ts +98 -46
  206. package/src/runtime/session-inbound-inputs.ts +252 -0
  207. package/src/runtime/session-spine-transport.ts +64 -0
  208. package/src/runtime/terminal-output-guard.ts +15 -8
  209. package/src/runtime/ui-services.ts +27 -3
  210. package/src/runtime/workstream-services.ts +327 -0
  211. package/src/runtime/wrfc-persistence.ts +124 -17
  212. package/src/shell/blocking-input.ts +68 -4
  213. package/src/shell/recovery-input-helpers.ts +170 -1
  214. package/src/shell/ui-openers.ts +171 -26
  215. package/src/utils/format-duration.ts +8 -18
  216. package/src/utils/splash-lines.ts +1 -1
  217. package/src/utils/terminal-width.ts +52 -0
  218. package/src/version.ts +1 -1
  219. package/src/panels/agent-inspector-panel.ts +0 -786
  220. package/src/panels/approval-panel.ts +0 -252
  221. package/src/panels/automation-control-panel.ts +0 -479
  222. package/src/panels/cockpit-panel.ts +0 -481
  223. package/src/panels/cockpit-read-model.ts +0 -238
  224. package/src/panels/communication-panel.ts +0 -256
  225. package/src/panels/control-plane-panel.ts +0 -470
  226. package/src/panels/debug-panel.ts +0 -615
  227. package/src/panels/docs-panel.ts +0 -384
  228. package/src/panels/eval-panel.ts +0 -627
  229. package/src/panels/file-explorer-panel.ts +0 -673
  230. package/src/panels/file-preview-panel.ts +0 -517
  231. package/src/panels/hooks-panel.ts +0 -401
  232. package/src/panels/incident-review-panel.ts +0 -406
  233. package/src/panels/intelligence-panel.ts +0 -383
  234. package/src/panels/knowledge-graph-panel.ts +0 -515
  235. package/src/panels/marketplace-panel.ts +0 -399
  236. package/src/panels/memory-panel.ts +0 -558
  237. package/src/panels/ops-control-panel.ts +0 -329
  238. package/src/panels/ops-strategy-panel.ts +0 -321
  239. package/src/panels/orchestration-panel.ts +0 -465
  240. package/src/panels/panel-list-panel.ts +0 -566
  241. package/src/panels/plan-dashboard-panel.ts +0 -707
  242. package/src/panels/policy-panel.ts +0 -517
  243. package/src/panels/project-planning-panel.ts +0 -731
  244. package/src/panels/provider-health-panel.ts +0 -678
  245. package/src/panels/provider-health-tracker.ts +0 -310
  246. package/src/panels/provider-health-views.ts +0 -567
  247. package/src/panels/qr-panel.ts +0 -280
  248. package/src/panels/remote-panel.ts +0 -534
  249. package/src/panels/routes-panel.ts +0 -241
  250. package/src/panels/sandbox-panel.ts +0 -456
  251. package/src/panels/security-panel.ts +0 -447
  252. package/src/panels/services-panel.ts +0 -329
  253. package/src/panels/session-browser-panel.ts +0 -496
  254. package/src/panels/settings-sync-panel.ts +0 -398
  255. package/src/panels/subscription-panel.ts +0 -342
  256. package/src/panels/symbol-outline-panel.ts +0 -619
  257. package/src/panels/system-messages-panel.ts +0 -364
  258. package/src/panels/tasks-panel.ts +0 -608
  259. package/src/panels/thinking-panel.ts +0 -333
  260. package/src/panels/tool-inspector-panel.ts +0 -537
  261. package/src/panels/work-plan-panel.ts +0 -540
  262. package/src/panels/worktree-panel.ts +0 -360
  263. package/src/panels/wrfc-panel.ts +0 -790
  264. package/src/renderer/agent-detail-modal.ts +0 -466
  265. package/src/renderer/live-tail-modal.ts +0 -156
  266. package/src/renderer/process-modal.ts +0 -671
  267. package/src/renderer/qr-renderer.ts +0 -120
@@ -1,11 +1,9 @@
1
1
  import { join } from 'node:path';
2
- import { ConfigManager } from '@pellux/goodvibes-sdk/platform/config';
2
+ import { FocusTracker } from '../core/focus-tracker.ts';
3
+ import { ConfigManager, ServiceRegistry, SubscriptionManager, ToolLLM } from '@pellux/goodvibes-sdk/platform/config';
3
4
  import { SecretsManager } from '../config/secrets.ts';
4
- import { ServiceRegistry } from '@pellux/goodvibes-sdk/platform/config';
5
- import { SubscriptionManager } from '@pellux/goodvibes-sdk/platform/config';
6
5
  import { AutomationDeliveryManager, AutomationManager, AutomationRouteStore } from '@pellux/goodvibes-sdk/platform/automation';
7
- import { ChannelPluginRegistry, ChannelPolicyManager, RouteBindingManager, SurfaceRegistry } from '@pellux/goodvibes-sdk/platform/channels';
8
- import { ChannelDeliveryRouter } from '@pellux/goodvibes-sdk/platform/channels';
6
+ import { ChannelDeliveryRouter, ChannelPluginRegistry, ChannelPolicyManager, RouteBindingManager, SurfaceRegistry } from '@pellux/goodvibes-sdk/platform/channels';
9
7
  import { ApprovalBroker, GatewayMethodCatalog, SharedSessionBroker } from '@pellux/goodvibes-sdk/platform/control-plane';
10
8
  import { WatcherRegistry } from '@pellux/goodvibes-sdk/platform/watchers';
11
9
  import { ArtifactStore } from '@pellux/goodvibes-sdk/platform/artifacts';
@@ -23,16 +21,10 @@ import {
23
21
  } from '@pellux/goodvibes-sdk/platform/knowledge';
24
22
  import { MediaProviderRegistry, ensureBuiltinMediaProviders } from '@pellux/goodvibes-sdk/platform/media';
25
23
  import { MultimodalService } from '@pellux/goodvibes-sdk/platform/multimodal';
26
- import { AgentManager } from '@pellux/goodvibes-sdk/platform/tools';
27
- import { AgentMessageBus } from '@pellux/goodvibes-sdk/platform/agents';
28
- import { WrfcController } from '@pellux/goodvibes-sdk/platform/agents';
29
- import { AgentOrchestrator } from '@pellux/goodvibes-sdk/platform/agents';
30
- import { ArchetypeLoader } from '@pellux/goodvibes-sdk/platform/agents';
31
- import { ProcessManager } from '@pellux/goodvibes-sdk/platform/tools';
32
- import { ModeManager } from '@pellux/goodvibes-sdk/platform/state';
33
- import { FileUndoManager } from '@pellux/goodvibes-sdk/platform/state';
34
- import { MemoryRegistry } from '@pellux/goodvibes-sdk/platform/state';
35
- import { MemoryStore } from '@pellux/goodvibes-sdk/platform/state';
24
+ import { AgentMessageBus, AgentOrchestrator, ArchetypeLoader, WrfcController } from '@pellux/goodvibes-sdk/platform/agents';
25
+ import { AgentManager, OverflowHandler, ProcessManager, createWorkflowServices, type WorkflowServices } from '@pellux/goodvibes-sdk/platform/tools';
26
+ import { FileStateCache, FileUndoManager, MemoryEmbeddingProviderRegistry, MemoryRegistry, MemoryStore, ModeManager, ProjectIndex, resolveCanonicalMemoryDbPath, type CodeIndexStore, type CodeIndexReindexScheduler } from '@pellux/goodvibes-sdk/platform/state';
27
+ import { WorkspaceCheckpointManager } from '@pellux/goodvibes-sdk/platform/workspace';
36
28
  import type { RuntimeEventBus } from '@/runtime/index.ts';
37
29
  import { createDomainDispatch } from './store/index.ts';
38
30
  import type { DomainDispatch, RuntimeStore } from './store/index.ts';
@@ -41,40 +33,20 @@ import { RemoteRunnerRegistry, RemoteSupervisor } from '@/runtime/index.ts';
41
33
  import { IntegrationHelperService } from '@/runtime/index.ts';
42
34
  import { VoiceProviderRegistry, VoiceService, ensureBuiltinVoiceProviders } from '@pellux/goodvibes-sdk/platform/voice';
43
35
  import { WebSearchProviderRegistry, WebSearchService } from '@pellux/goodvibes-sdk/platform/web-search';
44
- import { MemoryEmbeddingProviderRegistry } from '@pellux/goodvibes-sdk/platform/state';
45
36
  import { PanelManager } from '../panels/panel-manager.ts';
46
37
  import { HookActivityTracker } from '@pellux/goodvibes-sdk/platform/hooks';
47
38
  import { HookDispatcher, createHookWorkbench, type HookWorkbench } from '@pellux/goodvibes-sdk/platform/hooks';
48
39
  import { PluginManager } from '@pellux/goodvibes-sdk/platform/plugins';
49
40
  import { BookmarkManager } from '@pellux/goodvibes-sdk/platform/bookmarks';
50
41
  import { ProfileManager } from '@pellux/goodvibes-sdk/platform/profiles';
51
- import { SessionManager } from '@pellux/goodvibes-sdk/platform/sessions';
52
- import { CrossSessionTaskRegistry } from '@pellux/goodvibes-sdk/platform/sessions';
53
- import { ApiTokenAuditor } from '@pellux/goodvibes-sdk/platform/security';
54
- import { UserAuthManager } from '@pellux/goodvibes-sdk/platform/security';
42
+ import { SessionManager, CrossSessionTaskRegistry, SessionChangeTracker } from '@pellux/goodvibes-sdk/platform/sessions';
43
+ import { ApiTokenAuditor, UserAuthManager } from '@pellux/goodvibes-sdk/platform/security';
55
44
  import { WebhookNotifier } from '@pellux/goodvibes-sdk/platform/integrations';
56
45
  import { McpRegistry } from '@pellux/goodvibes-sdk/platform/mcp';
57
- import { DeterministicReplayEngine } from '@pellux/goodvibes-sdk/platform/core';
58
- import { ProviderOptimizer } from '@pellux/goodvibes-sdk/platform/providers';
59
- import { ProviderRegistry } from '@pellux/goodvibes-sdk/platform/providers';
60
- import type { ModelDefinition } from '@pellux/goodvibes-sdk/platform/providers';
61
- import { ProviderCapabilityRegistry } from '@pellux/goodvibes-sdk/platform/providers';
62
- import { CacheHitTracker } from '@pellux/goodvibes-sdk/platform/providers';
63
- import { FavoritesStore } from '@pellux/goodvibes-sdk/platform/providers';
64
- import { BenchmarkStore } from '@pellux/goodvibes-sdk/platform/providers';
65
- import { ModelLimitsService } from '@pellux/goodvibes-sdk/platform/providers';
66
- import { inferFallbackContextWindow } from '@pellux/goodvibes-sdk/platform/providers';
46
+ import { BenchmarkStore, CacheHitTracker, FavoritesStore, inferFallbackContextWindow, type ModelDefinition, ModelLimitsService, ProviderCapabilityRegistry, ProviderOptimizer, ProviderRegistry } from '@pellux/goodvibes-sdk/platform/providers';
67
47
  import { KeybindingsManager } from '../input/keybindings.ts';
68
- import { SessionMemoryStore } from '@pellux/goodvibes-sdk/platform/core';
69
- import { SessionLineageTracker } from '@pellux/goodvibes-sdk/platform/core';
70
- import { SessionChangeTracker } from '@pellux/goodvibes-sdk/platform/sessions';
71
- import { ExecutionPlanManager } from '@pellux/goodvibes-sdk/platform/core';
72
- import { AdaptivePlanner } from '@pellux/goodvibes-sdk/platform/core';
73
- import { FileStateCache } from '@pellux/goodvibes-sdk/platform/state';
74
- import { ProjectIndex } from '@pellux/goodvibes-sdk/platform/state';
48
+ import { AdaptivePlanner, DeterministicReplayEngine, ExecutionPlanManager, SessionLineageTracker, SessionMemoryStore } from '@pellux/goodvibes-sdk/platform/core';
75
49
  import { IdempotencyStore } from '@/runtime/index.ts';
76
- import { OverflowHandler } from '@pellux/goodvibes-sdk/platform/tools';
77
- import { ToolLLM } from '@pellux/goodvibes-sdk/platform/config';
78
50
  import { ComponentHealthMonitor } from '@/runtime/index.ts';
79
51
  import { WorktreeRegistry } from '@/runtime/index.ts';
80
52
  import { SandboxSessionRegistry } from '@/runtime/index.ts';
@@ -83,10 +55,10 @@ import { isFeatureFlagEnabled } from './surface-feature-flags.ts';
83
55
  import type { FeatureFlagManager } from '@/runtime/index.ts';
84
56
  import { createFeatureFlagManager } from '@/runtime/index.ts';
85
57
  import { PolicyRuntimeState } from '@/runtime/index.ts';
86
- import {
87
- createWorkflowServices,
88
- type WorkflowServices,
89
- } from '@pellux/goodvibes-sdk/platform/tools';
58
+ import { createProcessRegistry, type ProcessRegistry } from '@pellux/goodvibes-sdk/platform/runtime/fleet';
59
+ import { calcSessionCost, isModelPriced } from '../export/cost-utils.ts';
60
+ import { createWorkstreamServices, type OrchestrationEngine, type WorkstreamCommandService } from './workstream-services.ts';
61
+ import { codeIndexDbPath, createCodeIndexServices, isCodeInjectionSettingEnabled } from './code-index-services.ts';
90
62
  import { WorkPlanStore } from '../work-plans/work-plan-store.ts';
91
63
  import {
92
64
  registerDaemonHandlers,
@@ -221,6 +193,8 @@ export interface RuntimeServices {
221
193
  readonly worktreeRegistry: WorktreeRegistry;
222
194
  readonly sandboxSessionRegistry: SandboxSessionRegistry;
223
195
  readonly webhookNotifier: WebhookNotifier;
196
+ /** Terminal focus tracker (W2.3) — fed by input/handler-feed.ts, read by the alert notifiers in core/. */
197
+ readonly focusTracker: FocusTracker;
224
198
  readonly replayEngine: DeterministicReplayEngine;
225
199
  readonly providerOptimizer: ProviderOptimizer;
226
200
  readonly providerCapabilityRegistry: ProviderCapabilityRegistry;
@@ -248,8 +222,17 @@ export interface RuntimeServices {
248
222
  readonly agentOrchestrator: AgentOrchestrator;
249
223
  readonly wrfcController: WrfcController;
250
224
  readonly processManager: ProcessManager;
225
+ /** Wave 4 (wo703): the phase/work-item orchestration engine — see runtime/workstream-services.ts. */
226
+ readonly orchestrationEngine: OrchestrationEngine;
227
+ readonly workstreamCommands: WorkstreamCommandService;
228
+ /** Wave 5 (wo804): the repo source-tree code index — see runtime/code-index-services.ts. */
229
+ readonly codeIndexStore: CodeIndexStore;
230
+ readonly codeIndexReindexScheduler: CodeIndexReindexScheduler; // Wave-5 Stage B tool-site reindex
231
+ /** W2.1/W2.2: unified live process registry (agents, WRFC chains, workflows, watchers, background processes) backing the Fleet panel. */
232
+ readonly processRegistry: ProcessRegistry;
251
233
  readonly modeManager: ModeManager;
252
234
  readonly fileUndoManager: FileUndoManager;
235
+ readonly workspaceCheckpointManager: WorkspaceCheckpointManager;
253
236
  readonly integrationHelpers: IntegrationHelperService;
254
237
  /**
255
238
  * Re-root path-bound stores (MemoryStore, ProjectIndex) to a new working directory.
@@ -272,6 +255,9 @@ export function createRuntimeServices(options: RuntimeServicesOptions): RuntimeS
272
255
  const runtimeDispatch = createDomainDispatch(options.runtimeStore);
273
256
  const gatewayMethods = new GatewayMethodCatalog();
274
257
  const panelManager = new PanelManager();
258
+ // W6.1 (the purge): MIGRATE-TO-MODAL surface + redirect registration moved to
259
+ // registerBuiltinPanels (builtin-panels.ts), where the panels' resolved deps
260
+ // are available for the surfaces to close over.
275
261
  const keybindingsManager = new KeybindingsManager({
276
262
  configPath: shellPaths.resolveUserPath('tui', 'keybindings.json'),
277
263
  });
@@ -352,6 +338,10 @@ export function createRuntimeServices(options: RuntimeServicesOptions): RuntimeS
352
338
  executor: agentOrchestrator,
353
339
  configManager,
354
340
  });
341
+ agentOrchestrator.setConversationSink({ // Wave-3 Part C6 bridge (mirrors the SDK's own createRuntimeServices)
342
+ register: (agentId, source) => agentManager.registerConversationSource(agentId, source),
343
+ release: (agentId) => agentManager.releaseConversationSource(agentId),
344
+ });
355
345
  agentManager.setRuntimeBus(options.runtimeBus);
356
346
  const wrfcController = new WrfcController(options.runtimeBus, agentMessageBus, {
357
347
  agentManager,
@@ -397,7 +387,8 @@ export function createRuntimeServices(options: RuntimeServicesOptions): RuntimeS
397
387
  });
398
388
  const artifactStore = new ArtifactStore({ configManager });
399
389
  const memoryEmbeddingRegistry = new MemoryEmbeddingProviderRegistry({ configManager });
400
- const memoryDbPath = join(workingDirectory, '.goodvibes', 'tui', 'memory.sqlite');
390
+ // W6-C2 (E6): open the ONE home-scoped canonical store; legacy per-project TUI memory folds in at boot (foldTuiLegacyMemory).
391
+ const memoryDbPath = resolveCanonicalMemoryDbPath(homeDirectory);
401
392
  const memoryStore = new MemoryStore(memoryDbPath, {
402
393
  embeddingRegistry: memoryEmbeddingRegistry,
403
394
  });
@@ -580,6 +571,7 @@ export function createRuntimeServices(options: RuntimeServicesOptions): RuntimeS
580
571
  const componentHealthMonitor = new ComponentHealthMonitor();
581
572
  const worktreeRegistry = new WorktreeRegistry(workingDirectory);
582
573
  const webhookNotifier = new WebhookNotifier();
574
+ const focusTracker = new FocusTracker();
583
575
  const replayEngine = new DeterministicReplayEngine(workingDirectory);
584
576
  const providerOptimizer = new ProviderOptimizer(
585
577
  providerRegistry,
@@ -603,8 +595,58 @@ export function createRuntimeServices(options: RuntimeServicesOptions): RuntimeS
603
595
  artifactStore,
604
596
  });
605
597
  const processManager = new ProcessManager();
598
+ // Wave 4 (wo703): the phase/work-item orchestration engine, constructed
599
+ // before the process registry so its fleet nodes (workstream/phase/
600
+ // work-item) can be folded in below via the registry's optional
601
+ // orchestrationEngine dep.
602
+ const { orchestrationEngine, workstreamCommands } = createWorkstreamServices({
603
+ agentManager, configManager, adaptivePlanner, runtimeBus: options.runtimeBus, projectRoot: workingDirectory,
604
+ });
605
+ // Wave 5 (wo804): repo source-tree code index, sharing memoryEmbeddingRegistry
606
+ // with MemoryStore above. Auto-build is config-gated (default off) — see
607
+ // code-index-services.ts's header doc.
608
+ const { codeIndexStore, codeIndexReindexScheduler } = createCodeIndexServices({ workingDirectory, configManager, memoryEmbeddingRegistry });
609
+ const codeInjectionOrchestratorDeps = { codeIndex: codeIndexStore, isCodeInjectionSettingEnabled: () => isCodeInjectionSettingEnabled(configManager), codeIndexReindexScheduler }; // Wave-5 Stage B seam (agent here; main via orchestrator-core-services.ts)
610
+ // W2.1/W2.2: one shared process registry aggregating the managers above —
611
+ // the Fleet panel (panels/fleet-read-model.ts) is its first consumer.
612
+ // Constructed once here (not per-consumer) so the coalesced tick and the
613
+ // agent-activity side-table are shared, not duplicated.
614
+ const processRegistry = createProcessRegistry({
615
+ agentManager,
616
+ wrfcController,
617
+ orchestrationEngine, // Wave 4 (wo703): folds workstream/phase/work-item nodes into the fleet
618
+ codeIndexService: codeIndexStore, // Wave 5 (wo804): folds a single 'code-index' node into the fleet
619
+ processManager,
620
+ watcherRegistry,
621
+ workflow,
622
+ approvalBroker,
623
+ sessionBroker,
624
+ messageBus: agentMessageBus, // Wave-3: backs steer()/`steerable` (wo612 builds the composer UI on top)
625
+ automationManager, // Wave 6 (wo-F item d4): folds /schedule AutomationJobs into the fleet as 'schedule' nodes
626
+ runtimeBus: options.runtimeBus,
627
+ // Honest pricing: never fabricate a cost for an unrecognized model.
628
+ priceUsage: (model, usage) => {
629
+ const modelId = model ?? 'unknown';
630
+ if (!isModelPriced(modelId)) return null;
631
+ return calcSessionCost(usage.inputTokens, usage.outputTokens, usage.cacheReadTokens, usage.cacheWriteTokens, modelId);
632
+ },
633
+ });
606
634
  const modeManager = new ModeManager();
607
635
  const fileUndoManager = new FileUndoManager();
636
+ const workspaceCheckpointManager = new WorkspaceCheckpointManager({
637
+ workspaceRoot: workingDirectory,
638
+ runtimeBus: options.runtimeBus,
639
+ });
640
+ // Fire-and-forget: subscriptions go live immediately if init() succeeds.
641
+ // If it rejects, WorkspaceCheckpointManager caches that rejection on
642
+ // `initPromise` and never clears it, so every later call (create/list/
643
+ // diff/restore — each awaits init() first) re-throws the same error
644
+ // forever: checkpointing becomes entirely unavailable for the rest of the
645
+ // session, not "degraded to manual-only". The `.catch(() => {})` here only
646
+ // exists to prevent an unhandled rejection at startup; the checkpoint
647
+ // commands (checkpoint-runtime.ts) are what actually catch and report the
648
+ // failure to the user, on first use.
649
+ void workspaceCheckpointManager.init().catch(() => {});
608
650
  const integrationHelpers = new IntegrationHelperService({
609
651
  workingDirectory,
610
652
  homeDirectory,
@@ -639,6 +681,7 @@ export function createRuntimeServices(options: RuntimeServicesOptions): RuntimeS
639
681
  remoteRunnerRegistry,
640
682
  knowledgeService,
641
683
  memoryRegistry,
684
+ ...codeInjectionOrchestratorDeps, // Wave-5 Stage B: agent-run code injection + tool-site reindex
642
685
  archetypeLoader,
643
686
  configManager,
644
687
  providerRegistry,
@@ -709,6 +752,7 @@ export function createRuntimeServices(options: RuntimeServicesOptions): RuntimeS
709
752
  worktreeRegistry,
710
753
  sandboxSessionRegistry,
711
754
  webhookNotifier,
755
+ focusTracker,
712
756
  replayEngine,
713
757
  providerOptimizer,
714
758
  providerCapabilityRegistry,
@@ -736,12 +780,20 @@ export function createRuntimeServices(options: RuntimeServicesOptions): RuntimeS
736
780
  agentOrchestrator,
737
781
  wrfcController,
738
782
  processManager,
783
+ orchestrationEngine,
784
+ workstreamCommands,
785
+ codeIndexStore,
786
+ codeIndexReindexScheduler,
787
+ processRegistry,
739
788
  modeManager,
740
789
  fileUndoManager,
790
+ workspaceCheckpointManager,
741
791
  integrationHelpers,
742
792
  async rerootStores(newWorkingDir: string): Promise<void> {
743
- const newMemoryDbPath = join(newWorkingDir, '.goodvibes', 'tui', 'memory.sqlite');
744
- await memoryStore.reroot(newMemoryDbPath);
793
+ // W6-C2 (E6): the memory store is the home-scoped canonical cross-surface store and
794
+ // deliberately does NOT reroot per-project (that would re-silo memory, the E6
795
+ // regression). Only working-tree-bound stores (code index, project index) reroot.
796
+ await codeIndexStore.reroot(newWorkingDir, codeIndexDbPath(newWorkingDir));
745
797
  await projectIndex.reroot(newWorkingDir);
746
798
  },
747
799
  };
@@ -0,0 +1,252 @@
1
+ /**
2
+ * session-inbound-inputs.ts
3
+ *
4
+ * D3 (One-Platform Wave 2, live-surface steer delivery — the inbound half of the
5
+ * session spine): the TUI's INBOUND path for steer/follow-up inputs that another
6
+ * live surface (e.g. the webui) queued against THIS TUI's session.
7
+ *
8
+ * Charter: when the owning surface is LIVE (heartbeating within the daemon's
9
+ * SURFACE_ROUTE_FRESHNESS window), a steer is DELIVERED to that surface rather
10
+ * than spawning a daemon-side executor (which would fail on the daemon's empty
11
+ * model registry). The daemon already does its half — `sessions.steer` /
12
+ * `sessions.followUp` to a surface-managed session with a fresh participant
13
+ * QUEUES the input for the surface (mode 'queued-for-surface') instead of
14
+ * spawning. This poller is the surface completing that contract:
15
+ *
16
+ * 1. poll `sessions.inputs.list` for QUEUED inputs on ITS OWN sessionId
17
+ * (the query is scoped by sessionId, so a steer for another session can
18
+ * never land here — the per-session isolation the concurrent-sessions
19
+ * charter requires);
20
+ * 2. hand each steer/follow-up to `onSteer` (the surface-side injection into
21
+ * the TUI's turn machinery — narrate "steer received from <surface>" and
22
+ * fire the next-turn boundary), skipping the TUI's own submissions;
23
+ * 3. acknowledge on the wire via `sessions.inputs.deliver` (queued → delivered)
24
+ * so every surface sees delivery truth live and the input is not re-picked.
25
+ *
26
+ * Discipline (mirrors SessionSpineClient): every wire call is best-effort and
27
+ * never throws into the render/keystroke path; a failed poll leaves the cursor
28
+ * where it was so the input is retried on the next tick (deliver is the only
29
+ * de-dup — an input already advanced past 'queued' is not returned again).
30
+ */
31
+
32
+ import { logger } from '@pellux/goodvibes-sdk/platform/utils';
33
+ import type {
34
+ SharedSessionInputIntent,
35
+ SharedSessionInputRecord,
36
+ } from '@pellux/goodvibes-sdk/platform/control-plane';
37
+
38
+ /** The TUI's own participant surfaceId — inputs it authored are never re-injected. */
39
+ export const TUI_SURFACE_ID = 'surface:tui';
40
+
41
+ /** The narrow inbound wire surface this poller needs. Structurally satisfied by
42
+ * the http operator client's `sessions.inputs` (list + deliver). Typed against
43
+ * this shape rather than the concrete client so tests inject a stub. */
44
+ export interface SpineInboundInputsClient {
45
+ listInputs(
46
+ sessionId: string,
47
+ options: { readonly state?: string; readonly since?: number; readonly limit?: number },
48
+ ): Promise<{ readonly inputs: readonly SharedSessionInputRecord[] }>;
49
+ deliverInput(
50
+ sessionId: string,
51
+ inputId: string,
52
+ options?: { readonly consumed?: boolean },
53
+ ): Promise<unknown>;
54
+ }
55
+
56
+ /** A steer/follow-up collected from the wire for surface-side injection. */
57
+ export interface InboundSteer {
58
+ readonly inputId: string;
59
+ readonly sessionId: string;
60
+ readonly intent: SharedSessionInputIntent;
61
+ readonly body: string;
62
+ readonly surfaceKind: string | undefined;
63
+ readonly surfaceId: string | undefined;
64
+ readonly displayName: string | undefined;
65
+ }
66
+
67
+ type InboundLogger = Pick<typeof logger, 'debug' | 'info'>;
68
+
69
+ export interface SessionInboundInputPollerOptions {
70
+ /** Returns the TUI's current live sessionId, or null when there is none yet.
71
+ * Read on every tick so a session swap re-targets the poll automatically. */
72
+ readonly sessionId: () => string | null;
73
+ /** Surface-side injection: narrate + deliver the steer into the turn machinery. */
74
+ readonly onSteer: (steer: InboundSteer) => void;
75
+ readonly intervalMs?: number;
76
+ readonly now?: () => number;
77
+ readonly log?: InboundLogger;
78
+ /** The poller's own surfaceId — inputs it authored are skipped (default TUI). */
79
+ readonly ownSurfaceId?: string;
80
+ }
81
+
82
+ const DEFAULT_POLL_INTERVAL_MS = 4_000;
83
+
84
+ export class SessionInboundInputPoller {
85
+ private readonly sessionId: () => string | null;
86
+ private readonly onSteer: (steer: InboundSteer) => void;
87
+ private readonly intervalMs: number;
88
+ private readonly now: () => number;
89
+ private readonly log: InboundLogger;
90
+ private readonly ownSurfaceId: string;
91
+
92
+ private client: SpineInboundInputsClient | null = null;
93
+ private timer: ReturnType<typeof setInterval> | null = null;
94
+ private polling = false;
95
+ /** Exclusive createdAt cursor, per session — a session swap resets it. */
96
+ private cursor = 0;
97
+ private cursorSessionId: string | null = null;
98
+
99
+ constructor(options: SessionInboundInputPollerOptions) {
100
+ this.sessionId = options.sessionId;
101
+ this.onSteer = options.onSteer;
102
+ this.intervalMs = Math.max(250, options.intervalMs ?? DEFAULT_POLL_INTERVAL_MS);
103
+ this.now = options.now ?? (() => Date.now());
104
+ this.log = options.log ?? logger;
105
+ this.ownSurfaceId = options.ownSurfaceId ?? TUI_SURFACE_ID;
106
+ }
107
+
108
+ /** Whether a backend is attached (external daemon adopted). */
109
+ get active(): boolean {
110
+ return this.client !== null;
111
+ }
112
+
113
+ /**
114
+ * Attach the wire client and begin polling (external daemon adopted). Idempotent:
115
+ * re-activating swaps the client without stacking timers.
116
+ */
117
+ activate(client: SpineInboundInputsClient): void {
118
+ this.client = client;
119
+ if (this.timer === null) {
120
+ this.timer = setInterval(() => { void this.pollOnce(); }, this.intervalMs);
121
+ this.timer.unref?.();
122
+ }
123
+ this.log.info('session inbound-input poller activated — collecting queued steers for this surface', {});
124
+ }
125
+
126
+ /** Detach the backend and stop polling (daemon mode resolved to non-external, or lost). */
127
+ deactivate(reason: string): void {
128
+ this.client = null;
129
+ if (this.timer !== null) {
130
+ clearInterval(this.timer);
131
+ this.timer = null;
132
+ }
133
+ this.log.info('session inbound-input poller deactivated', { reason });
134
+ }
135
+
136
+ /** Stop the timer (shutdown). */
137
+ dispose(): void {
138
+ if (this.timer !== null) {
139
+ clearInterval(this.timer);
140
+ this.timer = null;
141
+ }
142
+ }
143
+
144
+ /**
145
+ * One poll pass: collect queued steer/follow-up inputs for the current session,
146
+ * inject each, and acknowledge delivery. Returns the number of steers delivered.
147
+ * Best-effort: any wire error is absorbed and the pass is retried next tick.
148
+ */
149
+ async pollOnce(): Promise<number> {
150
+ const client = this.client;
151
+ if (!client || this.polling) return 0;
152
+ const sessionId = this.sessionId();
153
+ if (!sessionId) return 0;
154
+
155
+ // A session swap resets the cursor so we never skip the new session's backlog.
156
+ if (sessionId !== this.cursorSessionId) {
157
+ this.cursor = 0;
158
+ this.cursorSessionId = sessionId;
159
+ }
160
+
161
+ this.polling = true;
162
+ let delivered = 0;
163
+ try {
164
+ const result = await client.listInputs(sessionId, { state: 'queued', since: this.cursor });
165
+ const inputs = result?.inputs ?? [];
166
+ for (const input of inputs) {
167
+ // Advance the cursor across every queued input we observe (even skipped
168
+ // ones) so it is monotonic; deliver() is the real de-dup for injected ones.
169
+ if (input.createdAt > this.cursor) this.cursor = input.createdAt;
170
+
171
+ if (input.state !== 'queued') continue;
172
+ if (input.intent !== 'steer' && input.intent !== 'follow-up') continue;
173
+ // Never re-inject this surface's OWN submissions.
174
+ if (input.surfaceId === this.ownSurfaceId) continue;
175
+
176
+ const steer: InboundSteer = {
177
+ inputId: input.id,
178
+ sessionId,
179
+ intent: input.intent,
180
+ body: input.body,
181
+ surfaceKind: input.surfaceKind,
182
+ surfaceId: input.surfaceId,
183
+ displayName: input.displayName,
184
+ };
185
+ try {
186
+ this.onSteer(steer);
187
+ } catch (error) {
188
+ // A failing injection must not wedge the poll loop or drop the ack —
189
+ // narrate-and-continue; the input is still acked so it is not re-picked.
190
+ this.log.debug('session inbound steer injection threw — continuing', {
191
+ error: error instanceof Error ? error.message : String(error),
192
+ });
193
+ }
194
+ try {
195
+ // consumed:false → queued → delivered (surface has collected it).
196
+ await client.deliverInput(sessionId, input.id, { consumed: false });
197
+ delivered += 1;
198
+ } catch (error) {
199
+ this.log.debug('session inbound deliver ack failed — will retry', {
200
+ error: error instanceof Error ? error.message : String(error),
201
+ });
202
+ }
203
+ }
204
+ } catch (error) {
205
+ // Poll failed entirely (daemon gone, transient): keep the cursor, retry next tick.
206
+ this.log.debug('session inbound-input poll failed — retry next tick', {
207
+ error: error instanceof Error ? error.message : String(error),
208
+ });
209
+ } finally {
210
+ this.polling = false;
211
+ }
212
+ return delivered;
213
+ }
214
+ }
215
+
216
+ /** Format the operator-facing narration for an inbound steer, e.g.
217
+ * "steer received from webui (Alice): resize the panel". */
218
+ export function narrateInboundSteer(steer: InboundSteer): string {
219
+ const surface = steer.surfaceKind ?? steer.surfaceId ?? 'another surface';
220
+ const who = steer.displayName ? ` (${steer.displayName})` : '';
221
+ const verb = steer.intent === 'follow-up' ? 'follow-up received from' : 'steer received from';
222
+ return `${verb} ${surface}${who}: ${steer.body}`;
223
+ }
224
+
225
+ /**
226
+ * Build the standard bootstrap-time poller: sessionId tracks the live runtime
227
+ * session ref, and onSteer narrates the steer to the operator then injects it
228
+ * via the SAME path COMPANION_MESSAGE_RECEIVED uses (orchestrator.handleUserInput
229
+ * when wired, else a bare conversation append + render request as a fallback
230
+ * before the orchestrator ref is patched). Extracted from bootstrap-core.ts to
231
+ * keep this module (not the bootstrap god-function) the owner of the wiring.
232
+ */
233
+ export function createBootstrapInboundInputPoller(deps: {
234
+ readonly runtimeSessionIdRef: { readonly value: string };
235
+ readonly routeOrBuffer: (text: string, priority: 'low' | 'high') => void;
236
+ readonly orchestratorHandleUserInputRef: { readonly value: ((text: string) => void) | null };
237
+ readonly conversation: { readonly addUserMessage: (text: string) => void };
238
+ readonly requestRender: () => void;
239
+ }): SessionInboundInputPoller {
240
+ return new SessionInboundInputPoller({
241
+ sessionId: () => deps.runtimeSessionIdRef.value || null,
242
+ onSteer: (steer) => {
243
+ deps.routeOrBuffer(narrateInboundSteer(steer), 'low');
244
+ if (deps.orchestratorHandleUserInputRef.value) {
245
+ deps.orchestratorHandleUserInputRef.value(steer.body);
246
+ } else {
247
+ deps.conversation.addUserMessage(steer.body);
248
+ deps.requestRender();
249
+ }
250
+ },
251
+ });
252
+ }
@@ -0,0 +1,64 @@
1
+ /**
2
+ * session-spine-transport.ts
3
+ *
4
+ * W3-T1 (One-Platform Wave 3, TUI session-spine cutover): the thin typed-client
5
+ * adapter that lets the TUI's bootstrap drive the SDK's
6
+ * `@pellux/goodvibes-sdk/platform/runtime/session-spine` `SessionSpineClient`
7
+ * (extracted in W3-S4) via its injected `SpineTransport` seam.
8
+ *
9
+ * The TUI is SDK-clean here: `SpineSessionsClient` is exactly the narrow wire
10
+ * surface the old TUI-local `session-spine-client.ts` declared (structurally
11
+ * satisfied by `HttpTransport.operator.sessions`), and `createTuiSpineTransport`
12
+ * folds it into the SDK's `SpineTransport` the SAME way the SDK's own
13
+ * consumability proof (`session-spine-daemon-integration.test.ts`) does:
14
+ * resolve -> `'ok'`, throw -> `'offline'`. The TUI never emits `'rejected'` — it
15
+ * has no durable-refusal vocabulary to fold (that distinction is the agent's
16
+ * REST mirror's job, per the SDK's divergence ruling #6/#7).
17
+ */
18
+ import type {
19
+ RegisterSharedSessionInput,
20
+ SharedSessionRecord,
21
+ SharedSessionRegisterResult,
22
+ } from '@pellux/goodvibes-sdk/platform/control-plane';
23
+ import type { SpineResult, SpineTransport } from '@pellux/goodvibes-sdk/platform/runtime/session-spine';
24
+
25
+ /**
26
+ * The minimal wire surface this adapter needs. Structurally satisfied by BOTH
27
+ * the SDK's in-process `OperatorClient['sessions']` (DirectTransport) and the
28
+ * wire `HttpTransportOperatorClient['sessions']` (HttpTransport) — bootstrap.ts
29
+ * only ever constructs an Http-backed instance in practice, but this is
30
+ * deliberately typed against the narrow shape rather than either concrete
31
+ * transport type so it has no compile-time dependency on which transport
32
+ * backs it.
33
+ */
34
+ export interface SpineSessionsClient {
35
+ register(input: RegisterSharedSessionInput): Promise<SharedSessionRegisterResult>;
36
+ close(sessionId: string): Promise<SharedSessionRecord | null>;
37
+ }
38
+
39
+ /**
40
+ * Adapt the TUI's typed `SpineSessionsClient` into the SDK `SessionSpineClient`'s
41
+ * injected `SpineTransport`. A rejecting wire call is treated as a transient
42
+ * connectivity fault (`'offline'`) rather than a durable refusal — the same
43
+ * binary reachability model the pre-extraction TUI-local client used.
44
+ */
45
+ export function createTuiSpineTransport(sessionsClient: SpineSessionsClient): SpineTransport {
46
+ return {
47
+ register: async (input: RegisterSharedSessionInput): Promise<SpineResult> => {
48
+ try {
49
+ await sessionsClient.register(input);
50
+ return { outcome: 'ok' };
51
+ } catch (error) {
52
+ return { outcome: 'offline', error: error instanceof Error ? error.message : String(error) };
53
+ }
54
+ },
55
+ close: async (sessionId: string): Promise<SpineResult> => {
56
+ try {
57
+ await sessionsClient.close(sessionId);
58
+ return { outcome: 'ok' };
59
+ } catch (error) {
60
+ return { outcome: 'offline', error: error instanceof Error ? error.message : String(error) };
61
+ }
62
+ },
63
+ };
64
+ }
@@ -40,7 +40,14 @@ export type TuiTerminalOutputGuardOptions = {
40
40
  readonly stdout: WritableStreamLike;
41
41
  readonly stderr?: WritableStreamLike;
42
42
  readonly active?: boolean;
43
- readonly notify: (message: string) => void;
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
+ * (UX-B item 1a.)
49
+ */
50
+ readonly onCapture?: (total: number) => void;
44
51
  };
45
52
 
46
53
  const MAX_LOG_TEXT = 4_000;
@@ -208,21 +215,21 @@ export function installTerminalOutputGuard(options: TerminalOutputGuardOptions):
208
215
  }
209
216
 
210
217
  export function installTuiTerminalOutputGuard(options: TuiTerminalOutputGuardOptions): TerminalOutputGuard {
211
- let capturedWriteCount = 0;
218
+ let totalInterceptedWrites = 0;
212
219
  let lastNoticeAt = 0;
213
220
  return installTerminalOutputGuard({
214
221
  stdout: options.stdout,
215
222
  stderr: options.stderr,
216
223
  active: options.active,
217
- onIntercept: (event) => {
218
- capturedWriteCount++;
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. (UX-B item 1a.)
228
+ totalInterceptedWrites++;
219
229
  const now = Date.now();
220
230
  if (now - lastNoticeAt < 5_000) return;
221
- const count = capturedWriteCount;
222
- capturedWriteCount = 0;
223
231
  lastNoticeAt = now;
224
- const plural = count === 1 ? '' : 's';
225
- options.notify(`[Terminal] Captured ${count} direct ${event.source} write${plural} that would have corrupted the TUI: ${event.preview}`);
232
+ options.onCapture?.(totalInterceptedWrites);
226
233
  },
227
234
  });
228
235
  }