@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.
- package/CHANGELOG.md +383 -0
- package/LICENSE +21 -0
- package/README.md +125 -0
- package/bin/goodvibes-daemon +100 -0
- package/bin/launcher-support.js +226 -0
- package/package.json +96 -0
- package/scripts/check-bun.sh +20 -0
- package/scripts/postinstall.js +244 -0
- package/src/cli/command-catalog.ts +828 -0
- package/src/cli/completion.ts +299 -0
- package/src/cli/help.ts +167 -0
- package/src/cli/index.ts +21 -0
- package/src/cli/parser.ts +55 -0
- package/src/cli/surface-catalog.ts +26 -0
- package/src/cli/types.ts +63 -0
- package/src/cluster/daemon-ws-call.ts +235 -0
- package/src/cluster/raw-reply-route.ts +111 -0
- package/src/config/checkpoint-settings.ts +113 -0
- package/src/config/run-daemon-config-migration.ts +47 -0
- package/src/config/secret-config.ts +175 -0
- package/src/config/secrets.ts +71 -0
- package/src/config/surface.ts +24 -0
- package/src/core/pairing-banner.ts +82 -0
- package/src/daemon/cli.ts +878 -0
- package/src/daemon/config-command.ts +281 -0
- package/src/daemon/handlers/context.ts +29 -0
- package/src/daemon/handlers/contracts.ts +43 -0
- package/src/daemon/handlers/credentials.ts +139 -0
- package/src/daemon/handlers/drafts/draft-store.ts +427 -0
- package/src/daemon/handlers/drafts/index.ts +17 -0
- package/src/daemon/handlers/drafts/register.ts +331 -0
- package/src/daemon/handlers/errors.ts +18 -0
- package/src/daemon/handlers/inbox/aggregator.ts +375 -0
- package/src/daemon/handlers/inbox/cursor-store.ts +512 -0
- package/src/daemon/handlers/inbox/index.ts +221 -0
- package/src/daemon/handlers/inbox/mapping.ts +192 -0
- package/src/daemon/handlers/inbox/poller.ts +239 -0
- package/src/daemon/handlers/inbox/provider-adapter.ts +171 -0
- package/src/daemon/handlers/inbox/providers/discord.ts +276 -0
- package/src/daemon/handlers/inbox/providers/email.ts +176 -0
- package/src/daemon/handlers/inbox/providers/imap-client.ts +300 -0
- package/src/daemon/handlers/inbox/providers/route-util.ts +24 -0
- package/src/daemon/handlers/inbox/providers/slack.ts +287 -0
- package/src/daemon/handlers/index.ts +117 -0
- package/src/daemon/handlers/register.ts +180 -0
- package/src/daemon/handlers/remote/backends/cloud-terminal.ts +143 -0
- package/src/daemon/handlers/remote/backends/docker.ts +79 -0
- package/src/daemon/handlers/remote/backends/index.ts +40 -0
- package/src/daemon/handlers/remote/backends/local-process.ts +113 -0
- package/src/daemon/handlers/remote/backends/process-runner.ts +127 -0
- package/src/daemon/handlers/remote/backends/ssh.ts +126 -0
- package/src/daemon/handlers/remote/backends/types.ts +97 -0
- package/src/daemon/handlers/remote/dispatcher.ts +181 -0
- package/src/daemon/handlers/remote/index.ts +120 -0
- package/src/daemon/handlers/remote/peer-registry.ts +357 -0
- package/src/daemon/handlers/remote/service.ts +191 -0
- package/src/daemon/handlers/routing/inbox-bridge.ts +71 -0
- package/src/daemon/handlers/routing/index.ts +261 -0
- package/src/daemon/handlers/routing/route-store.ts +319 -0
- package/src/daemon/handlers/routing/routing-resolver.ts +75 -0
- package/src/daemon/handlers/sqlite-store.ts +303 -0
- package/src/daemon/handlers/triage/index.ts +57 -0
- package/src/daemon/handlers/triage/integration.ts +213 -0
- package/src/daemon/handlers/triage/pipeline.ts +274 -0
- package/src/daemon/handlers/triage/scorer.ts +287 -0
- package/src/daemon/handlers/triage/tagger/discord.ts +187 -0
- package/src/daemon/handlers/triage/tagger/imap.ts +384 -0
- package/src/daemon/handlers/triage/tagger/index.ts +184 -0
- package/src/daemon/handlers/triage/tagger/shared.ts +70 -0
- package/src/daemon/handlers/triage/tagger/slack.ts +69 -0
- package/src/daemon/handlers/triage/types.ts +50 -0
- package/src/daemon/lifecycle.ts +41 -0
- package/src/daemon/local-daemon-state.ts +233 -0
- package/src/daemon/pair-command.ts +301 -0
- package/src/daemon/provision-wake-model.ts +81 -0
- package/src/daemon/send/channels.ts +200 -0
- package/src/daemon/send/command.ts +333 -0
- package/src/daemon/send/composition.ts +100 -0
- package/src/daemon/send/failure-text.ts +93 -0
- package/src/daemon/send/inert-text.ts +225 -0
- package/src/daemon/send/stdin.ts +24 -0
- package/src/daemon/service-commands.ts +530 -0
- package/src/daemon/sessions-command.ts +209 -0
- package/src/daemon/status-command.ts +481 -0
- package/src/daemon/webui-command.ts +339 -0
- package/src/runtime/boot-tasks.ts +110 -0
- package/src/runtime/cluster-composition.ts +124 -0
- package/src/runtime/cluster-group-composition.ts +284 -0
- package/src/runtime/conversation-rewind-port.ts +171 -0
- package/src/runtime/credential-composition.ts +54 -0
- package/src/runtime/daemon-handler-composition.ts +76 -0
- package/src/runtime/device-posture-composition.ts +115 -0
- package/src/runtime/disposal-wiring.ts +101 -0
- package/src/runtime/fleet-needs-input-push.ts +61 -0
- package/src/runtime/fleet-services.ts +41 -0
- package/src/runtime/hosted-session-composition.ts +128 -0
- package/src/runtime/index.ts +100 -0
- package/src/runtime/knowledge-services.ts +101 -0
- package/src/runtime/legacy-daemon-migration.ts +605 -0
- package/src/runtime/legacy-daemon-reconcile.ts +448 -0
- package/src/runtime/mail-composition.ts +65 -0
- package/src/runtime/notification-dispatch.ts +86 -0
- package/src/runtime/plugin-composition.ts +111 -0
- package/src/runtime/runtime-services-types.ts +268 -0
- package/src/runtime/services.ts +756 -0
- package/src/runtime/trigger-services.ts +62 -0
- package/src/runtime/trust/checkpoint-eligibility.ts +138 -0
- package/src/runtime/trust/trust-gated-approvals.ts +169 -0
- package/src/runtime/update-check.ts +61 -0
- package/src/runtime/workspace-checkpointing.ts +116 -0
- package/src/testing/daemon-fixture.ts +276 -0
- package/src/testing/hosted-session-failures.ts +92 -0
- 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
|
+
}
|