@vellumai/assistant 0.5.2 → 0.5.4
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 +109 -0
- package/docs/architecture/memory.md +105 -0
- package/docs/skills.md +100 -0
- package/package.json +1 -1
- package/src/__tests__/archive-recall.test.ts +560 -0
- package/src/__tests__/conversation-agent-loop-overflow.test.ts +7 -0
- package/src/__tests__/conversation-agent-loop.test.ts +7 -0
- package/src/__tests__/conversation-clear-safety.test.ts +259 -0
- package/src/__tests__/conversation-memory-dirty-tail.test.ts +150 -0
- package/src/__tests__/conversation-provider-retry-repair.test.ts +7 -0
- package/src/__tests__/conversation-switch-memory-reduction.test.ts +474 -0
- package/src/__tests__/conversation-wipe.test.ts +226 -0
- package/src/__tests__/db-memory-archive-migration.test.ts +372 -0
- package/src/__tests__/db-memory-brief-state-migration.test.ts +213 -0
- package/src/__tests__/db-memory-reducer-checkpoints.test.ts +273 -0
- package/src/__tests__/db-schedule-syntax-migration.test.ts +3 -0
- package/src/__tests__/inline-command-runner.test.ts +311 -0
- package/src/__tests__/inline-skill-authoring-guard.test.ts +220 -0
- package/src/__tests__/inline-skill-load-permissions.test.ts +435 -0
- package/src/__tests__/list-messages-attachments.test.ts +96 -0
- package/src/__tests__/memory-brief-open-loops.test.ts +530 -0
- package/src/__tests__/memory-brief-time.test.ts +285 -0
- package/src/__tests__/memory-brief-wrapper.test.ts +311 -0
- package/src/__tests__/memory-chunk-archive.test.ts +400 -0
- package/src/__tests__/memory-chunk-dual-write.test.ts +453 -0
- package/src/__tests__/memory-episode-archive.test.ts +370 -0
- package/src/__tests__/memory-episode-dual-write.test.ts +626 -0
- package/src/__tests__/memory-observation-archive.test.ts +375 -0
- package/src/__tests__/memory-observation-dual-write.test.ts +318 -0
- package/src/__tests__/memory-recall-quality.test.ts +2 -2
- package/src/__tests__/memory-reducer-job.test.ts +538 -0
- package/src/__tests__/memory-reducer-scheduling.test.ts +473 -0
- package/src/__tests__/memory-reducer-store.test.ts +728 -0
- package/src/__tests__/memory-reducer-types.test.ts +707 -0
- package/src/__tests__/memory-reducer.test.ts +704 -0
- package/src/__tests__/memory-regressions.test.ts +30 -8
- package/src/__tests__/memory-simplified-config.test.ts +281 -0
- package/src/__tests__/parse-identity-fields.test.ts +129 -0
- package/src/__tests__/simplified-memory-e2e.test.ts +666 -0
- package/src/__tests__/simplified-memory-runtime.test.ts +616 -0
- package/src/__tests__/skill-load-inline-command.test.ts +598 -0
- package/src/__tests__/skill-load-inline-includes.test.ts +644 -0
- package/src/__tests__/skills-inline-command-expansions.test.ts +301 -0
- package/src/__tests__/skills-transitive-hash.test.ts +333 -0
- package/src/__tests__/vellum-self-knowledge-inline-command.test.ts +320 -0
- package/src/__tests__/workspace-migration-backfill-installation-id.test.ts +4 -4
- package/src/cli/commands/conversations.ts +18 -0
- package/src/config/bundled-skills/app-builder/SKILL.md +8 -8
- package/src/config/bundled-skills/schedule/TOOLS.json +8 -0
- package/src/config/bundled-skills/skill-management/SKILL.md +1 -1
- package/src/config/bundled-skills/skill-management/TOOLS.json +2 -2
- package/src/config/feature-flag-registry.json +16 -0
- package/src/config/raw-config-utils.ts +28 -0
- package/src/config/schema.ts +12 -0
- package/src/config/schemas/memory-simplified.ts +101 -0
- package/src/config/schemas/memory.ts +4 -0
- package/src/config/skills.ts +50 -4
- package/src/daemon/conversation-agent-loop-handlers.ts +8 -3
- package/src/daemon/conversation-agent-loop.ts +71 -1
- package/src/daemon/conversation-lifecycle.ts +11 -1
- package/src/daemon/conversation-memory.ts +117 -0
- package/src/daemon/conversation-runtime-assembly.ts +3 -1
- package/src/daemon/conversation-surfaces.ts +31 -8
- package/src/daemon/conversation.ts +40 -23
- package/src/daemon/handlers/config-embeddings.ts +10 -2
- package/src/daemon/handlers/config-model.ts +0 -9
- package/src/daemon/handlers/conversations.ts +11 -0
- package/src/daemon/handlers/identity.ts +12 -1
- package/src/daemon/lifecycle.ts +52 -1
- package/src/daemon/message-types/conversations.ts +0 -1
- package/src/daemon/server.ts +1 -1
- package/src/followups/followup-store.ts +47 -1
- package/src/memory/archive-recall.ts +516 -0
- package/src/memory/archive-store.ts +400 -0
- package/src/memory/brief-formatting.ts +33 -0
- package/src/memory/brief-open-loops.ts +266 -0
- package/src/memory/brief-time.ts +162 -0
- package/src/memory/brief.ts +75 -0
- package/src/memory/conversation-crud.ts +455 -101
- package/src/memory/conversation-key-store.ts +33 -4
- package/src/memory/db-init.ts +16 -0
- package/src/memory/indexer.ts +106 -15
- package/src/memory/job-handlers/backfill-simplified-memory.ts +462 -0
- package/src/memory/job-handlers/conversation-starters.ts +9 -3
- package/src/memory/job-handlers/embedding.test.ts +1 -0
- package/src/memory/job-handlers/embedding.ts +83 -0
- package/src/memory/job-handlers/reduce-conversation-memory.ts +229 -0
- package/src/memory/job-utils.ts +1 -1
- package/src/memory/jobs-store.ts +8 -0
- package/src/memory/jobs-worker.ts +20 -0
- package/src/memory/migrations/036-normalize-phone-identities.ts +49 -14
- package/src/memory/migrations/135-backfill-contact-interaction-stats.ts +9 -1
- package/src/memory/migrations/141-rename-verification-table.ts +8 -0
- package/src/memory/migrations/142-rename-verification-session-id-column.ts +7 -2
- package/src/memory/migrations/174-rename-thread-starters-table.ts +8 -0
- package/src/memory/migrations/185-memory-brief-state.ts +52 -0
- package/src/memory/migrations/186-memory-archive.ts +109 -0
- package/src/memory/migrations/187-memory-reducer-checkpoints.ts +19 -0
- package/src/memory/migrations/188-schedule-quiet-flag.ts +13 -0
- package/src/memory/migrations/index.ts +4 -0
- package/src/memory/qdrant-client.ts +23 -4
- package/src/memory/reducer-scheduler.ts +242 -0
- package/src/memory/reducer-store.ts +271 -0
- package/src/memory/reducer-types.ts +106 -0
- package/src/memory/reducer.ts +467 -0
- package/src/memory/schema/conversations.ts +3 -0
- package/src/memory/schema/index.ts +2 -0
- package/src/memory/schema/infrastructure.ts +1 -0
- package/src/memory/schema/memory-archive.ts +121 -0
- package/src/memory/schema/memory-brief.ts +55 -0
- package/src/memory/search/semantic.ts +17 -4
- package/src/oauth/oauth-store.ts +3 -1
- package/src/permissions/checker.ts +89 -6
- package/src/permissions/defaults.ts +14 -0
- package/src/runtime/auth/route-policy.ts +10 -1
- package/src/runtime/routes/conversation-management-routes.ts +94 -2
- package/src/runtime/routes/conversation-query-routes.ts +7 -0
- package/src/runtime/routes/conversation-routes.ts +52 -5
- package/src/runtime/routes/guardian-bootstrap-routes.ts +19 -7
- package/src/runtime/routes/identity-routes.ts +2 -35
- package/src/runtime/routes/llm-context-normalization.ts +14 -1
- package/src/runtime/routes/memory-item-routes.ts +90 -5
- package/src/runtime/routes/secret-routes.ts +3 -0
- package/src/runtime/routes/surface-action-routes.ts +68 -1
- package/src/schedule/schedule-store.ts +28 -0
- package/src/schedule/scheduler.ts +6 -2
- package/src/skills/inline-command-expansions.ts +204 -0
- package/src/skills/inline-command-render.ts +127 -0
- package/src/skills/inline-command-runner.ts +242 -0
- package/src/skills/transitive-version-hash.ts +88 -0
- package/src/tasks/task-store.ts +43 -1
- package/src/telemetry/usage-telemetry-reporter.ts +1 -1
- package/src/tools/filesystem/edit.ts +6 -1
- package/src/tools/filesystem/read.ts +6 -1
- package/src/tools/filesystem/write.ts +6 -1
- package/src/tools/memory/handlers.ts +129 -1
- package/src/tools/permission-checker.ts +8 -1
- package/src/tools/schedule/create.ts +3 -0
- package/src/tools/schedule/list.ts +5 -1
- package/src/tools/schedule/update.ts +6 -0
- package/src/tools/skills/load.ts +140 -6
- package/src/util/platform.ts +18 -0
- package/src/workspace/migrations/{002-backfill-installation-id.ts → 011-backfill-installation-id.ts} +1 -1
- package/src/workspace/migrations/registry.ts +1 -1
|
@@ -38,7 +38,6 @@ import {
|
|
|
38
38
|
import { registerToolTraceListener } from "../events/tool-trace-listener.js";
|
|
39
39
|
import { getHookManager } from "../hooks/manager.js";
|
|
40
40
|
import { resolveCanonicalGuardianRequest } from "../memory/canonical-guardian-store.js";
|
|
41
|
-
import { getMessages } from "../memory/conversation-crud.js";
|
|
42
41
|
import { PermissionPrompter } from "../permissions/prompter.js";
|
|
43
42
|
import { SecretPrompter } from "../permissions/secret-prompter.js";
|
|
44
43
|
import { patternMatchesCandidate } from "../permissions/trust-store.js";
|
|
@@ -196,13 +195,24 @@ export class Conversation {
|
|
|
196
195
|
>();
|
|
197
196
|
/** @internal */ surfaceState = new Map<
|
|
198
197
|
string,
|
|
199
|
-
{
|
|
198
|
+
{
|
|
199
|
+
surfaceType: SurfaceType;
|
|
200
|
+
data: SurfaceData;
|
|
201
|
+
title?: string;
|
|
202
|
+
actions?: Array<{
|
|
203
|
+
id: string;
|
|
204
|
+
label: string;
|
|
205
|
+
style?: string;
|
|
206
|
+
data?: Record<string, unknown>;
|
|
207
|
+
}>;
|
|
208
|
+
}
|
|
200
209
|
>();
|
|
201
210
|
/** @internal */ surfaceUndoStacks = new Map<string, string[]>();
|
|
202
211
|
/** @internal */ accumulatedSurfaceState = new Map<
|
|
203
212
|
string,
|
|
204
213
|
Record<string, unknown>
|
|
205
214
|
>();
|
|
215
|
+
/** @internal */ broadcastToAllClients?: (msg: ServerMessage) => void;
|
|
206
216
|
/** @internal */ withSurface = createSurfaceMutex();
|
|
207
217
|
/** @internal */ currentTurnSurfaces: Array<{
|
|
208
218
|
surfaceId: string;
|
|
@@ -249,6 +259,7 @@ export class Conversation {
|
|
|
249
259
|
this.provider = provider;
|
|
250
260
|
this.workingDir = workingDir;
|
|
251
261
|
this.sendToClient = sendToClient;
|
|
262
|
+
this.broadcastToAllClients = broadcastToAllClients;
|
|
252
263
|
this.memoryPolicy = memoryPolicy
|
|
253
264
|
? { ...memoryPolicy }
|
|
254
265
|
: { ...DEFAULT_MEMORY_POLICY };
|
|
@@ -386,30 +397,36 @@ export class Conversation {
|
|
|
386
397
|
}
|
|
387
398
|
|
|
388
399
|
/**
|
|
389
|
-
* Scan
|
|
390
|
-
*
|
|
391
|
-
*
|
|
400
|
+
* Scan loaded conversation history for ui_surface content blocks and
|
|
401
|
+
* populate surfaceState so that findConversationBySurfaceId works for
|
|
402
|
+
* surfaces restored from history (e.g. after daemon restart).
|
|
403
|
+
*
|
|
404
|
+
* Only scans live (non-compacted) messages in this.messages — not all DB
|
|
405
|
+
* rows — because surface IDs are not globally unique and restoring stale
|
|
406
|
+
* compacted surfaces would let findConversationBySurfaceId route actions
|
|
407
|
+
* to the wrong conversation.
|
|
392
408
|
*/
|
|
393
409
|
private restoreSurfaceStateFromHistory(): void {
|
|
394
|
-
|
|
395
|
-
for (const
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
+
this.surfaceState.clear();
|
|
411
|
+
for (const msg of this.messages) {
|
|
412
|
+
if (!Array.isArray(msg.content)) continue;
|
|
413
|
+
for (const block of msg.content) {
|
|
414
|
+
const b = block as unknown as Record<string, unknown>;
|
|
415
|
+
if (b.type === "ui_surface" && typeof b.surfaceId === "string") {
|
|
416
|
+
this.surfaceState.set(b.surfaceId, {
|
|
417
|
+
surfaceType: (b.surfaceType ?? "dynamic_page") as SurfaceType,
|
|
418
|
+
data: (b.data ?? {}) as SurfaceData,
|
|
419
|
+
title: b.title as string | undefined,
|
|
420
|
+
actions: Array.isArray(b.actions)
|
|
421
|
+
? (b.actions as Array<{
|
|
422
|
+
id: string;
|
|
423
|
+
label: string;
|
|
424
|
+
style?: string;
|
|
425
|
+
data?: Record<string, unknown>;
|
|
426
|
+
}>)
|
|
427
|
+
: undefined,
|
|
428
|
+
});
|
|
410
429
|
}
|
|
411
|
-
} catch {
|
|
412
|
-
// Content isn't valid JSON — skip
|
|
413
430
|
}
|
|
414
431
|
}
|
|
415
432
|
}
|
|
@@ -3,7 +3,10 @@ import {
|
|
|
3
3
|
loadRawConfig,
|
|
4
4
|
saveRawConfig,
|
|
5
5
|
} from "../../config/loader.js";
|
|
6
|
-
import {
|
|
6
|
+
import {
|
|
7
|
+
deleteMemoryEmbeddingField,
|
|
8
|
+
setMemoryEmbeddingField,
|
|
9
|
+
} from "../../config/raw-config-utils.js";
|
|
7
10
|
import { VALID_MEMORY_EMBEDDING_PROVIDERS } from "../../config/schemas/memory-storage.js";
|
|
8
11
|
import {
|
|
9
12
|
clearEmbeddingBackendCache,
|
|
@@ -118,7 +121,12 @@ export async function setEmbeddingConfig(
|
|
|
118
121
|
if (model !== undefined) {
|
|
119
122
|
const fieldName = PROVIDER_MODEL_FIELD[provider];
|
|
120
123
|
if (fieldName) {
|
|
121
|
-
|
|
124
|
+
if (model === "") {
|
|
125
|
+
// Empty string means "clear override — use schema default"
|
|
126
|
+
deleteMemoryEmbeddingField(raw, fieldName);
|
|
127
|
+
} else {
|
|
128
|
+
setMemoryEmbeddingField(raw, fieldName, model);
|
|
129
|
+
}
|
|
122
130
|
}
|
|
123
131
|
}
|
|
124
132
|
|
|
@@ -16,7 +16,6 @@ import {
|
|
|
16
16
|
isProviderAvailable,
|
|
17
17
|
} from "../../providers/provider-availability.js";
|
|
18
18
|
import { initializeProviders } from "../../providers/registry.js";
|
|
19
|
-
import { getMaskedProviderKey } from "../../security/secure-keys.js";
|
|
20
19
|
import type {
|
|
21
20
|
ImageGenModelSetRequest,
|
|
22
21
|
ModelSetRequest,
|
|
@@ -44,7 +43,6 @@ export interface ModelInfo {
|
|
|
44
43
|
configuredProviders?: string[];
|
|
45
44
|
availableModels?: Array<{ id: string; displayName: string }>;
|
|
46
45
|
allProviders?: ProviderCatalogEntry[];
|
|
47
|
-
maskedKeys?: Record<string, string>;
|
|
48
46
|
}
|
|
49
47
|
|
|
50
48
|
/** Return current model configuration. */
|
|
@@ -52,19 +50,12 @@ export async function getModelInfo(): Promise<ModelInfo> {
|
|
|
52
50
|
const config = getConfig();
|
|
53
51
|
const provider = config.services.inference.provider;
|
|
54
52
|
|
|
55
|
-
const maskedKeys: Record<string, string> = {};
|
|
56
|
-
for (const p of VALID_INFERENCE_PROVIDERS) {
|
|
57
|
-
const masked = await getMaskedProviderKey(p);
|
|
58
|
-
if (masked) maskedKeys[p] = masked;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
53
|
return {
|
|
62
54
|
model: config.services.inference.model,
|
|
63
55
|
provider,
|
|
64
56
|
configuredProviders: await getConfiguredProviders(),
|
|
65
57
|
availableModels: PROVIDER_CATALOG.find((p) => p.id === provider)?.models,
|
|
66
58
|
allProviders: PROVIDER_CATALOG,
|
|
67
|
-
maskedKeys,
|
|
68
59
|
};
|
|
69
60
|
}
|
|
70
61
|
|
|
@@ -23,6 +23,7 @@ import {
|
|
|
23
23
|
queueGenerateConversationTitle,
|
|
24
24
|
UNTITLED_FALLBACK,
|
|
25
25
|
} from "../../memory/conversation-title-service.js";
|
|
26
|
+
import { reduceBeforeSwitch } from "../../memory/reducer-scheduler.js";
|
|
26
27
|
import * as pendingInteractions from "../../runtime/pending-interactions.js";
|
|
27
28
|
import { getSubagentManager } from "../../subagent/index.js";
|
|
28
29
|
import { truncate } from "../../util/truncate.js";
|
|
@@ -233,6 +234,12 @@ export async function handleConversationCreate(
|
|
|
233
234
|
conversationType: normalizeConversationType(conversation.conversationType),
|
|
234
235
|
});
|
|
235
236
|
|
|
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
|
+
|
|
236
243
|
// Auto-send the initial message if provided, kick-starting the skill.
|
|
237
244
|
if (msg.initialMessage) {
|
|
238
245
|
// Queue title generation eagerly — some processMessage paths (guardian
|
|
@@ -343,6 +350,10 @@ export async function switchConversation(
|
|
|
343
350
|
return null;
|
|
344
351
|
}
|
|
345
352
|
|
|
353
|
+
// Reduce the previous dirty conversation before switching so its memory
|
|
354
|
+
// is fresh for the next read.
|
|
355
|
+
await reduceBeforeSwitch(conversationId);
|
|
356
|
+
|
|
346
357
|
// If the target conversation is headless-locked (actively executing a task run),
|
|
347
358
|
// skip rebinding so tool confirmations stay suppressed.
|
|
348
359
|
const existingConversation = ctx.conversations.get(conversationId);
|
|
@@ -1,3 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Returns true when the value is a template placeholder that should be treated
|
|
3
|
+
* as empty/unset. Placeholders follow the pattern `_(…)_`, e.g.
|
|
4
|
+
* `_(not yet chosen)_` or `_(not yet established)_`.
|
|
5
|
+
*/
|
|
6
|
+
export function isTemplatePlaceholder(value: string): boolean {
|
|
7
|
+
return value.startsWith("_(") && value.endsWith(")_");
|
|
8
|
+
}
|
|
9
|
+
|
|
1
10
|
export interface IdentityFields {
|
|
2
11
|
name: string;
|
|
3
12
|
role: string;
|
|
@@ -14,7 +23,9 @@ export function parseIdentityFields(content: string): IdentityFields {
|
|
|
14
23
|
const lower = trimmed.toLowerCase();
|
|
15
24
|
const extract = (prefix: string): string | null => {
|
|
16
25
|
if (!lower.startsWith(prefix)) return null;
|
|
17
|
-
|
|
26
|
+
const value = trimmed.split(":**").pop()?.trim() ?? null;
|
|
27
|
+
if (value && isTemplatePlaceholder(value)) return null;
|
|
28
|
+
return value;
|
|
18
29
|
};
|
|
19
30
|
|
|
20
31
|
const name = extract("- **name:**");
|
package/src/daemon/lifecycle.ts
CHANGED
|
@@ -30,6 +30,7 @@ import {
|
|
|
30
30
|
getConversationType,
|
|
31
31
|
getMessages,
|
|
32
32
|
purgePrivateConversations,
|
|
33
|
+
sweepStaleReducerJobs,
|
|
33
34
|
} from "../memory/conversation-crud.js";
|
|
34
35
|
import { resolveConversationId } from "../memory/conversation-key-store.js";
|
|
35
36
|
import { initializeDb } from "../memory/db.js";
|
|
@@ -200,14 +201,46 @@ export async function runDaemon(): Promise<void> {
|
|
|
200
201
|
targetId: itemId,
|
|
201
202
|
});
|
|
202
203
|
}
|
|
204
|
+
for (const summaryId of deletedMemory.deletedSummaryIds) {
|
|
205
|
+
enqueueMemoryJob("delete_qdrant_vectors", {
|
|
206
|
+
targetType: "summary",
|
|
207
|
+
targetId: summaryId,
|
|
208
|
+
});
|
|
209
|
+
}
|
|
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
|
+
}
|
|
203
228
|
if (
|
|
204
229
|
deletedMemory.segmentIds.length > 0 ||
|
|
205
|
-
deletedMemory.orphanedItemIds.length > 0
|
|
230
|
+
deletedMemory.orphanedItemIds.length > 0 ||
|
|
231
|
+
deletedMemory.deletedSummaryIds.length > 0 ||
|
|
232
|
+
deletedMemory.deletedObservationIds.length > 0 ||
|
|
233
|
+
deletedMemory.deletedChunkIds.length > 0 ||
|
|
234
|
+
deletedMemory.deletedEpisodeIds.length > 0
|
|
206
235
|
) {
|
|
207
236
|
log.info(
|
|
208
237
|
{
|
|
209
238
|
segments: deletedMemory.segmentIds.length,
|
|
210
239
|
orphanedItems: deletedMemory.orphanedItemIds.length,
|
|
240
|
+
deletedSummaries: deletedMemory.deletedSummaryIds.length,
|
|
241
|
+
deletedObservations: deletedMemory.deletedObservationIds.length,
|
|
242
|
+
deletedChunks: deletedMemory.deletedChunkIds.length,
|
|
243
|
+
deletedEpisodes: deletedMemory.deletedEpisodeIds.length,
|
|
211
244
|
},
|
|
212
245
|
"Enqueued Qdrant vector cleanup jobs for purged private conversations",
|
|
213
246
|
);
|
|
@@ -232,6 +265,24 @@ export async function runDaemon(): Promise<void> {
|
|
|
232
265
|
);
|
|
233
266
|
}
|
|
234
267
|
|
|
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
|
+
|
|
235
286
|
// Ensure a vellum guardian binding exists so the identity system works
|
|
236
287
|
// without requiring a manual bootstrap step.
|
|
237
288
|
try {
|
package/src/daemon/server.ts
CHANGED
|
@@ -1262,7 +1262,7 @@ export class DaemonServer {
|
|
|
1262
1262
|
const appId = surfaceId.slice(appOpenPrefix.length);
|
|
1263
1263
|
for (const c of this.conversations.values()) {
|
|
1264
1264
|
for (const [, state] of c.surfaceState.entries()) {
|
|
1265
|
-
const data = state.data as Record<string, unknown>;
|
|
1265
|
+
const data = state.data as unknown as Record<string, unknown>;
|
|
1266
1266
|
if (data?.appId === appId) {
|
|
1267
1267
|
// Register this surfaceId so subsequent lookups are O(1)
|
|
1268
1268
|
c.surfaceState.set(surfaceId, state);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { and, eq, lte, or } from "drizzle-orm";
|
|
1
|
+
import { and, desc, eq, lte, or } from "drizzle-orm";
|
|
2
2
|
import { v4 as uuid } from "uuid";
|
|
3
3
|
|
|
4
4
|
import { getDb } from "../memory/db.js";
|
|
@@ -176,3 +176,49 @@ export function markNudged(id: string): FollowUp {
|
|
|
176
176
|
|
|
177
177
|
return getFollowUp(id)!;
|
|
178
178
|
}
|
|
179
|
+
|
|
180
|
+
// ── Brief Helpers ──────────────────────────────────────────────────────
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Lightweight projection of a follow-up used by the brief compiler.
|
|
184
|
+
*/
|
|
185
|
+
export interface BriefFollowUp {
|
|
186
|
+
id: string;
|
|
187
|
+
channel: string;
|
|
188
|
+
conversationId: string;
|
|
189
|
+
contactId: string | null;
|
|
190
|
+
expectedResponseBy: number | null;
|
|
191
|
+
status: FollowUpStatus;
|
|
192
|
+
updatedAt: number;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Return pending and overdue follow-ups for the brief compiler.
|
|
197
|
+
* Ordered by expectedResponseBy ascending (most urgent first).
|
|
198
|
+
*/
|
|
199
|
+
export function getPendingAndOverdueFollowUps(): BriefFollowUp[] {
|
|
200
|
+
const db = getDb();
|
|
201
|
+
|
|
202
|
+
const rows = db
|
|
203
|
+
.select({
|
|
204
|
+
id: followups.id,
|
|
205
|
+
channel: followups.channel,
|
|
206
|
+
conversationId: followups.conversationId,
|
|
207
|
+
contactId: followups.contactId,
|
|
208
|
+
expectedResponseBy: followups.expectedResponseBy,
|
|
209
|
+
status: followups.status,
|
|
210
|
+
updatedAt: followups.updatedAt,
|
|
211
|
+
})
|
|
212
|
+
.from(followups)
|
|
213
|
+
.where(
|
|
214
|
+
or(
|
|
215
|
+
eq(followups.status, "pending"),
|
|
216
|
+
eq(followups.status, "overdue"),
|
|
217
|
+
eq(followups.status, "nudged"),
|
|
218
|
+
),
|
|
219
|
+
)
|
|
220
|
+
.orderBy(desc(followups.expectedResponseBy))
|
|
221
|
+
.all();
|
|
222
|
+
|
|
223
|
+
return rows as BriefFollowUp[];
|
|
224
|
+
}
|