@pellux/goodvibes-daemon 1.28.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 (113) hide show
  1. package/CHANGELOG.md +383 -0
  2. package/LICENSE +21 -0
  3. package/README.md +125 -0
  4. package/bin/goodvibes-daemon +100 -0
  5. package/bin/launcher-support.js +226 -0
  6. package/package.json +96 -0
  7. package/scripts/check-bun.sh +20 -0
  8. package/scripts/postinstall.js +244 -0
  9. package/src/cli/command-catalog.ts +828 -0
  10. package/src/cli/completion.ts +299 -0
  11. package/src/cli/help.ts +167 -0
  12. package/src/cli/index.ts +21 -0
  13. package/src/cli/parser.ts +55 -0
  14. package/src/cli/surface-catalog.ts +26 -0
  15. package/src/cli/types.ts +63 -0
  16. package/src/cluster/daemon-ws-call.ts +235 -0
  17. package/src/cluster/raw-reply-route.ts +111 -0
  18. package/src/config/checkpoint-settings.ts +113 -0
  19. package/src/config/run-daemon-config-migration.ts +47 -0
  20. package/src/config/secret-config.ts +175 -0
  21. package/src/config/secrets.ts +71 -0
  22. package/src/config/surface.ts +24 -0
  23. package/src/core/pairing-banner.ts +82 -0
  24. package/src/daemon/cli.ts +878 -0
  25. package/src/daemon/config-command.ts +281 -0
  26. package/src/daemon/handlers/context.ts +29 -0
  27. package/src/daemon/handlers/contracts.ts +43 -0
  28. package/src/daemon/handlers/credentials.ts +139 -0
  29. package/src/daemon/handlers/drafts/draft-store.ts +427 -0
  30. package/src/daemon/handlers/drafts/index.ts +17 -0
  31. package/src/daemon/handlers/drafts/register.ts +331 -0
  32. package/src/daemon/handlers/errors.ts +18 -0
  33. package/src/daemon/handlers/inbox/aggregator.ts +375 -0
  34. package/src/daemon/handlers/inbox/cursor-store.ts +512 -0
  35. package/src/daemon/handlers/inbox/index.ts +221 -0
  36. package/src/daemon/handlers/inbox/mapping.ts +192 -0
  37. package/src/daemon/handlers/inbox/poller.ts +239 -0
  38. package/src/daemon/handlers/inbox/provider-adapter.ts +171 -0
  39. package/src/daemon/handlers/inbox/providers/discord.ts +276 -0
  40. package/src/daemon/handlers/inbox/providers/email.ts +176 -0
  41. package/src/daemon/handlers/inbox/providers/imap-client.ts +300 -0
  42. package/src/daemon/handlers/inbox/providers/route-util.ts +24 -0
  43. package/src/daemon/handlers/inbox/providers/slack.ts +287 -0
  44. package/src/daemon/handlers/index.ts +117 -0
  45. package/src/daemon/handlers/register.ts +180 -0
  46. package/src/daemon/handlers/remote/backends/cloud-terminal.ts +143 -0
  47. package/src/daemon/handlers/remote/backends/docker.ts +79 -0
  48. package/src/daemon/handlers/remote/backends/index.ts +40 -0
  49. package/src/daemon/handlers/remote/backends/local-process.ts +113 -0
  50. package/src/daemon/handlers/remote/backends/process-runner.ts +127 -0
  51. package/src/daemon/handlers/remote/backends/ssh.ts +126 -0
  52. package/src/daemon/handlers/remote/backends/types.ts +97 -0
  53. package/src/daemon/handlers/remote/dispatcher.ts +181 -0
  54. package/src/daemon/handlers/remote/index.ts +120 -0
  55. package/src/daemon/handlers/remote/peer-registry.ts +357 -0
  56. package/src/daemon/handlers/remote/service.ts +191 -0
  57. package/src/daemon/handlers/routing/inbox-bridge.ts +71 -0
  58. package/src/daemon/handlers/routing/index.ts +261 -0
  59. package/src/daemon/handlers/routing/route-store.ts +319 -0
  60. package/src/daemon/handlers/routing/routing-resolver.ts +75 -0
  61. package/src/daemon/handlers/sqlite-store.ts +303 -0
  62. package/src/daemon/handlers/triage/index.ts +57 -0
  63. package/src/daemon/handlers/triage/integration.ts +213 -0
  64. package/src/daemon/handlers/triage/pipeline.ts +274 -0
  65. package/src/daemon/handlers/triage/scorer.ts +287 -0
  66. package/src/daemon/handlers/triage/tagger/discord.ts +187 -0
  67. package/src/daemon/handlers/triage/tagger/imap.ts +384 -0
  68. package/src/daemon/handlers/triage/tagger/index.ts +184 -0
  69. package/src/daemon/handlers/triage/tagger/shared.ts +70 -0
  70. package/src/daemon/handlers/triage/tagger/slack.ts +69 -0
  71. package/src/daemon/handlers/triage/types.ts +50 -0
  72. package/src/daemon/lifecycle.ts +41 -0
  73. package/src/daemon/local-daemon-state.ts +233 -0
  74. package/src/daemon/pair-command.ts +301 -0
  75. package/src/daemon/provision-wake-model.ts +81 -0
  76. package/src/daemon/send/channels.ts +200 -0
  77. package/src/daemon/send/command.ts +333 -0
  78. package/src/daemon/send/composition.ts +100 -0
  79. package/src/daemon/send/failure-text.ts +93 -0
  80. package/src/daemon/send/inert-text.ts +225 -0
  81. package/src/daemon/send/stdin.ts +24 -0
  82. package/src/daemon/service-commands.ts +530 -0
  83. package/src/daemon/sessions-command.ts +209 -0
  84. package/src/daemon/status-command.ts +481 -0
  85. package/src/daemon/webui-command.ts +339 -0
  86. package/src/runtime/boot-tasks.ts +110 -0
  87. package/src/runtime/cluster-composition.ts +124 -0
  88. package/src/runtime/cluster-group-composition.ts +284 -0
  89. package/src/runtime/conversation-rewind-port.ts +171 -0
  90. package/src/runtime/credential-composition.ts +54 -0
  91. package/src/runtime/daemon-handler-composition.ts +76 -0
  92. package/src/runtime/device-posture-composition.ts +115 -0
  93. package/src/runtime/disposal-wiring.ts +101 -0
  94. package/src/runtime/fleet-needs-input-push.ts +61 -0
  95. package/src/runtime/fleet-services.ts +41 -0
  96. package/src/runtime/hosted-session-composition.ts +128 -0
  97. package/src/runtime/index.ts +100 -0
  98. package/src/runtime/knowledge-services.ts +101 -0
  99. package/src/runtime/legacy-daemon-migration.ts +605 -0
  100. package/src/runtime/legacy-daemon-reconcile.ts +448 -0
  101. package/src/runtime/mail-composition.ts +65 -0
  102. package/src/runtime/notification-dispatch.ts +86 -0
  103. package/src/runtime/plugin-composition.ts +111 -0
  104. package/src/runtime/runtime-services-types.ts +268 -0
  105. package/src/runtime/services.ts +756 -0
  106. package/src/runtime/trigger-services.ts +62 -0
  107. package/src/runtime/trust/checkpoint-eligibility.ts +138 -0
  108. package/src/runtime/trust/trust-gated-approvals.ts +169 -0
  109. package/src/runtime/update-check.ts +61 -0
  110. package/src/runtime/workspace-checkpointing.ts +116 -0
  111. package/src/testing/daemon-fixture.ts +276 -0
  112. package/src/testing/hosted-session-failures.ts +92 -0
  113. package/src/version.ts +26 -0
@@ -0,0 +1,756 @@
1
+ import { join } from 'node:path';
2
+ import { ServiceRegistry, SubscriptionManager, ToolLLM } from '@pellux/goodvibes-sdk/platform/config';
3
+ import { AutomationDeliveryManager, AutomationManager } from '@pellux/goodvibes-sdk/platform/automation';
4
+ import { ChannelPolicyManager } from '@pellux/goodvibes-sdk/platform/channels';
5
+ import { ApprovalBroker, GatewayMethodCatalog, SharedSessionBroker, buildSharedSessionAgentSpawnRoutingInput } from '@pellux/goodvibes-sdk/platform/control-plane';
6
+ import { AcpHostService } from '@pellux/goodvibes-sdk/platform/acp';
7
+ import { continuationChainOptions } from '@pellux/goodvibes-sdk/platform/agents';
8
+ import { resolvePairingWebOrigin } from '@pellux/goodvibes-sdk/platform/pairing';
9
+ import { attachWsOnlyGatewayVerbHandlers } from '@pellux/goodvibes-terminal-shell';
10
+ import { composeMailDeps } from './mail-composition.ts';
11
+ import { composeCredentialServices } from './credential-composition.ts';
12
+ import { createDisposalScope, registerDaemonRuntimePollers } from './disposal-wiring.ts';
13
+ import { attachConfigEmitBridge } from '@pellux/goodvibes-sdk/platform/runtime/config';
14
+ import { WatcherRegistry } from '@pellux/goodvibes-sdk/platform/watchers';
15
+ import { ArtifactStore } from '@pellux/goodvibes-sdk/platform/artifacts';
16
+ import { createWebKnowledgeGapRepairer } from '@pellux/goodvibes-sdk/platform/knowledge';
17
+ import { createKnowledgeServices } from './knowledge-services.ts';
18
+ import { MediaProviderRegistry, ensureBuiltinMediaProviders } from '@pellux/goodvibes-sdk/platform/media';
19
+ import { MultimodalService } from '@pellux/goodvibes-sdk/platform/multimodal';
20
+ import { OverflowHandler, ProcessManager, cancelAllAgentRuns, createWorkflowServices } from '@pellux/goodvibes-sdk/platform/tools';
21
+ import { FileStateCache, FileUndoManager, MemoryEmbeddingProviderRegistry, MemoryRegistry, MemoryStore, ModeManager, ProjectIndex, resolveCanonicalMemoryDbPath } from '@pellux/goodvibes-sdk/platform/state';
22
+ import { buildExecPromptAnswerHandler } from '@pellux/goodvibes-sdk/platform/runtime/permissions/exec-prompt-wiring';
23
+ import { buildLocalhostFetchApproval } from '@pellux/goodvibes-sdk/platform/runtime/permissions/localhost-fetch-approval';
24
+ import { createBrokeredPermissionManager } from '@pellux/goodvibes-sdk/platform/runtime/client-services';
25
+ import { wireMemoryPressureChannelNotice } from './notification-dispatch.ts';
26
+ import { operations } from '@pellux/goodvibes-sdk/platform/runtime';
27
+ const {
28
+ applyProviderOptimizerConfigMode, bindProviderOptimizerFeatureFlag, codeIndexDbPath,
29
+ createAgentGraph, createChannelComposition, createCodeIndexServices,
30
+ createRemoteExecutionServices, createSessionStorageServices, createStoreRerooter,
31
+ isCodeInjectionSettingEnabled, wireIdlePowerAndLiveTurn, wireVoiceSetup,
32
+ } = operations;
33
+ const { WorkspaceTrustManager } = operations;
34
+ import { MemorySpineClient, createLocalMemoryAccess } from '@pellux/goodvibes-sdk/platform/runtime/memory-spine';
35
+ import { createWorkspaceCheckpointing } from './workspace-checkpointing.ts';
36
+ import { createSessionConversationRewindPort } from './conversation-rewind-port.ts';
37
+ import { createDomainDispatch } from '@pellux/goodvibes-sdk/platform/runtime/store';
38
+ import { DistributedRuntimeManager, IntegrationHelperService, IdempotencyStore, ComponentHealthMonitor, WorktreeRegistry, createShellPathService, createFeatureFlagManager, createNoopPanelManager, createNoopKeybindingsManager, PolicyRuntimeState } from '@/runtime/index.ts';
39
+ import { VoiceProviderRegistry, VoiceService, ensureBuiltinVoiceProviders } from '@pellux/goodvibes-sdk/platform/voice';
40
+ import { CacheRegistry, PauseController, wireDaemonMemoryGovernance } from '@pellux/goodvibes-sdk/platform/runtime/memory';
41
+ import { WebSearchProviderRegistry, WebSearchService } from '@pellux/goodvibes-sdk/platform/web-search';
42
+ import { HookActivityTracker } from '@pellux/goodvibes-sdk/platform/hooks';
43
+ import { HookDispatcher, createHookWorkbench } from '@pellux/goodvibes-sdk/platform/hooks';
44
+ import { PluginManager } from '@pellux/goodvibes-sdk/platform/plugins';
45
+ import { BookmarkManager } from '@pellux/goodvibes-sdk/platform/bookmarks';
46
+ import { ProfileManager } from '@pellux/goodvibes-sdk/platform/profiles';
47
+ import { CrossSessionTaskRegistry, SessionChangeTracker } from '@pellux/goodvibes-sdk/platform/sessions';
48
+ import { ApiTokenAuditor, UserAuthManager } from '@pellux/goodvibes-sdk/platform/security';
49
+ import { WebhookNotifier } from '@pellux/goodvibes-sdk/platform/integrations';
50
+ import { BenchmarkStore, CacheHitTracker, FavoritesStore, ModelLimitsService, ProviderCapabilityRegistry, ProviderOptimizer, createLaunchTolerantProviderRegistry, ensureConfiguredModelIsRoutable } from '@pellux/goodvibes-sdk/platform/providers';
51
+ import { AdaptivePlanner, DeterministicReplayEngine, ExecutionPlanManager, SessionLineageTracker, SessionMemoryStore } from '@pellux/goodvibes-sdk/platform/core';
52
+ import { deriveFeatureStates, bindFeatureSettingsBridge } from '@pellux/goodvibes-sdk/platform/runtime/state';
53
+ import { createFleetServices } from './fleet-services.ts';
54
+ import { createTriggerServices } from './trigger-services.ts';
55
+ import { createWorkstreamServices } from '@pellux/goodvibes-sdk/platform/orchestration';
56
+ import { wireFleetNeedsInputPush } from './fleet-needs-input-push.ts';
57
+ import { createDaemonHandlerComposition } from './daemon-handler-composition.ts';
58
+ import { createDevicePostureServices } from './device-posture-composition.ts';
59
+ // Re-exported so the daemon entrypoint reaches the housekeeping sweep through
60
+ // the same module it already imports the runtime graph from. `installDevicePosture`
61
+ // is deliberately NOT re-exported: it registers the phone TOOL into a tool
62
+ // registry, and the daemon registers no tools — the sweep is the half it needs.
63
+ export { startDeviceHousekeeping } from './device-posture-composition.ts';
64
+ import { createClusterServices, startClusterServices } from './cluster-group-composition.ts';
65
+ import { createWorkspaceTrustDecisionAsk, trustGatedApprovalRaiser } from './trust/trust-gated-approvals.ts';
66
+ import { GOODVIBES_DAEMON_SURFACE_ROOT } from '../config/surface.ts';
67
+ import type { RuntimeServicesOptions, RuntimeServices } from './runtime-services-types.ts';
68
+ export type { RuntimeServicesOptions, RuntimeServices } from './runtime-services-types.ts';
69
+
70
+ /**
71
+ * createRuntimeServices — the daemon's service graph.
72
+ *
73
+ * This is the one composition root the daemon has. Capabilities a client and
74
+ * the daemon both need live in the SDK and are composed from there (memory
75
+ * governance, disposal, the continuation runner's conversation gating and
76
+ * spawn routing, the launch-tolerant provider registry, the trigger family,
77
+ * registration-gated checkpoints). Capabilities only this daemon needs are
78
+ * composed locally (cluster, mail, crash-residue housekeeping, device
79
+ * housekeeping, presence-aware needs-input push).
80
+ */
81
+ export function createRuntimeServices(options: RuntimeServicesOptions): RuntimeServices {
82
+ // The SDK's disposal scope and its all-required poller list, plus the four
83
+ // pollers only the daemon has — see disposal-wiring.ts.
84
+ const disposalScope = createDisposalScope('RuntimeServices');
85
+ const workingDirectory = options.workingDir;
86
+ const homeDirectory = options.homeDirectory;
87
+ const shellPaths = createShellPathService({
88
+ workingDirectory,
89
+ homeDirectory,
90
+ });
91
+ // Built before anything that touches session state.
92
+ const { surface, sessionManager } = createSessionStorageServices({ surfaceRoot: GOODVIBES_DAEMON_SURFACE_ROOT, workingDirectory, homeDirectory });
93
+ const workspaceTrustManager = new WorkspaceTrustManager({ shellPaths, surfaceRoot: GOODVIBES_DAEMON_SURFACE_ROOT });
94
+ const configManager = options.configManager;
95
+ const featureFlags = options.featureFlags ?? createFeatureFlagManager();
96
+ if (options.featureFlags === undefined) {
97
+ // Owned manager: gate states derive from domain settings keys + live bridge
98
+ // (mirrors the SDK composition root; a passed manager is the caller's to wire).
99
+ featureFlags.loadFromConfig({ flags: deriveFeatureStates(configManager) });
100
+ bindFeatureSettingsBridge(configManager, featureFlags);
101
+ }
102
+ const runtimeDispatch = createDomainDispatch(options.runtimeStore);
103
+ // Memory governance seams built EARLY (mirrors the SDK's own createRuntimeServices)
104
+ // so the scheduler gates and the knowledge background jobs can consult the pause
105
+ // controller before the MemoryGovernor (constructed at the composition tail)
106
+ // drives it. The admission gate is late-bound: expensive entry points capture
107
+ // this closure now and the governor binds into it at the tail — until then
108
+ // everything is admitted (the daemon is still booting).
109
+ const cacheRegistry = new CacheRegistry();
110
+ const pauseController = new PauseController();
111
+ const MEMORY_BACKGROUND_JOB_IDS = ['knowledge-self-improvement', 'memory-consolidation', 'code-index-reindex'];
112
+ const admitExpensiveWorkRef: { current: ((label: string) => { allowed: boolean; reason?: string | undefined }) | null } = { current: null };
113
+ const admitExpensiveWork = (label: string): { allowed: boolean; reason?: string | undefined } =>
114
+ admitExpensiveWorkRef.current?.(label) ?? { allowed: true };
115
+ const isKnowledgeBackgroundPaused = (): boolean => pauseController.isPaused('knowledge-self-improvement');
116
+ const gatewayMethods = new GatewayMethodCatalog();
117
+ // The daemon has no screen. The facade's service-graph contract names a panel
118
+ // manager and a keybindings manager because a surface that HAS a screen
119
+ // supplies real ones; the SDK ships no-ops for a host that does not, which is
120
+ // the honest answer rather than a stub that pretends to open panels.
121
+ const panelManager = createNoopPanelManager();
122
+ const keybindingsManager = createNoopKeybindingsManager();
123
+ // Channel/surface wiring, composed by the SDK helper.
124
+ const { routeBindings, surfaceRegistry, channelPlugins } = createChannelComposition({
125
+ configManager,
126
+ runtimeStore: options.runtimeStore,
127
+ runtimeBus: options.runtimeBus,
128
+ featureFlags,
129
+ });
130
+ // The credential/identity seam (credential-composition.ts).
131
+ const { secretsManager, stepUpService, pairingTokens } = composeCredentialServices({
132
+ workingDirectory, homeDirectory, configManager,
133
+ daemonHomeDirectory: options.daemonHomeDirectory,
134
+ pairingTokenPath: shellPaths.resolveUserPath('control-plane', 'pairing-tokens.json'),
135
+ });
136
+ const subscriptionManager = new SubscriptionManager(shellPaths.resolveUserPath(GOODVIBES_DAEMON_SURFACE_ROOT, 'subscriptions.json'));
137
+ const serviceRegistry = new ServiceRegistry(shellPaths.resolveProjectPath(GOODVIBES_DAEMON_SURFACE_ROOT, 'services.json'), {
138
+ secretsManager,
139
+ subscriptionManager,
140
+ });
141
+ const providerCapabilityRegistry = new ProviderCapabilityRegistry();
142
+ const cacheHitTracker = new CacheHitTracker();
143
+ const favoritesStore = new FavoritesStore({ dir: shellPaths.resolveUserPath(GOODVIBES_DAEMON_SURFACE_ROOT) });
144
+ const benchmarkStore = new BenchmarkStore({ dir: shellPaths.resolveUserPath(GOODVIBES_DAEMON_SURFACE_ROOT) });
145
+ const modelLimitsService = new ModelLimitsService({
146
+ cachePath: shellPaths.resolveUserPath(GOODVIBES_DAEMON_SURFACE_ROOT, 'model-limits.json'),
147
+ });
148
+ // Launch-tolerant: a provider whose API key is absent from the environment is
149
+ // constructed with a placeholder that is stripped immediately afterwards, so
150
+ // it lands unconfigured instead of throwing during construction. The daemon
151
+ // has the same must-boot property the agent has — it is a supervised service,
152
+ // and a constructor that throws on a missing key turns one unset variable into
153
+ // a crash loop with no screen to explain it.
154
+ const providerRegistry = createLaunchTolerantProviderRegistry({
155
+ configManager,
156
+ subscriptionManager,
157
+ secretsManager,
158
+ serviceRegistry,
159
+ capabilityRegistry: providerCapabilityRegistry,
160
+ cacheHitTracker,
161
+ favoritesStore,
162
+ benchmarkStore,
163
+ modelLimitsService,
164
+ featureFlags,
165
+ runtimeBus: options.runtimeBus,
166
+ });
167
+ ensureConfiguredModelIsRoutable(providerRegistry, configManager);
168
+ providerRegistry.initCustomProviders();
169
+ // Background, TTL-respecting live model discovery so provider model lists
170
+ // refresh from their own listing APIs.
171
+ providerRegistry.initProviderModelDiscovery();
172
+ const toolLLM = new ToolLLM({
173
+ configManager,
174
+ providerRegistry,
175
+ runtimeBus: options.runtimeBus,
176
+ });
177
+ const localUserAuthManager = options.localUserAuthManager ?? new UserAuthManager({
178
+ bootstrapFilePath: shellPaths.resolveUserPath(GOODVIBES_DAEMON_SURFACE_ROOT, 'auth-users.json'),
179
+ bootstrapCredentialPath: shellPaths.resolveUserPath(GOODVIBES_DAEMON_SURFACE_ROOT, 'auth-bootstrap.txt'),
180
+ });
181
+ const profileManager = new ProfileManager(shellPaths.resolveUserPath(GOODVIBES_DAEMON_SURFACE_ROOT, 'profiles'));
182
+ const bookmarkManager = new BookmarkManager(shellPaths.resolveUserPath(GOODVIBES_DAEMON_SURFACE_ROOT, 'bookmarks'));
183
+ const sessionOrchestration = new CrossSessionTaskRegistry(
184
+ join(surface.sessionsDir, 'task-graph.json'),
185
+ );
186
+ const hookActivityTracker = new HookActivityTracker();
187
+ // featureFlags is REQUIRED here in practice, even though the SDK types it
188
+ // optional. isFeatureGateEnabled(null, ...) is permissive by design — a narrow
189
+ // embed with no manager wired gets the capability rather than a silent off —
190
+ // so omitting it did not disable the watcher framework when watchers.enabled
191
+ // is turned off; it made the setting configure nothing.
192
+ const watcherRegistry = new WatcherRegistry({
193
+ storePath: shellPaths.resolveProjectPath(GOODVIBES_DAEMON_SURFACE_ROOT, 'watchers.json'),
194
+ featureFlags,
195
+ });
196
+ watcherRegistry.attachRuntime({
197
+ runtimeStore: options.runtimeStore,
198
+ runtimeBus: options.runtimeBus,
199
+ });
200
+ // The agent-execution graph, wired in both directions; see
201
+ // the SDK's agent-graph composition for why the six are built as one.
202
+ const {
203
+ agentMessageBus, archetypeLoader, agentOrchestrator,
204
+ agentManager, contextAccountingHolder, wrfcController,
205
+ } = createAgentGraph({
206
+ runtimeBus: options.runtimeBus, workingDirectory, configManager, providerRegistry,
207
+ });
208
+ const hookDispatcher = new HookDispatcher({ agentManager, toolLLM, projectRoot: workingDirectory }, hookActivityTracker);
209
+ configManager.attachHookDispatcher(hookDispatcher);
210
+ const hookWorkbench = createHookWorkbench({
211
+ hookDispatcher,
212
+ configManager,
213
+ });
214
+ const approvalBroker = new ApprovalBroker({
215
+ storePath: shellPaths.resolveProjectPath(GOODVIBES_DAEMON_SURFACE_ROOT, 'control-plane', 'approvals.json'),
216
+ });
217
+ const sessionBroker = new SharedSessionBroker({
218
+ storePath: shellPaths.resolveProjectPath(GOODVIBES_DAEMON_SURFACE_ROOT, 'control-plane', 'sessions.json'),
219
+ routeBindings,
220
+ agentStatusProvider: agentManager,
221
+ messageSender: agentMessageBus,
222
+ conversationGateConfig: configManager, // without this the gate runs on DEFAULTS: an inbound message landing in a live session takes the handover and starts work whatever conversationGate.mode/gatedSurfaces say
223
+ });
224
+ sessionBroker.setContinuationRunner(async ({ task, input }) => {
225
+ const record = agentManager.spawn({
226
+ mode: 'spawn',
227
+ task,
228
+ // Conversation first: a follow-up message in a session gets an answer, not
229
+ // a write-review-fix-confirm chain with a reviewer, quality gates and a
230
+ // second agent. A chain opens only for an explicit authorization marker —
231
+ // the channel confirmation the owner gave, or the schedule/trigger that
232
+ // was confirmed when it was created — or for a follow-up typed on a local
233
+ // surface. Both `conversationGate.mode` and the gated-surfaces list are
234
+ // read live.
235
+ ...continuationChainOptions(input, {
236
+ configReader: {
237
+ get: (key: string) => configManager.get(key as never),
238
+ getCategory: (name: string) => configManager.getCategory(name as never),
239
+ },
240
+ }),
241
+ // Spawn routing through the SDK's shared model-reference resolver
242
+ // (unique-across-registry auto-qualifies; ambiguous and unknown ids throw
243
+ // errors naming real candidates), against the live registry's models.
244
+ ...buildSharedSessionAgentSpawnRoutingInput(input.routing, { restrictTools: true, modelCandidates: providerRegistry.listModels() }),
245
+ context: `shared-session:${input.sessionId}`,
246
+ });
247
+ return { agentId: record.id };
248
+ });
249
+ const artifactStore = new ArtifactStore({ configManager });
250
+ const memoryEmbeddingRegistry = new MemoryEmbeddingProviderRegistry({ configManager });
251
+ // Open the ONE home-scoped canonical store; legacy per-project memory folds in at boot.
252
+ const memoryDbPath = resolveCanonicalMemoryDbPath(homeDirectory);
253
+ const memoryStore = new MemoryStore(memoryDbPath, {
254
+ embeddingRegistry: memoryEmbeddingRegistry,
255
+ });
256
+ const memoryRegistry = new MemoryRegistry(memoryStore);
257
+ // The daemon is the memory spine's HOST: it always serves the local store.
258
+ // Clients construct the same facade in wire mode against this process.
259
+ const memorySpine = new MemorySpineClient({ local: createLocalMemoryAccess(memoryRegistry) });
260
+ // featureFlags is REQUIRED here in practice, even though the SDK types it
261
+ // optional (same reasoning as the watcher registry above): without it,
262
+ // integrations.deliveryTracking configured nothing.
263
+ const deliveryManager = new AutomationDeliveryManager({
264
+ configManager,
265
+ // This manager builds the delivery router the daemon actually replies
266
+ // through. Without the secrets manager it cannot resolve a
267
+ // goodvibes://secrets/... credential, so Telegram accepted every inbound
268
+ // message and dropped every reply with "Missing Telegram bot token" while
269
+ // ntfy — which needs no secret — worked.
270
+ secretsManager,
271
+ serviceRegistry,
272
+ runtimeBus: options.runtimeBus,
273
+ runtimeStore: options.runtimeStore,
274
+ routeBindings,
275
+ artifactStore,
276
+ featureFlags,
277
+ });
278
+ const automationManager = new AutomationManager({
279
+ configManager,
280
+ // The daemon is a service, not a terminal: a job it creates is attributed to
281
+ // the service surface.
282
+ defaultSurfaceKind: 'service',
283
+ routeBindings,
284
+ sessionBroker,
285
+ runtimeStore: options.runtimeStore,
286
+ runtimeBus: options.runtimeBus,
287
+ deliveryManager,
288
+ // Same live registry: a bare model id on an automation job resolves through
289
+ // the shared resolver instead of a format-only rejection.
290
+ providerRegistry,
291
+ featureFlags,
292
+ spawnTask: (input) => {
293
+ const record = agentManager.spawn({
294
+ mode: 'spawn',
295
+ task: input.prompt,
296
+ ...(input.modelId ? { model: input.modelId } : {}),
297
+ ...(input.modelProvider ? { provider: input.modelProvider } : {}),
298
+ ...(input.fallbackModels !== undefined ? { fallbackModels: [...input.fallbackModels] } : {}),
299
+ ...(input.routing ? { routing: input.routing } : {}),
300
+ ...(input.executionIntent ? { executionIntent: input.executionIntent } : {}),
301
+ ...(input.template ? { template: input.template } : {}),
302
+ ...(input.reasoningEffort ? { reasoningEffort: input.reasoningEffort } : {}),
303
+ ...(input.toolAllowlist?.length ? { tools: [...input.toolAllowlist], restrictTools: true } : {}),
304
+ ...(input.context ? { context: input.context } : {}),
305
+ });
306
+ return record.id;
307
+ },
308
+ });
309
+ // Knowledge/wiki + home-graph stack (governor backpressure wired in) — see knowledge-services.ts.
310
+ const {
311
+ knowledgeStore, agentKnowledgeStore, homeGraphKnowledgeStore,
312
+ knowledgeSemanticService, homeGraphSemanticService, agentKnowledgeSemanticService,
313
+ knowledgeService, agentKnowledgeService, homeGraphService,
314
+ projectPlanningService, projectPlanningProjectId, workPlanStore,
315
+ } = createKnowledgeServices({ configManager, providerRegistry, artifactStore, memoryRegistry, runtimeBus: options.runtimeBus, workingDirectory, homeDirectory, isBackgroundPaused: isKnowledgeBackgroundPaused, admitExpensiveWork });
316
+ const voiceProviders = new VoiceProviderRegistry();
317
+ ensureBuiltinVoiceProviders(voiceProviders, { readConfig: (key) => configManager.get(key as Parameters<typeof configManager.get>[0]) });
318
+ const voiceService = new VoiceService(voiceProviders);
319
+ const webSearchProviders = new WebSearchProviderRegistry({
320
+ env: process.env,
321
+ serviceRegistry,
322
+ });
323
+ const webSearchService = new WebSearchService(webSearchProviders, {
324
+ serviceRegistry,
325
+ featureFlags,
326
+ });
327
+ for (const [semantic, ingest] of [[knowledgeSemanticService, knowledgeService], [agentKnowledgeSemanticService, agentKnowledgeService], [homeGraphSemanticService, homeGraphService]] as const) {
328
+ semantic.setGapRepairer(createWebKnowledgeGapRepairer({ searchService: webSearchService, ingestService: ingest }));
329
+ }
330
+ const mediaProviders = new MediaProviderRegistry();
331
+ ensureBuiltinMediaProviders(mediaProviders, artifactStore, providerRegistry);
332
+ const multimodalService = new MultimodalService(artifactStore, mediaProviders, voiceService, knowledgeService);
333
+ const pluginManager = new PluginManager({
334
+ pathOptions: {
335
+ cwd: shellPaths.workingDirectory,
336
+ homeDir: shellPaths.homeDirectory,
337
+ },
338
+ stateFilePath: shellPaths.resolveUserPath(GOODVIBES_DAEMON_SURFACE_ROOT, 'plugins.json'),
339
+ });
340
+ const workflow = createWorkflowServices();
341
+ hookDispatcher.setTriggerManager(workflow.triggerManager);
342
+ const channelPolicy = new ChannelPolicyManager({
343
+ storePath: shellPaths.resolveProjectPath(GOODVIBES_DAEMON_SURFACE_ROOT, 'channels', 'policies.json'),
344
+ });
345
+ const distributedRuntime = new DistributedRuntimeManager(
346
+ shellPaths.resolveProjectPath(GOODVIBES_DAEMON_SURFACE_ROOT, 'remote', 'distributed-runtime.json'),
347
+ );
348
+ distributedRuntime.attachRuntime({
349
+ sessionBridge: sessionBroker,
350
+ approvalBridge: approvalBroker,
351
+ automationBridge: automationManager,
352
+ });
353
+ // The paired-phone feature for this host, on the SAME runtime phones pair onto
354
+ // and the SAME approval broker every other confirmation rides. Every `device.*`
355
+ // setting is read live through this; see device-posture-composition.ts.
356
+ const { devicePosture } = createDevicePostureServices({
357
+ configManager,
358
+ distributedRuntime,
359
+ approvals: approvalBroker,
360
+ stateDirectory: shellPaths.resolveProjectPath(GOODVIBES_DAEMON_SURFACE_ROOT, 'devices'),
361
+ gatewayMethods,
362
+ });
363
+
364
+ // Which machines on this network are "us", and which of them reads the shared
365
+ // inbox. Both inert until startCluster() — no socket, no key material read;
366
+ // see cluster-group-composition.ts for why they are built together.
367
+ const { clusterGroup, clusterCoordinator } = createClusterServices({
368
+ configManager, shellPaths, secretsManager,
369
+ });
370
+ // Daemon handler surfaces (see daemon-handler-composition.ts); the inbox
371
+ // poller registers itself with the coordinator rather than starting eagerly.
372
+ const daemonHandlers = createDaemonHandlerComposition({
373
+ gatewayMethods,
374
+ secretsManager,
375
+ configManager,
376
+ workingDirectory,
377
+ homeDirectory,
378
+ distributedRuntime,
379
+ clusterCoordinator,
380
+ });
381
+
382
+ // Remote runners and the sandboxes tool calls are confined to; see
383
+ // the SDK's remote-execution composition for why the four are built as one.
384
+ const { remoteRunnerRegistry, remoteSupervisor, sandboxSessionRegistry, mcpRegistry }
385
+ = createRemoteExecutionServices({
386
+ agentManager, workingDirectory, hookDispatcher, configManager, runtimeBus: options.runtimeBus,
387
+ });
388
+ // Advisory reporting only: `managed` is hardcoded false here, so excess-scope
389
+ // and overdue tokens are reported and never blocked.
390
+ const tokenAuditor = new ApiTokenAuditor({ managed: false, featureFlags });
391
+ const componentHealthMonitor = new ComponentHealthMonitor();
392
+ const worktreeRegistry = new WorktreeRegistry(workingDirectory);
393
+ const webhookNotifier = new WebhookNotifier();
394
+ const replayEngine = new DeterministicReplayEngine(workingDirectory);
395
+ const providerOptimizer = new ProviderOptimizer(providerRegistry, providerCapabilityRegistry, false); // dark until its gate flips it
396
+ bindProviderOptimizerFeatureFlag(featureFlags, providerOptimizer);
397
+ applyProviderOptimizerConfigMode(configManager, providerOptimizer);
398
+ const sessionMemoryStore = new SessionMemoryStore();
399
+ const sessionLineageTracker = new SessionLineageTracker(); const sessionChangeTracker = new SessionChangeTracker();
400
+ const planManager = new ExecutionPlanManager(workingDirectory);
401
+ const adaptivePlanner = new AdaptivePlanner();
402
+ const idempotencyStore = new IdempotencyStore();
403
+ const overflowHandler = new OverflowHandler({ baseDir: workingDirectory });
404
+ const policyRuntimeState = new PolicyRuntimeState();
405
+ const fileCache = new FileStateCache();
406
+ const projectIndex = new ProjectIndex(workingDirectory);
407
+ // ONE router, not two. This was a second ChannelDeliveryRouter built from the
408
+ // same four arguments AutomationDeliveryManager builds its own from, so the
409
+ // router the gateway verbs held and the router replies actually leave through
410
+ // were different objects — and a delivery strategy registered on one was
411
+ // invisible to the other. The manager's is the one that replies; it is the one.
412
+ const channelDeliveryRouter = deliveryManager.getDeliveryRouter();
413
+ const processManager = new ProcessManager();
414
+ // The phase/work-item orchestration engine, constructed before the process
415
+ // registry so its fleet nodes (workstream/phase/work-item) can be folded in
416
+ // below via the registry's optional orchestrationEngine dep.
417
+ const { orchestrationEngine, workstreamCommands } = createWorkstreamServices({
418
+ agentManager, configManager, adaptivePlanner, runtimeBus: options.runtimeBus, projectRoot: workingDirectory,
419
+ });
420
+ // Repo source-tree code index, sharing memoryEmbeddingRegistry with MemoryStore
421
+ // above. Auto-build is config-gated (default off).
422
+ const { codeIndexStore, codeIndexReindexScheduler } = createCodeIndexServices({ workingDirectory, surfaceRoot: GOODVIBES_DAEMON_SURFACE_ROOT, configManager, memoryEmbeddingRegistry, isReindexPaused: () => pauseController.isPaused('code-index-reindex'), admitExpensiveWork });
423
+ // Store snapshots, the periodic append-only sweep, durable remembered-approval rules + the live credential chain.
424
+ const { storeSnapshotScheduler, appendOnlyRetentionScheduler, userPermissionRuleStore, stopDurabilityHousekeeping, stopConfigWatch } = operations.createDurabilityServices({
425
+ configManager, secretsManager, providerRegistry, memoryDbPath, codeIndexDbPath: codeIndexDbPath(workingDirectory, GOODVIBES_DAEMON_SURFACE_ROOT), surface, shellPaths, // + retention-sweep roots & live config watch (mirrors the SDK)
426
+ ...(options.currentSessionId ? { currentSessionId: options.currentSessionId } : {}), // exempts the running session from crash-residue reaping
427
+ });
428
+ const codeInjectionOrchestratorDeps = { codeIndex: codeIndexStore, isCodeInjectionSettingEnabled: () => isCodeInjectionSettingEnabled(configManager), codeIndexReindexScheduler };
429
+ // The trigger family: stream watchers, on-exit process triggers, condition
430
+ // checks — fed to the fleet below as its trigger supervisor, so a trigger
431
+ // is visible and steerable like every other running thing.
432
+ const triggerManager = createTriggerServices({
433
+ configManager, shellPaths, surfaceRoot: GOODVIBES_DAEMON_SURFACE_ROOT,
434
+ agentManager, processManager, sessionBroker,
435
+ });
436
+ // Hosted third-party coding agents (ACP): permission asks route through the
437
+ // SAME shared approval broker every other confirmation rides (approvals
438
+ // panel + push like any native ask), and each hosted agent maps onto a
439
+ // kind-'acp' shared session so it is attachable/steerable like any other.
440
+ // Mirrors the SDK's own createRuntimeServices composition (services.ts ~879).
441
+ const acpHost = new AcpHostService({
442
+ requestPermission: (request) => approvalBroker.requestApproval({ request }),
443
+ registerSession: ({ id, title, agentTitle, cwd }) => void sessionBroker
444
+ .register({ sessionId: id, kind: 'acp', title, project: cwd, participant: { surfaceKind: 'service', surfaceId: `acp-host:${agentTitle}`, lastSeenAt: Date.now() } })
445
+ .catch(() => { /* best-effort; the fleet row is authoritative */ }),
446
+ });
447
+ const { processRegistry } = createFleetServices({ // Shared archive-aware fleet registry (+ daemon observed rows) — see fleet-services.ts
448
+ agentManager, wrfcController,
449
+ orchestrationEngine, // Folds workstream/phase/work-item nodes into the fleet
450
+ codeIndexService: codeIndexStore, // Folds a single 'code-index' node into the fleet
451
+ processManager, watcherRegistry, workflow, approvalBroker, sessionBroker,
452
+ triggerSupervisor: triggerManager,
453
+ messageBus: agentMessageBus, // Backs steer()/`steerable` (the Fleet steer composer builds on top)
454
+ automationManager, // Folds scheduled AutomationJobs into the fleet as 'schedule' nodes
455
+ runtimeBus: options.runtimeBus,
456
+ observeExternalAgents: options.observeExternalAgents, providerRegistry, // observeExternalAgents is daemon-side only
457
+ acpHost, // Folds live hosted-agent sessions into the fleet as 'acp' rows
458
+ });
459
+ const modeManager = new ModeManager({ featureFlags }); const fileUndoManager = new FileUndoManager();
460
+ // Checkpoints, gated on live workspace registration — see workspace-checkpointing.ts.
461
+ const checkpointing = createWorkspaceCheckpointing({
462
+ workspaceRoot: workingDirectory, surface, runtimeBus: options.runtimeBus, configManager, shellPaths,
463
+ });
464
+ const workspaceCheckpointManager = checkpointing.manager;
465
+ // memory-consolidation honors governor backpressure: it ticks only when idle
466
+ // AND the 'memory-consolidation' job is not paused AND expensive work is
467
+ // admitted (mirrors the SDK's own createRuntimeServices idle gate).
468
+ 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 });
469
+
470
+ // Construct + start the MemoryGovernor (default ON — a safety feature) with the
471
+ // standard KNOWN cache adapters (knowledge stores + shared session broker),
472
+ // then late-bind the admission gate the expensive entry points captured
473
+ // earlier. The SDK owns this wiring.
474
+ const { memoryGovernor } = wireDaemonMemoryGovernance({
475
+ config: {
476
+ budgetMb: configManager.get('memory.budgetMb'),
477
+ elevatedPct: configManager.get('memory.tier.elevatedPct'),
478
+ highPct: configManager.get('memory.tier.highPct'),
479
+ criticalPct: configManager.get('memory.tier.criticalPct'),
480
+ tripwireRateMbPerSec: configManager.get('memory.tripwire.rateMbPerSec'),
481
+ tripwireSustainSec: configManager.get('memory.tripwire.sustainSec'),
482
+ hardLimitPct: configManager.get('memory.hardLimitPct'),
483
+ },
484
+ runtimeBus: options.runtimeBus,
485
+ cacheRegistry,
486
+ pauseController,
487
+ jobIds: MEMORY_BACKGROUND_JOB_IDS,
488
+ receiptPath: shellPaths.resolveProjectPath(GOODVIBES_DAEMON_SURFACE_ROOT, 'memory', 'tripwire-receipt.json'),
489
+ knowledgeStores: [knowledgeStore, agentKnowledgeStore, homeGraphKnowledgeStore],
490
+ sessionBroker,
491
+ // Graceful tripwire shutdown flushes in-flight state via ASYNC store
492
+ // snapshots so the governor's 10s shutdown ceiling stays enforceable.
493
+ onTripwireShutdown: async () => { await storeSnapshotScheduler.snapshotAllAsync('tripwire'); },
494
+ });
495
+ admitExpensiveWorkRef.current = (label) => memoryGovernor.admitExpensiveWork(label);
496
+
497
+ // Managed local-voice provisioning (voice.local.status/install) — single-flight
498
+ // one-act install + no-network status.
499
+ const { voiceSetup, stopWakeHousekeeping } = wireVoiceSetup({ configManager, shellPaths, voiceProviders, admitExpensiveWork,
500
+ // Boot provisioning of the wake-word model + its recovery sweep, opted into
501
+ // by the real entrypoint only (same treatment as powerSeam) so a one-shot CLI
502
+ // command and a test composing this graph fetch nothing and start no timer.
503
+ provisionWakeModelsAtBoot: options.provisionWakeModelsAtBoot === true });
504
+
505
+ // 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.
506
+ // calendar.*/email.* are platform-served; these two let it register (mail-composition.ts).
507
+ const { emailServiceDeps, describeEmailConfigProblem } = composeMailDeps({ configManager, secretsManager });
508
+ attachWsOnlyGatewayVerbHandlers(gatewayMethods, {
509
+ homeDirectory, emailServiceDeps, describeEmailConfigProblem, processRegistry,
510
+ // The registration-gated surface, not the raw manager: an explicit create in
511
+ // an unregistered workspace refuses with something actionable.
512
+ workspaceCheckpointManager: checkpointing.gatewayManager,
513
+ conversationRewindPort: createSessionConversationRewindPort(), sessionBroker, secretsManager, stepUpService,
514
+ approvalBroker, requestApproval: (input) => approvalBroker.requestApproval(input),
515
+ // approvals.raise — a surface CREATING an ask in this broker. Without it the
516
+ // verb is cataloged and unhandled, and a client whose prompt runs outside
517
+ // this process has no way to raise one.
518
+ approvalRaise: approvalBroker,
519
+ // credentials.set / credentials.delete — a credential written THROUGH the
520
+ // control plane, so a client with no access to the daemon's settings file can
521
+ // configure one. The value lands in the daemon's secret tier and the verb
522
+ // never echoes it back.
523
+ credentialWrites: { config: configManager, secrets: secretsManager },
524
+ watcherRegistry, userPermissionRuleStore, shellPaths, configManager, runtimeStore: options.runtimeStore,
525
+ channelDeliveryRouter, providerRegistry, automationManager, sessionLister: sessionBroker, sessionIntake: sessionBroker,
526
+ workingDirectory, memoryRegistry, pairingTokens, sessionLiveTurnControls, powerManager, memoryGovernor, voiceSetup,
527
+ acpHost, // Registers acp.agents.list (discovery) and acp.sessions.create (spawn) — see register-gateway-verb-groups.ts
528
+ attemptsController: orchestrationEngine,
529
+ relayAvailable: () => configManager.get('relay.enabled') === true,
530
+ pairingWebOrigin: () => resolvePairingWebOrigin(configManager).origin,
531
+ disposal: disposalScope.registry,
532
+ ...wireFleetNeedsInputPush({ registry: processRegistry, runtimeBus: options.runtimeBus, sessionBroker }),
533
+ });
534
+ // A loopback fetch that isn't allow-listed asks once through the approval
535
+ // broker; "allow for this project" persists and later fetches never ask. Built
536
+ // once and shared with the tool registry so both ask alike.
537
+ const localhostFetchApproval = buildLocalhostFetchApproval({ requestApproval: (input) => approvalBroker.requestApproval(input), configManager });
538
+ // Exec stuck on a terminal prompt rides the approval broker; the typed answer
539
+ // feeds the continuing run. Built once and shared (like localhostFetchApproval)
540
+ // so every setDependencies site installs the SAME handler; otherwise a
541
+ // wholesale replace drops it and prompts hang.
542
+ const execPromptAnswerHandler = buildExecPromptAnswerHandler({ requestApproval: (input) => approvalBroker.requestApproval(input) });
543
+ // Tool asks from the runs this daemon HOSTS. Without a manager here, the
544
+ // background permission gate short-circuits to approved and every hosted
545
+ // write, command and delegation ran ungated — the workspace trust decision
546
+ // was read by nobody in this process.
547
+ //
548
+ // The ask seam is the trust gate wrapping the approval broker: a workspace
549
+ // with no decision yet has the question raised as an approval record and
550
+ // answered by whichever surface is attached (trust-gated-approvals.ts) —
551
+ // there is no screen here to show a modal on, so the raise replaces it. The
552
+ // manager's own layers — permission mode, policy, session cache, durable
553
+ // user rules — still run first and are unchanged.
554
+ const permissionManager = createBrokeredPermissionManager({
555
+ requestApproval: trustGatedApprovalRaiser(
556
+ workspaceTrustManager,
557
+ (input) => approvalBroker.requestApproval(input),
558
+ createWorkspaceTrustDecisionAsk({
559
+ requestApproval: (input) => approvalBroker.requestApproval(input),
560
+ workingDirectory,
561
+ }),
562
+ ),
563
+ configManager,
564
+ policyRuntimeState,
565
+ hookDispatcher,
566
+ featureFlags,
567
+ userRuleStore: userPermissionRuleStore,
568
+ });
569
+ agentOrchestrator.setDependencies({
570
+ surfaceRoot: surface.surfaceRoot,
571
+ permissionManager,
572
+ execPromptAnswerHandler,
573
+ localhostFetchApproval,
574
+ fileCache,
575
+ projectIndex,
576
+ workingDirectory,
577
+ fileUndoManager,
578
+ modeManager,
579
+ processManager,
580
+ agentMessageBus,
581
+ webSearchService,
582
+ channelRegistry: channelPlugins,
583
+ remoteRunnerRegistry,
584
+ knowledgeService,
585
+ memoryRegistry,
586
+ ...codeInjectionOrchestratorDeps, // Agent-run code injection + tool-site reindex
587
+ archetypeLoader,
588
+ configManager,
589
+ providerRegistry,
590
+ providerOptimizer,
591
+ toolLLM,
592
+ serviceRegistry,
593
+ sessionOrchestration,
594
+ featureFlags,
595
+ overflowHandler,
596
+ sandboxSessionRegistry,
597
+ workflowServices: workflow,
598
+ contextAccountingHolder,
599
+ });
600
+
601
+ // Continuity reads (recovery-file presence, last-session pointer) scoped to
602
+ // the same surface the daemon writes with, so a reader never checks the
603
+ // unscoped legacy pair. Part of the facade's service-graph contract.
604
+ const integrationHelpers = new IntegrationHelperService({
605
+ surface, configManager, automationManager, approvalBroker, sessionBroker, distributedRuntime,
606
+ remoteRunnerRegistry, remoteSupervisor, panelManager, localUserAuthManager, providerRegistry,
607
+ serviceRegistry, subscriptionManager, secretsManager,
608
+ runtimeStore: options.runtimeStore, runtimeBus: options.runtimeBus,
609
+ getConversationTitle: options.getConversationTitle,
610
+ });
611
+
612
+ // This process's own memory pressure, to the operator's configured notice
613
+ // destination. Targeted at OPS_MEMORY_PRESSURE rather than subscribed to the
614
+ // whole high-churn 'ops' domain, and sent over the SAME WebhookNotifier the
615
+ // notification verbs keep live and boot-tasks attaches to the bus. There is
616
+ // no panel notification router here: its targets are all screen targets and
617
+ // this product has no screen — see notification-dispatch.ts.
618
+ wireMemoryPressureChannelNotice(options.runtimeBus, webhookNotifier);
619
+
620
+ // In-process config changes become key-level events on the `config` domain, so
621
+ // a client whose settings live HERE gets live change notices instead of
622
+ // polling. Secret-bearing keys are named and never valued.
623
+ disposalScope.registry.add('config event bridge', attachConfigEmitBridge({
624
+ config: { subscribe: (key, cb) => configManager.subscribe(key as never, cb as never) },
625
+ bus: options.runtimeBus,
626
+ }));
627
+
628
+ const services: RuntimeServices = {
629
+ workingDirectory,
630
+ homeDirectory,
631
+ surface,
632
+ shellPaths,
633
+ workspaceTrustManager,
634
+ permissionManager,
635
+ configManager,
636
+ featureFlags,
637
+ runtimeBus: options.runtimeBus,
638
+ runtimeStore: options.runtimeStore,
639
+ runtimeDispatch,
640
+ panelManager,
641
+ keybindingsManager,
642
+ routeBindings,
643
+ surfaceRegistry,
644
+ channelPlugins,
645
+ channelDeliveryRouter,
646
+ watcherRegistry,
647
+ approvalBroker,
648
+ localhostFetchApproval,
649
+ execPromptAnswerHandler,
650
+ userPermissionRuleStore,
651
+ sessionBroker,
652
+ deliveryManager,
653
+ automationManager,
654
+ gatewayMethods,
655
+ artifactStore,
656
+ knowledgeService,
657
+ agentKnowledgeService,
658
+ homeGraphService,
659
+ projectPlanningService,
660
+ projectPlanningProjectId,
661
+ workPlanStore,
662
+ memoryStore,
663
+ memoryRegistry,
664
+ memorySpine,
665
+ serviceRegistry,
666
+ secretsManager,
667
+ stepUpService,
668
+ pairingTokens,
669
+ subscriptionManager,
670
+ localUserAuthManager,
671
+ profileManager,
672
+ bookmarkManager,
673
+ sessionManager,
674
+ sessionOrchestration,
675
+ hookDispatcher,
676
+ hookActivityTracker,
677
+ hookWorkbench,
678
+ pluginManager,
679
+ workflow,
680
+ triggerManager,
681
+ voiceProviders,
682
+ voiceService,
683
+ webSearchProviders,
684
+ webSearchService,
685
+ mediaProviders,
686
+ multimodalService,
687
+ memoryEmbeddingRegistry,
688
+ channelPolicy,
689
+ mcpRegistry,
690
+ tokenAuditor,
691
+ componentHealthMonitor,
692
+ worktreeRegistry,
693
+ sandboxSessionRegistry,
694
+ webhookNotifier,
695
+ replayEngine,
696
+ providerOptimizer,
697
+ providerCapabilityRegistry,
698
+ cacheHitTracker,
699
+ favoritesStore,
700
+ benchmarkStore,
701
+ modelLimitsService,
702
+ providerRegistry,
703
+ toolLLM,
704
+ distributedRuntime,
705
+ devicePosture,
706
+ daemonHandlers,
707
+ clusterCoordinator,
708
+ clusterGroup,
709
+ startCluster: () => startClusterServices({ clusterGroup, clusterCoordinator }),
710
+ remoteRunnerRegistry,
711
+ remoteSupervisor,
712
+ sessionMemoryStore,
713
+ sessionLineageTracker,
714
+ sessionChangeTracker,
715
+ planManager,
716
+ adaptivePlanner,
717
+ idempotencyStore,
718
+ overflowHandler,
719
+ policyRuntimeState,
720
+ archetypeLoader,
721
+ agentManager,
722
+ agentMessageBus,
723
+ agentOrchestrator,
724
+ contextAccountingHolder,
725
+ wrfcController,
726
+ processManager,
727
+ orchestrationEngine,
728
+ workstreamCommands,
729
+ codeIndexStore,
730
+ codeIndexReindexScheduler,
731
+ storeSnapshotScheduler, appendOnlyRetentionScheduler, stopDurabilityHousekeeping, stopWakeHousekeeping,
732
+ memoryConsolidationScheduler,
733
+ powerManager,
734
+ memoryGovernor,
735
+ cacheRegistry,
736
+ pauseController,
737
+ sessionLiveTurnControls,
738
+ processRegistry,
739
+ modeManager,
740
+ fileUndoManager,
741
+ workspaceCheckpointManager,
742
+ checkpointsCurrentlyAllowed: checkpointing.currentlyAllowed,
743
+ integrationHelpers,
744
+ rerootStores: createStoreRerooter({ codeIndexStore, projectIndex, surfaceRoot: GOODVIBES_DAEMON_SURFACE_ROOT }),
745
+ // Cancels the agent runs this graph was hosting. By dispose() time the fleet
746
+ // registry, orchestration engine, process registry and bus these runs report
747
+ // through are already down, so a run still described as "running" is orphaned
748
+ // rather than preserved — and this is the only shutdown-reachable way to
749
+ // abort its in-flight provider call instead of letting it sleep out a retry
750
+ // backoff nobody is waiting on.
751
+ cancelHostedAgentRuns: () => cancelAllAgentRuns(agentManager),
752
+ dispose: (): void => disposalScope.dispose(),
753
+ };
754
+ registerDaemonRuntimePollers(disposalScope.registry, services, { stopConfigWatch });
755
+ return services;
756
+ }