@pellux/goodvibes-tui 1.17.0 → 1.19.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 (104) hide show
  1. package/CHANGELOG.md +208 -0
  2. package/README.md +1 -1
  3. package/docs/foundation-artifacts/operator-contract.json +5025 -1636
  4. package/package.json +7 -7
  5. package/src/cli/endpoints.ts +50 -4
  6. package/src/cli/help.ts +29 -2
  7. package/src/cli/management-commands.ts +99 -23
  8. package/src/cli/management.ts +0 -2
  9. package/src/cli/service-posture.ts +5 -3
  10. package/src/cli/status.ts +27 -6
  11. package/src/cli/surface-command.ts +29 -15
  12. package/src/core/consolidation-receipt.ts +41 -0
  13. package/src/core/conversation-rendering.ts +14 -5
  14. package/src/core/memory-diagnostics-gateway.ts +99 -0
  15. package/src/core/memory-provenance.ts +67 -0
  16. package/src/core/memory-status.ts +118 -0
  17. package/src/core/pairing-devices.ts +49 -0
  18. package/src/core/pairing-handoff.ts +101 -0
  19. package/src/core/pairing-offers.ts +83 -0
  20. package/src/core/pairing-origin.ts +78 -0
  21. package/src/core/pairing-tailscale-gateway.ts +56 -0
  22. package/src/core/power-chip-source.ts +91 -0
  23. package/src/core/power-status.ts +95 -0
  24. package/src/core/stable-host.ts +171 -0
  25. package/src/core/stream-event-wiring.ts +21 -0
  26. package/src/core/turn-budget-outcome.ts +60 -0
  27. package/src/core/turn-cancellation.ts +39 -0
  28. package/src/core/turn-event-wiring.ts +6 -0
  29. package/src/core/voice-provision-gateway.ts +202 -0
  30. package/src/core/voice-provision-status.ts +197 -0
  31. package/src/daemon/cli.ts +127 -60
  32. package/src/input/command-registry.ts +21 -0
  33. package/src/input/commands/acp-runtime.ts +72 -0
  34. package/src/input/commands/devices-runtime.ts +109 -0
  35. package/src/input/commands/experience-runtime.ts +25 -4
  36. package/src/input/commands/git-runtime.ts +28 -4
  37. package/src/input/commands/graph-runtime.ts +45 -0
  38. package/src/input/commands/health-runtime.ts +15 -1
  39. package/src/input/commands/operator-rpc.ts +32 -11
  40. package/src/input/commands/power-runtime.ts +56 -0
  41. package/src/input/commands/qrcode-runtime.ts +14 -19
  42. package/src/input/commands/queue-runtime.ts +70 -0
  43. package/src/input/commands/workstream-attempts.ts +6 -1
  44. package/src/input/commands/workstream-runtime.ts +44 -25
  45. package/src/input/commands/worktree-runtime.ts +35 -8
  46. package/src/input/commands.ts +10 -0
  47. package/src/input/config-modal-types.ts +11 -0
  48. package/src/input/config-modal.ts +65 -2
  49. package/src/input/handler-shortcuts.ts +23 -0
  50. package/src/input/keybindings.ts +19 -1
  51. package/src/input/settings-modal-data.ts +31 -37
  52. package/src/input/settings-modal-types.ts +5 -2
  53. package/src/input/tts-settings-actions.ts +27 -8
  54. package/src/input/voice-local-settings.ts +52 -0
  55. package/src/main.ts +42 -42
  56. package/src/panels/builtin/operations.ts +54 -1
  57. package/src/panels/builtin/shared.ts +12 -0
  58. package/src/panels/builtin-modals.ts +49 -32
  59. package/src/panels/diff-panel.ts +34 -1
  60. package/src/panels/fleet-acts.ts +446 -0
  61. package/src/panels/fleet-gateway.ts +117 -0
  62. package/src/panels/fleet-observed-render.ts +128 -0
  63. package/src/panels/fleet-panel-format.ts +120 -4
  64. package/src/panels/fleet-panel-worktree-detail.ts +14 -0
  65. package/src/panels/fleet-panel.ts +60 -44
  66. package/src/panels/fleet-read-model.ts +34 -3
  67. package/src/panels/fleet-spawn.ts +266 -0
  68. package/src/panels/fleet-stop.ts +38 -2
  69. package/src/panels/fleet-tabs.ts +2 -2
  70. package/src/panels/memory-consolidation-gateway.ts +94 -0
  71. package/src/panels/modals/devices-modal.ts +161 -0
  72. package/src/panels/modals/memory-modal.ts +149 -5
  73. package/src/panels/modals/pairing-modal.ts +198 -68
  74. package/src/panels/modals/security-modal.ts +6 -0
  75. package/src/panels/workstream-graph-render.ts +123 -0
  76. package/src/renderer/composer-fragments.ts +69 -0
  77. package/src/renderer/settings-modal-helpers.ts +9 -0
  78. package/src/renderer/settings-modal.ts +4 -2
  79. package/src/renderer/shell-surface.ts +3 -0
  80. package/src/renderer/tool-call.ts +12 -6
  81. package/src/renderer/ui-factory.ts +32 -0
  82. package/src/runtime/bootstrap-command-context.ts +3 -1
  83. package/src/runtime/bootstrap-command-parts.ts +3 -1
  84. package/src/runtime/bootstrap-core.ts +6 -6
  85. package/src/runtime/bootstrap-shell.ts +7 -0
  86. package/src/runtime/code-index-services.ts +15 -1
  87. package/src/runtime/durability-services.ts +36 -1
  88. package/src/runtime/fleet-services.ts +41 -0
  89. package/src/runtime/idle-power-services.ts +85 -0
  90. package/src/runtime/index.ts +2 -0
  91. package/src/runtime/interaction-seams.ts +109 -0
  92. package/src/runtime/knowledge-services.ts +94 -0
  93. package/src/runtime/legacy-daemon-migration.ts +38 -6
  94. package/src/runtime/legacy-daemon-reconcile.ts +439 -0
  95. package/src/runtime/memory-governance-services.ts +79 -0
  96. package/src/runtime/notification-dispatch.ts +28 -0
  97. package/src/runtime/power-keepawake-remote.ts +75 -0
  98. package/src/runtime/process-lifecycle.ts +3 -1
  99. package/src/runtime/services.ts +105 -107
  100. package/src/runtime/ui-services.ts +3 -0
  101. package/src/runtime/voice-setup-services.ts +104 -0
  102. package/src/shell/blocking-input.ts +21 -141
  103. package/src/shell/recovery-input-helpers.ts +53 -166
  104. package/src/version.ts +11 -2
@@ -4,33 +4,28 @@ import { ConfigManager, ServiceRegistry, SubscriptionManager, ToolLLM } from '@p
4
4
  import { SecretsManager } from '../config/secrets.ts';
5
5
  import { AutomationDeliveryManager, AutomationManager } from '@pellux/goodvibes-sdk/platform/automation';
6
6
  import { ChannelDeliveryRouter, ChannelPolicyManager, type ChannelPluginRegistry, type RouteBindingManager, type SurfaceRegistry } from '@pellux/goodvibes-sdk/platform/channels';
7
- import { ApprovalBroker, GatewayMethodCatalog, SharedSessionBroker } from '@pellux/goodvibes-sdk/platform/control-plane';
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
- import {
13
- HomeGraphService,
14
- GOODVIBES_AGENT_KNOWLEDGE_DB_FILE,
15
- HOME_GRAPH_KNOWLEDGE_EXTENSION,
16
- KnowledgeService,
17
- KnowledgeSemanticService,
18
- KnowledgeStore,
19
- ProjectPlanningService,
20
- createProviderBackedKnowledgeSemanticLlm,
21
- createWebKnowledgeGapRepairer,
22
- projectPlanningProjectIdFromPath,
23
- } from '@pellux/goodvibes-sdk/platform/knowledge';
16
+ import { createWebKnowledgeGapRepairer } from '@pellux/goodvibes-sdk/platform/knowledge';
17
+ import type { HomeGraphService, KnowledgeService, ProjectPlanningService } from '@pellux/goodvibes-sdk/platform/knowledge';
18
+ import { createKnowledgeServices } from './knowledge-services.ts';
24
19
  import { MediaProviderRegistry, ensureBuiltinMediaProviders } from '@pellux/goodvibes-sdk/platform/media';
25
20
  import { MultimodalService } from '@pellux/goodvibes-sdk/platform/multimodal';
26
21
  import { AgentMessageBus, AgentOrchestrator, ArchetypeLoader, WrfcController } from '@pellux/goodvibes-sdk/platform/agents';
27
22
  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';
23
+ import { FileStateCache, FileUndoManager, MemoryConsolidationScheduler, MemoryEmbeddingProviderRegistry, MemoryRegistry, MemoryStore, ModeManager, ProjectIndex, resolveCanonicalMemoryDbPath, type CodeIndexStore, type CodeIndexReindexScheduler } from '@pellux/goodvibes-sdk/platform/state';
29
24
  import type { StoreSnapshotScheduler } from '@pellux/goodvibes-sdk/platform/state/store-snapshots';
30
25
  import type { UserPermissionRuleStore } from '@pellux/goodvibes-sdk/platform/permissions';
31
26
  import { buildExecPromptAnswerHandler } from '@pellux/goodvibes-sdk/platform/runtime/permissions/exec-prompt-wiring';
32
27
  import { buildLocalhostFetchApproval } from '@pellux/goodvibes-sdk/platform/runtime/permissions/localhost-fetch-approval';
33
- import { createNotificationDispatcher, wireRuntimeNotificationBridge, type NotificationDispatcher } from './notification-dispatch.ts';
28
+ import { createNotificationDispatcher, wireRuntimeNotificationBridge, wireMemoryPressureNotice, type NotificationDispatcher } from './notification-dispatch.ts';
34
29
  import { createDurabilityServices } from './durability-services.ts';
35
30
  import { MemorySpineClient, createLocalMemoryAccess } from '@pellux/goodvibes-sdk/platform/runtime/memory-spine';
36
31
  import { WorkspaceCheckpointManager } from '@pellux/goodvibes-sdk/platform/workspace';
@@ -44,6 +39,9 @@ import {
44
39
  type ShellPathService, type FeatureFlagManager, createFeatureFlagManager, PolicyRuntimeState,
45
40
  } from '@/runtime/index.ts';
46
41
  import { VoiceProviderRegistry, VoiceService, ensureBuiltinVoiceProviders } from '@pellux/goodvibes-sdk/platform/voice';
42
+ import { CacheRegistry, PauseController, type MemoryGovernor } from '@pellux/goodvibes-sdk/platform/runtime/memory';
43
+ import { wireMemoryGovernance } from './memory-governance-services.ts';
44
+ import { wireVoiceSetup } from './voice-setup-services.ts';
47
45
  import { WebSearchProviderRegistry, WebSearchService } from '@pellux/goodvibes-sdk/platform/web-search';
48
46
  import { PanelManager } from '../panels/panel-manager.ts';
49
47
  import { HookActivityTracker } from '@pellux/goodvibes-sdk/platform/hooks';
@@ -55,17 +53,18 @@ import { SessionManager, CrossSessionTaskRegistry, SessionChangeTracker } from '
55
53
  import { ApiTokenAuditor, UserAuthManager } from '@pellux/goodvibes-sdk/platform/security';
56
54
  import { WebhookNotifier } from '@pellux/goodvibes-sdk/platform/integrations';
57
55
  import { McpRegistry } from '@pellux/goodvibes-sdk/platform/mcp';
58
- import { BenchmarkStore, CacheHitTracker, computeUsageCostUsd, FavoritesStore, ModelLimitsService, ProviderCapabilityRegistry, ProviderOptimizer, ProviderRegistry } from '@pellux/goodvibes-sdk/platform/providers';
56
+ import { BenchmarkStore, CacheHitTracker, FavoritesStore, ModelLimitsService, ProviderCapabilityRegistry, ProviderOptimizer, ProviderRegistry } from '@pellux/goodvibes-sdk/platform/providers';
59
57
  import { KeybindingsManager } from '../input/keybindings.ts';
60
58
  import { AdaptivePlanner, DeterministicReplayEngine, ExecutionPlanManager, SessionLineageTracker, SessionMemoryStore } from '@pellux/goodvibes-sdk/platform/core';
61
59
  import { deriveFeatureStates, bindFeatureSettingsBridge } from '@pellux/goodvibes-sdk/platform/runtime/state';
62
60
  import { createChannelComposition } from './channel-composition.ts';
63
61
  import { applyProviderOptimizerConfigMode, bindProviderOptimizerFeatureFlag } from './provider-optimizer-wiring.ts';
64
62
  import { type ArchivableProcessRegistry } from '@pellux/goodvibes-sdk/platform/runtime/fleet';
63
+ import { createFleetServices } from './fleet-services.ts';
65
64
  import { createWorkstreamServices, type OrchestrationEngine, type WorkstreamCommandService } from './workstream-services.ts';
66
65
  import { wireFleetNeedsInputPush } from './fleet-needs-input-push.ts';
67
66
  import { codeIndexDbPath, createCodeIndexServices, isCodeInjectionSettingEnabled } from './code-index-services.ts';
68
- import { WorkPlanStore } from '../work-plans/work-plan-store.ts';
67
+ import type { WorkPlanStore } from '../work-plans/work-plan-store.ts';
69
68
  import { registerDaemonHandlers, type DaemonHandlerSurfaces } from '../daemon/handlers/index.ts';
70
69
  import type { HandlerContext, HandlerLogger } from '../daemon/handlers/context.ts';
71
70
  import { createDaemonCredentialStore } from '../daemon/handlers/credentials.ts';
@@ -79,8 +78,6 @@ import { registerRemoteSurface } from '../daemon/handlers/remote/index.ts';
79
78
  import { WorkspaceTrustManager } from './trust/workspace-trust.ts';
80
79
  import { ensureConfiguredModelIsRoutable } from './provider-fallback.ts';
81
80
 
82
- const REGULAR_KNOWLEDGE_DB_FILE = 'knowledge-wiki.sqlite';
83
- const HOME_GRAPH_KNOWLEDGE_DB_FILE = 'knowledge-home-graph.sqlite';
84
81
  export interface RuntimeServicesOptions {
85
82
  readonly runtimeBus: RuntimeEventBus;
86
83
  readonly runtimeStore: RuntimeStore;
@@ -90,6 +87,13 @@ export interface RuntimeServicesOptions {
90
87
  readonly getConversationTitle?: () => string | undefined;
91
88
  readonly workingDir: string;
92
89
  readonly homeDirectory: string;
90
+ /** Opt-in (daemon-side only): fold host-observed external coding-agent sessions
91
+ * into the fleet as 'observed-external' rows. Interactive leaves it off and reads
92
+ * the daemon snapshot. Mirrors the SDK's own createRuntimeServices option. */
93
+ readonly observeExternalAgents?: boolean | undefined;
94
+ /** Host power seam opt-in. Fork mirrors the SDK: non-spawning unavailable-seam
95
+ * default (idle-power-services.ts); daemon + embedded runtime pass createHostPowerSeam(). */
96
+ readonly powerSeam?: Parameters<typeof wireIdlePowerAndLiveTurn>[0]['powerSeam'];
93
97
  }
94
98
 
95
99
  export interface RuntimeServices {
@@ -135,6 +139,7 @@ export interface RuntimeServices {
135
139
  readonly serviceRegistry: ServiceRegistry;
136
140
  readonly secretsManager: SecretsManager;
137
141
  readonly stepUpService: StepUpService;
142
+ readonly pairingTokens: PairingTokenManager; // backs pairing.tokens.* verbs + the settings device surface (mirrors the SDK composition)
138
143
  readonly subscriptionManager: SubscriptionManager;
139
144
  readonly localUserAuthManager: UserAuthManager;
140
145
  readonly profileManager: ProfileManager;
@@ -198,6 +203,15 @@ export interface RuntimeServices {
198
203
  readonly codeIndexReindexScheduler: CodeIndexReindexScheduler; // tool-site reindex
199
204
  /** 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). */
200
205
  readonly storeSnapshotScheduler: StoreSnapshotScheduler;
206
+ readonly memoryConsolidationScheduler: MemoryConsolidationScheduler;
207
+ readonly powerManager: PowerManager;
208
+ /** The daemon's memory governor (default ON). Backs ops.memory.get and defends the daemon's footprint by tier. */
209
+ readonly memoryGovernor: MemoryGovernor;
210
+ /** Registry of every retained cache the governor can shrink (knowledge stores + shared session broker). */
211
+ readonly cacheRegistry: CacheRegistry;
212
+ /** Controller the governor uses to pause/resume the deferrable background jobs under pressure. */
213
+ readonly pauseController: PauseController;
214
+ readonly sessionLiveTurnControls: SessionLiveTurnControlsHolder;
201
215
  /** 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. */
202
216
  readonly processRegistry: ArchivableProcessRegistry;
203
217
  readonly modeManager: ModeManager;
@@ -227,6 +241,19 @@ export function createRuntimeServices(options: RuntimeServicesOptions): RuntimeS
227
241
  bindFeatureSettingsBridge(configManager, featureFlags);
228
242
  }
229
243
  const runtimeDispatch = createDomainDispatch(options.runtimeStore);
244
+ // Memory governance seams built EARLY (mirrors the SDK's own createRuntimeServices)
245
+ // so the scheduler gates and the knowledge background jobs can consult the pause
246
+ // controller before the MemoryGovernor (constructed at the composition tail)
247
+ // drives it. The admission gate is late-bound: expensive entry points capture
248
+ // this closure now and the governor binds into it at the tail — until then
249
+ // everything is admitted (the daemon is still booting).
250
+ const cacheRegistry = new CacheRegistry();
251
+ const pauseController = new PauseController();
252
+ const MEMORY_BACKGROUND_JOB_IDS = ['knowledge-self-improvement', 'memory-consolidation', 'code-index-reindex'];
253
+ const admitExpensiveWorkRef: { current: ((label: string) => { allowed: boolean; reason?: string | undefined }) | null } = { current: null };
254
+ const admitExpensiveWork = (label: string): { allowed: boolean; reason?: string | undefined } =>
255
+ admitExpensiveWorkRef.current?.(label) ?? { allowed: true };
256
+ const isKnowledgeBackgroundPaused = (): boolean => pauseController.isPaused('knowledge-self-improvement');
230
257
  const gatewayMethods = new GatewayMethodCatalog();
231
258
  const panelManager = new PanelManager();
232
259
  // (the purge): MIGRATE-TO-MODAL surface + redirect registration moved to
@@ -249,6 +276,7 @@ export function createRuntimeServices(options: RuntimeServicesOptions): RuntimeS
249
276
  });
250
277
  // Step-up (WebAuthn) ceremony service, shared between the ceremony gateway verbs and the relay gate's verifier.
251
278
  const stepUpService = new StepUpService({ secrets: secretsManager });
279
+ const pairingTokens = new PairingTokenManager(shellPaths.resolveUserPath('control-plane', 'pairing-tokens.json'));
252
280
  const subscriptionManager = new SubscriptionManager(shellPaths.resolveUserPath('tui', 'subscriptions.json'));
253
281
  const serviceRegistry = new ServiceRegistry(shellPaths.resolveProjectPath('tui', 'services.json'), {
254
282
  secretsManager,
@@ -414,61 +442,15 @@ export function createRuntimeServices(options: RuntimeServicesOptions): RuntimeS
414
442
  return record.id;
415
443
  },
416
444
  });
417
- const knowledgeStore = new KnowledgeStore({
418
- configManager,
419
- dbFileName: REGULAR_KNOWLEDGE_DB_FILE,
420
- });
421
- const agentKnowledgeStore = new KnowledgeStore({
422
- configManager,
423
- dbFileName: GOODVIBES_AGENT_KNOWLEDGE_DB_FILE,
424
- });
425
- const homeGraphKnowledgeStore = new KnowledgeStore({
426
- configManager,
427
- dbFileName: HOME_GRAPH_KNOWLEDGE_DB_FILE,
428
- });
429
- const knowledgeSemanticLlm = createProviderBackedKnowledgeSemanticLlm(providerRegistry, {
430
- timeoutMs: 20_000,
431
- maxConcurrent: 1,
432
- });
433
- const knowledgeSemanticService = new KnowledgeSemanticService(knowledgeStore, {
434
- llm: knowledgeSemanticLlm,
435
- maxLlmSourcesPerReindex: 3,
436
- });
437
- const homeGraphSemanticService = new KnowledgeSemanticService(homeGraphKnowledgeStore, {
438
- llm: knowledgeSemanticLlm,
439
- maxLlmSourcesPerReindex: 3,
440
- objectProfiles: HOME_GRAPH_KNOWLEDGE_EXTENSION.objectProfiles,
441
- });
442
- const agentKnowledgeSemanticService = new KnowledgeSemanticService(agentKnowledgeStore, {
443
- llm: knowledgeSemanticLlm,
444
- maxLlmSourcesPerReindex: 3,
445
- });
446
- const knowledgeService = new KnowledgeService(knowledgeStore, artifactStore, undefined, {
447
- memoryRegistry,
448
- runtimeBus: options.runtimeBus,
449
- semanticService: knowledgeSemanticService,
450
- });
451
- knowledgeService.attachRuntimeBus(options.runtimeBus);
452
- const agentKnowledgeService = new KnowledgeService(agentKnowledgeStore, artifactStore, undefined, {
453
- memoryRegistry,
454
- runtimeBus: options.runtimeBus,
455
- semanticService: agentKnowledgeSemanticService,
456
- });
457
- agentKnowledgeService.attachRuntimeBus(options.runtimeBus);
458
- const homeGraphService = new HomeGraphService(homeGraphKnowledgeStore, artifactStore, {
459
- semanticService: homeGraphSemanticService,
460
- });
461
- const projectPlanningProjectId = projectPlanningProjectIdFromPath(workingDirectory);
462
- const projectPlanningService = new ProjectPlanningService(knowledgeStore, {
463
- defaultProjectId: projectPlanningProjectId,
464
- });
465
- const workPlanStore = new WorkPlanStore({
466
- homeDirectory,
467
- projectId: projectPlanningProjectId,
468
- projectRoot: workingDirectory,
469
- });
445
+ // Knowledge/wiki + home-graph stack (governor backpressure wired in) — see knowledge-services.ts.
446
+ const {
447
+ knowledgeStore, agentKnowledgeStore, homeGraphKnowledgeStore,
448
+ knowledgeSemanticService, homeGraphSemanticService, agentKnowledgeSemanticService,
449
+ knowledgeService, agentKnowledgeService, homeGraphService,
450
+ projectPlanningService, projectPlanningProjectId, workPlanStore,
451
+ } = createKnowledgeServices({ configManager, providerRegistry, artifactStore, memoryRegistry, runtimeBus: options.runtimeBus, workingDirectory, homeDirectory, isBackgroundPaused: isKnowledgeBackgroundPaused, admitExpensiveWork });
470
452
  const voiceProviders = new VoiceProviderRegistry();
471
- ensureBuiltinVoiceProviders(voiceProviders);
453
+ ensureBuiltinVoiceProviders(voiceProviders, { readConfig: (key) => configManager.get(key as Parameters<typeof configManager.get>[0]) });
472
454
  const voiceService = new VoiceService(voiceProviders);
473
455
  const webSearchProviders = new WebSearchProviderRegistry({
474
456
  env: process.env,
@@ -478,18 +460,9 @@ export function createRuntimeServices(options: RuntimeServicesOptions): RuntimeS
478
460
  serviceRegistry,
479
461
  featureFlags,
480
462
  });
481
- knowledgeSemanticService.setGapRepairer(createWebKnowledgeGapRepairer({
482
- searchService: webSearchService,
483
- ingestService: knowledgeService,
484
- }));
485
- agentKnowledgeSemanticService.setGapRepairer(createWebKnowledgeGapRepairer({
486
- searchService: webSearchService,
487
- ingestService: agentKnowledgeService,
488
- }));
489
- homeGraphSemanticService.setGapRepairer(createWebKnowledgeGapRepairer({
490
- searchService: webSearchService,
491
- ingestService: homeGraphService,
492
- }));
463
+ for (const [semantic, ingest] of [[knowledgeSemanticService, knowledgeService], [agentKnowledgeSemanticService, agentKnowledgeService], [homeGraphSemanticService, homeGraphService]] as const) {
464
+ semantic.setGapRepairer(createWebKnowledgeGapRepairer({ searchService: webSearchService, ingestService: ingest }));
465
+ }
493
466
  const mediaProviders = new MediaProviderRegistry();
494
467
  ensureBuiltinMediaProviders(mediaProviders, artifactStore, providerRegistry);
495
468
  const multimodalService = new MultimodalService(artifactStore, mediaProviders, voiceService, knowledgeService);
@@ -587,40 +560,55 @@ export function createRuntimeServices(options: RuntimeServicesOptions): RuntimeS
587
560
  // Repo source-tree code index, sharing memoryEmbeddingRegistry
588
561
  // with MemoryStore above. Auto-build is config-gated (default off) — see
589
562
  // code-index-services.ts's header doc.
590
- const { codeIndexStore, codeIndexReindexScheduler } = createCodeIndexServices({ workingDirectory, configManager, memoryEmbeddingRegistry });
563
+ const { codeIndexStore, codeIndexReindexScheduler } = createCodeIndexServices({ workingDirectory, configManager, memoryEmbeddingRegistry, isReindexPaused: () => pauseController.isPaused('code-index-reindex'), admitExpensiveWork });
591
564
  // Store snapshots + durable remembered-approval rules + the live credential
592
565
  // chain, mirroring the SDK composition — see durability-services.ts.
593
566
  const { storeSnapshotScheduler, userPermissionRuleStore } = createDurabilityServices({
594
- configManager, secretsManager, providerRegistry, memoryDbPath, codeIndexDbPath: codeIndexDbPath(workingDirectory),
567
+ configManager, secretsManager, providerRegistry, memoryDbPath, codeIndexDbPath: codeIndexDbPath(workingDirectory), workingDirectory, surfaceRoot: 'tui', homeDirectory, shellPaths, // + retention-sweep roots & live config watch (mirrors the SDK)
595
568
  });
596
569
  const codeInjectionOrchestratorDeps = { codeIndex: codeIndexStore, isCodeInjectionSettingEnabled: () => isCodeInjectionSettingEnabled(configManager), codeIndexReindexScheduler }; // Code-injection seam (agent here; main via orchestrator-core-services.ts)
597
- // Shared, archive-aware fleet registry — see gateway-verbs.ts's factory doc.
598
- const processRegistry = createArchivableFleetRegistry({
599
- agentManager,
600
- wrfcController,
570
+ const { processRegistry } = createFleetServices({ // Shared archive-aware fleet registry (+ daemon observed rows) — see fleet-services.ts
571
+ agentManager, wrfcController,
601
572
  orchestrationEngine, // Folds workstream/phase/work-item nodes into the fleet
602
573
  codeIndexService: codeIndexStore, // Folds a single 'code-index' node into the fleet
603
- processManager,
604
- watcherRegistry,
605
- workflow,
606
- approvalBroker,
607
- sessionBroker,
574
+ processManager, watcherRegistry, workflow, approvalBroker, sessionBroker,
608
575
  messageBus: agentMessageBus, // Backs steer()/`steerable` (the Fleet steer composer builds on top)
609
576
  automationManager, // Folds /schedule AutomationJobs into the fleet as 'schedule' nodes
610
577
  runtimeBus: options.runtimeBus,
611
- // Honest-unpriced through the ONE pricing resolver (manual -> registration -> provider-served
612
- // -> catalog -> unknown); unknown/subscription yields null, never $0. Mirrors the SDK composition.
613
- priceUsage: (model, usage) => (model ? computeUsageCostUsd(providerRegistry.resolveModelPricing(model), usage) : null),
578
+ observeExternalAgents: options.observeExternalAgents, providerRegistry, // observeExternalAgents is daemon-side only
614
579
  });
615
- const modeManager = new ModeManager();
616
- const fileUndoManager = new FileUndoManager();
580
+ const modeManager = new ModeManager(); const fileUndoManager = new FileUndoManager();
617
581
  const workspaceCheckpointManager = createWorkspaceCheckpointing({ workspaceRoot: workingDirectory, runtimeBus: options.runtimeBus, configManager });
582
+ // memory-consolidation honors governor backpressure: it ticks only when idle
583
+ // AND the 'memory-consolidation' job is not paused AND expensive work is
584
+ // admitted (mirrors the SDK's own createRuntimeServices idle gate).
585
+ const { memoryConsolidationScheduler, powerManager, sessionLiveTurnControls } = wireIdlePowerAndLiveTurn({ configManager, memoryRegistry, runtimeBus: options.runtimeBus, isIdle: () => sessionBroker.countBusySessions() === 0 && !pauseController.isPaused('memory-consolidation') && admitExpensiveWork('memory consolidation').allowed, snapshotTick: () => storeSnapshotScheduler.tick(), heartbeat: async () => { await automationManager.triggerHeartbeat({ source: 'wake-catchup' }); }, powerSeam: options.powerSeam });
586
+
587
+ // Construct + start the MemoryGovernor (default ON — a safety feature) with
588
+ // the standard KNOWN cache adapters (knowledge stores + shared session
589
+ // broker), then late-bind the admission gate the expensive entry points
590
+ // captured earlier. Mirrors the SDK's own createRuntimeServices composition tail.
591
+ const { memoryGovernor } = wireMemoryGovernance({
592
+ configManager,
593
+ runtimeBus: options.runtimeBus,
594
+ cacheRegistry,
595
+ pauseController,
596
+ jobIds: MEMORY_BACKGROUND_JOB_IDS,
597
+ receiptPath: shellPaths.resolveProjectPath('tui', 'memory', 'tripwire-receipt.json'),
598
+ knowledgeStores: [knowledgeStore, agentKnowledgeStore, homeGraphKnowledgeStore],
599
+ sessionBroker,
600
+ // Graceful tripwire shutdown flushes in-flight state via ASYNC store
601
+ // snapshots so the governor's 10s shutdown ceiling stays enforceable.
602
+ onTripwireShutdown: async () => { await storeSnapshotScheduler.snapshotAllAsync('tripwire'); },
603
+ });
604
+ admitExpensiveWorkRef.current = (label) => memoryGovernor.admitExpensiveWork(label);
605
+
606
+ // Managed local-voice provisioning (voice.local.status/install) — single-flight
607
+ // one-act install + no-network status; see voice-setup-services.ts.
608
+ const { voiceSetup } = wireVoiceSetup({ configManager, shellPaths, voiceProviders, admitExpensiveWork });
618
609
 
619
- // Shared terminal-shell wrapper over the SDK's registerGatewayVerbGroups (see gateway-verbs.ts): 501s the ws-only
620
- // family without it. principals.*/channels.profiles.*/ci.* register unconditionally; checkin.* only with
621
- // channelDeliveryRouter/providerRegistry/automationManager/sessionLister ALL present; fleet needs-input push gated by
622
- // runtimeBus/sessionPresence (fleet-needs-input-push.ts). conversationRewindPort serves conversation-scope rewind live.
623
- 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, ...wireFleetNeedsInputPush({ registry: processRegistry, runtimeBus: options.runtimeBus, sessionBroker }) });
610
+ // Terminal-shell wrapper over the SDK registerGatewayVerbGroups (gateway-verbs.ts); checkin.*/fleet-needs-input/pairing.* register only when their deps are present. memoryGovernor lights up ops.memory.get; voiceSetup lights up voice.local.status/install.
611
+ 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, memoryGovernor, voiceSetup, relayAvailable: () => configManager.get('relay.enabled') === true, pairingWebOrigin: () => resolvePairingWebOrigin(configManager).origin, ...wireFleetNeedsInputPush({ registry: processRegistry, runtimeBus: options.runtimeBus, sessionBroker }) });
624
612
  const integrationHelpers = new IntegrationHelperService({
625
613
  workingDirectory,
626
614
  homeDirectory,
@@ -683,6 +671,9 @@ export function createRuntimeServices(options: RuntimeServicesOptions): RuntimeS
683
671
  // panel feed (panel_only and burst-collapsed decisions land there).
684
672
  const notificationDispatcher = createNotificationDispatcher(configManager);
685
673
  wireRuntimeNotificationBridge(options.runtimeBus, notificationDispatcher);
674
+ // OPS_MEMORY_PRESSURE is lifted into the notice feed on its own targeted
675
+ // bridge (the high-churn 'ops' domain stays out of the wholesale allowlist).
676
+ wireMemoryPressureNotice(options.runtimeBus, notificationDispatcher);
686
677
 
687
678
  return {
688
679
  workingDirectory,
@@ -723,6 +714,7 @@ export function createRuntimeServices(options: RuntimeServicesOptions): RuntimeS
723
714
  serviceRegistry,
724
715
  secretsManager,
725
716
  stepUpService,
717
+ pairingTokens,
726
718
  subscriptionManager,
727
719
  localUserAuthManager,
728
720
  profileManager,
@@ -782,6 +774,12 @@ export function createRuntimeServices(options: RuntimeServicesOptions): RuntimeS
782
774
  codeIndexStore,
783
775
  codeIndexReindexScheduler,
784
776
  storeSnapshotScheduler,
777
+ memoryConsolidationScheduler,
778
+ powerManager,
779
+ memoryGovernor,
780
+ cacheRegistry,
781
+ pauseController,
782
+ sessionLiveTurnControls,
785
783
  processRegistry,
786
784
  modeManager,
787
785
  fileUndoManager,
@@ -59,6 +59,8 @@ export interface UiPlatformServices {
59
59
  readonly surfaceRegistry: RuntimeServices['surfaceRegistry'];
60
60
  readonly subscriptionManager: RuntimeServices['subscriptionManager'];
61
61
  readonly secretsManager: SecretsManager;
62
+ /** Per-device pairing tokens — backs the pairing modal QR and the settings device surface. */
63
+ readonly pairingTokens: RuntimeServices['pairingTokens'];
62
64
  readonly tokenAuditor: RuntimeServices['tokenAuditor'];
63
65
  readonly replayEngine: RuntimeServices['replayEngine'];
64
66
  readonly webhookNotifier: RuntimeServices['webhookNotifier'];
@@ -188,6 +190,7 @@ export function createUiRuntimeServices(
188
190
  surfaceRegistry: runtimeServices.surfaceRegistry,
189
191
  subscriptionManager: runtimeServices.subscriptionManager,
190
192
  secretsManager: runtimeServices.secretsManager,
193
+ pairingTokens: runtimeServices.pairingTokens,
191
194
  tokenAuditor: runtimeServices.tokenAuditor,
192
195
  replayEngine: runtimeServices.replayEngine,
193
196
  webhookNotifier: runtimeServices.webhookNotifier,
@@ -0,0 +1,104 @@
1
+ // ---------------------------------------------------------------------------
2
+ // voice-setup-services.ts — managed local-voice provisioning composition (TUI wiring)
3
+ //
4
+ // Backs the daemon's voice.local.status / voice.local.install verbs. Extracted
5
+ // into its own module rather than built inline in services.ts, which sits at
6
+ // the architecture check's 800-line cap (scripts/check-architecture.ts) — new
7
+ // service construction gets its own module and a single wiring call there.
8
+ //
9
+ // Mirrors the SDK's own createRuntimeServices voiceSetup: a single-flight
10
+ // one-act install that provisions the piper engine + a default voice (and
11
+ // whisper STT where a bundle is published) and pre-configures the voice.local.*
12
+ // keys with ownership awareness (values THIS installer previously wrote update
13
+ // to the new managed paths; genuinely user-set values still win; a user-cleared
14
+ // installer value stays cleared). status() is a no-network read.
15
+ // ---------------------------------------------------------------------------
16
+
17
+ import {
18
+ createVoiceInstallProgressTracker,
19
+ localVoiceRuntimeStatus,
20
+ preconfigureLocalVoiceKeys,
21
+ provisionLocalVoiceRuntime,
22
+ readVoiceInstallStamp,
23
+ writeVoiceInstallStamp,
24
+ type VoiceProviderRegistry,
25
+ } from '@pellux/goodvibes-sdk/platform/voice';
26
+ import { singleFlight } from '@pellux/goodvibes-sdk/platform/utils';
27
+ import type { ConfigManager } from '@pellux/goodvibes-sdk/platform/config';
28
+ import type { ShellPathService } from '@/runtime/index.ts';
29
+ import type { VoiceLocalInstallResult } from '../core/voice-provision-status.ts';
30
+
31
+ export interface VoiceSetupServicesDeps {
32
+ readonly configManager: ConfigManager;
33
+ readonly shellPaths: Pick<ShellPathService, 'resolveUserPath'>;
34
+ readonly voiceProviders: VoiceProviderRegistry;
35
+ /** Critical-tier admission gate — a provision run allocates archive + model buffers. */
36
+ readonly admitExpensiveWork: (label: string) => { allowed: boolean; reason?: string | undefined };
37
+ }
38
+
39
+ /** The narrow provisioning slice the voice.local.* gateway verbs consume. */
40
+ export interface VoiceSetupService {
41
+ status(): unknown;
42
+ install(): Promise<VoiceLocalInstallResult>;
43
+ }
44
+
45
+ /** Compose the managed-voice single-flight install + no-network status read. */
46
+ export function wireVoiceSetup(deps: VoiceSetupServicesDeps): { voiceSetup: VoiceSetupService } {
47
+ const { configManager, voiceProviders } = deps;
48
+ const managedVoiceRoot = deps.shellPaths.resolveUserPath('voice');
49
+ // Live install progress: the install verb is plain request/response, so during
50
+ // a multi-hundred-MB provision a surface would otherwise only see busy→receipt.
51
+ // The tracker folds the provisioner's onProgress stream into a poll-able
52
+ // snapshot that status() carries as `installInProgress` WHILE (and only while)
53
+ // an install runs — surfaces poll status during install (mirrors the SDK).
54
+ const progress = createVoiceInstallProgressTracker();
55
+ // Single-flight: concurrent installs join the in-progress promise instead of
56
+ // starting parallel multi-hundred-MB downloads.
57
+ const runVoiceInstall = singleFlight(async () => {
58
+ progress.begin();
59
+ try {
60
+ const provision = await provisionLocalVoiceRuntime({ managedRoot: managedVoiceRoot, onProgress: (p) => progress.onProgress(p) });
61
+ let configured: { set: { key: string; value: string }[]; skipped: { key: string; reason: string }[] } = { set: [], skipped: [] };
62
+ if (provision.tts.state === 'provisioned' && provision.tts.binaryPath && provision.tts.modelPath) {
63
+ const stamp = readVoiceInstallStamp(managedVoiceRoot);
64
+ const receipt = preconfigureLocalVoiceKeys({
65
+ getConfig: (k) => String(configManager.get(k as Parameters<typeof configManager.get>[0]) ?? ''),
66
+ setConfig: (k, v) => configManager.setDynamic(k as Parameters<typeof configManager.setDynamic>[0], v),
67
+ ttsEngine: provision.tts.engine,
68
+ ttsBinary: provision.tts.binaryPath,
69
+ ttsModelPath: provision.tts.modelPath,
70
+ ...(provision.stt.state === 'provisioned' && provision.stt.binaryPath && provision.stt.modelPath
71
+ ? { sttEngine: provision.stt.engine, sttBinary: provision.stt.binaryPath, sttModelPath: provision.stt.modelPath }
72
+ : {}),
73
+ priorInstallWrites: stamp?.configWrites,
74
+ });
75
+ configured = { set: [...receipt.set], skipped: [...receipt.skipped] };
76
+ if (stamp) {
77
+ writeVoiceInstallStamp(managedVoiceRoot, { ...stamp, configWrites: { ...stamp.configWrites, ...receipt.installWrites } });
78
+ }
79
+ // A successful (re-)install clears any tripped local-engine breaker.
80
+ voiceProviders.get('local')?.resetEngineFailureState?.();
81
+ }
82
+ return { provisioned: provision.tts.state === 'provisioned', platform: provision.platform, tts: provision.tts, stt: provision.stt, components: provision.components, configured };
83
+ } finally {
84
+ progress.end();
85
+ }
86
+ });
87
+ const voiceSetup: VoiceSetupService = {
88
+ status: () => {
89
+ const base = localVoiceRuntimeStatus({ managedRoot: managedVoiceRoot });
90
+ const snapshot = progress.snapshot();
91
+ // Carry live per-component progress ONLY while an install is active.
92
+ return snapshot ? { ...base, installInProgress: snapshot } : base;
93
+ },
94
+ install: async () => {
95
+ // Critical-tier admission: refuse honestly instead of piling onto pressure.
96
+ const admission = deps.admitExpensiveWork('voice runtime install');
97
+ if (!admission.allowed) {
98
+ throw new Error(admission.reason ?? 'voice runtime install refused: daemon is under critical memory pressure.');
99
+ }
100
+ return runVoiceInstall();
101
+ },
102
+ };
103
+ return { voiceSetup };
104
+ }