@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
@@ -74,6 +74,7 @@ export interface BootstrapCommandActionOptions {
74
74
 
75
75
  export interface BootstrapCommandSectionOptions {
76
76
  readonly configManager: ConfigManager;
77
+ readonly pairingTokens?: import('@pellux/goodvibes-sdk/platform/pairing').PairingTokenManager;
77
78
  readonly featureFlagManager?: FeatureFlagManager;
78
79
  readonly providerRegistry: ProviderRegistry;
79
80
  readonly conversation: ConversationManager;
@@ -86,7 +87,7 @@ export interface BootstrapCommandSectionOptions {
86
87
  readonly mcpRegistry: McpRegistry;
87
88
  readonly voiceProviderRegistry?: VoiceProviderRegistry;
88
89
  readonly voiceService?: VoiceService;
89
- /** B31: direct-command consumers (`/search`, `/image`) of already-constructed RuntimeServices. */
90
+ /** Direct-command consumers (`/search`, `/image`) of already-constructed RuntimeServices. */
90
91
  readonly webSearchService?: import('@pellux/goodvibes-sdk/platform/web-search').WebSearchService;
91
92
  readonly mediaProviders?: import('@pellux/goodvibes-sdk/platform/media').MediaProviderRegistry;
92
93
  readonly artifactStore?: import('@pellux/goodvibes-sdk/platform/artifacts').ArtifactStore;
@@ -139,6 +140,7 @@ export interface BootstrapCommandSectionOptions {
139
140
  readonly remoteRuntime?: RemoteCommandService;
140
141
  readonly planRuntime?: PlanRuntimeService;
141
142
  readonly operatorClient?: OperatorClient;
143
+ readonly userPermissionRuleStore?: import('@pellux/goodvibes-sdk/platform/permissions').UserPermissionRuleStore;
142
144
  readonly peerClient?: PeerClient;
143
145
  readonly providerApi?: ProviderApi;
144
146
  readonly knowledgeApi?: KnowledgeApi;
@@ -203,7 +205,7 @@ export function createBootstrapCommandActions(
203
205
  } = options;
204
206
 
205
207
  const showPanel = (panelId: string, pane?: 'top' | 'bottom', target?: PanelDeepLinkTarget) => {
206
- // W6.1 (the purge): a MIGRATE-TO-MODAL id resolves to a modal, not a panel.
208
+ // (the purge): a MIGRATE-TO-MODAL id resolves to a modal, not a panel.
207
209
  // panelManager.open() fires the injected openModal callback and returns a
208
210
  // no-op sentinel — so skip panelManager.show() (which would reveal an empty
209
211
  // panel workspace behind the modal) when this id redirects. Keeps every
@@ -307,7 +309,7 @@ export function createBootstrapCommandActions(
307
309
  openMemoryPanel: () => {
308
310
  showPanel('memory');
309
311
  },
310
- // W6.1: remote/subscription migrated to config-modal surfaces. open() hits
312
+ // remote/subscription migrated to config-modal surfaces. open() hits
311
313
  // the modal redirect and invokes the openModal callback — do NOT go through
312
314
  // showPanel here, which would additionally reveal + focus an (empty) panel
313
315
  // workspace behind the fullscreen modal.
@@ -395,13 +397,14 @@ export function createBootstrapCommandWorkspaceSection(
395
397
  export function createBootstrapCommandPlatformSection(
396
398
  options: Pick<
397
399
  BootstrapCommandSectionOptions,
398
- 'configManager' | 'featureFlagManager' | 'voiceProviderRegistry' | 'voiceService' | 'webSearchService' | 'mediaProviders' | 'artifactStore'
400
+ 'configManager' | 'pairingTokens' | 'featureFlagManager' | 'voiceProviderRegistry' | 'voiceService' | 'webSearchService' | 'mediaProviders' | 'artifactStore'
399
401
  >,
400
402
  shellServices: BootstrapCommandShellServices,
401
403
  ): BootstrapCommandPlatformSection {
402
404
  return {
403
405
  config: getConfigSnapshot(options.configManager),
404
406
  configManager: options.configManager,
407
+ ...(options.pairingTokens ? { pairingTokens: options.pairingTokens } : {}),
405
408
  featureFlagManager: options.featureFlagManager,
406
409
  voiceProviderRegistry: options.voiceProviderRegistry,
407
410
  voiceService: options.voiceService,
@@ -435,11 +438,12 @@ export function createBootstrapCommandExtensionsSection(
435
438
  export function createBootstrapCommandClientsSection(
436
439
  options: Pick<
437
440
  BootstrapCommandSectionOptions,
438
- 'operatorClient' | 'peerClient' | 'providerApi' | 'knowledgeApi' | 'memorySpine' | 'hookApi' | 'mcpApi' | 'opsApi' | 'directTransport'
441
+ 'operatorClient' | 'userPermissionRuleStore' | 'peerClient' | 'providerApi' | 'knowledgeApi' | 'memorySpine' | 'hookApi' | 'mcpApi' | 'opsApi' | 'directTransport'
439
442
  >,
440
443
  ): BootstrapCommandClientSection {
441
444
  return {
442
445
  operator: options.operatorClient,
446
+ userPermissionRuleStore: options.userPermissionRuleStore,
443
447
  peer: options.peerClient,
444
448
  providerApi: options.providerApi,
445
449
  knowledgeApi: options.knowledgeApi,
@@ -6,6 +6,7 @@ import { ConfigManager, getConfiguredSystemPrompt } from '../config/index.ts';
6
6
  import { getProviderIdFromModel } from '../config/provider-model.ts';
7
7
  import { ToolRegistry } from '@pellux/goodvibes-sdk/platform/tools';
8
8
  import { registerAllTools } from '@pellux/goodvibes-sdk/platform/tools';
9
+ import { createSandboxContainmentNotice } from './daemon-attach-notices.ts';
9
10
  import { PermissionManager, createPermissionConfigReader } from '@pellux/goodvibes-sdk/platform/permissions';
10
11
  import { Notifier } from '@pellux/goodvibes-sdk/platform/integrations';
11
12
  import { Compositor } from '../renderer/compositor.ts';
@@ -17,15 +18,16 @@ import type { ControlPlaneRecentEvent } from '@pellux/goodvibes-sdk/platform/con
17
18
  import type { BootstrapOptions } from './context.ts';
18
19
  import { createRuntimeStore, createDomainDispatch, type RuntimeStore } from './store/index.ts';
19
20
  import {
20
- type MutableRuntimeState, type SessionEvent, createFeatureFlagManager, RuntimeEventBus, ForensicsCollector,
21
+ type MutableRuntimeState, type SessionEvent, bindFeatureSettingsBridge, createFeatureFlagManager, deriveFeatureStates, RuntimeEventBus, ForensicsCollector,
21
22
  ForensicsRegistry, generateUserSessionId, loadBootstrapSystemPrompt, syncConfiguredServices,
22
23
  registerBootstrapHookBridge, registerBootstrapRuntimeEvents,
23
24
  } from '@/runtime/index.ts';
24
25
  import { readExecEnvScrubAllowlist } from '../input/exec-env-scrub-config.ts';
25
26
  import { createSandboxExecAsk, sandboxExecAskDepsFromRuntime } from '../permissions/sandbox-exec-gate.ts';
26
27
  import { createRuntimeServices, type RuntimeServices } from './services.ts';
28
+ import { createHostPowerSeam } from '@pellux/goodvibes-sdk/platform/power';
27
29
  import { runBootMemoryFold } from './memory-fold.ts';
28
- import { setPricingSource } from '../export/cost-utils.ts';
30
+ import { wireCostPricing } from '../export/cost-utils.ts';
29
31
  import { createUiRuntimeServices, type UiRuntimeServices } from './ui-services.ts';
30
32
  import { join } from 'node:path';
31
33
  import { installWrfcAgentToolGuard } from '../tools/wrfc-agent-guard.ts';
@@ -33,12 +35,10 @@ import { createWrfcPersistence, type WrfcPersistence } from './wrfc-persistence.
33
35
  import type { SystemMessagePriority } from '../core/system-message-router.ts';
34
36
  import { SessionSpineClient, SessionUnionCache, TUI_SPINE_PARTICIPANT } from '@pellux/goodvibes-sdk/platform/runtime/session-spine';
35
37
  import { SessionInboundInputPoller, createBootstrapInboundInputPoller } from './session-inbound-inputs.ts';
36
- import { detectPriorWorkspaceState, trustGatedAsk } from './trust/workspace-trust.ts';
38
+ import { trustGatedAsk, type WorkspaceTrustLevel } from './trust/workspace-trust.ts';
37
39
  import { syncNotifierQueueIntegrations } from './bootstrap-notifier-sync.ts';
38
40
 
39
- // ---------------------------------------------------------------------------
40
- // Pre-router buffer
41
- // ---------------------------------------------------------------------------
41
+ // --- Pre-router buffer ---
42
42
 
43
43
  const PRE_ROUTER_BUFFER_MAX = 100;
44
44
 
@@ -105,6 +105,7 @@ export interface BootstrapCoreState {
105
105
  readonly runtimeUnsubs: Array<() => void>;
106
106
  readonly agentStatusIntervalRef: { value: ReturnType<typeof setInterval> | null };
107
107
  readonly permissionPromptRef: { requestPermission: PermissionRequestHandler };
108
+ readonly trustPromptRef: { requestTrustDecision: () => Promise<WorkspaceTrustLevel> }; // trust-at-consequence-time bridge, patched with the real modal-driving impl once the UI layer exists
108
109
  readonly systemMessageRouterRef: { value: SystemMessageRouter | null };
109
110
  readonly conversationFollowUpRef: { value: ((item: ConversationFollowUpItem) => void) | null };
110
111
  /**
@@ -131,9 +132,7 @@ export interface BootstrapCoreState {
131
132
 
132
133
  export type CompanionMessagePayload = Extract<SessionEvent, { type: 'COMPANION_MESSAGE_RECEIVED' }>;
133
134
 
134
- // ---------------------------------------------------------------------------
135
- // Operator narration of inbound channel events
136
- // ---------------------------------------------------------------------------
135
+ // --- Operator narration of inbound channel events ---
137
136
 
138
137
  /** Narrate an inbound channel event (GitHub, Slack, ntfy, etc.) that triggered an
139
138
  * agent turn, via the SystemMessageRouter — null for internal/companion sources. */
@@ -231,10 +230,12 @@ export async function initializeBootstrapCore(
231
230
  const homeDirectory = options.homeDirectory;
232
231
  const configManager = options.configManager;
233
232
 
233
+ // Gate states derive from the domain settings keys; the bridge keeps live
234
+ // config writes flowing (honest pending-restart markers for startup-gated
235
+ // features). Mirrors the SDK composition root's owned-manager wiring.
234
236
  const featureFlags = createFeatureFlagManager();
235
- featureFlags.loadFromConfig({
236
- flags: (configManager.getCategory('featureFlags') as Record<string, import('@/runtime/index.ts').FlagState>) ?? {},
237
- });
237
+ featureFlags.loadFromConfig({ flags: deriveFeatureStates(configManager) });
238
+ bindFeatureSettingsBridge(configManager, featureFlags);
238
239
 
239
240
  const userSessionId = `user-${generateUserSessionId()}`;
240
241
  const runtimeBus = new RuntimeEventBus();
@@ -247,20 +248,19 @@ export async function initializeBootstrapCore(
247
248
  runtimeBus,
248
249
  runtimeStore: store,
249
250
  getConversationTitle: () => getConversationTitle(),
250
- workingDir,
251
- homeDirectory,
252
- // Sampled BEFORE any service writes to <cwd>/.goodvibes so a new workspace isn't misread as used.
253
- workspaceHadPriorState: detectPriorWorkspaceState(workingDir),
251
+ workingDir, homeDirectory,
252
+ // Embedded topology: the in-process runtime IS the daemon and holds its own OS
253
+ // inhibitor, so opt into the real host seam (fork default is non-spawning). See services.ts.
254
+ powerSeam: createHostPowerSeam(),
254
255
  });
255
- await services.workspaceTrustManager.load(); // settle trust (+ grandfathering) before any tool runs
256
+ await services.workspaceTrustManager.load(); // settle any already-persisted trust decision before any tool runs
256
257
  const providerRegistry = services.providerRegistry;
257
258
  providerRegistry.initModelLimits();
258
259
  services.benchmarkStore.initBenchmarks();
259
260
  providerRegistry.initCatalog();
260
- // Wire cost-utils to the live catalog so cost displays distinguish real
261
- // pricing from unpriced (WO-315) instead of silently reading zero for any
262
- // model the small static fallback table doesn't cover.
263
- setPricingSource(() => providerRegistry.getRawCatalogModels());
261
+ // Wire cost-utils to the live catalog AND the ONE pricing resolver so every
262
+ // cost surface distinguishes real pricing from unpriced and names its source ("your price" vs "catalog price, as of <date>") — see cost-utils.
263
+ wireCostPricing(providerRegistry);
264
264
  services.keybindingsManager.loadFromDisk();
265
265
  domainDispatch.syncControlPlaneState({
266
266
  enabled: Boolean(configManager.get('controlPlane.enabled')),
@@ -351,6 +351,8 @@ export async function initializeBootstrapCore(
351
351
  const toolRegistry = new ToolRegistry();
352
352
  const { fileCache, projectIndex } = registerAllTools(toolRegistry, {
353
353
  surfaceRoot: 'tui',
354
+ localhostFetchApproval: services.localhostFetchApproval, // loopback-fetch ask, built once in the services composition
355
+ execPromptAnswerHandler: services.execPromptAnswerHandler, // terminal prompt-answer path, built once in the services composition
354
356
  fileUndoManager: services.fileUndoManager,
355
357
  modeManager: services.modeManager,
356
358
  processManager: services.processManager,
@@ -373,21 +375,19 @@ export async function initializeBootstrapCore(
373
375
  serviceRegistry: services.serviceRegistry,
374
376
  overflowHandler: services.overflowHandler,
375
377
  changeTracker: services.sessionChangeTracker,
376
- // Master switch stays on (SDK default); this only widens the allowlist of
377
- // variable NAMES kept even though they look credential-bearing. See
378
- // exec-env-scrub-config.ts (permissions.execEnvScrubAllowlist).
378
+ // Widens the allowlist of credential-looking variable NAMES kept (master switch stays on). See exec-env-scrub-config.ts.
379
379
  credentialEnvScrub: { allowlist: readExecEnvScrubAllowlist(configManager) },
380
- // Registers the context_accounting tool on this (the real, interactive)
381
- // registry against OUR holder rather than a fresh internal one, so the
382
- // Orchestrator-backed source bound at bootstrap.ts is what the tool
383
- // actually reads from. See runtime/context-accounting-source.ts.
380
+ // Register context_accounting against OUR holder (the Orchestrator-backed source bound at bootstrap.ts). See runtime/context-accounting-source.ts.
384
381
  contextAccountingHolder: services.contextAccountingHolder,
382
+ // First contained (sandboxed) command run announces "commands now run contained" once — recorded and surfaced now.
383
+ onSandboxedRun: createSandboxContainmentNotice({ configManager, notify: (text) => conversation.log(`[Sandbox] ${text}`, { fg: '135' }) }),
385
384
  });
386
- // Note: installWrfcAgentToolGuard is called after routeOrBuffer is defined
387
- // (further below) so the onTrace callback can route guard decisions through
388
- // the pre-router buffer.
385
+ // Note: installWrfcAgentToolGuard is called after routeOrBuffer is defined (further below) so the onTrace callback routes guard decisions through the pre-router buffer.
389
386
  services.agentOrchestrator.setDependencies({
390
387
  surfaceRoot: 'tui',
388
+ // setDependencies is a wholesale replace: re-install the prompt-answer handler and the loopback-fetch ask here or this rewire silently drops them.
389
+ execPromptAnswerHandler: services.execPromptAnswerHandler,
390
+ localhostFetchApproval: services.localhostFetchApproval,
391
391
  fileCache,
392
392
  projectIndex,
393
393
  workingDirectory: services.workingDirectory,
@@ -424,7 +424,7 @@ export async function initializeBootstrapCore(
424
424
  await runBootMemoryFold(memoryStore, services.memoryEmbeddingRegistry, workingDir, logger);
425
425
 
426
426
  const renderRequestRef = { value: (): void => {} };
427
- // R1: Coalescing render scheduler — collapses N requestRender() calls into 1 and
427
+ // Coalescing render scheduler — collapses N requestRender() calls into 1 and
428
428
  // enforces a 16ms minimum interval to cap repaints at ~60fps. renderScheduled stays
429
429
  // set for the ENTIRE window (until run() executes), so requestRender() calls arriving
430
430
  // on later event-loop ticks within the same 16ms window coalesce into the one
@@ -467,6 +467,9 @@ export async function initializeBootstrapCore(
467
467
  const permissionPromptRef = {
468
468
  requestPermission: (async () => ({ approved: false, remember: false })) as PermissionRequestHandler,
469
469
  };
470
+ // Trust-at-consequence-time: raised by trustGatedAsk on the first non-read
471
+ // request in an undecided workspace; overridden once the UI layer exists — same ref-patching pattern as permissionPromptRef above.
472
+ const trustPromptRef = { requestTrustDecision: (async () => 'restricted') as () => Promise<WorkspaceTrustLevel> };
470
473
  approvalBroker.start().catch((err) => logger.warn('approval broker start failed at bootstrap', { err }));
471
474
  sharedSessionBroker.start().catch((err) => logger.warn('shared session broker start failed at bootstrap', { err }));
472
475
  const runtimeSessionIdRef = { value: userSessionId };
@@ -663,7 +666,7 @@ export async function initializeBootstrapCore(
663
666
 
664
667
  providerRegistry.startWatching(runtimeBus);
665
668
 
666
- // W2.3: attach the SAME WebhookNotifier instance that `/notify add|remove|clear`
669
+ // attach the SAME WebhookNotifier instance that `/notify add|remove|clear`
667
670
  // (notify-runtime.ts) keeps live via ctx.services.webhookNotifier, rather than
668
671
  // constructing a second, boot-time-only instance here. Before this fix, a
669
672
  // webhook URL added mid-session reached long-task notifications (which read
@@ -696,8 +699,7 @@ export async function initializeBootstrapCore(
696
699
  await syncConfiguredServices(domainDispatch.syncIntegration, services.serviceRegistry);
697
700
 
698
701
  const permissionManager = new PermissionManager(
699
- // Composed ask layer: the workspace trust gate (outer) wraps the sandbox-aware
700
- // exec gate (inner); see sandbox-exec-gate.ts. The catastrophic block is untouched.
702
+ // Composed ask layer: the workspace trust gate (outer) wraps the sandbox-aware exec gate (inner); see sandbox-exec-gate.ts. The catastrophic block is untouched.
701
703
  trustGatedAsk(
702
704
  services.workspaceTrustManager,
703
705
  createSandboxExecAsk(
@@ -708,11 +710,13 @@ export async function initializeBootstrapCore(
708
710
  localPrompt: permissionPromptRef.requestPermission,
709
711
  }),
710
712
  ),
713
+ () => trustPromptRef.requestTrustDecision(), // indirection through the ref, not bound early — main.ts patches the real impl in later
711
714
  ),
712
715
  createPermissionConfigReader(configManager),
713
716
  policyRuntimeState,
714
717
  services.hookDispatcher,
715
718
  featureFlags,
719
+ services.userPermissionRuleStore, // durable remembered approvals (mirrors the SDK composition); permissions.rules.* lists/deletes them
716
720
  );
717
721
  await hookWorkbench.loadAndApplyManagedHooks();
718
722
 
@@ -757,11 +761,7 @@ export async function initializeBootstrapCore(
757
761
  }, 'bootstrap.session');
758
762
 
759
763
  runtimeUnsubs.push(
760
- ...registerBootstrapHookBridge({
761
- runtimeBus,
762
- hookDispatcher,
763
- runtime,
764
- }),
764
+ ...registerBootstrapHookBridge({ runtimeBus, hookDispatcher, runtime }),
765
765
  );
766
766
 
767
767
  return {
@@ -783,7 +783,7 @@ export async function initializeBootstrapCore(
783
783
  bootstrapUnsubs,
784
784
  runtimeUnsubs,
785
785
  agentStatusIntervalRef,
786
- permissionPromptRef,
786
+ permissionPromptRef, trustPromptRef,
787
787
  systemMessageRouterRef,
788
788
  conversationFollowUpRef,
789
789
  orchestratorHandleUserInputRef,
@@ -32,7 +32,7 @@ export interface ResumeSessionOptions {
32
32
  readonly sessionManager: SessionManager;
33
33
  readonly panelManager: PanelManager;
34
34
  readonly configManager: Pick<ConfigManager, 'get' | 'getCategory'>;
35
- readonly providerRegistry: Pick<ProviderRegistry, 'get' | 'getCurrentModel' | 'getForModel' | 'require'>;
35
+ readonly providerRegistry: Pick<ProviderRegistry, 'get' | 'getCurrentModel' | 'getForModel' | 'require' | 'resolveModelPricing'>;
36
36
  readonly homeDirectory: string;
37
37
  /** See CommandSessionServices.hydrateSessionUsage (command-registry.ts). */
38
38
  readonly hydrateSessionUsage?: () => void;
@@ -93,7 +93,7 @@ export function createBootstrapShell(options: BootstrapShellOptions): BootstrapS
93
93
  completeModelSelectionSideEffect,
94
94
  } = options;
95
95
 
96
- // W0.9: after any resume seam replays historical messages into `conversation`,
96
+ // after any resume seam replays historical messages into `conversation`,
97
97
  // the freshly-constructed `orchestrator` still has its zeroed default usage
98
98
  // (SDK gap — Orchestrator.usage is never persisted/reseeded). Recompute it
99
99
  // from the replayed history so the footer doesn't show Input: 0 post-resume.
@@ -140,11 +140,11 @@ export function createBootstrapShell(options: BootstrapShellOptions): BootstrapS
140
140
  runtimeBus,
141
141
  });
142
142
 
143
- // WO-139: initial cost-budget alert threshold (USD; 0/unset = disabled).
143
+ // initial cost-budget alert threshold (USD; 0/unset = disabled).
144
144
  // Once the session starts, the real control surface is the CostTrackerPanel
145
145
  // itself — the in-panel 'b' key and /cost budget <usd> both call
146
146
  // CostTrackerPanel.setBudgetThreshold() directly on the live panel instance,
147
- // which (W2.3) now writes through to the behavior.budgetAlertUsd config key
147
+ // which now writes through to the behavior.budgetAlertUsd config key
148
148
  // so the background budget-breach notifier reads the same value. The env
149
149
  // var remains a first-run convenience only: it seeds the config key when
150
150
  // that key has never been set, so it doesn't silently override a value the
@@ -206,6 +206,12 @@ export function createBootstrapShell(options: BootstrapShellOptions): BootstrapS
206
206
  runtimeStore,
207
207
  openPanel: (panelId: string) => { services.panelManager.open(panelId); },
208
208
  knowledgeApi,
209
+ // Fleet acts (pick / conflict / discard) surface receipts through the command
210
+ // context's print and reuse its one-key jump affordance — both late-bound
211
+ // (the command context is assigned to commandContextRef after bootstrap, and
212
+ // main.ts patches armFixSessionAttach onto it).
213
+ fleetActsNotify: (message: string) => { commandContextRef?.print?.(message); requestRender(); },
214
+ armFixSessionAttach: (sessionId: string) => { commandContextRef?.armFixSessionAttach?.(sessionId); },
209
215
  });
210
216
  services.panelManager.prewarmRegistered();
211
217
 
@@ -220,7 +226,7 @@ export function createBootstrapShell(options: BootstrapShellOptions): BootstrapS
220
226
  {
221
227
  // Suppress stale WRFC replay re-notifications for chains that can no
222
228
  // longer act — gone (killed/removed → getChain null) or terminal
223
- // (passed/failed). (UX-B item 1c.)
229
+ // (passed/failed). (item 1c.)
224
230
  isChainTerminal: (chainId) => {
225
231
  const chain = services.wrfcController.getChain(chainId);
226
232
  return chain === null || chain.state === 'passed' || chain.state === 'failed';
@@ -239,6 +245,7 @@ export function createBootstrapShell(options: BootstrapShellOptions): BootstrapS
239
245
 
240
246
  const commandContext: CommandContext = createBootstrapCommandContext({
241
247
  configManager,
248
+ pairingTokens: services.pairingTokens,
242
249
  providerRegistry: services.providerRegistry,
243
250
  conversation,
244
251
  runtime,
@@ -307,6 +314,7 @@ export function createBootstrapShell(options: BootstrapShellOptions): BootstrapS
307
314
  adaptivePlanner: services.adaptivePlanner,
308
315
  sessionOrchestration: services.sessionOrchestration,
309
316
  operatorClient: directTransport.operator,
317
+ userPermissionRuleStore: services.userPermissionRuleStore,
310
318
  peerClient: directTransport.peer,
311
319
  knowledgeApi,
312
320
  // /recall's browse/link/queue/export/import subcommands and the per-turn
@@ -345,7 +353,7 @@ export function createBootstrapShell(options: BootstrapShellOptions): BootstrapS
345
353
  lastGitInfoRef.value = info;
346
354
  requestRender();
347
355
  }).catch(() => { /* non-fatal */ });
348
- // W1.6 FIX 2: the header's git segment otherwise only refreshes on
356
+ // FIX 2: the header's git segment otherwise only refreshes on
349
357
  // TURN_COMPLETED/TOOL_SUCCEEDED/TOOL_FAILED (see turn-event-wiring.ts's
350
358
  // refreshGit()) — if the user runs `git init` externally and never submits
351
359
  // another turn, the header stays stuck on the startup-time fallback
@@ -15,6 +15,7 @@ import { AcpManager } from '@pellux/goodvibes-sdk/platform/acp';
15
15
  import { getTierPromptSupplement, getTierForContextWindow } from '@pellux/goodvibes-sdk/platform/providers';
16
16
  import { logger, summarizeError } from '@pellux/goodvibes-sdk/platform/utils';
17
17
  import type { PermissionRequestHandler } from '@pellux/goodvibes-sdk/platform/permissions';
18
+ import type { WorkspaceTrustLevel } from './trust/workspace-trust.ts';
18
19
  import type { CommandContext } from '../input/command-registry.ts';
19
20
  import type { InputHistory } from '../input/input-history.ts';
20
21
  import type { GitStatusProvider, GitHeaderInfo } from '../renderer/git-status.ts';
@@ -40,9 +41,9 @@ import { createBootstrapShell } from './bootstrap-shell.ts';
40
41
  import { announceResumeState } from './resume-notice.ts';
41
42
  import { announceInstallSelfCheck } from './install-self-check-startup.ts';
42
43
  import { buildSharedOrchestratorCoreServices, refreshMemoryRecallSnapshot } from './orchestrator-core-services.ts';
44
+ import { consumeDaemonAttachNotices, consumeExternalDaemonAttachNotices } from './daemon-attach-notices.ts';
43
45
  import { createContextAccountingSource } from './context-accounting-source.ts';
44
- import { DaemonServer, HttpListener } from '@pellux/goodvibes-sdk/platform/daemon';
45
- import { createSafeHostServeFactory } from '../daemon/safe-serve.ts';
46
+ import { createEmbeddedServiceFactories } from './embedded-service-factories.ts';
46
47
  import { buildRelayExternalServiceMethods } from './relay-reachability-bridge.ts';
47
48
  import { startMcpConfigAutoReload } from '../mcp/runtime-reload.ts';
48
49
 
@@ -95,6 +96,8 @@ export type BootstrapContext = RuntimeContext & {
95
96
  setRenderRequest: (fn: () => void) => void;
96
97
  /** Shell-owned permission prompt bridge that main.ts patches after UI setup. */
97
98
  permissionPromptRef: { requestPermission: PermissionRequestHandler };
99
+ /** Shell-owned trust-decision bridge that main.ts patches after UI setup (trust asks at consequence time). */
100
+ trustPromptRef: { requestTrustDecision: () => Promise<WorkspaceTrustLevel> };
98
101
  /** Load the most recently saved conversation from disk. */
99
102
  loadLastConversation: () => { messages: Array<Record<string, unknown>> } | null;
100
103
  /** Write the last-session pointer file (used after session resume). */
@@ -172,7 +175,7 @@ export async function bootstrapRuntime(
172
175
  bootstrapUnsubs,
173
176
  runtimeUnsubs,
174
177
  agentStatusIntervalRef,
175
- permissionPromptRef,
178
+ permissionPromptRef, trustPromptRef,
176
179
  systemMessageRouterRef,
177
180
  conversationFollowUpRef,
178
181
  orchestratorHandleUserInputRef,
@@ -192,9 +195,8 @@ export async function bootstrapRuntime(
192
195
  pluginManager,
193
196
  } = services;
194
197
 
195
- // A liveness flip is only PAINTED once something calls requestRender();
196
- // without this, the footer's spine segment sat correct-but-undrawn until
197
- // incidental activity redrew it (minutes, during an idle stretch).
198
+ // A liveness flip is only PAINTED once something calls requestRender(); without
199
+ // this, the footer's spine segment sat correct-but-undrawn until incidental activity redrew it (minutes, during an idle stretch).
198
200
  sessionUnionCache.setOnTransition(() => requestRender());
199
201
 
200
202
  // ── Phase 6: Orchestrator + AcpManager ───────────────────────────────────
@@ -237,7 +239,7 @@ export async function bootstrapRuntime(
237
239
  logger.debug('companion handleUserInput safety catch', { error: String(err) });
238
240
  });
239
241
  };
240
- // Shared payload (single source of truth, includes wo805's memoryRegistry —
242
+ // Shared payload (single source of truth, includes the memoryRegistry —
241
243
  // see orchestrator-core-services.ts) plus this site's cacheHitTracker.
242
244
  orchestrator.setCoreServices({
243
245
  ...buildSharedOrchestratorCoreServices({ services, configManager, providerRegistry }),
@@ -254,8 +256,7 @@ export async function bootstrapRuntime(
254
256
 
255
257
  const acpManager = new AcpManager({
256
258
  requestPermission: (request) => permissionPromptRef.requestPermission(request),
257
- runtimeBus,
258
- hookDispatcher: services.hookDispatcher,
259
+ runtimeBus, hookDispatcher: services.hookDispatcher,
259
260
  });
260
261
  const acpTaskAdapter = new AcpTaskAdapter(store);
261
262
  const ACP_TASK_SYNC_INTERVAL_MS = 1_000;
@@ -265,7 +266,9 @@ export async function bootstrapRuntime(
265
266
  bootstrapUnsubs.push(() => clearInterval(acpTaskSyncInterval));
266
267
  orchestrator.registerDelegateTool(acpManager);
267
268
  const opsTaskManager = createTaskManager(store, runtimeBus, userSessionId);
268
- const opsControlPlane = services.featureFlags.isEnabled('operator-control-plane')
269
+ // Operator interventions ride the control-plane gateway capability
270
+ // (controlPlane.gateway, on by default; the old never-registered gate id shipped this dead).
271
+ const opsControlPlane = services.featureFlags.isEnabled('control-plane-gateway')
269
272
  ? new OpsControlPlane(opsTaskManager, runtimeBus, store, userSessionId)
270
273
  : undefined;
271
274
 
@@ -300,7 +303,7 @@ export async function bootstrapRuntime(
300
303
  wrfcPersistence.rehydrate();
301
304
  const commandRegistry = shell.commandRegistry;
302
305
  const commandContext = shell.commandContext;
303
- // Boot resume notice (UX-D item 1): after rehydrate() so chain history is ready, before
306
+ // Boot resume notice (item 1): after rehydrate() so chain history is ready, before
304
307
  // the operator can type anything. Fire-and-forget, same as main.ts's non-blocking
305
308
  // `void workspaceCheckpointManager.init().catch(() => {})` — local file I/O only,
306
309
  // resolves well before a human can react to the first rendered frame.
@@ -318,7 +321,7 @@ export async function bootstrapRuntime(
318
321
  });
319
322
  announceInstallSelfCheck(systemMessageRouter);
320
323
  const { gitStatusProvider, inputHistory, lastGitInfoRef } = shell;
321
- // W1.6 FIX 2: dispose the header's live-repo-state poll (git-status.ts
324
+ // FIX 2: dispose the header's live-repo-state poll (git-status.ts
322
325
  // startPolling) on shutdown, same pattern as acpTaskSyncInterval above.
323
326
  bootstrapUnsubs.push(() => gitStatusProvider.stopPolling());
324
327
  const pluginCommandRegistry = {
@@ -475,21 +478,9 @@ export async function bootstrapRuntime(
475
478
  ]);
476
479
  };
477
480
 
478
- const createExternalServiceFactories = (sharedDaemonToken: string): ExternalServiceFactories => ({
479
- sharedDaemonToken,
480
- createDaemonServer: (bus, userAuth, runtimeServices) => new DaemonServer({
481
- runtimeBus: bus,
482
- userAuth,
483
- runtimeServices,
484
- serveFactory: createSafeHostServeFactory('Embedded daemon'),
485
- }),
486
- createHttpListener: (dispatcher, userAuth, configManager) => new HttpListener({
487
- hookDispatcher: dispatcher,
488
- userAuth,
489
- configManager,
490
- serveFactory: createSafeHostServeFactory('Embedded HTTP listener'),
491
- }),
492
- });
481
+ // Embedded DaemonServer/HttpListener factories (incl. the facade auto-update
482
+ // suppression) — see embedded-service-factories.ts.
483
+ const createExternalServiceFactories = createEmbeddedServiceFactories;
493
484
 
494
485
  let externalServices: ExternalServicesHandle = {
495
486
  daemonServer: null, httpListener: null,
@@ -505,6 +496,9 @@ export async function bootstrapRuntime(
505
496
  platformExternalServices.externalServices = {
506
497
  ...buildRelayExternalServiceMethods(() => externalServices.daemonServer),
507
498
  inspect: inspectExternalServices,
499
+ // Undelivered daemon receipts (update/crash/migration) for the attach-time
500
+ // consuming read; empty when no daemon runs. Marked delivered once served.
501
+ collectDaemonReceipts: () => (externalServices.daemonServer as { collectDaemonReceipts?: () => readonly { id: string; text: string; at: number }[] } | null)?.collectDaemonReceipts?.() ?? [],
508
502
  restart: async () => {
509
503
  if (externalServicesPromise) {
510
504
  try {
@@ -527,10 +521,19 @@ export async function bootstrapRuntime(
527
521
  externalServices = await externalServicesPromise;
528
522
  controlPlaneRecentEventsRef.value = (limit) => externalServices.listRecentControlPlaneEvents(limit);
529
523
  syncSessionSpineToHostStatus(externalServices.daemonStatus, companionTokenRecord.token);
524
+ await renderDaemonAttachNotices(companionTokenRecord.token);
530
525
  requestRender();
531
526
  return inspectExternalServices();
532
527
  },
533
528
  };
529
+ // The attach-time consuming read, exactly once as notices. An adopted EXTERNAL daemon has no in-process handle (daemonServer null), so read its own /status?receipts=consume over HTTP; the embedded daemon keeps the in-process fold.
530
+ const renderDaemonAttachNotices = async (daemonToken: string): Promise<void> => {
531
+ const daemonStatus = externalServices.daemonStatus;
532
+ const notices = daemonStatus.mode === 'external' && daemonStatus.baseUrl
533
+ ? await consumeExternalDaemonAttachNotices({ baseUrl: daemonStatus.baseUrl, authToken: daemonToken })
534
+ : consumeDaemonAttachNotices({ configManager, collectReceipts: () => platformExternalServices.externalServices.collectDaemonReceipts() });
535
+ for (const notice of notices) systemMessageRouter.high(`[Daemon] ${notice}`);
536
+ };
534
537
  deferredStartup.schedule({
535
538
  label: 'plugins',
536
539
  run: async () => {
@@ -591,6 +594,7 @@ export async function bootstrapRuntime(
591
594
  externalServices = await externalServicesPromise;
592
595
  controlPlaneRecentEventsRef.value = (limit) => externalServices.listRecentControlPlaneEvents(limit);
593
596
  syncSessionSpineToHostStatus(externalServices.daemonStatus, companionTokenRecord.token);
597
+ await renderDaemonAttachNotices(companionTokenRecord.token);
594
598
  requestRender();
595
599
  },
596
600
  onError: (error) => {
@@ -713,7 +717,7 @@ export async function bootstrapRuntime(
713
717
  agentStatusIntervalRef,
714
718
  orchestratorRefs,
715
719
  setRenderRequest,
716
- permissionPromptRef,
720
+ permissionPromptRef, trustPromptRef,
717
721
  loadLastConversation: () => loadLastConversation({
718
722
  workingDirectory: services.workingDirectory,
719
723
  homeDirectory: services.homeDirectory,
@@ -774,10 +778,8 @@ export async function bootstrapRuntime(
774
778
  },
775
779
  };
776
780
 
777
- // ── Phase 12b: Operator Control Plane wiring (feature-gated) ──────────────
778
- // Wire the OpsControlPlane into CommandContext when the feature flag is enabled.
779
- // The store and task manager are created unconditionally so they reflect the
780
- // real runtime state (tasks registered before the flag check are visible).
781
+ // ── Phase 12b: Operator Control Plane wiring (capability-gated); store and
782
+ // task manager exist unconditionally so pre-gate tasks stay visible. ───────
781
783
  ctx.commandContext.ops.acpManager = acpManager;
782
784
  if (opsControlPlane) {
783
785
  ctx.commandContext.openOpsPanel = () => {
@@ -0,0 +1,46 @@
1
+ /**
2
+ * channel-composition — route bindings, surface registry, and channel plugin
3
+ * registry for the TUI composition root (split out of services.ts for the
4
+ * line cap).
5
+ *
6
+ * RECORDED DIVERGENCE from the SDK composition root: SurfaceRegistry and
7
+ * ChannelPluginRegistry are constructed WITHOUT the gate manager. The SDK's
8
+ * surface gate map only names six surfaces (web/slack/discord/ntfy/webhook/
9
+ * homeassistant) and reads every other adapter (telegram, whatsapp, signal,
10
+ * imessage, msteams, ...) as OFF whenever a manager is present — no settings
11
+ * key could re-enable them. Until the gate map covers every adapter, the
12
+ * honest governing switch here stays the surfaces.<id>.enabled config each
13
+ * adapter already reads, exactly as the feature surface's own
14
+ * constant-binding rule states. RouteBindingManager keeps its manager — its
15
+ * route-binding gate is a real registry feature.
16
+ */
17
+
18
+ import { AutomationRouteStore } from '@pellux/goodvibes-sdk/platform/automation';
19
+ import { ChannelPluginRegistry, RouteBindingManager, SurfaceRegistry } from '@pellux/goodvibes-sdk/platform/channels';
20
+ import type { ConfigManager } from '@pellux/goodvibes-sdk/platform/config';
21
+ import type { FeatureFlagManager, RuntimeEventBus } from '@/runtime/index.ts';
22
+ import type { RuntimeStore } from './store/index.ts';
23
+
24
+ export interface ChannelComposition {
25
+ readonly routeBindings: RouteBindingManager;
26
+ readonly surfaceRegistry: SurfaceRegistry;
27
+ readonly channelPlugins: ChannelPluginRegistry;
28
+ }
29
+
30
+ export function createChannelComposition(options: {
31
+ readonly configManager: ConfigManager;
32
+ readonly runtimeStore: RuntimeStore;
33
+ readonly runtimeBus: RuntimeEventBus;
34
+ readonly featureFlags: FeatureFlagManager;
35
+ }): ChannelComposition {
36
+ const routeBindings = new RouteBindingManager({
37
+ store: new AutomationRouteStore({ configManager: options.configManager }),
38
+ runtimeStore: options.runtimeStore,
39
+ runtimeBus: options.runtimeBus,
40
+ featureFlags: options.featureFlags,
41
+ });
42
+ const surfaceRegistry = new SurfaceRegistry(options.configManager, options.runtimeStore);
43
+ const channelPlugins = new ChannelPluginRegistry();
44
+ surfaceRegistry.attachPluginRegistry(channelPlugins);
45
+ return { routeBindings, surfaceRegistry, channelPlugins };
46
+ }
@@ -2,8 +2,7 @@
2
2
  // code-index-services.ts — repo source-tree code index (TUI wiring)
3
3
  //
4
4
  // Constructs the TUI's repo source-tree code index: CodeIndexStore
5
- // (@pellux/goodvibes-sdk/platform/state, landed on SDK main as wo802/W5.3
6
- // Stage A). Extracted into its own module rather than built inline in
5
+ // (@pellux/goodvibes-sdk/platform/state). Extracted into its own module rather than built inline in
7
6
  // services.ts: services.ts sits at the architecture check's 800-line cap
8
7
  // (scripts/check-architecture.ts), so any new service gets its own
9
8
  // construction module and a single wiring call there (mirrors
@@ -28,7 +27,7 @@
28
27
  // (interactive main.ts, the daemon, and every test fixture) shares one
29
28
  // honest, user-visible on/off switch rather than needing to thread a new
30
29
  // boolean through every call site. With it off (the default), `/codebase
31
- // build` is the explicit, visible trigger — exactly the shape wo804's brief
30
+ // build` is the explicit, visible trigger — exactly the shape this design
32
31
  // asks for.
33
32
  //
34
33
  // Shares memoryEmbeddingRegistry with MemoryStore (constructed in
@@ -68,7 +67,7 @@ export interface CodeIndexServicesDeps {
68
67
  }
69
68
 
70
69
  /**
71
- * Whether Stage B code auto-injection / tool-site reindex may run: the SAME
70
+ * Whether code auto-injection / tool-site reindex may run: the SAME
72
71
  * storage.codeIndexEnabled switch that gates auto-build. Read live so a runtime
73
72
  * /config toggle takes effect without restart. (The separate, default-off
74
73
  * `agent-passive-code-injection` feature flag gates injection additionally; this