@pellux/goodvibes-tui 1.16.1 → 1.18.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 (257) hide show
  1. package/CHANGELOG.md +138 -9
  2. package/README.md +1 -1
  3. package/docs/foundation-artifacts/operator-contract.json +5490 -1806
  4. package/package.json +7 -7
  5. package/src/cli/completions/generate.ts +5 -4
  6. package/src/cli/config-overrides.ts +51 -11
  7. package/src/cli/doctor.ts +4 -5
  8. package/src/cli/entrypoint.ts +13 -8
  9. package/src/cli/help.ts +7 -6
  10. package/src/cli/launch-auto-update.ts +214 -0
  11. package/src/cli/management-commands.ts +82 -15
  12. package/src/cli/management.ts +0 -2
  13. package/src/cli/parser.ts +5 -0
  14. package/src/cli/status.ts +13 -0
  15. package/src/cli/surface-command.ts +5 -5
  16. package/src/cli/tui-startup.ts +60 -97
  17. package/src/cli/types.ts +2 -0
  18. package/src/config/index.ts +1 -1
  19. package/src/config/tui-extension-settings.ts +33 -0
  20. package/src/core/composer-state.ts +1 -1
  21. package/src/core/consolidation-receipt.ts +41 -0
  22. package/src/core/conversation-line-cache.ts +3 -3
  23. package/src/core/conversation-rendering.ts +18 -9
  24. package/src/core/conversation.ts +2 -2
  25. package/src/core/long-task-notifier.ts +7 -7
  26. package/src/core/memory-provenance.ts +67 -0
  27. package/src/core/pairing-devices.ts +49 -0
  28. package/src/core/pairing-handoff.ts +101 -0
  29. package/src/core/pairing-offers.ts +83 -0
  30. package/src/core/pairing-origin.ts +78 -0
  31. package/src/core/pairing-tailscale-gateway.ts +56 -0
  32. package/src/core/power-chip-source.ts +91 -0
  33. package/src/core/power-status.ts +95 -0
  34. package/src/core/stable-host.ts +171 -0
  35. package/src/core/stream-event-wiring.ts +29 -6
  36. package/src/core/system-message-noise.ts +1 -1
  37. package/src/core/system-message-router.ts +6 -6
  38. package/src/core/turn-budget-outcome.ts +60 -0
  39. package/src/core/turn-cancellation.ts +40 -1
  40. package/src/core/turn-event-wiring.ts +14 -8
  41. package/src/daemon/cli.ts +64 -58
  42. package/src/daemon/lifecycle.ts +41 -0
  43. package/src/daemon/service-commands.ts +5 -5
  44. package/src/export/cost-utils.ts +128 -6
  45. package/src/input/autocomplete.ts +2 -2
  46. package/src/input/command-registry.ts +55 -9
  47. package/src/input/commands/acp-runtime.ts +72 -0
  48. package/src/input/commands/ci-runtime.ts +5 -1
  49. package/src/input/commands/codebase-runtime.ts +6 -6
  50. package/src/input/commands/cost-runtime.ts +1 -1
  51. package/src/input/commands/devices-runtime.ts +109 -0
  52. package/src/input/commands/git-runtime.ts +28 -4
  53. package/src/input/commands/graph-runtime.ts +45 -0
  54. package/src/input/commands/health-runtime.ts +2 -2
  55. package/src/input/commands/intelligence-runtime.ts +1 -1
  56. package/src/input/commands/local-auth-runtime.ts +1 -1
  57. package/src/input/commands/local-provider-runtime.ts +49 -10
  58. package/src/input/commands/local-runtime.ts +53 -5
  59. package/src/input/commands/memory.ts +2 -2
  60. package/src/input/commands/operator-panel-runtime.ts +5 -5
  61. package/src/input/commands/operator-rpc.ts +32 -11
  62. package/src/input/commands/permissions-provenance.ts +1 -1
  63. package/src/input/commands/permissions-runtime.ts +99 -8
  64. package/src/input/commands/planning-runtime.ts +3 -3
  65. package/src/input/commands/platform-sandbox-runtime.ts +1 -1
  66. package/src/input/commands/power-runtime.ts +56 -0
  67. package/src/input/commands/provider-accounts-runtime.ts +71 -6
  68. package/src/input/commands/provider.ts +7 -5
  69. package/src/input/commands/qrcode-runtime.ts +14 -19
  70. package/src/input/commands/queue-runtime.ts +70 -0
  71. package/src/input/commands/recall-files-git.ts +13 -51
  72. package/src/input/commands/recall-files.ts +8 -11
  73. package/src/input/commands/recall-review.ts +2 -2
  74. package/src/input/commands/relay-runtime.ts +1 -1
  75. package/src/input/commands/remote-runtime.ts +1 -1
  76. package/src/input/commands/runtime-services.ts +4 -4
  77. package/src/input/commands/schedule-runtime.ts +14 -7
  78. package/src/input/commands/services-runtime.ts +1 -1
  79. package/src/input/commands/session-workflow.ts +2 -2
  80. package/src/input/commands/settings-sync-runtime.ts +2 -2
  81. package/src/input/commands/share-runtime.ts +1 -1
  82. package/src/input/commands/shell-core.ts +46 -68
  83. package/src/input/commands/subscription-runtime.ts +1 -1
  84. package/src/input/commands/update-runtime.ts +153 -96
  85. package/src/input/commands/work-plan-runtime.ts +1 -1
  86. package/src/input/commands/workstream-attempts.ts +6 -1
  87. package/src/input/commands/workstream-runtime.ts +46 -27
  88. package/src/input/commands/worktree-runtime.ts +35 -8
  89. package/src/input/commands.ts +10 -2
  90. package/src/input/config-modal-types.ts +14 -3
  91. package/src/input/config-modal.ts +75 -13
  92. package/src/input/feature-unit-layout.ts +93 -82
  93. package/src/input/feed-context-factory.ts +2 -2
  94. package/src/input/handler-command-route.ts +2 -2
  95. package/src/input/handler-content-actions.ts +1 -1
  96. package/src/input/handler-feed-routes.ts +7 -7
  97. package/src/input/handler-feed.ts +4 -4
  98. package/src/input/handler-interactions.ts +3 -2
  99. package/src/input/handler-modal-routes.ts +12 -4
  100. package/src/input/handler-modal-token-routes.ts +1 -1
  101. package/src/input/handler-onboarding-daemon-adopt.ts +2 -2
  102. package/src/input/handler-onboarding.ts +3 -2
  103. package/src/input/handler-picker-routes.ts +26 -13
  104. package/src/input/handler-shortcuts.ts +29 -6
  105. package/src/input/handler.ts +2 -2
  106. package/src/input/keybindings.ts +21 -3
  107. package/src/input/onboarding/onboarding-feature-units.ts +34 -27
  108. package/src/input/onboarding/onboarding-provider-key-fields.ts +112 -0
  109. package/src/input/onboarding/onboarding-wizard-apply.ts +22 -21
  110. package/src/input/onboarding/onboarding-wizard-external-surfaces.ts +17 -0
  111. package/src/input/onboarding/onboarding-wizard-network-adopt.ts +2 -2
  112. package/src/input/onboarding/onboarding-wizard-steps.ts +30 -42
  113. package/src/input/panel-integration-actions.ts +2 -2
  114. package/src/input/panel-mouse-geometry.ts +1 -1
  115. package/src/input/panel-paste-flood-guard.ts +1 -1
  116. package/src/input/provider-key-intake.ts +167 -0
  117. package/src/input/sandbox-exec-config.ts +2 -2
  118. package/src/input/selection-modal.ts +1 -1
  119. package/src/input/settings-modal-activation.ts +5 -0
  120. package/src/input/settings-modal-adjustment.ts +4 -31
  121. package/src/input/settings-modal-data.ts +52 -57
  122. package/src/input/settings-modal-mutations.ts +12 -68
  123. package/src/input/settings-modal-types.ts +27 -17
  124. package/src/input/settings-modal.ts +74 -55
  125. package/src/main.ts +90 -90
  126. package/src/panels/agent-inspector-shared.ts +4 -4
  127. package/src/panels/base-panel.ts +2 -2
  128. package/src/panels/builtin/agent.ts +4 -4
  129. package/src/panels/builtin/development.ts +4 -4
  130. package/src/panels/builtin/knowledge.ts +1 -1
  131. package/src/panels/builtin/operations.ts +83 -11
  132. package/src/panels/builtin/session.ts +5 -5
  133. package/src/panels/builtin/shared.ts +16 -4
  134. package/src/panels/builtin-modals.ts +59 -42
  135. package/src/panels/builtin-panels.ts +1 -1
  136. package/src/panels/cost-tracker-panel.ts +124 -7
  137. package/src/panels/diff-panel.ts +39 -6
  138. package/src/panels/eval-registry.ts +2 -2
  139. package/src/panels/fleet-acts.ts +446 -0
  140. package/src/panels/fleet-deep-link.ts +1 -1
  141. package/src/panels/fleet-gateway.ts +117 -0
  142. package/src/panels/fleet-observed-render.ts +128 -0
  143. package/src/panels/fleet-panel-format.ts +163 -11
  144. package/src/panels/fleet-panel-worktree-detail.ts +14 -0
  145. package/src/panels/fleet-panel.ts +73 -57
  146. package/src/panels/fleet-read-model.ts +84 -14
  147. package/src/panels/fleet-spawn.ts +266 -0
  148. package/src/panels/fleet-steer.ts +2 -2
  149. package/src/panels/fleet-stop.ts +41 -5
  150. package/src/panels/fleet-tabs.ts +15 -16
  151. package/src/panels/fleet-transcript.ts +5 -5
  152. package/src/panels/git-panel.ts +2 -2
  153. package/src/panels/index.ts +4 -1
  154. package/src/panels/local-auth-panel.ts +1 -1
  155. package/src/panels/memory-consolidation-gateway.ts +94 -0
  156. package/src/panels/modals/devices-modal.ts +161 -0
  157. package/src/panels/modals/hooks-modal.ts +1 -1
  158. package/src/panels/modals/keybindings-modal.ts +2 -2
  159. package/src/panels/modals/knowledge-modal.ts +1 -1
  160. package/src/panels/modals/marketplace-modal.ts +4 -4
  161. package/src/panels/modals/memory-modal.ts +153 -9
  162. package/src/panels/modals/modal-surface-helpers.ts +1 -1
  163. package/src/panels/modals/modal-theme.ts +1 -1
  164. package/src/panels/modals/pairing-modal.ts +198 -68
  165. package/src/panels/modals/planning-modal.ts +2 -2
  166. package/src/panels/modals/plugins-modal.ts +1 -1
  167. package/src/panels/modals/policy-modal.ts +1 -1
  168. package/src/panels/modals/security-modal.ts +7 -1
  169. package/src/panels/modals/skills-modal.ts +1 -1
  170. package/src/panels/modals/work-plan-modal.ts +3 -3
  171. package/src/panels/notifications-feed.ts +127 -0
  172. package/src/panels/notifications-panel.ts +160 -0
  173. package/src/panels/panel-manager.ts +9 -9
  174. package/src/panels/plugins-panel.ts +4 -4
  175. package/src/panels/project-planning-answer-actions.ts +1 -1
  176. package/src/panels/provider-health-routes.ts +2 -2
  177. package/src/panels/skills-panel.ts +4 -5
  178. package/src/panels/token-budget-panel.ts +11 -3
  179. package/src/panels/types.ts +5 -5
  180. package/src/panels/workstream-graph-render.ts +123 -0
  181. package/src/permissions/broker-approval-card.ts +201 -0
  182. package/src/permissions/hunk-selection.ts +28 -4
  183. package/src/permissions/prompt.ts +244 -29
  184. package/src/renderer/autocomplete-overlay.ts +107 -54
  185. package/src/renderer/code-block.ts +2 -2
  186. package/src/renderer/compaction-history-modal.ts +1 -1
  187. package/src/renderer/compaction-preview.ts +1 -1
  188. package/src/renderer/compaction-quality.ts +1 -1
  189. package/src/renderer/composer-fragments.ts +69 -0
  190. package/src/renderer/compositor.ts +2 -2
  191. package/src/renderer/config-modal.ts +1 -1
  192. package/src/renderer/conversation-overlays.ts +1 -1
  193. package/src/renderer/footer-tips.ts +3 -3
  194. package/src/renderer/hint-grammar.ts +1 -1
  195. package/src/renderer/modal-factory.ts +1 -1
  196. package/src/renderer/model-picker-overlay.ts +1 -1
  197. package/src/renderer/model-workspace.ts +7 -2
  198. package/src/renderer/panel-composite.ts +2 -2
  199. package/src/renderer/panel-workspace-bar.ts +1 -1
  200. package/src/renderer/process-indicator.ts +1 -1
  201. package/src/renderer/selection-modal-overlay.ts +1 -1
  202. package/src/renderer/session-picker-modal.ts +2 -2
  203. package/src/renderer/settings-modal-helpers.ts +23 -5
  204. package/src/renderer/settings-modal.ts +129 -39
  205. package/src/renderer/shell-surface.ts +6 -0
  206. package/src/renderer/terminal-bg-probe.ts +1 -1
  207. package/src/renderer/theme.ts +3 -3
  208. package/src/renderer/tool-call.ts +13 -7
  209. package/src/renderer/tool-result-summary.ts +1 -1
  210. package/src/renderer/turn-injection.ts +7 -6
  211. package/src/renderer/ui-factory.ts +38 -1
  212. package/src/runtime/bootstrap-command-context.ts +7 -2
  213. package/src/runtime/bootstrap-command-parts.ts +9 -5
  214. package/src/runtime/bootstrap-core.ts +41 -41
  215. package/src/runtime/bootstrap-hook-bridge.ts +1 -1
  216. package/src/runtime/bootstrap-shell.ts +13 -5
  217. package/src/runtime/bootstrap.ts +34 -32
  218. package/src/runtime/channel-composition.ts +46 -0
  219. package/src/runtime/code-index-services.ts +3 -4
  220. package/src/runtime/daemon-attach-notices.ts +120 -0
  221. package/src/runtime/durability-services.ts +90 -0
  222. package/src/runtime/embedded-service-factories.ts +40 -0
  223. package/src/runtime/feature-settings.ts +147 -0
  224. package/src/runtime/fleet-services.ts +41 -0
  225. package/src/runtime/idle-power-services.ts +85 -0
  226. package/src/runtime/index.ts +6 -21
  227. package/src/runtime/interaction-seams.ts +109 -0
  228. package/src/runtime/legacy-daemon-migration.ts +29 -19
  229. package/src/runtime/memory-fold.ts +1 -1
  230. package/src/runtime/notification-dispatch.ts +110 -0
  231. package/src/runtime/onboarding/apply.ts +3 -29
  232. package/src/runtime/onboarding/fast-path.ts +39 -6
  233. package/src/runtime/onboarding/provider-key-capture.ts +126 -0
  234. package/src/runtime/onboarding/snapshot.ts +0 -1
  235. package/src/runtime/onboarding/types.ts +9 -3
  236. package/src/runtime/power-keepawake-remote.ts +75 -0
  237. package/src/runtime/process-lifecycle.ts +3 -1
  238. package/src/runtime/provider-fallback.ts +55 -0
  239. package/src/runtime/provider-optimizer-wiring.ts +55 -0
  240. package/src/runtime/release-artifacts.ts +19 -103
  241. package/src/runtime/resume-notice.ts +1 -1
  242. package/src/runtime/services.ts +113 -113
  243. package/src/runtime/session-continuity-hints.ts +38 -0
  244. package/src/runtime/surface-feature-flags.ts +40 -9
  245. package/src/runtime/terminal-output-guard.ts +2 -2
  246. package/src/runtime/trust/workspace-registration.ts +10 -2
  247. package/src/runtime/trust/workspace-trust.ts +58 -31
  248. package/src/runtime/ui-services.ts +6 -1
  249. package/src/runtime/update-check.ts +15 -69
  250. package/src/runtime/workstream-services.ts +2 -2
  251. package/src/shell/blocking-input.ts +108 -132
  252. package/src/shell/recovery-input-helpers.ts +53 -166
  253. package/src/shell/ui-openers.ts +38 -6
  254. package/src/utils/format-duration.ts +1 -1
  255. package/src/verification/verification-ledger.ts +21 -4
  256. package/src/version.ts +1 -1
  257. package/src/input/commands/flags-runtime.ts +0 -328
@@ -2,11 +2,15 @@ import { join } from 'node:path';
2
2
  import { FocusTracker } from '../core/focus-tracker.ts';
3
3
  import { ConfigManager, ServiceRegistry, SubscriptionManager, ToolLLM } from '@pellux/goodvibes-sdk/platform/config';
4
4
  import { SecretsManager } from '../config/secrets.ts';
5
- import { AutomationDeliveryManager, AutomationManager, AutomationRouteStore } from '@pellux/goodvibes-sdk/platform/automation';
6
- import { ChannelDeliveryRouter, ChannelPluginRegistry, ChannelPolicyManager, RouteBindingManager, SurfaceRegistry } from '@pellux/goodvibes-sdk/platform/channels';
7
- import { ApprovalBroker, GatewayMethodCatalog, SharedSessionBroker } from '@pellux/goodvibes-sdk/platform/control-plane';
5
+ import { AutomationDeliveryManager, AutomationManager } from '@pellux/goodvibes-sdk/platform/automation';
6
+ import { ChannelDeliveryRouter, ChannelPolicyManager, type ChannelPluginRegistry, type RouteBindingManager, type SurfaceRegistry } from '@pellux/goodvibes-sdk/platform/channels';
7
+ import { ApprovalBroker, GatewayMethodCatalog, SessionLiveTurnControlsHolder, SharedSessionBroker } from '@pellux/goodvibes-sdk/platform/control-plane';
8
+ import type { PowerManager } from '@pellux/goodvibes-sdk/platform/power';
9
+ import { wireIdlePowerAndLiveTurn } from './idle-power-services.ts';
8
10
  import { StepUpService } from '@pellux/goodvibes-sdk/daemon';
9
- import { attachWsOnlyGatewayVerbHandlers, createArchivableFleetRegistry } from '@pellux/goodvibes-terminal-shell';
11
+ import { PairingTokenManager } from '@pellux/goodvibes-sdk/platform/pairing';
12
+ import { resolvePairingWebOrigin } from '../core/pairing-origin.ts';
13
+ import { attachWsOnlyGatewayVerbHandlers } from '@pellux/goodvibes-terminal-shell';
10
14
  import { WatcherRegistry } from '@pellux/goodvibes-sdk/platform/watchers';
11
15
  import { ArtifactStore } from '@pellux/goodvibes-sdk/platform/artifacts';
12
16
  import {
@@ -25,7 +29,13 @@ import { MediaProviderRegistry, ensureBuiltinMediaProviders } from '@pellux/good
25
29
  import { MultimodalService } from '@pellux/goodvibes-sdk/platform/multimodal';
26
30
  import { AgentMessageBus, AgentOrchestrator, ArchetypeLoader, WrfcController } from '@pellux/goodvibes-sdk/platform/agents';
27
31
  import { AgentManager, ContextAccountingHolder, OverflowHandler, ProcessManager, createWorkflowServices, type WorkflowServices } from '@pellux/goodvibes-sdk/platform/tools';
28
- import { FileStateCache, FileUndoManager, MemoryEmbeddingProviderRegistry, MemoryRegistry, MemoryStore, ModeManager, ProjectIndex, resolveCanonicalMemoryDbPath, type CodeIndexStore, type CodeIndexReindexScheduler } from '@pellux/goodvibes-sdk/platform/state';
32
+ import { FileStateCache, FileUndoManager, MemoryConsolidationScheduler, MemoryEmbeddingProviderRegistry, MemoryRegistry, MemoryStore, ModeManager, ProjectIndex, resolveCanonicalMemoryDbPath, type CodeIndexStore, type CodeIndexReindexScheduler } from '@pellux/goodvibes-sdk/platform/state';
33
+ import type { StoreSnapshotScheduler } from '@pellux/goodvibes-sdk/platform/state/store-snapshots';
34
+ import type { UserPermissionRuleStore } from '@pellux/goodvibes-sdk/platform/permissions';
35
+ import { buildExecPromptAnswerHandler } from '@pellux/goodvibes-sdk/platform/runtime/permissions/exec-prompt-wiring';
36
+ import { buildLocalhostFetchApproval } from '@pellux/goodvibes-sdk/platform/runtime/permissions/localhost-fetch-approval';
37
+ import { createNotificationDispatcher, wireRuntimeNotificationBridge, type NotificationDispatcher } from './notification-dispatch.ts';
38
+ import { createDurabilityServices } from './durability-services.ts';
29
39
  import { MemorySpineClient, createLocalMemoryAccess } from '@pellux/goodvibes-sdk/platform/runtime/memory-spine';
30
40
  import { WorkspaceCheckpointManager } from '@pellux/goodvibes-sdk/platform/workspace';
31
41
  import { createWorkspaceCheckpointing } from './workspace-checkpointing.ts';
@@ -49,20 +59,19 @@ import { SessionManager, CrossSessionTaskRegistry, SessionChangeTracker } from '
49
59
  import { ApiTokenAuditor, UserAuthManager } from '@pellux/goodvibes-sdk/platform/security';
50
60
  import { WebhookNotifier } from '@pellux/goodvibes-sdk/platform/integrations';
51
61
  import { McpRegistry } from '@pellux/goodvibes-sdk/platform/mcp';
52
- import { BenchmarkStore, CacheHitTracker, FavoritesStore, inferFallbackContextWindow, type ModelDefinition, ModelLimitsService, ProviderCapabilityRegistry, ProviderOptimizer, ProviderRegistry } from '@pellux/goodvibes-sdk/platform/providers';
62
+ import { BenchmarkStore, CacheHitTracker, FavoritesStore, ModelLimitsService, ProviderCapabilityRegistry, ProviderOptimizer, ProviderRegistry } from '@pellux/goodvibes-sdk/platform/providers';
53
63
  import { KeybindingsManager } from '../input/keybindings.ts';
54
64
  import { AdaptivePlanner, DeterministicReplayEngine, ExecutionPlanManager, SessionLineageTracker, SessionMemoryStore } from '@pellux/goodvibes-sdk/platform/core';
55
- import { isFeatureFlagEnabled } from './surface-feature-flags.ts';
65
+ import { deriveFeatureStates, bindFeatureSettingsBridge } from '@pellux/goodvibes-sdk/platform/runtime/state';
66
+ import { createChannelComposition } from './channel-composition.ts';
67
+ import { applyProviderOptimizerConfigMode, bindProviderOptimizerFeatureFlag } from './provider-optimizer-wiring.ts';
56
68
  import { type ArchivableProcessRegistry } from '@pellux/goodvibes-sdk/platform/runtime/fleet';
57
- import { calcSessionCost, isModelPriced } from '../export/cost-utils.ts';
69
+ import { createFleetServices } from './fleet-services.ts';
58
70
  import { createWorkstreamServices, type OrchestrationEngine, type WorkstreamCommandService } from './workstream-services.ts';
59
71
  import { wireFleetNeedsInputPush } from './fleet-needs-input-push.ts';
60
72
  import { codeIndexDbPath, createCodeIndexServices, isCodeInjectionSettingEnabled } from './code-index-services.ts';
61
73
  import { WorkPlanStore } from '../work-plans/work-plan-store.ts';
62
- import {
63
- registerDaemonHandlers,
64
- type DaemonHandlerSurfaces,
65
- } from '../daemon/handlers/index.ts';
74
+ import { registerDaemonHandlers, type DaemonHandlerSurfaces } from '../daemon/handlers/index.ts';
66
75
  import type { HandlerContext, HandlerLogger } from '../daemon/handlers/context.ts';
67
76
  import { createDaemonCredentialStore } from '../daemon/handlers/credentials.ts';
68
77
  import { registerRouting } from '../daemon/handlers/routing/index.ts';
@@ -73,58 +82,10 @@ import { registerCalendar } from '../daemon/handlers/calendar/index.ts';
73
82
  import { registerEmailMethods } from '../daemon/handlers/email/index.ts';
74
83
  import { registerRemoteSurface } from '../daemon/handlers/remote/index.ts';
75
84
  import { WorkspaceTrustManager } from './trust/workspace-trust.ts';
85
+ import { ensureConfiguredModelIsRoutable } from './provider-fallback.ts';
76
86
 
77
87
  const REGULAR_KNOWLEDGE_DB_FILE = 'knowledge-wiki.sqlite';
78
88
  const HOME_GRAPH_KNOWLEDGE_DB_FILE = 'knowledge-home-graph.sqlite';
79
-
80
- function buildFallbackModelDefinition(provider: string, modelId: string): ModelDefinition {
81
- const providerLower = provider.toLowerCase();
82
- const isReasoningProvider = providerLower.includes('openai')
83
- || providerLower.includes('anthropic')
84
- || providerLower.includes('gemini')
85
- || providerLower.includes('google');
86
-
87
- return {
88
- id: modelId,
89
- provider,
90
- registryKey: `${provider}:${modelId}`,
91
- displayName: modelId,
92
- description: 'Configured model available before the model catalog cache has loaded.',
93
- capabilities: {
94
- toolCalling: true,
95
- codeEditing: true,
96
- reasoning: isReasoningProvider,
97
- multimodal: isReasoningProvider,
98
- },
99
- // Pre-catalog fallback uses the SDK's family-aware inference (SDK 0.35.0+),
100
- // matching the post-catalog window so the meter/compaction denominator agrees.
101
- contextWindow: inferFallbackContextWindow(provider, modelId),
102
- contextWindowProvenance: 'fallback',
103
- selectable: true,
104
- tier: 'standard',
105
- ...(isReasoningProvider ? { reasoningEffort: ['instant', 'low', 'medium', 'high'] } : {}),
106
- };
107
- }
108
-
109
- function ensureConfiguredModelIsRoutable(providerRegistry: ProviderRegistry, configManager: ConfigManager): void {
110
- const configuredModel = String(configManager.get('provider.model') ?? '').trim();
111
- if (!configuredModel.includes(':')) return;
112
- if (providerRegistry.listModels().some((model) => model.registryKey === configuredModel)) return;
113
-
114
- const [providerId, ...modelParts] = configuredModel.split(':');
115
- const modelId = modelParts.join(':').trim();
116
- if (!providerId || !modelId) return;
117
-
118
- const provider = providerRegistry.tryGet(providerId);
119
- if (!provider) return;
120
-
121
- providerRegistry.registerRuntimeProvider({
122
- provider,
123
- replace: true,
124
- models: [buildFallbackModelDefinition(providerId, modelId)],
125
- });
126
- }
127
-
128
89
  export interface RuntimeServicesOptions {
129
90
  readonly runtimeBus: RuntimeEventBus;
130
91
  readonly runtimeStore: RuntimeStore;
@@ -134,8 +95,13 @@ export interface RuntimeServicesOptions {
134
95
  readonly getConversationTitle?: () => string | undefined;
135
96
  readonly workingDir: string;
136
97
  readonly homeDirectory: string;
137
- /** Prior GoodVibes runtime state existed at startup — sampled before any write; seeds the trust gate's grandfathering. */
138
- readonly workspaceHadPriorState?: boolean;
98
+ /** Opt-in (daemon-side only): fold host-observed external coding-agent sessions
99
+ * into the fleet as 'observed-external' rows. Interactive leaves it off and reads
100
+ * the daemon snapshot. Mirrors the SDK's own createRuntimeServices option. */
101
+ readonly observeExternalAgents?: boolean | undefined;
102
+ /** Host power seam opt-in. Fork mirrors the SDK: non-spawning unavailable-seam
103
+ * default (idle-power-services.ts); daemon + embedded runtime pass createHostPowerSeam(). */
104
+ readonly powerSeam?: Parameters<typeof wireIdlePowerAndLiveTurn>[0]['powerSeam'];
139
105
  }
140
106
 
141
107
  export interface RuntimeServices {
@@ -155,6 +121,14 @@ export interface RuntimeServices {
155
121
  readonly channelDeliveryRouter: ChannelDeliveryRouter;
156
122
  readonly watcherRegistry: WatcherRegistry;
157
123
  readonly approvalBroker: ApprovalBroker;
124
+ /** Loopback-fetch approval that rides the approval broker; shared by the tool registry and orchestrator so every surface asks the same way. */
125
+ readonly localhostFetchApproval: ReturnType<typeof buildLocalhostFetchApproval>;
126
+ /** Terminal prompt-answer handler that rides the approval broker; shared by the tool registry and orchestrator so an interactive command's prompt gets an ask/card on every surface. */
127
+ readonly execPromptAnswerHandler: ReturnType<typeof buildExecPromptAnswerHandler>;
128
+ /** Routes curated runtime-domain events into the panel_only notification feed (the panel's live producer). */
129
+ readonly notificationDispatcher: NotificationDispatcher;
130
+ /** Durable user-origin permission rules (remembered approvals); permissions.rules.* surface. Mirrors the SDK composition. */
131
+ readonly userPermissionRuleStore: UserPermissionRuleStore;
158
132
  readonly sessionBroker: SharedSessionBroker;
159
133
  readonly deliveryManager: AutomationDeliveryManager;
160
134
  readonly automationManager: AutomationManager;
@@ -173,6 +147,7 @@ export interface RuntimeServices {
173
147
  readonly serviceRegistry: ServiceRegistry;
174
148
  readonly secretsManager: SecretsManager;
175
149
  readonly stepUpService: StepUpService;
150
+ readonly pairingTokens: PairingTokenManager; // backs pairing.tokens.* verbs + the settings device surface (mirrors the SDK composition)
176
151
  readonly subscriptionManager: SubscriptionManager;
177
152
  readonly localUserAuthManager: UserAuthManager;
178
153
  readonly profileManager: ProfileManager;
@@ -198,7 +173,7 @@ export interface RuntimeServices {
198
173
  readonly worktreeRegistry: WorktreeRegistry;
199
174
  readonly sandboxSessionRegistry: SandboxSessionRegistry;
200
175
  readonly webhookNotifier: WebhookNotifier;
201
- /** Terminal focus tracker (W2.3) — fed by input/handler-feed.ts, read by the alert notifiers in core/. */
176
+ /** Terminal focus tracker — fed by input/handler-feed.ts, read by the alert notifiers in core/. */
202
177
  readonly focusTracker: FocusTracker;
203
178
  readonly replayEngine: DeterministicReplayEngine;
204
179
  readonly providerOptimizer: ProviderOptimizer;
@@ -234,6 +209,11 @@ export interface RuntimeServices {
234
209
  /** The repo source-tree code index — see runtime/code-index-services.ts. */
235
210
  readonly codeIndexStore: CodeIndexStore;
236
211
  readonly codeIndexReindexScheduler: CodeIndexReindexScheduler; // tool-site reindex
212
+ /** Daily snapshots of every SQLite store this runtime writes, with bounded retention; unref'd timers (mirrors the SDK composition — hosts that tear down a runtime stop() it themselves). */
213
+ readonly storeSnapshotScheduler: StoreSnapshotScheduler;
214
+ readonly memoryConsolidationScheduler: MemoryConsolidationScheduler;
215
+ readonly powerManager: PowerManager;
216
+ readonly sessionLiveTurnControls: SessionLiveTurnControlsHolder;
237
217
  /** Unified live process registry (agents, WRFC chains, workflows, watchers, background processes) backing the Fleet panel; archive-aware — finished subtrees can be moved to the session archive view. */
238
218
  readonly processRegistry: ArchivableProcessRegistry;
239
219
  readonly modeManager: ModeManager;
@@ -253,26 +233,31 @@ export function createRuntimeServices(options: RuntimeServicesOptions): RuntimeS
253
233
  workingDirectory,
254
234
  homeDirectory,
255
235
  });
256
- const workspaceTrustManager = new WorkspaceTrustManager({ shellPaths, hadPriorState: options.workspaceHadPriorState ?? false });
236
+ const workspaceTrustManager = new WorkspaceTrustManager({ shellPaths });
257
237
  const configManager = options.configManager;
258
238
  const featureFlags = options.featureFlags ?? createFeatureFlagManager();
239
+ if (options.featureFlags === undefined) {
240
+ // Owned manager: gate states derive from domain settings keys + live
241
+ // bridge (mirrors the SDK composition root; a passed manager is the caller's to wire).
242
+ featureFlags.loadFromConfig({ flags: deriveFeatureStates(configManager) });
243
+ bindFeatureSettingsBridge(configManager, featureFlags);
244
+ }
259
245
  const runtimeDispatch = createDomainDispatch(options.runtimeStore);
260
246
  const gatewayMethods = new GatewayMethodCatalog();
261
247
  const panelManager = new PanelManager();
262
- // W6.1 (the purge): MIGRATE-TO-MODAL surface + redirect registration moved to
248
+ // (the purge): MIGRATE-TO-MODAL surface + redirect registration moved to
263
249
  // registerBuiltinPanels (builtin-panels.ts), where the panels' resolved deps
264
250
  // are available for the surfaces to close over.
265
251
  const keybindingsManager = new KeybindingsManager({
266
252
  configPath: shellPaths.resolveUserPath('tui', 'keybindings.json'),
267
253
  });
268
- const routeBindings = new RouteBindingManager({
269
- store: new AutomationRouteStore({ configManager }),
254
+ // Channel/surface wiring: see channel-composition.ts (incl. the recorded surface-gating divergence note).
255
+ const { routeBindings, surfaceRegistry, channelPlugins } = createChannelComposition({
256
+ configManager,
270
257
  runtimeStore: options.runtimeStore,
271
258
  runtimeBus: options.runtimeBus,
259
+ featureFlags,
272
260
  });
273
- const surfaceRegistry = new SurfaceRegistry(configManager, options.runtimeStore);
274
- const channelPlugins = new ChannelPluginRegistry();
275
- surfaceRegistry.attachPluginRegistry(channelPlugins);
276
261
  const secretsManager = new SecretsManager({
277
262
  projectRoot: workingDirectory,
278
263
  globalHome: homeDirectory,
@@ -280,6 +265,7 @@ export function createRuntimeServices(options: RuntimeServicesOptions): RuntimeS
280
265
  });
281
266
  // Step-up (WebAuthn) ceremony service, shared between the ceremony gateway verbs and the relay gate's verifier.
282
267
  const stepUpService = new StepUpService({ secrets: secretsManager });
268
+ const pairingTokens = new PairingTokenManager(shellPaths.resolveUserPath('control-plane', 'pairing-tokens.json'));
283
269
  const subscriptionManager = new SubscriptionManager(shellPaths.resolveUserPath('tui', 'subscriptions.json'));
284
270
  const serviceRegistry = new ServiceRegistry(shellPaths.resolveProjectPath('tui', 'services.json'), {
285
271
  secretsManager,
@@ -307,6 +293,10 @@ export function createRuntimeServices(options: RuntimeServicesOptions): RuntimeS
307
293
  });
308
294
  ensureConfiguredModelIsRoutable(providerRegistry, configManager);
309
295
  providerRegistry.initCustomProviders();
296
+ // Kick off the background, TTL-respecting live model discovery sweep so
297
+ // provider model lists refresh from their own listing APIs — matching the
298
+ // SDK and agent compositions (this call was the one omitted here).
299
+ providerRegistry.initProviderModelDiscovery();
310
300
  const toolLLM = new ToolLLM({
311
301
  configManager,
312
302
  providerRegistry,
@@ -341,6 +331,9 @@ export function createRuntimeServices(options: RuntimeServicesOptions): RuntimeS
341
331
  messageBus: agentMessageBus,
342
332
  executor: agentOrchestrator,
343
333
  configManager,
334
+ // The live registry lets a bare model id in a spawn() override resolve
335
+ // through the shared resolver instead of being rejected as unqualified.
336
+ providerRegistry,
344
337
  });
345
338
  const contextAccountingHolder = new ContextAccountingHolder();
346
339
  agentOrchestrator.setConversationSink({ // Conversation-snapshot bridge (mirrors the SDK's own createRuntimeServices)
@@ -418,6 +411,9 @@ export function createRuntimeServices(options: RuntimeServicesOptions): RuntimeS
418
411
  runtimeStore: options.runtimeStore,
419
412
  runtimeBus: options.runtimeBus,
420
413
  deliveryManager,
414
+ // Same live registry: a bare model id on an automation job resolves through
415
+ // the shared resolver instead of a format-only rejection.
416
+ providerRegistry,
421
417
  spawnTask: (input) => {
422
418
  const record = agentManager.spawn({
423
419
  mode: 'spawn',
@@ -489,7 +485,7 @@ export function createRuntimeServices(options: RuntimeServicesOptions): RuntimeS
489
485
  projectRoot: workingDirectory,
490
486
  });
491
487
  const voiceProviders = new VoiceProviderRegistry();
492
- ensureBuiltinVoiceProviders(voiceProviders);
488
+ ensureBuiltinVoiceProviders(voiceProviders, { readConfig: (key) => configManager.get(key as Parameters<typeof configManager.get>[0]) });
493
489
  const voiceService = new VoiceService(voiceProviders);
494
490
  const webSearchProviders = new WebSearchProviderRegistry({
495
491
  env: process.env,
@@ -499,18 +495,9 @@ export function createRuntimeServices(options: RuntimeServicesOptions): RuntimeS
499
495
  serviceRegistry,
500
496
  featureFlags,
501
497
  });
502
- knowledgeSemanticService.setGapRepairer(createWebKnowledgeGapRepairer({
503
- searchService: webSearchService,
504
- ingestService: knowledgeService,
505
- }));
506
- agentKnowledgeSemanticService.setGapRepairer(createWebKnowledgeGapRepairer({
507
- searchService: webSearchService,
508
- ingestService: agentKnowledgeService,
509
- }));
510
- homeGraphSemanticService.setGapRepairer(createWebKnowledgeGapRepairer({
511
- searchService: webSearchService,
512
- ingestService: homeGraphService,
513
- }));
498
+ for (const [semantic, ingest] of [[knowledgeSemanticService, knowledgeService], [agentKnowledgeSemanticService, agentKnowledgeService], [homeGraphSemanticService, homeGraphService]] as const) {
499
+ semantic.setGapRepairer(createWebKnowledgeGapRepairer({ searchService: webSearchService, ingestService: ingest }));
500
+ }
514
501
  const mediaProviders = new MediaProviderRegistry();
515
502
  ensureBuiltinMediaProviders(mediaProviders, artifactStore, providerRegistry);
516
503
  const multimodalService = new MultimodalService(artifactStore, mediaProviders, voiceService, knowledgeService);
@@ -578,11 +565,9 @@ export function createRuntimeServices(options: RuntimeServicesOptions): RuntimeS
578
565
  const webhookNotifier = new WebhookNotifier();
579
566
  const focusTracker = new FocusTracker();
580
567
  const replayEngine = new DeterministicReplayEngine(workingDirectory);
581
- const providerOptimizer = new ProviderOptimizer(
582
- providerRegistry,
583
- providerCapabilityRegistry,
584
- isFeatureFlagEnabled(configManager, 'provider-optimizer'),
585
- );
568
+ const providerOptimizer = new ProviderOptimizer(providerRegistry, providerCapabilityRegistry, false); // dark until its gate flips it (see provider-optimizer-wiring.ts)
569
+ bindProviderOptimizerFeatureFlag(featureFlags, providerOptimizer);
570
+ applyProviderOptimizerConfigMode(configManager, providerOptimizer);
586
571
  const sessionMemoryStore = new SessionMemoryStore();
587
572
  const sessionLineageTracker = new SessionLineageTracker();
588
573
  const sessionChangeTracker = new SessionChangeTracker();
@@ -611,37 +596,28 @@ export function createRuntimeServices(options: RuntimeServicesOptions): RuntimeS
611
596
  // with MemoryStore above. Auto-build is config-gated (default off) — see
612
597
  // code-index-services.ts's header doc.
613
598
  const { codeIndexStore, codeIndexReindexScheduler } = createCodeIndexServices({ workingDirectory, configManager, memoryEmbeddingRegistry });
599
+ // Store snapshots + durable remembered-approval rules + the live credential
600
+ // chain, mirroring the SDK composition — see durability-services.ts.
601
+ const { storeSnapshotScheduler, userPermissionRuleStore } = createDurabilityServices({
602
+ configManager, secretsManager, providerRegistry, memoryDbPath, codeIndexDbPath: codeIndexDbPath(workingDirectory), workingDirectory, surfaceRoot: 'tui', homeDirectory, shellPaths, // + retention-sweep roots & live config watch (mirrors the SDK)
603
+ });
614
604
  const codeInjectionOrchestratorDeps = { codeIndex: codeIndexStore, isCodeInjectionSettingEnabled: () => isCodeInjectionSettingEnabled(configManager), codeIndexReindexScheduler }; // Code-injection seam (agent here; main via orchestrator-core-services.ts)
615
- // Shared, archive-aware fleet registry — see gateway-verbs.ts's factory doc.
616
- const processRegistry = createArchivableFleetRegistry({
617
- agentManager,
618
- wrfcController,
605
+ const { processRegistry } = createFleetServices({ // Shared archive-aware fleet registry (+ daemon observed rows) — see fleet-services.ts
606
+ agentManager, wrfcController,
619
607
  orchestrationEngine, // Folds workstream/phase/work-item nodes into the fleet
620
608
  codeIndexService: codeIndexStore, // Folds a single 'code-index' node into the fleet
621
- processManager,
622
- watcherRegistry,
623
- workflow,
624
- approvalBroker,
625
- sessionBroker,
609
+ processManager, watcherRegistry, workflow, approvalBroker, sessionBroker,
626
610
  messageBus: agentMessageBus, // Backs steer()/`steerable` (the Fleet steer composer builds on top)
627
611
  automationManager, // Folds /schedule AutomationJobs into the fleet as 'schedule' nodes
628
612
  runtimeBus: options.runtimeBus,
629
- // Honest pricing: never fabricate a cost for an unrecognized model.
630
- priceUsage: (model, usage) => {
631
- const modelId = model ?? 'unknown';
632
- if (!isModelPriced(modelId)) return null;
633
- return calcSessionCost(usage.inputTokens, usage.outputTokens, usage.cacheReadTokens, usage.cacheWriteTokens, modelId);
634
- },
613
+ observeExternalAgents: options.observeExternalAgents, providerRegistry, // observeExternalAgents is daemon-side only
635
614
  });
636
- const modeManager = new ModeManager();
637
- const fileUndoManager = new FileUndoManager();
615
+ const modeManager = new ModeManager(); const fileUndoManager = new FileUndoManager();
638
616
  const workspaceCheckpointManager = createWorkspaceCheckpointing({ workspaceRoot: workingDirectory, runtimeBus: options.runtimeBus, configManager });
617
+ const { memoryConsolidationScheduler, powerManager, sessionLiveTurnControls } = wireIdlePowerAndLiveTurn({ configManager, memoryRegistry, runtimeBus: options.runtimeBus, isIdle: () => sessionBroker.countBusySessions() === 0, snapshotTick: () => storeSnapshotScheduler.tick(), heartbeat: async () => { await automationManager.triggerHeartbeat({ source: 'wake-catchup' }); }, powerSeam: options.powerSeam });
639
618
 
640
- // Shared terminal-shell wrapper over the SDK's registerGatewayVerbGroups (see gateway-verbs.ts): 501s the ws-only
641
- // family without it. principals.*/channels.profiles.*/ci.* register unconditionally; checkin.* only with
642
- // channelDeliveryRouter/providerRegistry/automationManager/sessionLister ALL present; fleet needs-input push gated by
643
- // runtimeBus/sessionPresence (fleet-needs-input-push.ts). conversationRewindPort serves conversation-scope rewind live.
644
- attachWsOnlyGatewayVerbHandlers(gatewayMethods, { processRegistry, workspaceCheckpointManager, conversationRewindPort: createSessionConversationRewindPort(), sessionBroker, secretsManager, stepUpService, approvalBroker, shellPaths, configManager, runtimeStore: options.runtimeStore, channelDeliveryRouter, providerRegistry, automationManager, sessionLister: sessionBroker, ...wireFleetNeedsInputPush({ registry: processRegistry, runtimeBus: options.runtimeBus, sessionBroker }) });
619
+ // Terminal-shell wrapper over the SDK registerGatewayVerbGroups (gateway-verbs.ts); checkin.*/fleet-needs-input/pairing.* register only when their deps are present.
620
+ attachWsOnlyGatewayVerbHandlers(gatewayMethods, { processRegistry, workspaceCheckpointManager, conversationRewindPort: createSessionConversationRewindPort(), sessionBroker, secretsManager, stepUpService, approvalBroker, requestApproval: (input) => approvalBroker.requestApproval(input), watcherRegistry, userPermissionRuleStore, shellPaths, configManager, runtimeStore: options.runtimeStore, channelDeliveryRouter, providerRegistry, automationManager, sessionLister: sessionBroker, sessionIntake: sessionBroker, workingDirectory, memoryRegistry, pairingTokens, sessionLiveTurnControls, powerManager, relayAvailable: () => configManager.get('relay.enabled') === true, pairingWebOrigin: () => resolvePairingWebOrigin(configManager).origin, ...wireFleetNeedsInputPush({ registry: processRegistry, runtimeBus: options.runtimeBus, sessionBroker }) });
645
621
  const integrationHelpers = new IntegrationHelperService({
646
622
  workingDirectory,
647
623
  homeDirectory,
@@ -662,8 +638,16 @@ export function createRuntimeServices(options: RuntimeServicesOptions): RuntimeS
662
638
  subscriptionManager,
663
639
  secretsManager,
664
640
  });
641
+ // A loopback fetch that isn't allow-listed asks once through the approval
642
+ // broker; "allow for this project" persists and later fetches never ask. Built
643
+ // once and shared with the tool registry (bootstrap-core) so both ask alike.
644
+ const localhostFetchApproval = buildLocalhostFetchApproval({ requestApproval: (input) => approvalBroker.requestApproval(input), configManager });
645
+ // Exec stuck on a terminal prompt rides the approval broker; the typed answer feeds the continuing run. Built once and shared (like localhostFetchApproval) so the tool registry AND every setDependencies site — incl. the post-clobber rewire in bootstrap-core — install the SAME handler; otherwise a wholesale replace drops it and interactive prompts hang.
646
+ const execPromptAnswerHandler = buildExecPromptAnswerHandler({ requestApproval: (input) => approvalBroker.requestApproval(input) });
665
647
  agentOrchestrator.setDependencies({
666
648
  surfaceRoot: 'tui',
649
+ execPromptAnswerHandler,
650
+ localhostFetchApproval,
667
651
  fileCache,
668
652
  projectIndex,
669
653
  workingDirectory,
@@ -691,6 +675,12 @@ export function createRuntimeServices(options: RuntimeServicesOptions): RuntimeS
691
675
  contextAccountingHolder,
692
676
  });
693
677
 
678
+ // Give the panel_only notification target a live producer: curated
679
+ // runtime-domain events route through the notification router into the shared
680
+ // panel feed (panel_only and burst-collapsed decisions land there).
681
+ const notificationDispatcher = createNotificationDispatcher(configManager);
682
+ wireRuntimeNotificationBridge(options.runtimeBus, notificationDispatcher);
683
+
694
684
  return {
695
685
  workingDirectory,
696
686
  homeDirectory,
@@ -709,6 +699,10 @@ export function createRuntimeServices(options: RuntimeServicesOptions): RuntimeS
709
699
  channelDeliveryRouter,
710
700
  watcherRegistry,
711
701
  approvalBroker,
702
+ localhostFetchApproval,
703
+ execPromptAnswerHandler,
704
+ notificationDispatcher,
705
+ userPermissionRuleStore,
712
706
  sessionBroker,
713
707
  deliveryManager,
714
708
  automationManager,
@@ -726,6 +720,7 @@ export function createRuntimeServices(options: RuntimeServicesOptions): RuntimeS
726
720
  serviceRegistry,
727
721
  secretsManager,
728
722
  stepUpService,
723
+ pairingTokens,
729
724
  subscriptionManager,
730
725
  localUserAuthManager,
731
726
  profileManager,
@@ -784,6 +779,10 @@ export function createRuntimeServices(options: RuntimeServicesOptions): RuntimeS
784
779
  workstreamCommands,
785
780
  codeIndexStore,
786
781
  codeIndexReindexScheduler,
782
+ storeSnapshotScheduler,
783
+ memoryConsolidationScheduler,
784
+ powerManager,
785
+ sessionLiveTurnControls,
787
786
  processRegistry,
788
787
  modeManager,
789
788
  fileUndoManager,
@@ -791,8 +790,9 @@ export function createRuntimeServices(options: RuntimeServicesOptions): RuntimeS
791
790
  integrationHelpers,
792
791
  async rerootStores(newWorkingDir: string): Promise<void> {
793
792
  // 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.
793
+ // deliberately does NOT reroot per-project (that would re-silo memory back
794
+ // into per-project stores). Only working-tree-bound stores (code index,
795
+ // project index) reroot.
796
796
  await codeIndexStore.reroot(newWorkingDir, codeIndexDbPath(newWorkingDir));
797
797
  await projectIndex.reroot(newWorkingDir);
798
798
  },
@@ -0,0 +1,38 @@
1
+ /**
2
+ * session-continuity-hints — the recovery snapshot's continuity summary.
3
+ *
4
+ * Extracted from main.ts (which sits at the architecture line cap) so the
5
+ * startup wiring there stays within budget. Builds the small counts/paths
6
+ * bundle written into the recovery file so a resumed session can describe what
7
+ * was in flight (pending approvals, active/blocked tasks, remotes, worktrees,
8
+ * open panels) without re-reading live state.
9
+ */
10
+ import type { UiReadModels } from './ui-read-models.ts';
11
+ import type { PanelManager } from '../panels/panel-manager.ts';
12
+
13
+ /**
14
+ * The continuity summary written into the recovery file. A loose serializable
15
+ * bag (Record) because it is JSON-persisted and read back by the recovery
16
+ * continuity-hints consumer, which types it structurally.
17
+ */
18
+ export function createSessionContinuityHintsBuilder(deps: {
19
+ readonly readModels: UiReadModels;
20
+ readonly panelManager: Pick<PanelManager, 'getAllOpen'>;
21
+ }): () => Record<string, unknown> {
22
+ return () => {
23
+ const sessionSnapshot = deps.readModels.session.getSnapshot();
24
+ const tasksSnapshot = deps.readModels.tasks.getSnapshot();
25
+ const remoteSnapshot = deps.readModels.remote.getSnapshot();
26
+ const worktreeSnapshot = deps.readModels.worktrees.getSnapshot();
27
+ return {
28
+ pendingApprovals: sessionSnapshot.pendingApproval ? 1 : 0,
29
+ activeTasks: tasksSnapshot.tasks.filter((task) => task.status === 'running' || task.status === 'queued').length,
30
+ blockedTasks: tasksSnapshot.tasks.filter((task) => task.status === 'blocked').length,
31
+ remoteContracts: remoteSnapshot.contracts.length,
32
+ remoteRunners: remoteSnapshot.contracts.slice(0, 4).map((contract) => contract.runnerId),
33
+ worktreeCount: worktreeSnapshot.records.length,
34
+ worktreePaths: worktreeSnapshot.records.slice(0, 3).map((record) => record.path),
35
+ openPanels: deps.panelManager.getAllOpen().map((panel) => panel.id),
36
+ };
37
+ };
38
+ }
@@ -1,5 +1,16 @@
1
- import type { ConfigKey, ConfigManager, PersistedFlagState } from '../config/index.ts';
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';
2
12
  import { surfaceFeatureGateId } from '@/runtime/index.ts';
13
+ import { featureEnablementWrite, getFeatureSetting, isFeatureConfigEnabled } from './feature-settings.ts';
3
14
 
4
15
  export const CONTROL_PLANE_FEATURE_FLAG = 'control-plane-gateway';
5
16
  export const ROUTE_BINDING_FEATURE_FLAG = 'route-binding';
@@ -12,10 +23,15 @@ const CORE_CHANNEL_FEATURE_FLAGS = [
12
23
  DELIVERY_ENGINE_FEATURE_FLAG,
13
24
  ] as const;
14
25
 
15
- export type FeatureFlagConfigKey = 'featureFlags' | `featureFlags.${string}`;
16
-
17
26
  export function getSurfaceFeatureFlag(surfaceId: string): string | null {
18
- return surfaceFeatureGateId(surfaceId);
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);
19
35
  }
20
36
 
21
37
  export function getServerSurfaceFeatureFlags(options: {
@@ -46,20 +62,35 @@ export function getServerSurfaceFeatureFlags(options: {
46
62
  return [...flags].sort((left, right) => left.localeCompare(right));
47
63
  }
48
64
 
49
- export function isFeatureFlagEnabled(config: Pick<ConfigManager, 'getCategory'>, flagId: string): boolean {
50
- const flags = (config.getCategory('featureFlags') as Record<string, PersistedFlagState | undefined>) ?? {};
51
- return flags[flagId] === 'enabled';
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));
52
81
  }
53
82
 
54
- export function getMissingSurfaceFeatureFlags(config: Pick<ConfigManager, 'getCategory'>, surfaceId: string): readonly string[] {
83
+ export function getMissingSurfaceFeatureFlags(config: Pick<ConfigManager, 'get'>, surfaceId: string): readonly string[] {
55
84
  const required = surfaceId === 'web'
56
85
  ? getServerSurfaceFeatureFlags({ web: true })
57
86
  : getServerSurfaceFeatureFlags({ externalSurfaces: [surfaceId] });
58
87
  return required.filter((flagId) => !isFeatureFlagEnabled(config, flagId));
59
88
  }
60
89
 
90
+ /** Switch each capability on by writing its real enablement settings key. */
61
91
  export function enableFeatureFlags(config: Pick<ConfigManager, 'setDynamic'>, flagIds: readonly string[]): void {
62
92
  for (const flagId of flagIds) {
63
- config.setDynamic(`featureFlags.${flagId}` as ConfigKey, 'enabled');
93
+ const write = featureEnablementWrite(flagId, true);
94
+ if (write) config.setDynamic(write.key, write.value);
64
95
  }
65
96
  }
@@ -45,7 +45,7 @@ export type TuiTerminalOutputGuardOptions = {
45
45
  * writes captured this session, so an honest quiet counter (e.g. /debug) can
46
46
  * refresh. The per-write detail is already recorded to the activity log via
47
47
  * logger.warn — this callback intentionally does NOT push transcript lines.
48
- * (UX-B item 1a.)
48
+ * (item 1a.)
49
49
  */
50
50
  readonly onCapture?: (total: number) => void;
51
51
  };
@@ -224,7 +224,7 @@ export function installTuiTerminalOutputGuard(options: TuiTerminalOutputGuardOpt
224
224
  onIntercept: () => {
225
225
  // Each intercept is already logged (logger.warn in record()). Here we only
226
226
  // maintain a cumulative counter and, rate-limited, refresh a quiet counter
227
- // surface — no repeated transcript lines. (UX-B item 1a.)
227
+ // surface — no repeated transcript lines. (item 1a.)
228
228
  totalInterceptedWrites++;
229
229
  const now = Date.now();
230
230
  if (now - lastNoticeAt < 5_000) return;
@@ -137,13 +137,21 @@ export class WorkspaceRegistrationManager {
137
137
  * Register the current working directory at its resolved project root. The
138
138
  * store refuses broad roots (WorkspaceRegistrationError) — we surface that
139
139
  * honestly rather than record a phantom registration.
140
+ *
141
+ * `origin` stamps which surface wrote the record (honest provenance). This
142
+ * NEVER passes `checkpointEligible`: absent means false, so a TUI self-record
143
+ * can never widen the checkpoint-owning consumer's boundary — only that
144
+ * consumer stamps its own roots eligible on boot.
140
145
  */
141
- async register(label?: string): Promise<
146
+ async register(label?: string, origin?: string): Promise<
142
147
  | { readonly registered: true; readonly result: RegisterWorkspaceResult }
143
148
  | { readonly registered: false; readonly refusedReason: string }
144
149
  > {
150
+ const opts = label || origin
151
+ ? { ...(label ? { label } : {}), ...(origin ? { origin } : {}) }
152
+ : undefined;
145
153
  try {
146
- const result = await this.store.add(this.workingDirectory, label ? { label } : undefined);
154
+ const result = await this.store.add(this.workingDirectory, opts);
147
155
  return { registered: true, result };
148
156
  } catch (error) {
149
157
  if (error instanceof WorkspaceRegistrationError) {