@vellumai/assistant 0.4.11 → 0.4.13
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/ARCHITECTURE.md +401 -385
- package/package.json +1 -1
- package/src/__tests__/guardian-verify-setup-skill-regression.test.ts +75 -61
- package/src/__tests__/registry.test.ts +235 -187
- package/src/__tests__/secure-keys.test.ts +27 -0
- package/src/__tests__/session-agent-loop.test.ts +521 -256
- package/src/__tests__/session-surfaces-task-progress.test.ts +1 -0
- package/src/__tests__/session-tool-setup-app-refresh.test.ts +1 -0
- package/src/__tests__/session-tool-setup-memory-scope.test.ts +1 -0
- package/src/__tests__/session-tool-setup-side-effect-flag.test.ts +1 -0
- package/src/__tests__/skills.test.ts +334 -276
- package/src/__tests__/slack-skill.test.ts +124 -0
- package/src/__tests__/starter-task-flow.test.ts +7 -17
- package/src/agent/loop.ts +10 -3
- package/src/config/bundled-skills/chatgpt-import/tools/chatgpt-import.ts +449 -0
- package/src/config/bundled-skills/doordash/SKILL.md +171 -0
- package/src/config/bundled-skills/doordash/__tests__/doordash-client.test.ts +203 -0
- package/src/config/bundled-skills/doordash/__tests__/doordash-session.test.ts +164 -0
- package/src/config/bundled-skills/doordash/doordash-cli.ts +1193 -0
- package/src/config/bundled-skills/doordash/doordash-entry.ts +22 -0
- package/src/config/bundled-skills/doordash/lib/cart-queries.ts +787 -0
- package/src/config/bundled-skills/doordash/lib/client.ts +1071 -0
- package/src/config/bundled-skills/doordash/lib/order-queries.ts +85 -0
- package/src/config/bundled-skills/doordash/lib/queries.ts +28 -0
- package/src/config/bundled-skills/doordash/lib/query-extractor.ts +94 -0
- package/src/config/bundled-skills/doordash/lib/search-queries.ts +203 -0
- package/src/config/bundled-skills/doordash/lib/session.ts +93 -0
- package/src/config/bundled-skills/doordash/lib/shared/errors.ts +61 -0
- package/src/config/bundled-skills/doordash/lib/shared/ipc.ts +32 -0
- package/src/config/bundled-skills/doordash/lib/shared/network-recorder.ts +380 -0
- package/src/config/bundled-skills/doordash/lib/shared/platform.ts +35 -0
- package/src/config/bundled-skills/doordash/lib/shared/recording-store.ts +43 -0
- package/src/config/bundled-skills/doordash/lib/shared/recording-types.ts +49 -0
- package/src/config/bundled-skills/doordash/lib/shared/truncate.ts +6 -0
- package/src/config/bundled-skills/doordash/lib/store-queries.ts +246 -0
- package/src/config/bundled-skills/doordash/lib/types.ts +367 -0
- package/src/config/bundled-skills/google-calendar/SKILL.md +4 -5
- package/src/config/bundled-skills/google-oauth-setup/SKILL.md +41 -41
- package/src/config/bundled-skills/messaging/SKILL.md +59 -42
- package/src/config/bundled-skills/messaging/TOOLS.json +14 -92
- package/src/config/bundled-skills/messaging/tools/gmail-archive-by-query.ts +5 -1
- package/src/config/bundled-skills/messaging/tools/gmail-batch-archive.ts +11 -2
- package/src/config/bundled-skills/messaging/tools/gmail-outreach-scan.ts +8 -1
- package/src/config/bundled-skills/messaging/tools/gmail-sender-digest.ts +12 -4
- package/src/config/bundled-skills/messaging/tools/gmail-unsubscribe.ts +5 -1
- package/src/config/bundled-skills/messaging/tools/messaging-archive-by-sender.ts +5 -1
- package/src/config/bundled-skills/messaging/tools/messaging-sender-digest.ts +5 -2
- package/src/config/bundled-skills/notion/SKILL.md +240 -0
- package/src/config/bundled-skills/notion-oauth-setup/SKILL.md +127 -0
- package/src/config/bundled-skills/oauth-setup/SKILL.md +144 -0
- package/src/config/bundled-skills/phone-calls/SKILL.md +76 -45
- package/src/config/bundled-skills/skills-catalog/SKILL.md +32 -29
- package/src/config/bundled-skills/slack/SKILL.md +49 -0
- package/src/config/bundled-skills/slack/TOOLS.json +167 -0
- package/src/config/bundled-skills/slack/tools/shared.ts +23 -0
- package/src/config/bundled-skills/{messaging → slack}/tools/slack-add-reaction.ts +2 -5
- package/src/config/bundled-skills/slack/tools/slack-channel-details.ts +33 -0
- package/src/config/bundled-skills/slack/tools/slack-configure-channels.ts +75 -0
- package/src/config/bundled-skills/{messaging → slack}/tools/slack-delete-message.ts +2 -5
- package/src/config/bundled-skills/{messaging → slack}/tools/slack-leave-channel.ts +2 -5
- package/src/config/bundled-skills/slack/tools/slack-scan-digest.ts +193 -0
- package/src/config/{vellum-skills → bundled-skills}/sms-setup/SKILL.md +29 -22
- package/src/config/{vellum-skills → bundled-skills}/telegram-setup/SKILL.md +17 -14
- package/src/config/{vellum-skills → bundled-skills}/twilio-setup/SKILL.md +20 -5
- package/src/config/bundled-tool-registry.ts +292 -267
- package/src/config/schema.ts +1 -1
- package/src/daemon/handlers/skills.ts +334 -234
- package/src/daemon/ipc-contract/messages.ts +2 -0
- package/src/daemon/ipc-contract/surfaces.ts +2 -0
- package/src/daemon/lifecycle.ts +358 -221
- package/src/daemon/response-tier.ts +2 -0
- package/src/daemon/server.ts +453 -193
- package/src/daemon/session-agent-loop-handlers.ts +43 -2
- package/src/daemon/session-agent-loop.ts +3 -0
- package/src/daemon/session-lifecycle.ts +3 -0
- package/src/daemon/session-process.ts +1 -0
- package/src/daemon/session-surfaces.ts +22 -20
- package/src/daemon/session-tool-setup.ts +1 -0
- package/src/daemon/session.ts +5 -2
- package/src/messaging/outreach-classifier.ts +12 -5
- package/src/messaging/provider-types.ts +5 -0
- package/src/messaging/provider.ts +1 -1
- package/src/messaging/providers/gmail/adapter.ts +11 -5
- package/src/messaging/providers/gmail/client.ts +2 -0
- package/src/messaging/providers/slack/adapter.ts +1 -0
- package/src/messaging/providers/slack/client.ts +8 -0
- package/src/messaging/providers/slack/types.ts +5 -0
- package/src/runtime/http-errors.ts +33 -20
- package/src/runtime/http-server.ts +706 -291
- package/src/runtime/http-types.ts +26 -16
- package/src/runtime/routes/secret-routes.ts +57 -2
- package/src/runtime/routes/surface-action-routes.ts +66 -0
- package/src/runtime/routes/trust-rules-routes.ts +140 -0
- package/src/security/keychain-to-encrypted-migration.ts +59 -0
- package/src/security/secure-keys.ts +17 -0
- package/src/skills/frontmatter.ts +9 -7
- package/src/tools/apps/executors.ts +2 -1
- package/src/tools/tool-manifest.ts +44 -42
- package/src/tools/types.ts +9 -0
- package/src/__tests__/skill-mirror-parity.test.ts +0 -176
- package/src/config/vellum-skills/catalog.json +0 -63
- package/src/config/vellum-skills/chatgpt-import/tools/chatgpt-import.ts +0 -295
- package/src/skills/vellum-catalog-remote.ts +0 -166
- package/src/tools/skills/vellum-catalog.ts +0 -168
- /package/src/config/{vellum-skills → bundled-skills}/chatgpt-import/SKILL.md +0 -0
- /package/src/config/{vellum-skills → bundled-skills}/chatgpt-import/TOOLS.json +0 -0
- /package/src/config/{vellum-skills → bundled-skills}/deploy-fullstack-vercel/SKILL.md +0 -0
- /package/src/config/{vellum-skills → bundled-skills}/document-writer/SKILL.md +0 -0
- /package/src/config/{vellum-skills → bundled-skills}/guardian-verify-setup/SKILL.md +0 -0
- /package/src/config/{vellum-skills → bundled-skills}/slack-oauth-setup/SKILL.md +0 -0
- /package/src/config/{vellum-skills → bundled-skills}/trusted-contacts/SKILL.md +0 -0
package/src/daemon/lifecycle.ts
CHANGED
|
@@ -1,47 +1,54 @@
|
|
|
1
|
-
import { randomBytes } from
|
|
2
|
-
import { chmodSync,readFileSync, writeFileSync } from
|
|
3
|
-
import { join } from
|
|
4
|
-
|
|
5
|
-
import { config as dotenvConfig } from
|
|
6
|
-
|
|
7
|
-
import { setPointerMessageProcessor } from
|
|
8
|
-
import { reconcileCallsOnStartup } from
|
|
9
|
-
import { setRelayBroadcast } from
|
|
10
|
-
import { TwilioConversationRelayProvider } from
|
|
11
|
-
import { setVoiceBridgeDeps } from
|
|
12
|
-
import { isAssistantFeatureFlagEnabled } from
|
|
1
|
+
import { randomBytes } from "node:crypto";
|
|
2
|
+
import { chmodSync, readFileSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
|
|
5
|
+
import { config as dotenvConfig } from "dotenv";
|
|
6
|
+
|
|
7
|
+
import { setPointerMessageProcessor } from "../calls/call-pointer-messages.js";
|
|
8
|
+
import { reconcileCallsOnStartup } from "../calls/call-recovery.js";
|
|
9
|
+
import { setRelayBroadcast } from "../calls/relay-server.js";
|
|
10
|
+
import { TwilioConversationRelayProvider } from "../calls/twilio-provider.js";
|
|
11
|
+
import { setVoiceBridgeDeps } from "../calls/voice-session-bridge.js";
|
|
12
|
+
import { isAssistantFeatureFlagEnabled } from "../config/assistant-feature-flags.js";
|
|
13
13
|
import {
|
|
14
14
|
getQdrantUrlEnv,
|
|
15
15
|
getRuntimeHttpHost,
|
|
16
16
|
getRuntimeHttpPort,
|
|
17
17
|
getRuntimeProxyBearerToken,
|
|
18
18
|
validateEnv,
|
|
19
|
-
} from
|
|
20
|
-
import { loadConfig } from
|
|
21
|
-
import { ensurePromptFiles } from
|
|
22
|
-
import { syncUpdateBulletinOnStartup } from
|
|
23
|
-
import { HeartbeatService } from
|
|
24
|
-
import { getHookManager } from
|
|
25
|
-
import { installTemplates } from
|
|
26
|
-
import { closeSentry, initSentry } from
|
|
27
|
-
import { initLogfire } from
|
|
28
|
-
import { getMcpServerManager } from
|
|
29
|
-
import * as attachmentsStore from
|
|
30
|
-
import * as conversationStore from
|
|
31
|
-
import { initializeDb } from
|
|
32
|
-
import { startMemoryJobsWorker } from
|
|
33
|
-
import { initQdrantClient } from
|
|
34
|
-
import { QdrantManager } from
|
|
35
|
-
import { rotateToolInvocations } from
|
|
36
|
-
import { migrateToDataLayout } from
|
|
37
|
-
import { migrateToWorkspaceLayout } from
|
|
38
|
-
import {
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
import {
|
|
43
|
-
|
|
44
|
-
|
|
19
|
+
} from "../config/env.js";
|
|
20
|
+
import { loadConfig } from "../config/loader.js";
|
|
21
|
+
import { ensurePromptFiles } from "../config/system-prompt.js";
|
|
22
|
+
import { syncUpdateBulletinOnStartup } from "../config/update-bulletin.js";
|
|
23
|
+
import { HeartbeatService } from "../heartbeat/heartbeat-service.js";
|
|
24
|
+
import { getHookManager } from "../hooks/manager.js";
|
|
25
|
+
import { installTemplates } from "../hooks/templates.js";
|
|
26
|
+
import { closeSentry, initSentry } from "../instrument.js";
|
|
27
|
+
import { initLogfire } from "../logfire.js";
|
|
28
|
+
import { getMcpServerManager } from "../mcp/manager.js";
|
|
29
|
+
import * as attachmentsStore from "../memory/attachments-store.js";
|
|
30
|
+
import * as conversationStore from "../memory/conversation-store.js";
|
|
31
|
+
import { initializeDb } from "../memory/db.js";
|
|
32
|
+
import { startMemoryJobsWorker } from "../memory/jobs-worker.js";
|
|
33
|
+
import { initQdrantClient } from "../memory/qdrant-client.js";
|
|
34
|
+
import { QdrantManager } from "../memory/qdrant-manager.js";
|
|
35
|
+
import { rotateToolInvocations } from "../memory/tool-usage-store.js";
|
|
36
|
+
import { migrateToDataLayout } from "../migrations/data-layout.js";
|
|
37
|
+
import { migrateToWorkspaceLayout } from "../migrations/workspace-layout.js";
|
|
38
|
+
import {
|
|
39
|
+
emitNotificationSignal,
|
|
40
|
+
registerBroadcastFn,
|
|
41
|
+
} from "../notifications/emit-signal.js";
|
|
42
|
+
import {
|
|
43
|
+
initSigningKey,
|
|
44
|
+
loadOrCreateSigningKey,
|
|
45
|
+
} from "../runtime/actor-token-service.js";
|
|
46
|
+
import { assistantEventHub } from "../runtime/assistant-event-hub.js";
|
|
47
|
+
import { ensureVellumGuardianBinding } from "../runtime/guardian-vellum-migration.js";
|
|
48
|
+
import { RuntimeHttpServer } from "../runtime/http-server.js";
|
|
49
|
+
import { startScheduler } from "../schedule/scheduler.js";
|
|
50
|
+
import { migrateKeychainToEncrypted } from "../security/keychain-to-encrypted-migration.js";
|
|
51
|
+
import { getLogger, initLogger } from "../util/logger.js";
|
|
45
52
|
import {
|
|
46
53
|
ensureDataDir,
|
|
47
54
|
getHttpTokenPath,
|
|
@@ -49,24 +56,44 @@ import {
|
|
|
49
56
|
getRootDir,
|
|
50
57
|
getSocketPath,
|
|
51
58
|
removeSocketFile,
|
|
52
|
-
} from
|
|
53
|
-
import {
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
import {
|
|
58
|
-
import {
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
import {
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
import {
|
|
59
|
+
} from "../util/platform.js";
|
|
60
|
+
import {
|
|
61
|
+
listWorkItems,
|
|
62
|
+
updateWorkItem,
|
|
63
|
+
} from "../work-items/work-item-store.js";
|
|
64
|
+
import { WorkspaceHeartbeatService } from "../workspace/heartbeat-service.js";
|
|
65
|
+
import {
|
|
66
|
+
createApprovalConversationGenerator,
|
|
67
|
+
createApprovalCopyGenerator,
|
|
68
|
+
} from "./approval-generators.js";
|
|
69
|
+
import {
|
|
70
|
+
hasNoAuthOverride,
|
|
71
|
+
hasUngatedNoAuthOverride,
|
|
72
|
+
} from "./connection-policy.js";
|
|
73
|
+
import {
|
|
74
|
+
cleanupPidFile,
|
|
75
|
+
cleanupPidFileIfOwner,
|
|
76
|
+
writePid,
|
|
77
|
+
} from "./daemon-control.js";
|
|
78
|
+
import {
|
|
79
|
+
createGuardianActionCopyGenerator,
|
|
80
|
+
createGuardianFollowUpConversationGenerator,
|
|
81
|
+
} from "./guardian-action-generators.js";
|
|
82
|
+
import { initPairingHandlers } from "./handlers/pairing.js";
|
|
83
|
+
import { installCliLaunchers } from "./install-cli-launchers.js";
|
|
84
|
+
import type { ServerMessage } from "./ipc-protocol.js";
|
|
85
|
+
import {
|
|
86
|
+
initializeProvidersAndTools,
|
|
87
|
+
registerMessagingProviders,
|
|
88
|
+
registerWatcherProviders,
|
|
89
|
+
} from "./providers-setup.js";
|
|
90
|
+
import { seedInterfaceFiles } from "./seed-files.js";
|
|
91
|
+
import { DaemonServer } from "./server.js";
|
|
92
|
+
import { initSlashPairingContext } from "./session-slash.js";
|
|
93
|
+
import { installShutdownHandlers } from "./shutdown-handlers.js";
|
|
67
94
|
|
|
68
95
|
// Re-export public API so existing consumers don't need to change imports
|
|
69
|
-
export type { StopResult } from
|
|
96
|
+
export type { StopResult } from "./daemon-control.js";
|
|
70
97
|
export {
|
|
71
98
|
cleanupPidFile,
|
|
72
99
|
ensureDaemonRunning,
|
|
@@ -74,12 +101,12 @@ export {
|
|
|
74
101
|
isDaemonRunning,
|
|
75
102
|
startDaemon,
|
|
76
103
|
stopDaemon,
|
|
77
|
-
} from
|
|
104
|
+
} from "./daemon-control.js";
|
|
78
105
|
|
|
79
|
-
const log = getLogger(
|
|
106
|
+
const log = getLogger("lifecycle");
|
|
80
107
|
|
|
81
108
|
function loadDotEnv(): void {
|
|
82
|
-
dotenvConfig({ path: join(getRootDir(),
|
|
109
|
+
dotenvConfig({ path: join(getRootDir(), ".env"), quiet: true });
|
|
83
110
|
}
|
|
84
111
|
|
|
85
112
|
// Entry point for the daemon process itself
|
|
@@ -88,9 +115,13 @@ export async function runDaemon(): Promise<void> {
|
|
|
88
115
|
validateEnv();
|
|
89
116
|
|
|
90
117
|
if (hasUngatedNoAuthOverride()) {
|
|
91
|
-
log.warn(
|
|
118
|
+
log.warn(
|
|
119
|
+
"VELLUM_DAEMON_NOAUTH is set but VELLUM_UNSAFE_AUTH_BYPASS=1 is not — auth bypass is IGNORED and authentication remains enabled. Set VELLUM_UNSAFE_AUTH_BYPASS=1 to confirm the bypass.",
|
|
120
|
+
);
|
|
92
121
|
} else if (hasNoAuthOverride()) {
|
|
93
|
-
log.warn(
|
|
122
|
+
log.warn(
|
|
123
|
+
"VELLUM_DAEMON_NOAUTH is set — IPC authentication is DISABLED. This should only be used for development or SSH-forwarded sockets. Do not use in production.",
|
|
124
|
+
);
|
|
94
125
|
}
|
|
95
126
|
|
|
96
127
|
// Track whether the IPC socket has been created so we can clean it up
|
|
@@ -113,6 +144,10 @@ export async function runDaemon(): Promise<void> {
|
|
|
113
144
|
migrateToWorkspaceLayout();
|
|
114
145
|
ensureDataDir();
|
|
115
146
|
|
|
147
|
+
// Copy any existing macOS keychain secrets into the encrypted file store
|
|
148
|
+
// before config loads, so the new encrypted-store-first read path sees them.
|
|
149
|
+
migrateKeychainToEncrypted();
|
|
150
|
+
|
|
116
151
|
// Resolve and write the bearer token as early as possible so the CLI
|
|
117
152
|
// (which polls for this file during gateway startup) doesn't time out
|
|
118
153
|
// waiting for Qdrant or other slow init steps to finish.
|
|
@@ -120,81 +155,102 @@ export async function runDaemon(): Promise<void> {
|
|
|
120
155
|
let bearerToken = getRuntimeProxyBearerToken();
|
|
121
156
|
if (!bearerToken) {
|
|
122
157
|
try {
|
|
123
|
-
const existing = readFileSync(httpTokenPath,
|
|
158
|
+
const existing = readFileSync(httpTokenPath, "utf-8").trim();
|
|
124
159
|
if (existing) bearerToken = existing;
|
|
125
160
|
} catch {
|
|
126
161
|
// File doesn't exist or can't be read — will generate below
|
|
127
162
|
}
|
|
128
163
|
}
|
|
129
164
|
if (!bearerToken) {
|
|
130
|
-
bearerToken = randomBytes(32).toString(
|
|
165
|
+
bearerToken = randomBytes(32).toString("hex");
|
|
131
166
|
}
|
|
132
167
|
writeFileSync(httpTokenPath, bearerToken, { mode: 0o600 });
|
|
133
168
|
chmodSync(httpTokenPath, 0o600);
|
|
134
|
-
log.info(
|
|
169
|
+
log.info("Daemon startup: bearer token written");
|
|
135
170
|
|
|
136
171
|
// Load (or generate + persist) the actor-token signing key so tokens
|
|
137
172
|
// survive daemon restarts. Must happen after ensureDataDir() creates
|
|
138
173
|
// the protected directory.
|
|
139
174
|
initSigningKey(loadOrCreateSigningKey());
|
|
140
175
|
|
|
141
|
-
log.info(
|
|
176
|
+
log.info("Daemon startup: migrations complete");
|
|
142
177
|
|
|
143
178
|
seedInterfaceFiles();
|
|
144
179
|
|
|
145
|
-
log.info(
|
|
180
|
+
log.info("Daemon startup: installing templates and initializing DB");
|
|
146
181
|
installTemplates();
|
|
147
182
|
ensurePromptFiles();
|
|
148
183
|
|
|
149
184
|
try {
|
|
150
185
|
installCliLaunchers();
|
|
151
186
|
} catch (err) {
|
|
152
|
-
log.warn(
|
|
187
|
+
log.warn(
|
|
188
|
+
{ err },
|
|
189
|
+
"CLI launcher installation failed — continuing startup",
|
|
190
|
+
);
|
|
153
191
|
}
|
|
154
192
|
initializeDb();
|
|
155
|
-
log.info(
|
|
193
|
+
log.info("Daemon startup: DB initialized");
|
|
156
194
|
|
|
157
195
|
// Backfill vellum guardian binding for existing installations
|
|
158
196
|
try {
|
|
159
|
-
ensureVellumGuardianBinding(
|
|
197
|
+
ensureVellumGuardianBinding("self");
|
|
160
198
|
} catch (err) {
|
|
161
|
-
log.warn(
|
|
199
|
+
log.warn(
|
|
200
|
+
{ err },
|
|
201
|
+
"Vellum guardian binding backfill failed — continuing startup",
|
|
202
|
+
);
|
|
162
203
|
}
|
|
163
204
|
|
|
164
205
|
try {
|
|
165
206
|
syncUpdateBulletinOnStartup();
|
|
166
207
|
} catch (err) {
|
|
167
|
-
log.warn({ err },
|
|
208
|
+
log.warn({ err }, "Bulletin sync failed — continuing startup");
|
|
168
209
|
}
|
|
169
210
|
|
|
170
211
|
// Recover orphaned work items that were left in 'running' state when the
|
|
171
212
|
// daemon previously crashed or was killed mid-task.
|
|
172
|
-
const orphanedRunning = listWorkItems({ status:
|
|
213
|
+
const orphanedRunning = listWorkItems({ status: "running" });
|
|
173
214
|
if (orphanedRunning.length > 0) {
|
|
174
215
|
for (const item of orphanedRunning) {
|
|
175
|
-
updateWorkItem(item.id, {
|
|
176
|
-
|
|
216
|
+
updateWorkItem(item.id, {
|
|
217
|
+
status: "failed",
|
|
218
|
+
lastRunStatus: "interrupted",
|
|
219
|
+
});
|
|
220
|
+
log.info(
|
|
221
|
+
{ workItemId: item.id, title: item.title },
|
|
222
|
+
"Recovered orphaned running work item → failed (interrupted)",
|
|
223
|
+
);
|
|
177
224
|
}
|
|
178
|
-
log.info(
|
|
225
|
+
log.info(
|
|
226
|
+
{ count: orphanedRunning.length },
|
|
227
|
+
"Recovered orphaned running work items",
|
|
228
|
+
);
|
|
179
229
|
}
|
|
180
230
|
|
|
181
231
|
try {
|
|
182
232
|
const twilioProvider = new TwilioConversationRelayProvider();
|
|
183
233
|
await reconcileCallsOnStartup(twilioProvider, log);
|
|
184
234
|
} catch (err) {
|
|
185
|
-
log.warn({ err },
|
|
235
|
+
log.warn({ err }, "Call recovery failed — continuing startup");
|
|
186
236
|
}
|
|
187
237
|
|
|
188
|
-
log.info(
|
|
238
|
+
log.info("Daemon startup: loading config");
|
|
189
239
|
const config = loadConfig();
|
|
190
240
|
|
|
191
241
|
if (config.logFile.dir) {
|
|
192
|
-
initLogger({
|
|
242
|
+
initLogger({
|
|
243
|
+
dir: config.logFile.dir,
|
|
244
|
+
retentionDays: config.logFile.retentionDays,
|
|
245
|
+
});
|
|
193
246
|
}
|
|
194
247
|
|
|
195
248
|
// If the user has opted out of crash reporting, stop Sentry from capturing
|
|
196
249
|
// future events. Early-startup crashes before this point are still captured.
|
|
197
|
-
const collectUsageData = isAssistantFeatureFlagEnabled(
|
|
250
|
+
const collectUsageData = isAssistantFeatureFlagEnabled(
|
|
251
|
+
"feature_flags.collect-usage-data.enabled",
|
|
252
|
+
config,
|
|
253
|
+
);
|
|
198
254
|
if (!collectUsageData) {
|
|
199
255
|
await closeSentry();
|
|
200
256
|
}
|
|
@@ -204,15 +260,15 @@ export async function runDaemon(): Promise<void> {
|
|
|
204
260
|
// Start the IPC socket BEFORE Qdrant so that clients can connect
|
|
205
261
|
// immediately. Qdrant startup can take 30+ seconds (binary download,
|
|
206
262
|
// /readyz polling) which previously blocked the socket from appearing.
|
|
207
|
-
log.info(
|
|
263
|
+
log.info("Daemon startup: starting DaemonServer (IPC socket)");
|
|
208
264
|
const server = new DaemonServer();
|
|
209
265
|
await server.start();
|
|
210
266
|
socketCreated = true;
|
|
211
|
-
log.info(
|
|
267
|
+
log.info("Daemon startup: DaemonServer started");
|
|
212
268
|
|
|
213
269
|
// Initialize Qdrant vector store — non-fatal so the daemon stays up without it
|
|
214
270
|
const qdrantUrl = getQdrantUrlEnv() || config.memory.qdrant.url;
|
|
215
|
-
log.info({ qdrantUrl },
|
|
271
|
+
log.info({ qdrantUrl }, "Daemon startup: initializing Qdrant");
|
|
216
272
|
const qdrantManager = new QdrantManager({ url: qdrantUrl });
|
|
217
273
|
try {
|
|
218
274
|
await qdrantManager.start();
|
|
@@ -223,12 +279,15 @@ export async function runDaemon(): Promise<void> {
|
|
|
223
279
|
onDisk: config.memory.qdrant.onDisk,
|
|
224
280
|
quantization: config.memory.qdrant.quantization,
|
|
225
281
|
});
|
|
226
|
-
log.info(
|
|
282
|
+
log.info("Qdrant vector store initialized");
|
|
227
283
|
} catch (err) {
|
|
228
|
-
log.warn(
|
|
284
|
+
log.warn(
|
|
285
|
+
{ err },
|
|
286
|
+
"Qdrant failed to start — memory features will be unavailable",
|
|
287
|
+
);
|
|
229
288
|
}
|
|
230
289
|
|
|
231
|
-
log.info(
|
|
290
|
+
log.info("Daemon startup: starting memory worker");
|
|
232
291
|
const memoryWorker = startMemoryJobsWorker();
|
|
233
292
|
|
|
234
293
|
registerWatcherProviders();
|
|
@@ -244,12 +303,12 @@ export async function runDaemon(): Promise<void> {
|
|
|
244
303
|
},
|
|
245
304
|
(reminder) => {
|
|
246
305
|
void emitNotificationSignal({
|
|
247
|
-
sourceEventName:
|
|
248
|
-
sourceChannel:
|
|
306
|
+
sourceEventName: "reminder.fired",
|
|
307
|
+
sourceChannel: "scheduler",
|
|
249
308
|
sourceSessionId: reminder.id,
|
|
250
309
|
attentionHints: {
|
|
251
310
|
requiresAction: true,
|
|
252
|
-
urgency:
|
|
311
|
+
urgency: "high",
|
|
253
312
|
isAsyncBackground: false,
|
|
254
313
|
visibleInSourceNow: false,
|
|
255
314
|
},
|
|
@@ -265,12 +324,12 @@ export async function runDaemon(): Promise<void> {
|
|
|
265
324
|
},
|
|
266
325
|
(schedule) => {
|
|
267
326
|
void emitNotificationSignal({
|
|
268
|
-
sourceEventName:
|
|
269
|
-
sourceChannel:
|
|
327
|
+
sourceEventName: "schedule.complete",
|
|
328
|
+
sourceChannel: "scheduler",
|
|
270
329
|
sourceSessionId: schedule.id,
|
|
271
330
|
attentionHints: {
|
|
272
331
|
requiresAction: false,
|
|
273
|
-
urgency:
|
|
332
|
+
urgency: "medium",
|
|
274
333
|
isAsyncBackground: true,
|
|
275
334
|
visibleInSourceNow: false,
|
|
276
335
|
},
|
|
@@ -282,12 +341,12 @@ export async function runDaemon(): Promise<void> {
|
|
|
282
341
|
},
|
|
283
342
|
(notification) => {
|
|
284
343
|
void emitNotificationSignal({
|
|
285
|
-
sourceEventName:
|
|
286
|
-
sourceChannel:
|
|
344
|
+
sourceEventName: "watcher.notification",
|
|
345
|
+
sourceChannel: "watcher",
|
|
287
346
|
sourceSessionId: `watcher-${Date.now()}`,
|
|
288
347
|
attentionHints: {
|
|
289
348
|
requiresAction: false,
|
|
290
|
-
urgency:
|
|
349
|
+
urgency: "low",
|
|
291
350
|
isAsyncBackground: true,
|
|
292
351
|
visibleInSourceNow: false,
|
|
293
352
|
},
|
|
@@ -299,12 +358,12 @@ export async function runDaemon(): Promise<void> {
|
|
|
299
358
|
},
|
|
300
359
|
(params) => {
|
|
301
360
|
void emitNotificationSignal({
|
|
302
|
-
sourceEventName:
|
|
303
|
-
sourceChannel:
|
|
361
|
+
sourceEventName: "watcher.escalation",
|
|
362
|
+
sourceChannel: "watcher",
|
|
304
363
|
sourceSessionId: `watcher-escalation-${Date.now()}`,
|
|
305
364
|
attentionHints: {
|
|
306
365
|
requiresAction: true,
|
|
307
|
-
urgency:
|
|
366
|
+
urgency: "high",
|
|
308
367
|
isAsyncBackground: false,
|
|
309
368
|
visibleInSourceNow: false,
|
|
310
369
|
},
|
|
@@ -320,7 +379,7 @@ export async function runDaemon(): Promise<void> {
|
|
|
320
379
|
// to it) and optional REST API access. Defaults to port 7821.
|
|
321
380
|
let runtimeHttp: RuntimeHttpServer | null = null;
|
|
322
381
|
const httpPort = getRuntimeHttpPort();
|
|
323
|
-
log.info({ httpPort },
|
|
382
|
+
log.info({ httpPort }, "Daemon startup: starting runtime HTTP server");
|
|
324
383
|
|
|
325
384
|
const hostname = getRuntimeHttpHost();
|
|
326
385
|
|
|
@@ -328,15 +387,44 @@ export async function runDaemon(): Promise<void> {
|
|
|
328
387
|
port: httpPort,
|
|
329
388
|
hostname,
|
|
330
389
|
bearerToken,
|
|
331
|
-
processMessage: (
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
390
|
+
processMessage: (
|
|
391
|
+
conversationId,
|
|
392
|
+
content,
|
|
393
|
+
attachmentIds,
|
|
394
|
+
options,
|
|
395
|
+
sourceChannel,
|
|
396
|
+
sourceInterface,
|
|
397
|
+
) =>
|
|
398
|
+
server.processMessage(
|
|
399
|
+
conversationId,
|
|
400
|
+
content,
|
|
401
|
+
attachmentIds,
|
|
402
|
+
options,
|
|
403
|
+
sourceChannel,
|
|
404
|
+
sourceInterface,
|
|
405
|
+
),
|
|
406
|
+
persistAndProcessMessage: (
|
|
407
|
+
conversationId,
|
|
408
|
+
content,
|
|
409
|
+
attachmentIds,
|
|
410
|
+
options,
|
|
411
|
+
sourceChannel,
|
|
412
|
+
sourceInterface,
|
|
413
|
+
) =>
|
|
414
|
+
server.persistAndProcessMessage(
|
|
415
|
+
conversationId,
|
|
416
|
+
content,
|
|
417
|
+
attachmentIds,
|
|
418
|
+
options,
|
|
419
|
+
sourceChannel,
|
|
420
|
+
sourceInterface,
|
|
421
|
+
),
|
|
335
422
|
interfacesDir: getInterfacesDir(),
|
|
336
423
|
approvalCopyGenerator: createApprovalCopyGenerator(),
|
|
337
424
|
approvalConversationGenerator: createApprovalConversationGenerator(),
|
|
338
425
|
guardianActionCopyGenerator: createGuardianActionCopyGenerator(),
|
|
339
|
-
guardianFollowUpConversationGenerator:
|
|
426
|
+
guardianFollowUpConversationGenerator:
|
|
427
|
+
createGuardianFollowUpConversationGenerator(),
|
|
340
428
|
sendMessageDeps: {
|
|
341
429
|
getOrCreateSession: (conversationId) =>
|
|
342
430
|
server.getSessionForMessages(conversationId),
|
|
@@ -349,6 +437,7 @@ export async function runDaemon(): Promise<void> {
|
|
|
349
437
|
data: a.dataBase64,
|
|
350
438
|
})),
|
|
351
439
|
},
|
|
440
|
+
findSession: (sessionId) => server.findSession(sessionId),
|
|
352
441
|
});
|
|
353
442
|
|
|
354
443
|
// Inject voice bridge deps BEFORE attempting to start the HTTP server.
|
|
@@ -364,138 +453,174 @@ export async function runDaemon(): Promise<void> {
|
|
|
364
453
|
data: a.dataBase64,
|
|
365
454
|
})),
|
|
366
455
|
deriveDefaultStrictSideEffects: (conversationId) => {
|
|
367
|
-
const threadType =
|
|
368
|
-
|
|
456
|
+
const threadType =
|
|
457
|
+
conversationStore.getConversationThreadType(conversationId);
|
|
458
|
+
return threadType === "private";
|
|
369
459
|
},
|
|
370
460
|
});
|
|
371
461
|
try {
|
|
372
462
|
await runtimeHttp.start();
|
|
373
463
|
setRelayBroadcast((msg) => server.broadcast(msg));
|
|
374
|
-
setPointerMessageProcessor(
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
// A depth counter (rather than a boolean) ensures that overlapping
|
|
384
|
-
// pointer requests on the same session don't clear each other's
|
|
385
|
-
// constraint — each caller increments on entry and decrements in
|
|
386
|
-
// its own finally block.
|
|
387
|
-
session.toolsDisabledDepth++;
|
|
388
|
-
try {
|
|
389
|
-
const messageId = await session.persistUserMessage(
|
|
390
|
-
instruction,
|
|
391
|
-
[],
|
|
392
|
-
undefined,
|
|
393
|
-
{ pointerInstruction: true },
|
|
394
|
-
'[Call status event]',
|
|
395
|
-
);
|
|
396
|
-
|
|
397
|
-
// Helper: roll back persisted messages on failure, then reload
|
|
398
|
-
// in-memory history from the (now cleaned) DB. Reloading avoids
|
|
399
|
-
// stale-index issues when context compaction reassigns the
|
|
400
|
-
// messages array during runAgentLoop.
|
|
401
|
-
const rollback = async (extraMessageIds?: string[]) => {
|
|
402
|
-
try { conversationStore.deleteMessageById(messageId); } catch { /* best effort */ }
|
|
403
|
-
for (const id of extraMessageIds ?? []) {
|
|
404
|
-
try { conversationStore.deleteMessageById(id); } catch { /* best effort */ }
|
|
405
|
-
}
|
|
406
|
-
try { await session.loadFromDb(); } catch { /* best effort */ }
|
|
407
|
-
};
|
|
408
|
-
|
|
409
|
-
// Snapshot message IDs before the agent loop so we can diff
|
|
410
|
-
// afterwards to find exactly which messages this run created,
|
|
411
|
-
// avoiding positional heuristics that break under concurrency.
|
|
464
|
+
setPointerMessageProcessor(
|
|
465
|
+
async (conversationId, instruction, requiredFacts) => {
|
|
466
|
+
const session = await server.getSessionForMessages(conversationId);
|
|
467
|
+
|
|
468
|
+
// Constrain pointer generation to a tool-disabled path so call-
|
|
469
|
+
// status events cannot trigger unintended side-effect tools.
|
|
470
|
+
// Incrementing toolsDisabledDepth causes the resolveTools callback
|
|
471
|
+
// to return an empty tool list, preventing the LLM from seeing or
|
|
472
|
+
// invoking any tools during the pointer agent loop.
|
|
412
473
|
//
|
|
413
|
-
//
|
|
414
|
-
//
|
|
415
|
-
//
|
|
416
|
-
//
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
474
|
+
// A depth counter (rather than a boolean) ensures that overlapping
|
|
475
|
+
// pointer requests on the same session don't clear each other's
|
|
476
|
+
// constraint — each caller increments on entry and decrements in
|
|
477
|
+
// its own finally block.
|
|
478
|
+
session.toolsDisabledDepth++;
|
|
479
|
+
try {
|
|
480
|
+
const messageId = await session.persistUserMessage(
|
|
481
|
+
instruction,
|
|
482
|
+
[],
|
|
483
|
+
undefined,
|
|
484
|
+
{ pointerInstruction: true },
|
|
485
|
+
"[Call status event]",
|
|
486
|
+
);
|
|
487
|
+
|
|
488
|
+
// Helper: roll back persisted messages on failure, then reload
|
|
489
|
+
// in-memory history from the (now cleaned) DB. Reloading avoids
|
|
490
|
+
// stale-index issues when context compaction reassigns the
|
|
491
|
+
// messages array during runAgentLoop.
|
|
492
|
+
const rollback = async (extraMessageIds?: string[]) => {
|
|
493
|
+
try {
|
|
494
|
+
conversationStore.deleteMessageById(messageId);
|
|
495
|
+
} catch {
|
|
496
|
+
/* best effort */
|
|
497
|
+
}
|
|
498
|
+
for (const id of extraMessageIds ?? []) {
|
|
499
|
+
try {
|
|
500
|
+
conversationStore.deleteMessageById(id);
|
|
501
|
+
} catch {
|
|
502
|
+
/* best effort */
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
try {
|
|
506
|
+
await session.loadFromDb();
|
|
507
|
+
} catch {
|
|
508
|
+
/* best effort */
|
|
509
|
+
}
|
|
510
|
+
};
|
|
511
|
+
|
|
512
|
+
// Snapshot message IDs before the agent loop so we can diff
|
|
513
|
+
// afterwards to find exactly which messages this run created,
|
|
514
|
+
// avoiding positional heuristics that break under concurrency.
|
|
515
|
+
//
|
|
516
|
+
// Caveat: the diff captures *all* new messages in the
|
|
517
|
+
// conversation during the loop window, not just those from
|
|
518
|
+
// this specific agent loop. If a concurrent pointer event
|
|
519
|
+
// falls back to a deterministic addMessage() while our loop
|
|
520
|
+
// is in flight, that message lands in our diff. The race
|
|
521
|
+
// requires two pointer events for the same conversation
|
|
522
|
+
// within the agent loop window *and* this run must fail or
|
|
523
|
+
// fail fact-check — narrow enough to accept. A future
|
|
524
|
+
// improvement could tag messages with a per-run correlation
|
|
525
|
+
// ID so rollback only targets its own output.
|
|
526
|
+
const preRunMessageIds = new Set(
|
|
527
|
+
conversationStore.getMessages(conversationId).map((m) => m.id),
|
|
528
|
+
);
|
|
529
|
+
|
|
530
|
+
let agentLoopError: string | undefined;
|
|
531
|
+
let generatedText = "";
|
|
532
|
+
await session.runAgentLoop(instruction, messageId, (msg) => {
|
|
533
|
+
if (
|
|
534
|
+
"type" in msg &&
|
|
535
|
+
msg.type === "assistant_text_delta" &&
|
|
536
|
+
"text" in msg
|
|
537
|
+
) {
|
|
538
|
+
generatedText += (msg as { text: string }).text;
|
|
539
|
+
}
|
|
540
|
+
if (
|
|
541
|
+
"type" in msg &&
|
|
542
|
+
(msg.type === "error" || msg.type === "session_error")
|
|
543
|
+
) {
|
|
544
|
+
agentLoopError =
|
|
545
|
+
"message" in msg
|
|
546
|
+
? (msg as { message: string }).message
|
|
547
|
+
: "userMessage" in msg
|
|
548
|
+
? (msg as { userMessage: string }).userMessage
|
|
549
|
+
: "Agent loop failed";
|
|
550
|
+
}
|
|
551
|
+
});
|
|
552
|
+
|
|
553
|
+
// Identify messages created during this run by diffing against
|
|
554
|
+
// the pre-run snapshot. This captures all messages added to the
|
|
555
|
+
// conversation during the loop window, which may include messages
|
|
556
|
+
// from concurrent pointer events (see over-capture caveat above).
|
|
557
|
+
const postRunMessages =
|
|
558
|
+
conversationStore.getMessages(conversationId);
|
|
559
|
+
const createdMessageIds = postRunMessages
|
|
560
|
+
.filter((m) => !preRunMessageIds.has(m.id) && m.id !== messageId)
|
|
561
|
+
.map((m) => m.id);
|
|
562
|
+
|
|
563
|
+
if (agentLoopError) {
|
|
564
|
+
await rollback(createdMessageIds);
|
|
565
|
+
throw new Error(agentLoopError);
|
|
439
566
|
}
|
|
440
|
-
});
|
|
441
|
-
|
|
442
|
-
// Identify messages created during this run by diffing against
|
|
443
|
-
// the pre-run snapshot. This captures all messages added to the
|
|
444
|
-
// conversation during the loop window, which may include messages
|
|
445
|
-
// from concurrent pointer events (see over-capture caveat above).
|
|
446
|
-
const postRunMessages = conversationStore.getMessages(conversationId);
|
|
447
|
-
const createdMessageIds = postRunMessages
|
|
448
|
-
.filter((m) => !preRunMessageIds.has(m.id) && m.id !== messageId)
|
|
449
|
-
.map((m) => m.id);
|
|
450
|
-
|
|
451
|
-
if (agentLoopError) {
|
|
452
|
-
await rollback(createdMessageIds);
|
|
453
|
-
throw new Error(agentLoopError);
|
|
454
|
-
}
|
|
455
567
|
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
log.warn(
|
|
470
|
-
{ conversationId, missingFacts },
|
|
471
|
-
'Generated pointer text failed fact validation — falling back to deterministic',
|
|
568
|
+
// Post-generation fact check: verify the assistant's response
|
|
569
|
+
// includes all required factual details (phone number, duration,
|
|
570
|
+
// outcome keyword, etc.). If the model omitted or rewrote them,
|
|
571
|
+
// remove both the instruction and generated messages and throw so
|
|
572
|
+
// the deterministic fallback fires.
|
|
573
|
+
//
|
|
574
|
+
// Validation uses text accumulated from assistant_text_delta
|
|
575
|
+
// events during the agent loop rather than a DB lookup, avoiding
|
|
576
|
+
// any positional ambiguity when concurrent pointer events
|
|
577
|
+
// interleave messages in the conversation.
|
|
578
|
+
if (requiredFacts && requiredFacts.length > 0) {
|
|
579
|
+
const missingFacts = requiredFacts.filter(
|
|
580
|
+
(fact) => !generatedText.includes(fact),
|
|
472
581
|
);
|
|
473
|
-
|
|
474
|
-
|
|
582
|
+
if (missingFacts.length > 0) {
|
|
583
|
+
log.warn(
|
|
584
|
+
{ conversationId, missingFacts },
|
|
585
|
+
"Generated pointer text failed fact validation — falling back to deterministic",
|
|
586
|
+
);
|
|
587
|
+
await rollback(createdMessageIds);
|
|
588
|
+
throw new Error(
|
|
589
|
+
"Generated pointer text failed fact validation",
|
|
590
|
+
);
|
|
591
|
+
}
|
|
475
592
|
}
|
|
593
|
+
} finally {
|
|
594
|
+
// Restore tool availability so subsequent turns aren't affected.
|
|
595
|
+
session.toolsDisabledDepth--;
|
|
476
596
|
}
|
|
477
|
-
}
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
runtimeHttp.setPairingBroadcast((msg) => server.broadcast(msg as ServerMessage));
|
|
597
|
+
},
|
|
598
|
+
);
|
|
599
|
+
runtimeHttp.setPairingBroadcast((msg) =>
|
|
600
|
+
server.broadcast(msg as ServerMessage),
|
|
601
|
+
);
|
|
483
602
|
initPairingHandlers(runtimeHttp.getPairingStore(), bearerToken);
|
|
484
603
|
initSlashPairingContext(runtimeHttp.getPairingStore());
|
|
485
604
|
server.setHttpPort(httpPort);
|
|
486
|
-
log.info(
|
|
605
|
+
log.info(
|
|
606
|
+
{ port: httpPort, hostname },
|
|
607
|
+
"Daemon startup: runtime HTTP server listening",
|
|
608
|
+
);
|
|
487
609
|
} catch (err) {
|
|
488
|
-
log.warn(
|
|
610
|
+
log.warn(
|
|
611
|
+
{ err, port: httpPort },
|
|
612
|
+
"Failed to start runtime HTTP server, continuing without it",
|
|
613
|
+
);
|
|
489
614
|
runtimeHttp = null;
|
|
490
615
|
}
|
|
491
616
|
|
|
492
617
|
writePid(process.pid);
|
|
493
|
-
log.info({ pid: process.pid },
|
|
618
|
+
log.info({ pid: process.pid }, "Daemon started");
|
|
494
619
|
|
|
495
620
|
const hookManager = getHookManager();
|
|
496
621
|
hookManager.watch();
|
|
497
622
|
|
|
498
|
-
void hookManager.trigger(
|
|
623
|
+
void hookManager.trigger("daemon-start", {
|
|
499
624
|
pid: process.pid,
|
|
500
625
|
socketPath: getSocketPath(),
|
|
501
626
|
});
|
|
@@ -504,18 +629,23 @@ export async function runDaemon(): Promise<void> {
|
|
|
504
629
|
// If download fails, local embeddings gracefully fall back to cloud backends.
|
|
505
630
|
void (async () => {
|
|
506
631
|
try {
|
|
507
|
-
const { EmbeddingRuntimeManager } =
|
|
632
|
+
const { EmbeddingRuntimeManager } =
|
|
633
|
+
await import("../memory/embedding-runtime-manager.js");
|
|
508
634
|
const runtimeManager = new EmbeddingRuntimeManager();
|
|
509
635
|
if (!runtimeManager.isReady()) {
|
|
510
|
-
log.info(
|
|
636
|
+
log.info("Downloading embedding runtime in background...");
|
|
511
637
|
await runtimeManager.ensureInstalled();
|
|
512
638
|
// Reset the localBackendBroken flag so auto mode retries local embeddings
|
|
513
|
-
const { clearEmbeddingBackendCache } =
|
|
639
|
+
const { clearEmbeddingBackendCache } =
|
|
640
|
+
await import("../memory/embedding-backend.js");
|
|
514
641
|
clearEmbeddingBackendCache();
|
|
515
|
-
log.info(
|
|
642
|
+
log.info("Embedding runtime download complete");
|
|
516
643
|
}
|
|
517
644
|
} catch (err) {
|
|
518
|
-
log.warn(
|
|
645
|
+
log.warn(
|
|
646
|
+
{ err },
|
|
647
|
+
"Embedding runtime download failed — local embeddings will use cloud fallback",
|
|
648
|
+
);
|
|
519
649
|
}
|
|
520
650
|
})();
|
|
521
651
|
|
|
@@ -523,7 +653,7 @@ export async function runDaemon(): Promise<void> {
|
|
|
523
653
|
try {
|
|
524
654
|
rotateToolInvocations(config.auditLog.retentionDays);
|
|
525
655
|
} catch (err) {
|
|
526
|
-
log.warn({ err },
|
|
656
|
+
log.warn({ err }, "Audit log rotation failed");
|
|
527
657
|
}
|
|
528
658
|
}
|
|
529
659
|
|
|
@@ -538,13 +668,20 @@ export async function runDaemon(): Promise<void> {
|
|
|
538
668
|
});
|
|
539
669
|
heartbeat.start();
|
|
540
670
|
server.setHeartbeatService(heartbeat);
|
|
541
|
-
log.info(
|
|
671
|
+
log.info(
|
|
672
|
+
{
|
|
673
|
+
enabled: heartbeatConfig.enabled,
|
|
674
|
+
intervalMs: heartbeatConfig.intervalMs,
|
|
675
|
+
},
|
|
676
|
+
"Heartbeat service configured",
|
|
677
|
+
);
|
|
542
678
|
|
|
543
679
|
// Retrieve the MCP manager if MCP servers were configured.
|
|
544
680
|
// The manager is a singleton created during initializeProvidersAndTools().
|
|
545
|
-
const mcpManager =
|
|
546
|
-
|
|
547
|
-
|
|
681
|
+
const mcpManager =
|
|
682
|
+
config.mcp?.servers && Object.keys(config.mcp.servers).length > 0
|
|
683
|
+
? getMcpServerManager()
|
|
684
|
+
: null;
|
|
548
685
|
|
|
549
686
|
installShutdownHandlers({
|
|
550
687
|
server,
|
|
@@ -559,7 +696,7 @@ export async function runDaemon(): Promise<void> {
|
|
|
559
696
|
cleanupPidFile,
|
|
560
697
|
});
|
|
561
698
|
} catch (err) {
|
|
562
|
-
log.error({ err },
|
|
699
|
+
log.error({ err }, "Daemon startup failed — cleaning up");
|
|
563
700
|
cleanupPidFileIfOwner(process.pid);
|
|
564
701
|
if (socketCreated) {
|
|
565
702
|
try {
|