@vellumai/assistant 0.5.4 → 0.5.6
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/Dockerfile +17 -27
- package/node_modules/@vellumai/ces-contracts/src/index.ts +1 -0
- package/node_modules/@vellumai/ces-contracts/src/trust-rules.ts +42 -0
- package/package.json +1 -1
- package/src/__tests__/actor-token-service.test.ts +113 -0
- package/src/__tests__/config-schema.test.ts +2 -2
- package/src/__tests__/context-window-manager.test.ts +78 -0
- package/src/__tests__/conversation-title-service.test.ts +30 -1
- package/src/__tests__/credential-security-invariants.test.ts +2 -0
- package/src/__tests__/docker-signing-key-bootstrap.test.ts +207 -0
- package/src/__tests__/memory-regressions.test.ts +8 -30
- package/src/__tests__/openai-whisper.test.ts +93 -0
- package/src/__tests__/require-fresh-approval.test.ts +4 -0
- package/src/__tests__/slack-messaging-token-resolution.test.ts +319 -0
- package/src/__tests__/tool-executor-lifecycle-events.test.ts +4 -0
- package/src/__tests__/tool-executor.test.ts +4 -0
- package/src/__tests__/volume-security-guard.test.ts +155 -0
- package/src/cli/commands/conversations.ts +0 -18
- package/src/config/bundled-skills/messaging/tools/shared.ts +1 -0
- package/src/config/bundled-skills/transcribe/tools/transcribe-media.ts +16 -37
- package/src/config/env-registry.ts +9 -0
- package/src/config/env.ts +8 -2
- package/src/config/feature-flag-registry.json +8 -8
- package/src/config/schema.ts +0 -12
- package/src/config/schemas/memory.ts +0 -4
- package/src/config/schemas/platform.ts +1 -1
- package/src/config/schemas/security.ts +4 -0
- package/src/context/window-manager.ts +53 -2
- package/src/credential-execution/managed-catalog.ts +5 -15
- package/src/daemon/conversation-agent-loop.ts +0 -60
- package/src/daemon/conversation-memory.ts +0 -117
- package/src/daemon/conversation-runtime-assembly.ts +0 -2
- package/src/daemon/daemon-control.ts +7 -0
- package/src/daemon/handlers/conversations.ts +0 -11
- package/src/daemon/lifecycle.ts +10 -47
- package/src/daemon/providers-setup.ts +2 -1
- package/src/followups/followup-store.ts +5 -2
- package/src/hooks/manager.ts +7 -0
- package/src/instrument.ts +33 -1
- package/src/memory/conversation-crud.ts +0 -236
- package/src/memory/conversation-title-service.ts +26 -10
- package/src/memory/db-init.ts +5 -13
- package/src/memory/embedding-local.ts +11 -5
- package/src/memory/indexer.ts +15 -106
- package/src/memory/job-handlers/conversation-starters.ts +24 -36
- package/src/memory/job-handlers/embedding.ts +0 -79
- package/src/memory/job-utils.ts +1 -1
- package/src/memory/jobs-store.ts +0 -8
- package/src/memory/jobs-worker.ts +0 -20
- package/src/memory/migrations/189-drop-simplified-memory.ts +42 -0
- package/src/memory/migrations/index.ts +1 -3
- package/src/memory/qdrant-client.ts +4 -6
- package/src/memory/schema/conversations.ts +0 -3
- package/src/memory/schema/index.ts +0 -2
- package/src/messaging/draft-store.ts +2 -2
- package/src/messaging/provider.ts +9 -0
- package/src/messaging/providers/slack/adapter.ts +29 -2
- package/src/oauth/connection-resolver.test.ts +22 -18
- package/src/oauth/connection-resolver.ts +92 -7
- package/src/oauth/platform-connection.test.ts +78 -69
- package/src/oauth/platform-connection.ts +12 -19
- package/src/permissions/defaults.ts +3 -3
- package/src/permissions/trust-client.ts +332 -0
- package/src/permissions/trust-store-interface.ts +105 -0
- package/src/permissions/trust-store.ts +531 -39
- package/src/platform/client.test.ts +148 -0
- package/src/platform/client.ts +71 -0
- package/src/providers/speech-to-text/openai-whisper.test.ts +190 -0
- package/src/providers/speech-to-text/openai-whisper.ts +68 -0
- package/src/providers/speech-to-text/resolve.ts +9 -0
- package/src/providers/speech-to-text/types.ts +17 -0
- package/src/runtime/auth/route-policy.ts +14 -0
- package/src/runtime/auth/token-service.ts +133 -0
- package/src/runtime/http-server.ts +4 -2
- package/src/runtime/routes/conversation-management-routes.ts +0 -36
- package/src/runtime/routes/conversation-query-routes.ts +44 -2
- package/src/runtime/routes/conversation-routes.ts +2 -1
- package/src/runtime/routes/inbound-message-handler.ts +27 -3
- package/src/runtime/routes/inbound-stages/acl-enforcement.ts +16 -1
- package/src/runtime/routes/inbound-stages/transcribe-audio.test.ts +287 -0
- package/src/runtime/routes/inbound-stages/transcribe-audio.ts +122 -0
- package/src/runtime/routes/log-export-routes.ts +1 -0
- package/src/runtime/routes/memory-item-routes.test.ts +221 -3
- package/src/runtime/routes/memory-item-routes.ts +124 -2
- package/src/runtime/routes/secret-routes.ts +4 -1
- package/src/runtime/routes/upgrade-broadcast-routes.ts +151 -0
- package/src/schedule/schedule-store.ts +0 -21
- package/src/security/ces-credential-client.ts +173 -0
- package/src/security/secure-keys.ts +65 -22
- package/src/signals/bash.ts +3 -0
- package/src/signals/cancel.ts +3 -0
- package/src/signals/confirm.ts +3 -0
- package/src/signals/conversation-undo.ts +3 -0
- package/src/signals/event-stream.ts +7 -0
- package/src/signals/shotgun.ts +3 -0
- package/src/signals/trust-rule.ts +3 -0
- package/src/skills/inline-command-render.ts +5 -1
- package/src/skills/inline-command-runner.ts +30 -2
- package/src/telemetry/usage-telemetry-reporter.test.ts +23 -36
- package/src/telemetry/usage-telemetry-reporter.ts +21 -19
- package/src/tools/memory/handlers.ts +1 -129
- package/src/tools/permission-checker.ts +18 -0
- package/src/tools/skills/load.ts +9 -2
- package/src/util/device-id.ts +70 -7
- package/src/util/logger.ts +35 -9
- package/src/util/platform.ts +29 -5
- package/src/util/xml.ts +8 -0
- package/src/workspace/heartbeat-service.ts +5 -24
- package/src/workspace/migrations/migrate-to-workspace-volume.ts +113 -0
- package/src/workspace/migrations/registry.ts +2 -0
- package/src/__tests__/archive-recall.test.ts +0 -560
- package/src/__tests__/conversation-memory-dirty-tail.test.ts +0 -150
- package/src/__tests__/conversation-switch-memory-reduction.test.ts +0 -474
- package/src/__tests__/db-memory-archive-migration.test.ts +0 -372
- package/src/__tests__/db-memory-brief-state-migration.test.ts +0 -213
- package/src/__tests__/db-memory-reducer-checkpoints.test.ts +0 -273
- package/src/__tests__/memory-brief-open-loops.test.ts +0 -530
- package/src/__tests__/memory-brief-time.test.ts +0 -285
- package/src/__tests__/memory-brief-wrapper.test.ts +0 -311
- package/src/__tests__/memory-chunk-archive.test.ts +0 -400
- package/src/__tests__/memory-chunk-dual-write.test.ts +0 -453
- package/src/__tests__/memory-episode-archive.test.ts +0 -370
- package/src/__tests__/memory-episode-dual-write.test.ts +0 -626
- package/src/__tests__/memory-observation-archive.test.ts +0 -375
- package/src/__tests__/memory-observation-dual-write.test.ts +0 -318
- package/src/__tests__/memory-reducer-job.test.ts +0 -538
- package/src/__tests__/memory-reducer-scheduling.test.ts +0 -473
- package/src/__tests__/memory-reducer-store.test.ts +0 -728
- package/src/__tests__/memory-reducer-types.test.ts +0 -707
- package/src/__tests__/memory-reducer.test.ts +0 -704
- package/src/__tests__/memory-simplified-config.test.ts +0 -281
- package/src/__tests__/simplified-memory-e2e.test.ts +0 -666
- package/src/__tests__/simplified-memory-runtime.test.ts +0 -616
- package/src/config/schemas/memory-simplified.ts +0 -101
- package/src/memory/archive-recall.ts +0 -516
- package/src/memory/archive-store.ts +0 -400
- package/src/memory/brief-formatting.ts +0 -33
- package/src/memory/brief-open-loops.ts +0 -266
- package/src/memory/brief-time.ts +0 -162
- package/src/memory/brief.ts +0 -75
- package/src/memory/job-handlers/backfill-simplified-memory.ts +0 -462
- package/src/memory/job-handlers/reduce-conversation-memory.ts +0 -229
- package/src/memory/migrations/185-memory-brief-state.ts +0 -52
- package/src/memory/migrations/186-memory-archive.ts +0 -109
- package/src/memory/migrations/187-memory-reducer-checkpoints.ts +0 -19
- package/src/memory/reducer-scheduler.ts +0 -242
- package/src/memory/reducer-store.ts +0 -271
- package/src/memory/reducer-types.ts +0 -106
- package/src/memory/reducer.ts +0 -467
- package/src/memory/schema/memory-archive.ts +0 -121
- package/src/memory/schema/memory-brief.ts +0 -55
|
@@ -1,8 +1,5 @@
|
|
|
1
1
|
import { getConfig } from "../config/loader.js";
|
|
2
2
|
import { estimatePromptTokens } from "../context/token-estimator.js";
|
|
3
|
-
import { buildArchiveRecall } from "../memory/archive-recall.js";
|
|
4
|
-
import { compileMemoryBrief } from "../memory/brief.js";
|
|
5
|
-
import { getDb } from "../memory/db.js";
|
|
6
3
|
import { buildMemoryQuery } from "../memory/query-builder.js";
|
|
7
4
|
import { computeRecallBudget } from "../memory/retrieval-budget.js";
|
|
8
5
|
import {
|
|
@@ -12,11 +9,8 @@ import {
|
|
|
12
9
|
import type { ScopePolicyOverride } from "../memory/search/types.js";
|
|
13
10
|
import type { Message } from "../providers/types.js";
|
|
14
11
|
import type { Provider } from "../providers/types.js";
|
|
15
|
-
import { getLogger } from "../util/logger.js";
|
|
16
12
|
import type { ServerMessage } from "./message-protocol.js";
|
|
17
13
|
|
|
18
|
-
const log = getLogger("conversation-memory");
|
|
19
|
-
|
|
20
14
|
export interface MemoryRecallResult {
|
|
21
15
|
runMessages: Message[];
|
|
22
16
|
recall: Awaited<ReturnType<typeof buildMemoryRecall>>;
|
|
@@ -121,14 +115,6 @@ export async function prepareMemoryContext(
|
|
|
121
115
|
|
|
122
116
|
const runtimeConfig = getConfig();
|
|
123
117
|
|
|
124
|
-
// ── Simplified memory path ──────────────────────────────────────────
|
|
125
|
-
// When `memory.simplified.enabled` is true, inject the brief and
|
|
126
|
-
// optional archive recall instead of the legacy hybrid pipeline.
|
|
127
|
-
if (runtimeConfig.memory?.simplified?.enabled) {
|
|
128
|
-
return prepareSimplifiedMemoryContext(ctx, content, userMessageId, onEvent);
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
// ── Legacy memory path (fallback) ──────────────────────────────────
|
|
132
118
|
// Memory recall via the V2 hybrid pipeline
|
|
133
119
|
const recallQuery = buildMemoryQuery(content, ctx.messages);
|
|
134
120
|
const dynamicBudgetConfig = runtimeConfig.memory?.retrieval?.dynamicBudget;
|
|
@@ -221,106 +207,3 @@ export async function prepareMemoryContext(
|
|
|
221
207
|
recall,
|
|
222
208
|
};
|
|
223
209
|
}
|
|
224
|
-
|
|
225
|
-
// ── Simplified memory injection ─────────────────────────────────────────
|
|
226
|
-
|
|
227
|
-
/**
|
|
228
|
-
* Build simplified memory context for a turn: compiles the `<memory_brief>`
|
|
229
|
-
* block and conditionally appends `<supporting_recall>` from the archive.
|
|
230
|
-
*
|
|
231
|
-
* Non-empty blocks are injected as text content blocks prepended to the
|
|
232
|
-
* last user message, following the same injection pattern as the legacy
|
|
233
|
-
* pipeline. Stripping is handled by `RUNTIME_INJECTION_PREFIXES` which
|
|
234
|
-
* already includes `<memory_brief>`.
|
|
235
|
-
*/
|
|
236
|
-
function prepareSimplifiedMemoryContext(
|
|
237
|
-
ctx: MemoryPrepareContext,
|
|
238
|
-
content: string,
|
|
239
|
-
userMessageId: string,
|
|
240
|
-
onEvent: (msg: ServerMessage) => void,
|
|
241
|
-
): MemoryRecallResult {
|
|
242
|
-
const start = Date.now();
|
|
243
|
-
|
|
244
|
-
// Build a no-op recall result matching the legacy shape.
|
|
245
|
-
const noopRecall = (): Awaited<ReturnType<typeof buildMemoryRecall>> =>
|
|
246
|
-
({
|
|
247
|
-
enabled: true,
|
|
248
|
-
degraded: false,
|
|
249
|
-
injectedText: "",
|
|
250
|
-
semanticHits: 0,
|
|
251
|
-
recencyHits: 0,
|
|
252
|
-
mergedCount: 0,
|
|
253
|
-
selectedCount: 0,
|
|
254
|
-
injectedTokens: 0,
|
|
255
|
-
latencyMs: 0,
|
|
256
|
-
topCandidates: [],
|
|
257
|
-
tier1Count: 0,
|
|
258
|
-
tier2Count: 0,
|
|
259
|
-
}) as Awaited<ReturnType<typeof buildMemoryRecall>>;
|
|
260
|
-
|
|
261
|
-
try {
|
|
262
|
-
const db = getDb();
|
|
263
|
-
|
|
264
|
-
// Step 1: Build the memory brief
|
|
265
|
-
const briefResult = compileMemoryBrief(db, ctx.scopeId, userMessageId);
|
|
266
|
-
|
|
267
|
-
// Step 2: Conditionally build supporting recall from the archive
|
|
268
|
-
const archiveResult = buildArchiveRecall(ctx.scopeId, content);
|
|
269
|
-
|
|
270
|
-
// Step 3: Assemble the injection blocks (non-empty only)
|
|
271
|
-
const blocks: string[] = [];
|
|
272
|
-
if (briefResult.text.length > 0) {
|
|
273
|
-
blocks.push(briefResult.text);
|
|
274
|
-
}
|
|
275
|
-
if (archiveResult.text.length > 0) {
|
|
276
|
-
blocks.push(archiveResult.text);
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
const latencyMs = Date.now() - start;
|
|
280
|
-
|
|
281
|
-
// Emit memory status for the simplified path
|
|
282
|
-
onEvent({
|
|
283
|
-
type: "memory_status",
|
|
284
|
-
enabled: true,
|
|
285
|
-
degraded: false,
|
|
286
|
-
});
|
|
287
|
-
|
|
288
|
-
// Inject non-empty blocks into the last user message
|
|
289
|
-
let runMessages = ctx.messages;
|
|
290
|
-
if (blocks.length > 0) {
|
|
291
|
-
const injectedText = blocks.join("\n\n");
|
|
292
|
-
const userTail = ctx.messages[ctx.messages.length - 1];
|
|
293
|
-
if (userTail && userTail.role === "user") {
|
|
294
|
-
runMessages = injectMemoryRecallAsUserBlock(ctx.messages, injectedText);
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
log.debug(
|
|
298
|
-
{
|
|
299
|
-
briefLength: briefResult.text.length,
|
|
300
|
-
recallTrigger: archiveResult.trigger,
|
|
301
|
-
recallBullets: archiveResult.bullets.length,
|
|
302
|
-
latencyMs,
|
|
303
|
-
},
|
|
304
|
-
"Simplified memory injection completed",
|
|
305
|
-
);
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
return {
|
|
309
|
-
runMessages,
|
|
310
|
-
recall: {
|
|
311
|
-
...noopRecall(),
|
|
312
|
-
injectedText: blocks.length > 0 ? blocks.join("\n\n") : "",
|
|
313
|
-
latencyMs,
|
|
314
|
-
},
|
|
315
|
-
};
|
|
316
|
-
} catch (err) {
|
|
317
|
-
log.warn({ err }, "Simplified memory injection failed, returning no-op");
|
|
318
|
-
return {
|
|
319
|
-
runMessages: ctx.messages,
|
|
320
|
-
recall: {
|
|
321
|
-
...noopRecall(),
|
|
322
|
-
latencyMs: Date.now() - start,
|
|
323
|
-
},
|
|
324
|
-
};
|
|
325
|
-
}
|
|
326
|
-
}
|
|
@@ -961,8 +961,6 @@ const RUNTIME_INJECTION_PREFIXES = [
|
|
|
961
961
|
"<inbound_actor_context>",
|
|
962
962
|
"<interface_turn_context>",
|
|
963
963
|
"<turn_context>",
|
|
964
|
-
"<memory_brief>",
|
|
965
|
-
"<supporting_recall>",
|
|
966
964
|
"<memory_context __injected>",
|
|
967
965
|
"<memory_context>", // backward-compat: strip legacy blocks from pre-__injected history
|
|
968
966
|
"<voice_call_control>",
|
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
import { join, resolve } from "node:path";
|
|
12
12
|
|
|
13
13
|
import { getRuntimeHttpHost, getRuntimeHttpPort } from "../config/env.js";
|
|
14
|
+
import { getIsContainerized } from "../config/env-registry.js";
|
|
14
15
|
import { DaemonError } from "../util/errors.js";
|
|
15
16
|
import { getLogger } from "../util/logger.js";
|
|
16
17
|
import {
|
|
@@ -157,6 +158,7 @@ export async function isHttpHealthy(): Promise<boolean> {
|
|
|
157
158
|
}
|
|
158
159
|
|
|
159
160
|
function readPid(): number | null {
|
|
161
|
+
if (getIsContainerized()) return null; // Docker manages process lifecycle
|
|
160
162
|
const pidPath = getPidPath();
|
|
161
163
|
if (!existsSync(pidPath)) return null;
|
|
162
164
|
try {
|
|
@@ -168,10 +170,12 @@ function readPid(): number | null {
|
|
|
168
170
|
}
|
|
169
171
|
|
|
170
172
|
export function writePid(pid: number): void {
|
|
173
|
+
if (getIsContainerized()) return; // Docker manages process lifecycle
|
|
171
174
|
writeFileSync(getPidPath(), String(pid));
|
|
172
175
|
}
|
|
173
176
|
|
|
174
177
|
export function cleanupPidFile(): void {
|
|
178
|
+
if (getIsContainerized()) return; // Docker manages process lifecycle
|
|
175
179
|
const pidPath = getPidPath();
|
|
176
180
|
if (existsSync(pidPath)) {
|
|
177
181
|
unlinkSync(pidPath);
|
|
@@ -181,6 +185,7 @@ export function cleanupPidFile(): void {
|
|
|
181
185
|
/** Only remove the PID file if it belongs to the given process. Prevents a
|
|
182
186
|
* failing second startup from deleting the PID of an already-running daemon. */
|
|
183
187
|
export function cleanupPidFileIfOwner(ownerPid: number): void {
|
|
188
|
+
if (getIsContainerized()) return; // Docker manages process lifecycle
|
|
184
189
|
const currentPid = readPid();
|
|
185
190
|
if (currentPid === ownerPid) {
|
|
186
191
|
cleanupPidFile();
|
|
@@ -188,6 +193,7 @@ export function cleanupPidFileIfOwner(ownerPid: number): void {
|
|
|
188
193
|
}
|
|
189
194
|
|
|
190
195
|
export function isDaemonRunning(): boolean {
|
|
196
|
+
if (getIsContainerized()) return true; // Container orchestrator manages lifecycle
|
|
191
197
|
const pid = readPid();
|
|
192
198
|
if (pid == null) return false;
|
|
193
199
|
if (!isProcessRunning(pid)) {
|
|
@@ -201,6 +207,7 @@ export async function getDaemonStatus(): Promise<{
|
|
|
201
207
|
running: boolean;
|
|
202
208
|
pid?: number;
|
|
203
209
|
}> {
|
|
210
|
+
if (getIsContainerized()) return { running: true, pid: process.pid }; // Container orchestrator manages lifecycle
|
|
204
211
|
const pid = readPid();
|
|
205
212
|
if (pid == null) return { running: false };
|
|
206
213
|
if (!isProcessRunning(pid)) {
|
|
@@ -23,7 +23,6 @@ import {
|
|
|
23
23
|
queueGenerateConversationTitle,
|
|
24
24
|
UNTITLED_FALLBACK,
|
|
25
25
|
} from "../../memory/conversation-title-service.js";
|
|
26
|
-
import { reduceBeforeSwitch } from "../../memory/reducer-scheduler.js";
|
|
27
26
|
import * as pendingInteractions from "../../runtime/pending-interactions.js";
|
|
28
27
|
import { getSubagentManager } from "../../subagent/index.js";
|
|
29
28
|
import { truncate } from "../../util/truncate.js";
|
|
@@ -234,12 +233,6 @@ export async function handleConversationCreate(
|
|
|
234
233
|
conversationType: normalizeConversationType(conversation.conversationType),
|
|
235
234
|
});
|
|
236
235
|
|
|
237
|
-
// Reduce the previous dirty conversation before processing the initial
|
|
238
|
-
// message so its memory is fresh for the next read.
|
|
239
|
-
if (msg.initialMessage) {
|
|
240
|
-
await reduceBeforeSwitch(conversation.id);
|
|
241
|
-
}
|
|
242
|
-
|
|
243
236
|
// Auto-send the initial message if provided, kick-starting the skill.
|
|
244
237
|
if (msg.initialMessage) {
|
|
245
238
|
// Queue title generation eagerly — some processMessage paths (guardian
|
|
@@ -350,10 +343,6 @@ export async function switchConversation(
|
|
|
350
343
|
return null;
|
|
351
344
|
}
|
|
352
345
|
|
|
353
|
-
// Reduce the previous dirty conversation before switching so its memory
|
|
354
|
-
// is fresh for the next read.
|
|
355
|
-
await reduceBeforeSwitch(conversationId);
|
|
356
|
-
|
|
357
346
|
// If the target conversation is headless-locked (actively executing a task run),
|
|
358
347
|
// skip rebinding so tool confirmations stay suppressed.
|
|
359
348
|
const existingConversation = ctx.conversations.get(conversationId);
|
package/src/daemon/lifecycle.ts
CHANGED
|
@@ -20,7 +20,7 @@ import { loadConfig } from "../config/loader.js";
|
|
|
20
20
|
import { HeartbeatService } from "../heartbeat/heartbeat-service.js";
|
|
21
21
|
import { getHookManager } from "../hooks/manager.js";
|
|
22
22
|
import { installTemplates } from "../hooks/templates.js";
|
|
23
|
-
import { closeSentry, initSentry } from "../instrument.js";
|
|
23
|
+
import { closeSentry, initSentry, setSentryDeviceId } from "../instrument.js";
|
|
24
24
|
import { disableLogfire, initLogfire } from "../logfire.js";
|
|
25
25
|
import { getMcpServerManager } from "../mcp/manager.js";
|
|
26
26
|
import * as attachmentsStore from "../memory/attachments-store.js";
|
|
@@ -30,7 +30,6 @@ import {
|
|
|
30
30
|
getConversationType,
|
|
31
31
|
getMessages,
|
|
32
32
|
purgePrivateConversations,
|
|
33
|
-
sweepStaleReducerJobs,
|
|
34
33
|
} from "../memory/conversation-crud.js";
|
|
35
34
|
import { resolveConversationId } from "../memory/conversation-key-store.js";
|
|
36
35
|
import { initializeDb } from "../memory/db.js";
|
|
@@ -57,14 +56,15 @@ import { assistantEventHub } from "../runtime/assistant-event-hub.js";
|
|
|
57
56
|
import { DAEMON_INTERNAL_ASSISTANT_ID } from "../runtime/assistant-scope.js";
|
|
58
57
|
import {
|
|
59
58
|
initAuthSigningKey,
|
|
60
|
-
loadOrCreateSigningKey,
|
|
61
59
|
mintPairingBearerToken,
|
|
60
|
+
resolveSigningKey,
|
|
62
61
|
} from "../runtime/auth/token-service.js";
|
|
63
62
|
import { ensureVellumGuardianBinding } from "../runtime/guardian-vellum-migration.js";
|
|
64
63
|
import { RuntimeHttpServer } from "../runtime/http-server.js";
|
|
65
64
|
import { startScheduler } from "../schedule/scheduler.js";
|
|
66
65
|
import { seedCatalogSkillMemories } from "../skills/skill-memory.js";
|
|
67
66
|
import { UsageTelemetryReporter } from "../telemetry/usage-telemetry-reporter.js";
|
|
67
|
+
import { getDeviceId } from "../util/device-id.js";
|
|
68
68
|
import { getLogger, initLogger } from "../util/logger.js";
|
|
69
69
|
import {
|
|
70
70
|
ensureDataDir,
|
|
@@ -147,7 +147,7 @@ export async function runDaemon(): Promise<void> {
|
|
|
147
147
|
// Load (or generate + persist) the auth signing key so tokens survive
|
|
148
148
|
// daemon restarts. Must happen after ensureDataDir() creates the
|
|
149
149
|
// protected directory.
|
|
150
|
-
const signingKey =
|
|
150
|
+
const signingKey = await resolveSigningKey();
|
|
151
151
|
initAuthSigningKey(signingKey);
|
|
152
152
|
|
|
153
153
|
seedInterfaceFiles();
|
|
@@ -179,6 +179,11 @@ export async function runDaemon(): Promise<void> {
|
|
|
179
179
|
await runWorkspaceMigrations(getWorkspaceDir(), WORKSPACE_MIGRATIONS);
|
|
180
180
|
log.info("Daemon startup: workspace migrations complete");
|
|
181
181
|
|
|
182
|
+
// Now that workspace migrations have run (including 003-seed-device-id
|
|
183
|
+
// which may copy the legacy installationId into device.json), it is safe
|
|
184
|
+
// to read the device ID and set the Sentry tag.
|
|
185
|
+
setSentryDeviceId(getDeviceId());
|
|
186
|
+
|
|
182
187
|
// Purge private (temporary) conversations from the previous daemon run.
|
|
183
188
|
// These are ephemeral by design and should not survive daemon restarts.
|
|
184
189
|
const { count: purgedCount, deletedMemory } = purgePrivateConversations();
|
|
@@ -207,40 +212,16 @@ export async function runDaemon(): Promise<void> {
|
|
|
207
212
|
targetId: summaryId,
|
|
208
213
|
});
|
|
209
214
|
}
|
|
210
|
-
for (const obsId of deletedMemory.deletedObservationIds) {
|
|
211
|
-
enqueueMemoryJob("delete_qdrant_vectors", {
|
|
212
|
-
targetType: "observation",
|
|
213
|
-
targetId: obsId,
|
|
214
|
-
});
|
|
215
|
-
}
|
|
216
|
-
for (const chunkId of deletedMemory.deletedChunkIds) {
|
|
217
|
-
enqueueMemoryJob("delete_qdrant_vectors", {
|
|
218
|
-
targetType: "chunk",
|
|
219
|
-
targetId: chunkId,
|
|
220
|
-
});
|
|
221
|
-
}
|
|
222
|
-
for (const episodeId of deletedMemory.deletedEpisodeIds) {
|
|
223
|
-
enqueueMemoryJob("delete_qdrant_vectors", {
|
|
224
|
-
targetType: "episode",
|
|
225
|
-
targetId: episodeId,
|
|
226
|
-
});
|
|
227
|
-
}
|
|
228
215
|
if (
|
|
229
216
|
deletedMemory.segmentIds.length > 0 ||
|
|
230
217
|
deletedMemory.orphanedItemIds.length > 0 ||
|
|
231
|
-
deletedMemory.deletedSummaryIds.length > 0
|
|
232
|
-
deletedMemory.deletedObservationIds.length > 0 ||
|
|
233
|
-
deletedMemory.deletedChunkIds.length > 0 ||
|
|
234
|
-
deletedMemory.deletedEpisodeIds.length > 0
|
|
218
|
+
deletedMemory.deletedSummaryIds.length > 0
|
|
235
219
|
) {
|
|
236
220
|
log.info(
|
|
237
221
|
{
|
|
238
222
|
segments: deletedMemory.segmentIds.length,
|
|
239
223
|
orphanedItems: deletedMemory.orphanedItemIds.length,
|
|
240
224
|
deletedSummaries: deletedMemory.deletedSummaryIds.length,
|
|
241
|
-
deletedObservations: deletedMemory.deletedObservationIds.length,
|
|
242
|
-
deletedChunks: deletedMemory.deletedChunkIds.length,
|
|
243
|
-
deletedEpisodes: deletedMemory.deletedEpisodeIds.length,
|
|
244
225
|
},
|
|
245
226
|
"Enqueued Qdrant vector cleanup jobs for purged private conversations",
|
|
246
227
|
);
|
|
@@ -265,24 +246,6 @@ export async function runDaemon(): Promise<void> {
|
|
|
265
246
|
);
|
|
266
247
|
}
|
|
267
248
|
|
|
268
|
-
// Sweep dirty conversations whose tail messages are already past the
|
|
269
|
-
// idle delay — they should have been reduced while the daemon was down.
|
|
270
|
-
// Enqueue immediate reducer jobs so the memory worker picks them up.
|
|
271
|
-
try {
|
|
272
|
-
const sweepCount = sweepStaleReducerJobs();
|
|
273
|
-
if (sweepCount > 0) {
|
|
274
|
-
log.info(
|
|
275
|
-
{ sweepCount },
|
|
276
|
-
`Enqueued reducer jobs for ${sweepCount} stale dirty conversation(s)`,
|
|
277
|
-
);
|
|
278
|
-
}
|
|
279
|
-
} catch (err) {
|
|
280
|
-
log.warn(
|
|
281
|
-
{ err },
|
|
282
|
-
"Startup sweep for stale reducer jobs failed — continuing startup",
|
|
283
|
-
);
|
|
284
|
-
}
|
|
285
|
-
|
|
286
249
|
// Ensure a vellum guardian binding exists so the identity system works
|
|
287
250
|
// without requiring a manual bootstrap step.
|
|
288
251
|
try {
|
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
setPlatformUserId,
|
|
6
6
|
} from "../config/env.js";
|
|
7
7
|
import type { AssistantConfig } from "../config/types.js";
|
|
8
|
-
import { setSentryOrganizationId } from "../instrument.js";
|
|
8
|
+
import { setSentryOrganizationId, setSentryUserId } from "../instrument.js";
|
|
9
9
|
import { getMcpServerManager } from "../mcp/manager.js";
|
|
10
10
|
import { gmailMessagingProvider } from "../messaging/providers/gmail/adapter.js";
|
|
11
11
|
import { slackProvider as slackMessagingProvider } from "../messaging/providers/slack/adapter.js";
|
|
@@ -91,6 +91,7 @@ export async function initializeProvidersAndTools(
|
|
|
91
91
|
const trimmed = persisted?.trim();
|
|
92
92
|
if (trimmed) {
|
|
93
93
|
setPlatformUserId(trimmed);
|
|
94
|
+
setSentryUserId(trimmed);
|
|
94
95
|
log.info("Rehydrated platform user ID from credential store");
|
|
95
96
|
}
|
|
96
97
|
} catch (err) {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { and,
|
|
1
|
+
import { and, asc, eq, lte, or, sql } from "drizzle-orm";
|
|
2
2
|
import { v4 as uuid } from "uuid";
|
|
3
3
|
|
|
4
4
|
import { getDb } from "../memory/db.js";
|
|
@@ -217,7 +217,10 @@ export function getPendingAndOverdueFollowUps(): BriefFollowUp[] {
|
|
|
217
217
|
eq(followups.status, "nudged"),
|
|
218
218
|
),
|
|
219
219
|
)
|
|
220
|
-
.orderBy(
|
|
220
|
+
.orderBy(
|
|
221
|
+
sql`CASE WHEN ${followups.expectedResponseBy} IS NULL THEN 1 ELSE 0 END`,
|
|
222
|
+
asc(followups.expectedResponseBy),
|
|
223
|
+
)
|
|
221
224
|
.all();
|
|
222
225
|
|
|
223
226
|
return rows as BriefFollowUp[];
|
package/src/hooks/manager.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { type FSWatcher, watch } from "node:fs";
|
|
2
2
|
|
|
3
|
+
import { getIsContainerized } from "../config/env-registry.js";
|
|
3
4
|
import { Debouncer } from "../util/debounce.js";
|
|
4
5
|
import { pathExists } from "../util/fs.js";
|
|
5
6
|
import { getLogger } from "../util/logger.js";
|
|
@@ -32,6 +33,10 @@ export class HookManager {
|
|
|
32
33
|
private readonly debouncer = new Debouncer(500);
|
|
33
34
|
|
|
34
35
|
initialize(): void {
|
|
36
|
+
if (getIsContainerized()) {
|
|
37
|
+
log.info("Hooks disabled in containerized mode");
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
35
40
|
this.hooks = discoverHooks();
|
|
36
41
|
this.buildEventIndex();
|
|
37
42
|
const enabled = this.hooks.filter((h) => h.enabled).length;
|
|
@@ -107,6 +112,7 @@ export class HookManager {
|
|
|
107
112
|
}
|
|
108
113
|
|
|
109
114
|
reload(): void {
|
|
115
|
+
if (getIsContainerized()) return;
|
|
110
116
|
this.hooks = discoverHooks();
|
|
111
117
|
this.buildEventIndex();
|
|
112
118
|
const enabled = this.hooks.filter((h) => h.enabled).length;
|
|
@@ -114,6 +120,7 @@ export class HookManager {
|
|
|
114
120
|
}
|
|
115
121
|
|
|
116
122
|
watch(): void {
|
|
123
|
+
if (getIsContainerized()) return;
|
|
117
124
|
const hooksDir = getHooksDir();
|
|
118
125
|
if (!pathExists(hooksDir)) return;
|
|
119
126
|
|
package/src/instrument.ts
CHANGED
|
@@ -2,7 +2,11 @@ import { arch, hostname, platform, release } from "node:os";
|
|
|
2
2
|
|
|
3
3
|
import * as Sentry from "@sentry/node";
|
|
4
4
|
|
|
5
|
-
import {
|
|
5
|
+
import {
|
|
6
|
+
getPlatformOrganizationId,
|
|
7
|
+
getPlatformUserId,
|
|
8
|
+
getSentryDsn,
|
|
9
|
+
} from "./config/env.js";
|
|
6
10
|
import { APP_VERSION, COMMIT_SHA } from "./version.js";
|
|
7
11
|
|
|
8
12
|
/** Patterns that match sensitive data in Sentry event values. */
|
|
@@ -51,6 +55,7 @@ export function initSentry(): void {
|
|
|
51
55
|
initialScope: {
|
|
52
56
|
tags: {
|
|
53
57
|
commit: COMMIT_SHA,
|
|
58
|
+
assistant_version: APP_VERSION,
|
|
54
59
|
os_platform: platform(),
|
|
55
60
|
os_release: release(),
|
|
56
61
|
os_arch: arch(),
|
|
@@ -58,9 +63,14 @@ export function initSentry(): void {
|
|
|
58
63
|
runtime: "bun",
|
|
59
64
|
runtime_version:
|
|
60
65
|
typeof Bun !== "undefined" ? Bun.version : process.version,
|
|
66
|
+
// NOTE: device_id is NOT set here. It is deferred to setSentryDeviceId()
|
|
67
|
+
// which is called after workspace migrations run, so that migration
|
|
68
|
+
// 003-seed-device-id can copy the legacy installationId into device.json
|
|
69
|
+
// before getDeviceId() eagerly creates a new random UUID.
|
|
61
70
|
...(getPlatformOrganizationId()
|
|
62
71
|
? { organization_id: getPlatformOrganizationId() }
|
|
63
72
|
: {}),
|
|
73
|
+
...(getPlatformUserId() ? { user_id: getPlatformUserId() } : {}),
|
|
64
74
|
},
|
|
65
75
|
},
|
|
66
76
|
beforeSend(event) {
|
|
@@ -109,6 +119,28 @@ export function setSentryOrganizationId(
|
|
|
109
119
|
Sentry.setTag("organization_id", organizationId || undefined);
|
|
110
120
|
}
|
|
111
121
|
|
|
122
|
+
/**
|
|
123
|
+
* Set (or clear) the user_id tag on the global Sentry scope.
|
|
124
|
+
*
|
|
125
|
+
* Called after the platform user ID is rehydrated from the credential
|
|
126
|
+
* store or updated at runtime so that every subsequent Sentry event
|
|
127
|
+
* includes the user context.
|
|
128
|
+
*/
|
|
129
|
+
export function setSentryUserId(userId: string | undefined): void {
|
|
130
|
+
Sentry.setTag("user_id", userId || undefined);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Set the device_id tag on the global Sentry scope.
|
|
135
|
+
*
|
|
136
|
+
* Called after workspace migrations complete so that migration
|
|
137
|
+
* 003-seed-device-id has a chance to copy the legacy installationId
|
|
138
|
+
* into device.json before getDeviceId() is invoked.
|
|
139
|
+
*/
|
|
140
|
+
export function setSentryDeviceId(deviceId: string): void {
|
|
141
|
+
Sentry.setTag("device_id", deviceId);
|
|
142
|
+
}
|
|
143
|
+
|
|
112
144
|
// ── Dynamic conversation-scoped Sentry tags ─────────────────────────
|
|
113
145
|
//
|
|
114
146
|
// These tags change per conversation turn and are set on the current
|