@vellumai/assistant 0.5.2 → 0.5.3
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/skills.md +100 -0
- package/package.json +1 -1
- 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-memory-dirty-tail.test.ts +150 -0
- package/src/__tests__/conversation-provider-retry-repair.test.ts +7 -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__/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-store.test.ts +728 -0
- package/src/__tests__/memory-reducer-types.test.ts +699 -0
- package/src/__tests__/memory-reducer.test.ts +698 -0
- package/src/__tests__/memory-regressions.test.ts +6 -4
- package/src/__tests__/memory-simplified-config.test.ts +281 -0
- package/src/__tests__/parse-identity-fields.test.ts +129 -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/config/bundled-skills/app-builder/SKILL.md +8 -8
- 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/loader.ts +1 -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-runtime-assembly.ts +2 -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/identity.ts +12 -1
- package/src/daemon/lifecycle.ts +9 -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-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 +161 -0
- package/src/memory/brief.ts +75 -0
- package/src/memory/conversation-crud.ts +245 -101
- package/src/memory/db-init.ts +12 -0
- package/src/memory/indexer.ts +106 -15
- package/src/memory/job-handlers/embedding.test.ts +1 -0
- package/src/memory/job-handlers/embedding.ts +83 -0
- package/src/memory/job-utils.ts +1 -1
- package/src/memory/jobs-store.ts +6 -0
- package/src/memory/jobs-worker.ts +12 -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/index.ts +3 -0
- package/src/memory/qdrant-client.ts +23 -4
- package/src/memory/reducer-store.ts +271 -0
- package/src/memory/reducer-types.ts +99 -0
- package/src/memory/reducer.ts +453 -0
- package/src/memory/schema/conversations.ts +3 -0
- package/src/memory/schema/index.ts +2 -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/routes/conversation-management-routes.ts +6 -0
- package/src/runtime/routes/conversation-query-routes.ts +7 -0
- package/src/runtime/routes/conversation-routes.ts +52 -5
- 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 +2 -0
- package/src/runtime/routes/surface-action-routes.ts +68 -1
- package/src/schedule/schedule-store.ts +21 -0
- 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/tools/permission-checker.ts +8 -1
- 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
|
@@ -45,6 +45,7 @@ import { enqueueMemoryJob } from "./jobs-store.js";
|
|
|
45
45
|
import {
|
|
46
46
|
channelInboundEvents,
|
|
47
47
|
conversations,
|
|
48
|
+
conversationStarters,
|
|
48
49
|
llmRequestLogs,
|
|
49
50
|
memoryEmbeddings,
|
|
50
51
|
memoryItems,
|
|
@@ -171,6 +172,9 @@ export interface ConversationRow {
|
|
|
171
172
|
forkParentMessageId: string | null;
|
|
172
173
|
isAutoTitle: number;
|
|
173
174
|
scheduleJobId: string | null;
|
|
175
|
+
memoryReducedThroughMessageId: string | null;
|
|
176
|
+
memoryDirtyTailSinceMessageId: string | null;
|
|
177
|
+
memoryLastReducedAt: number | null;
|
|
174
178
|
}
|
|
175
179
|
|
|
176
180
|
export const parseConversation = createRowMapper<
|
|
@@ -196,6 +200,9 @@ export const parseConversation = createRowMapper<
|
|
|
196
200
|
forkParentMessageId: "forkParentMessageId",
|
|
197
201
|
isAutoTitle: "isAutoTitle",
|
|
198
202
|
scheduleJobId: "scheduleJobId",
|
|
203
|
+
memoryReducedThroughMessageId: "memoryReducedThroughMessageId",
|
|
204
|
+
memoryDirtyTailSinceMessageId: "memoryDirtyTailSinceMessageId",
|
|
205
|
+
memoryLastReducedAt: "memoryLastReducedAt",
|
|
199
206
|
});
|
|
200
207
|
|
|
201
208
|
export interface MessageRow {
|
|
@@ -375,127 +382,153 @@ export function forkConversation(params: {
|
|
|
375
382
|
: ([] as MessageRow[]);
|
|
376
383
|
const forkParentMessageId = messagesToCopy.at(-1)?.id ?? null;
|
|
377
384
|
const forkTitle = `${sourceConversation.title ?? "Untitled"} (Fork)`;
|
|
378
|
-
const forkedConversation = createConversation({
|
|
379
|
-
title: forkTitle,
|
|
380
|
-
conversationType: "standard",
|
|
381
|
-
});
|
|
382
385
|
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
386
|
+
// Collect disk-sync work to run after the transaction commits.
|
|
387
|
+
const diskSyncQueue: Array<{
|
|
388
|
+
conversationId: string;
|
|
389
|
+
messageId: string;
|
|
390
|
+
createdAt: number;
|
|
391
|
+
}> = [];
|
|
392
|
+
|
|
393
|
+
// Wrap all DB mutations in a single transaction so a mid-flight failure
|
|
394
|
+
// rolls back cleanly instead of leaving a partial fork. Helper functions
|
|
395
|
+
// (linkAttachmentToMessage, relinkAttachments, seedForkedConversationAttention)
|
|
396
|
+
// use the same underlying bun:sqlite connection, so their writes participate
|
|
397
|
+
// in this transaction automatically.
|
|
398
|
+
const forkedConversation = db.transaction(() => {
|
|
399
|
+
const fc = createConversation({
|
|
400
|
+
title: forkTitle,
|
|
401
|
+
conversationType: "standard",
|
|
402
|
+
});
|
|
399
403
|
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
metadata: cloneForkMessageMetadata(message.metadata, message.id),
|
|
404
|
+
db.update(conversations)
|
|
405
|
+
.set({
|
|
406
|
+
forkParentConversationId: sourceConversation.id,
|
|
407
|
+
forkParentMessageId,
|
|
408
|
+
contextSummary: preserveSourceCompactionState
|
|
409
|
+
? sourceConversation.contextSummary
|
|
410
|
+
: null,
|
|
411
|
+
contextCompactedMessageCount: preserveSourceCompactionState
|
|
412
|
+
? sourceConversation.contextCompactedMessageCount
|
|
413
|
+
: 0,
|
|
414
|
+
contextCompactedAt: preserveSourceCompactionState
|
|
415
|
+
? sourceConversation.contextCompactedAt
|
|
416
|
+
: null,
|
|
414
417
|
})
|
|
418
|
+
.where(eq(conversations.id, fc.id))
|
|
415
419
|
.run();
|
|
416
|
-
forkedMessageIds.set(message.id, forkedMessageId);
|
|
417
|
-
|
|
418
|
-
if (message.role === "assistant") {
|
|
419
|
-
latestForkedAssistant = {
|
|
420
|
-
messageId: forkedMessageId,
|
|
421
|
-
messageAt: message.createdAt,
|
|
422
|
-
};
|
|
423
|
-
}
|
|
424
|
-
}
|
|
425
420
|
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
421
|
+
const forkedMessageIds = new Map<string, string>();
|
|
422
|
+
let latestForkedAssistant: {
|
|
423
|
+
messageId: string;
|
|
424
|
+
messageAt: number;
|
|
425
|
+
} | null = null;
|
|
430
426
|
|
|
431
|
-
const
|
|
432
|
-
|
|
433
|
-
attachmentId: messageAttachments.attachmentId,
|
|
434
|
-
position: messageAttachments.position,
|
|
435
|
-
})
|
|
436
|
-
.from(messageAttachments)
|
|
437
|
-
.where(eq(messageAttachments.messageId, message.id))
|
|
438
|
-
.orderBy(messageAttachments.position)
|
|
439
|
-
.all();
|
|
440
|
-
const uncachedAttachmentLinks = attachmentLinks.filter(
|
|
441
|
-
(link) => !attachmentIdMap.has(link.attachmentId),
|
|
442
|
-
);
|
|
443
|
-
const stagingMessageId = uncachedAttachmentLinks.length > 0 ? uuid() : null;
|
|
444
|
-
|
|
445
|
-
if (stagingMessageId) {
|
|
427
|
+
for (const message of messagesToCopy) {
|
|
428
|
+
const forkedMessageId = uuid();
|
|
446
429
|
db.insert(messages)
|
|
447
430
|
.values({
|
|
448
|
-
id:
|
|
449
|
-
conversationId:
|
|
431
|
+
id: forkedMessageId,
|
|
432
|
+
conversationId: fc.id,
|
|
450
433
|
role: message.role,
|
|
451
|
-
content:
|
|
434
|
+
content: message.content,
|
|
452
435
|
createdAt: message.createdAt,
|
|
453
|
-
metadata:
|
|
436
|
+
metadata: cloneForkMessageMetadata(message.metadata, message.id),
|
|
454
437
|
})
|
|
455
438
|
.run();
|
|
439
|
+
forkedMessageIds.set(message.id, forkedMessageId);
|
|
440
|
+
|
|
441
|
+
if (message.role === "assistant") {
|
|
442
|
+
latestForkedAssistant = {
|
|
443
|
+
messageId: forkedMessageId,
|
|
444
|
+
messageAt: message.createdAt,
|
|
445
|
+
};
|
|
446
|
+
}
|
|
456
447
|
}
|
|
457
448
|
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
449
|
+
const attachmentIdMap = new Map<string, string>();
|
|
450
|
+
for (const message of messagesToCopy) {
|
|
451
|
+
const forkedMessageId = forkedMessageIds.get(message.id);
|
|
452
|
+
if (!forkedMessageId) continue;
|
|
453
|
+
|
|
454
|
+
const attachmentLinks = db
|
|
455
|
+
.select({
|
|
456
|
+
attachmentId: messageAttachments.attachmentId,
|
|
457
|
+
position: messageAttachments.position,
|
|
458
|
+
})
|
|
459
|
+
.from(messageAttachments)
|
|
460
|
+
.where(eq(messageAttachments.messageId, message.id))
|
|
461
|
+
.orderBy(messageAttachments.position)
|
|
462
|
+
.all();
|
|
463
|
+
const uncachedAttachmentLinks = attachmentLinks.filter(
|
|
464
|
+
(link) => !attachmentIdMap.has(link.attachmentId),
|
|
465
|
+
);
|
|
466
|
+
const stagingMessageId =
|
|
467
|
+
uncachedAttachmentLinks.length > 0 ? uuid() : null;
|
|
468
|
+
|
|
469
|
+
if (stagingMessageId) {
|
|
470
|
+
db.insert(messages)
|
|
462
471
|
.values({
|
|
463
|
-
id:
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
createdAt:
|
|
472
|
+
id: stagingMessageId,
|
|
473
|
+
conversationId: fc.id,
|
|
474
|
+
role: message.role,
|
|
475
|
+
content: "",
|
|
476
|
+
createdAt: message.createdAt,
|
|
477
|
+
metadata: null,
|
|
468
478
|
})
|
|
469
479
|
.run();
|
|
470
|
-
continue;
|
|
471
480
|
}
|
|
472
481
|
|
|
473
|
-
const
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
482
|
+
for (const link of attachmentLinks) {
|
|
483
|
+
const cachedAttachmentId = attachmentIdMap.get(link.attachmentId);
|
|
484
|
+
if (cachedAttachmentId) {
|
|
485
|
+
db.insert(messageAttachments)
|
|
486
|
+
.values({
|
|
487
|
+
id: uuid(),
|
|
488
|
+
messageId: forkedMessageId,
|
|
489
|
+
attachmentId: cachedAttachmentId,
|
|
490
|
+
position: link.position,
|
|
491
|
+
createdAt: Date.now(),
|
|
492
|
+
})
|
|
493
|
+
.run();
|
|
494
|
+
continue;
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
const scopedAttachmentId = linkAttachmentToMessage(
|
|
498
|
+
stagingMessageId ?? forkedMessageId,
|
|
499
|
+
link.attachmentId,
|
|
500
|
+
link.position,
|
|
501
|
+
);
|
|
502
|
+
attachmentIdMap.set(link.attachmentId, scopedAttachmentId);
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
if (stagingMessageId) {
|
|
506
|
+
relinkAttachments([stagingMessageId], forkedMessageId);
|
|
507
|
+
db.delete(messages).where(eq(messages.id, stagingMessageId)).run();
|
|
508
|
+
}
|
|
480
509
|
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
510
|
+
diskSyncQueue.push({
|
|
511
|
+
conversationId: fc.id,
|
|
512
|
+
messageId: forkedMessageId,
|
|
513
|
+
createdAt: fc.createdAt,
|
|
514
|
+
});
|
|
484
515
|
}
|
|
485
516
|
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
);
|
|
491
|
-
}
|
|
517
|
+
seedForkedConversationAttention({
|
|
518
|
+
conversationId: fc.id,
|
|
519
|
+
latestAssistantMessageId: latestForkedAssistant?.messageId ?? null,
|
|
520
|
+
latestAssistantMessageAt: latestForkedAssistant?.messageAt ?? null,
|
|
521
|
+
});
|
|
492
522
|
|
|
493
|
-
|
|
494
|
-
conversationId: forkedConversation.id,
|
|
495
|
-
latestAssistantMessageId: latestForkedAssistant?.messageId ?? null,
|
|
496
|
-
latestAssistantMessageAt: latestForkedAssistant?.messageAt ?? null,
|
|
523
|
+
return fc;
|
|
497
524
|
});
|
|
498
525
|
|
|
526
|
+
// Disk-view sync runs after commit — file I/O is idempotent and
|
|
527
|
+
// conversation deletion cleans up orphaned directories.
|
|
528
|
+
for (const entry of diskSyncQueue) {
|
|
529
|
+
syncMessageToDisk(entry.conversationId, entry.messageId, entry.createdAt);
|
|
530
|
+
}
|
|
531
|
+
|
|
499
532
|
const persistedFork = getConversation(forkedConversation.id);
|
|
500
533
|
if (!persistedFork) {
|
|
501
534
|
throw new Error(
|
|
@@ -513,12 +546,18 @@ export function forkConversation(params: {
|
|
|
513
546
|
*/
|
|
514
547
|
export function deleteConversation(id: string): DeletedMemoryIds {
|
|
515
548
|
const db = getDb();
|
|
516
|
-
const result: DeletedMemoryIds = {
|
|
549
|
+
const result: DeletedMemoryIds = {
|
|
550
|
+
segmentIds: [],
|
|
551
|
+
orphanedItemIds: [],
|
|
552
|
+
deletedSummaryIds: [],
|
|
553
|
+
};
|
|
517
554
|
|
|
518
555
|
// Capture createdAt before the transaction deletes the row — needed to
|
|
519
556
|
// resolve the conversation's disk-view directory path after deletion.
|
|
520
557
|
const convBeforeDelete = getConversation(id);
|
|
521
558
|
const createdAtForDiskCleanup = convBeforeDelete?.createdAt;
|
|
559
|
+
const memoryScopeId = convBeforeDelete?.memoryScopeId;
|
|
560
|
+
const isPrivateScope = memoryScopeId?.startsWith("private:") ?? false;
|
|
522
561
|
|
|
523
562
|
db.transaction((tx) => {
|
|
524
563
|
// Collect all message IDs for this conversation.
|
|
@@ -607,6 +646,65 @@ export function deleteConversation(id: string): DeletedMemoryIds {
|
|
|
607
646
|
.run();
|
|
608
647
|
}
|
|
609
648
|
|
|
649
|
+
if (isPrivateScope && memoryScopeId) {
|
|
650
|
+
// Sweep remaining memory items with this private scopeId.
|
|
651
|
+
const scopeItems = tx
|
|
652
|
+
.select({ id: memoryItems.id })
|
|
653
|
+
.from(memoryItems)
|
|
654
|
+
.where(eq(memoryItems.scopeId, memoryScopeId))
|
|
655
|
+
.all();
|
|
656
|
+
const alreadyDeleted = new Set(result.orphanedItemIds);
|
|
657
|
+
const scopeItemIds = scopeItems
|
|
658
|
+
.map((r) => r.id)
|
|
659
|
+
.filter((id) => !alreadyDeleted.has(id));
|
|
660
|
+
|
|
661
|
+
if (scopeItemIds.length > 0) {
|
|
662
|
+
tx.delete(memoryEmbeddings)
|
|
663
|
+
.where(
|
|
664
|
+
and(
|
|
665
|
+
eq(memoryEmbeddings.targetType, "item"),
|
|
666
|
+
inArray(memoryEmbeddings.targetId, scopeItemIds),
|
|
667
|
+
),
|
|
668
|
+
)
|
|
669
|
+
.run();
|
|
670
|
+
tx.delete(memoryItemSources)
|
|
671
|
+
.where(inArray(memoryItemSources.memoryItemId, scopeItemIds))
|
|
672
|
+
.run();
|
|
673
|
+
tx.delete(memoryItems)
|
|
674
|
+
.where(inArray(memoryItems.id, scopeItemIds))
|
|
675
|
+
.run();
|
|
676
|
+
result.orphanedItemIds.push(...scopeItemIds);
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
// Sweep memory summaries with this private scopeId.
|
|
680
|
+
const scopeSummaries = tx
|
|
681
|
+
.select({ id: memorySummaries.id })
|
|
682
|
+
.from(memorySummaries)
|
|
683
|
+
.where(eq(memorySummaries.scopeId, memoryScopeId))
|
|
684
|
+
.all();
|
|
685
|
+
const scopeSummaryIds = scopeSummaries.map((r) => r.id);
|
|
686
|
+
|
|
687
|
+
if (scopeSummaryIds.length > 0) {
|
|
688
|
+
tx.delete(memoryEmbeddings)
|
|
689
|
+
.where(
|
|
690
|
+
and(
|
|
691
|
+
eq(memoryEmbeddings.targetType, "summary"),
|
|
692
|
+
inArray(memoryEmbeddings.targetId, scopeSummaryIds),
|
|
693
|
+
),
|
|
694
|
+
)
|
|
695
|
+
.run();
|
|
696
|
+
tx.delete(memorySummaries)
|
|
697
|
+
.where(inArray(memorySummaries.id, scopeSummaryIds))
|
|
698
|
+
.run();
|
|
699
|
+
result.deletedSummaryIds.push(...scopeSummaryIds);
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
// Sweep conversation starters with this private scopeId.
|
|
703
|
+
tx.delete(conversationStarters)
|
|
704
|
+
.where(eq(conversationStarters.scopeId, memoryScopeId))
|
|
705
|
+
.run();
|
|
706
|
+
}
|
|
707
|
+
|
|
610
708
|
tx.delete(conversations).where(eq(conversations.id, id)).run();
|
|
611
709
|
});
|
|
612
710
|
|
|
@@ -799,7 +897,10 @@ export function wipeConversation(id: string): WipeConversationResult {
|
|
|
799
897
|
return {
|
|
800
898
|
...deletedMemoryIds,
|
|
801
899
|
unsupersededItemIds,
|
|
802
|
-
deletedSummaryIds
|
|
900
|
+
deletedSummaryIds: [
|
|
901
|
+
...deletedSummaryIds,
|
|
902
|
+
...deletedMemoryIds.deletedSummaryIds,
|
|
903
|
+
],
|
|
803
904
|
cancelledJobCount,
|
|
804
905
|
};
|
|
805
906
|
}
|
|
@@ -821,16 +922,25 @@ export function purgePrivateConversations(): {
|
|
|
821
922
|
.all();
|
|
822
923
|
|
|
823
924
|
if (privateConvs.length === 0) {
|
|
824
|
-
return {
|
|
925
|
+
return {
|
|
926
|
+
count: 0,
|
|
927
|
+
deletedMemory: {
|
|
928
|
+
segmentIds: [],
|
|
929
|
+
orphanedItemIds: [],
|
|
930
|
+
deletedSummaryIds: [],
|
|
931
|
+
},
|
|
932
|
+
};
|
|
825
933
|
}
|
|
826
934
|
|
|
827
935
|
const allSegmentIds: string[] = [];
|
|
828
936
|
const allOrphanedItemIds: string[] = [];
|
|
937
|
+
const allDeletedSummaryIds: string[] = [];
|
|
829
938
|
|
|
830
939
|
for (const conv of privateConvs) {
|
|
831
940
|
const deleted = deleteConversation(conv.id);
|
|
832
941
|
allSegmentIds.push(...deleted.segmentIds);
|
|
833
942
|
allOrphanedItemIds.push(...deleted.orphanedItemIds);
|
|
943
|
+
allDeletedSummaryIds.push(...deleted.deletedSummaryIds);
|
|
834
944
|
}
|
|
835
945
|
|
|
836
946
|
return {
|
|
@@ -838,6 +948,7 @@ export function purgePrivateConversations(): {
|
|
|
838
948
|
deletedMemory: {
|
|
839
949
|
segmentIds: allSegmentIds,
|
|
840
950
|
orphanedItemIds: allOrphanedItemIds,
|
|
951
|
+
deletedSummaryIds: allDeletedSummaryIds,
|
|
841
952
|
},
|
|
842
953
|
};
|
|
843
954
|
}
|
|
@@ -920,6 +1031,13 @@ export async function addMessage(
|
|
|
920
1031
|
throw err;
|
|
921
1032
|
}
|
|
922
1033
|
}
|
|
1034
|
+
|
|
1035
|
+
// Mark the conversation dirty for delayed memory reduction. This runs
|
|
1036
|
+
// after the insert transaction succeeds so the reducer knows which
|
|
1037
|
+
// conversations have unprocessed messages. The helper preserves the
|
|
1038
|
+
// earliest unreduced boundary (no-op when already dirty).
|
|
1039
|
+
markConversationMemoryDirty(conversationId, messageId);
|
|
1040
|
+
|
|
923
1041
|
const message = {
|
|
924
1042
|
id: messageId,
|
|
925
1043
|
conversationId,
|
|
@@ -1214,11 +1332,11 @@ export function deleteLastExchange(conversationId: string): number {
|
|
|
1214
1332
|
export interface DeletedMemoryIds {
|
|
1215
1333
|
segmentIds: string[];
|
|
1216
1334
|
orphanedItemIds: string[];
|
|
1335
|
+
deletedSummaryIds: string[];
|
|
1217
1336
|
}
|
|
1218
1337
|
|
|
1219
1338
|
export interface WipeConversationResult extends DeletedMemoryIds {
|
|
1220
1339
|
unsupersededItemIds: string[];
|
|
1221
|
-
deletedSummaryIds: string[];
|
|
1222
1340
|
cancelledJobCount: number;
|
|
1223
1341
|
}
|
|
1224
1342
|
|
|
@@ -1284,7 +1402,11 @@ export function relinkAttachments(
|
|
|
1284
1402
|
*/
|
|
1285
1403
|
export function deleteMessageById(messageId: string): DeletedMemoryIds {
|
|
1286
1404
|
const db = getDb();
|
|
1287
|
-
const result: DeletedMemoryIds = {
|
|
1405
|
+
const result: DeletedMemoryIds = {
|
|
1406
|
+
segmentIds: [],
|
|
1407
|
+
orphanedItemIds: [],
|
|
1408
|
+
deletedSummaryIds: [],
|
|
1409
|
+
};
|
|
1288
1410
|
|
|
1289
1411
|
// Collect attachment IDs linked to this message before cascade-delete
|
|
1290
1412
|
// so we can scope orphan cleanup to only those candidates.
|
|
@@ -1372,6 +1494,28 @@ export function deleteMessageById(messageId: string): DeletedMemoryIds {
|
|
|
1372
1494
|
return result;
|
|
1373
1495
|
}
|
|
1374
1496
|
|
|
1497
|
+
/**
|
|
1498
|
+
* Mark a conversation as having unreduced messages starting from the given
|
|
1499
|
+
* message. Sets `memoryDirtyTailSinceMessageId` only when it is currently
|
|
1500
|
+
* null so the earliest unreduced boundary is preserved across multiple
|
|
1501
|
+
* messages — later messages must not clobber the original dirty marker.
|
|
1502
|
+
*/
|
|
1503
|
+
export function markConversationMemoryDirty(
|
|
1504
|
+
conversationId: string,
|
|
1505
|
+
messageId: string,
|
|
1506
|
+
): void {
|
|
1507
|
+
const db = getDb();
|
|
1508
|
+
db.update(conversations)
|
|
1509
|
+
.set({ memoryDirtyTailSinceMessageId: messageId })
|
|
1510
|
+
.where(
|
|
1511
|
+
and(
|
|
1512
|
+
eq(conversations.id, conversationId),
|
|
1513
|
+
isNull(conversations.memoryDirtyTailSinceMessageId),
|
|
1514
|
+
),
|
|
1515
|
+
)
|
|
1516
|
+
.run();
|
|
1517
|
+
}
|
|
1518
|
+
|
|
1375
1519
|
export function setConversationOriginChannelIfUnset(
|
|
1376
1520
|
conversationId: string,
|
|
1377
1521
|
channel: ChannelId,
|
package/src/memory/db-init.ts
CHANGED
|
@@ -82,7 +82,10 @@ import {
|
|
|
82
82
|
migrateInviteContactId,
|
|
83
83
|
migrateLlmRequestLogMessageId,
|
|
84
84
|
migrateLlmRequestLogProvider,
|
|
85
|
+
migrateMemoryArchiveTables,
|
|
86
|
+
migrateMemoryBriefState,
|
|
85
87
|
migrateMemoryItemSupersession,
|
|
88
|
+
migrateMemoryReducerCheckpoints,
|
|
86
89
|
migrateMessagesFtsBackfill,
|
|
87
90
|
migrateNormalizePhoneIdentities,
|
|
88
91
|
migrateNotificationDeliveryThreadDecision,
|
|
@@ -484,6 +487,15 @@ export function initializeDb(): void {
|
|
|
484
487
|
// 84. Add nullable conversation fork lineage columns and parent lookup index
|
|
485
488
|
migrateConversationForkLineage(database);
|
|
486
489
|
|
|
490
|
+
// 85. Memory brief state tables (time_contexts, open_loops) for simplified memory system
|
|
491
|
+
migrateMemoryBriefState(database);
|
|
492
|
+
|
|
493
|
+
// 86. Memory archive tables (observations, chunks, episodes) for simplified memory v1
|
|
494
|
+
migrateMemoryArchiveTables(database);
|
|
495
|
+
|
|
496
|
+
// 87. Add memory reducer checkpoint columns to conversations
|
|
497
|
+
migrateMemoryReducerCheckpoints(database);
|
|
498
|
+
|
|
487
499
|
validateMigrationState(database);
|
|
488
500
|
|
|
489
501
|
if (process.env.BUN_TEST === "1") {
|