@vellumai/assistant 0.6.0 → 0.6.1
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/AGENTS.md +4 -0
- package/ARCHITECTURE.md +68 -15
- package/Dockerfile +2 -2
- package/bun.lock +6 -2
- package/docker-entrypoint.sh +32 -1
- package/docs/architecture/integrations.md +1 -1
- package/docs/architecture/memory.md +21 -24
- package/openapi.yaml +538 -3
- package/package.json +5 -1
- package/src/__tests__/anthropic-provider.test.ts +160 -95
- package/src/__tests__/app-dir-path-guard.test.ts +1 -0
- package/src/__tests__/app-executors.test.ts +47 -1
- package/src/__tests__/app-source-watcher.test.ts +159 -0
- package/src/__tests__/checker.test.ts +38 -6
- package/src/__tests__/config-schema.test.ts +5 -0
- package/src/__tests__/conversation-agent-loop-overflow.test.ts +4 -6
- package/src/__tests__/conversation-agent-loop.test.ts +4 -51
- package/src/__tests__/conversation-history-web-search.test.ts +1 -1
- package/src/__tests__/conversation-runtime-assembly.test.ts +653 -832
- package/src/__tests__/conversation-runtime-workspace.test.ts +1 -93
- package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +17 -4
- package/src/__tests__/conversation-wipe.test.ts +2 -6
- package/src/__tests__/conversation-workspace-cache-state.test.ts +6 -12
- package/src/__tests__/conversation-workspace-injection.test.ts +25 -26
- package/src/__tests__/conversation-workspace-tool-tracking.test.ts +1 -1
- package/src/__tests__/copy-composer-tc-templates.test.ts +335 -0
- package/src/__tests__/date-context.test.ts +76 -210
- package/src/__tests__/db-schedule-syntax-migration.test.ts +16 -1
- package/src/__tests__/file-list-tool.test.ts +219 -0
- package/src/__tests__/first-greeting.test.ts +1 -1
- package/src/__tests__/heartbeat-service.test.ts +180 -3
- package/src/__tests__/identity-routes.test.ts +328 -0
- package/src/__tests__/injection-block.test.ts +24 -0
- package/src/__tests__/install-skill-routing.test.ts +7 -6
- package/src/__tests__/jobs-store-qdrant-breaker.test.ts +15 -14
- package/src/__tests__/list-messages-tool-merge.test.ts +300 -0
- package/src/__tests__/llm-context-normalization.test.ts +18 -18
- package/src/__tests__/llm-context-route-provider.test.ts +101 -0
- package/src/__tests__/llm-request-log-turn-query.test.ts +162 -0
- package/src/__tests__/log-export-workspace.test.ts +72 -105
- package/src/__tests__/mcp-abort-signal.test.ts +5 -0
- package/src/__tests__/mcp-client-auth.test.ts +5 -0
- package/src/__tests__/memory-recall-log-store.test.ts +132 -0
- package/src/__tests__/migration-export-streaming.test.ts +304 -0
- package/src/__tests__/migration-import-commit-http.test.ts +11 -10
- package/src/__tests__/mock-fetch.ts +87 -0
- package/src/__tests__/notification-decision-recipient-context.test.ts +282 -0
- package/src/__tests__/onboarding-template-contract.test.ts +62 -14
- package/src/__tests__/parser.test.ts +32 -0
- package/src/__tests__/permission-checker-host-gate.test.ts +452 -0
- package/src/__tests__/permission-controls-v2-flag.test.ts +55 -0
- package/src/__tests__/permission-mode-sse.test.ts +418 -0
- package/src/__tests__/permission-mode-store.test.ts +277 -0
- package/src/__tests__/permission-mode.test.ts +101 -0
- package/src/__tests__/platform-bash-auto-approve.test.ts +359 -0
- package/src/__tests__/profiler-routes.test.ts +502 -0
- package/src/__tests__/profiler-run-store.test.ts +441 -0
- package/src/__tests__/proxy-approval-callback.test.ts +4 -75
- package/src/__tests__/registry.test.ts +1 -1
- package/src/__tests__/sandbox-host-parity.test.ts +5 -4
- package/src/__tests__/scheduler-reuse-conversation.test.ts +368 -0
- package/src/__tests__/scrub-corrupted-image-attachments.test.ts +278 -0
- package/src/__tests__/search-skills-unified.test.ts +4 -3
- package/src/__tests__/send-endpoint-busy.test.ts +42 -3
- package/src/__tests__/set-permission-mode.test.ts +274 -0
- package/src/__tests__/skill-load-feature-flag.test.ts +12 -0
- package/src/__tests__/skill-memory.test.ts +2 -783
- package/src/__tests__/strip-memory-injections.test.ts +187 -0
- package/src/__tests__/subagent-detail.test.ts +84 -0
- package/src/__tests__/subagent-disposal.test.ts +308 -0
- package/src/__tests__/subagent-manager-notify.test.ts +19 -10
- package/src/__tests__/subagent-notify-parent.test.ts +390 -0
- package/src/__tests__/subagent-role-registry.test.ts +108 -0
- package/src/__tests__/subagent-tool-filtering.test.ts +71 -0
- package/src/__tests__/subagent-tools.test.ts +464 -4
- package/src/__tests__/system-prompt-ask-mode.test.ts +139 -0
- package/src/__tests__/task-memory-cleanup.test.ts +12 -12
- package/src/__tests__/terminal-tools.test.ts +17 -27
- package/src/__tests__/test-preload.ts +4 -0
- package/src/__tests__/tool-executor.test.ts +4 -26
- package/src/__tests__/tool-side-effects-slack-dm.test.ts +1 -0
- package/src/__tests__/top-level-renderer.test.ts +10 -13
- package/src/__tests__/trusted-contact-lifecycle-notifications.test.ts +116 -2
- package/src/__tests__/workspace-migration-028-recover-conversations-from-disk-view.test.ts +387 -0
- package/src/agent/loop.ts +6 -0
- package/src/approvals/guardian-request-resolvers.ts +24 -0
- package/src/avatar/traits-png-sync.ts +3 -3
- package/src/cli/__tests__/run-assistant-command.ts +29 -0
- package/src/cli/commands/__tests__/email-download.test.ts +245 -0
- package/src/cli/commands/__tests__/email-list.test.ts +192 -0
- package/src/cli/commands/__tests__/email-register.test.ts +186 -0
- package/src/cli/commands/__tests__/email-send.test.ts +291 -0
- package/src/cli/commands/__tests__/email-status.test.ts +181 -0
- package/src/cli/commands/__tests__/email-unregister.test.ts +139 -0
- package/src/cli/commands/__tests__/routes.test.ts +562 -0
- package/src/cli/commands/conversations.ts +1 -8
- package/src/cli/commands/email.ts +584 -835
- package/src/cli/commands/memory.ts +1 -34
- package/src/cli/commands/notifications.ts +7 -2
- package/src/cli/commands/oauth/connect.ts +14 -5
- package/src/cli/commands/routes.ts +396 -0
- package/src/cli/commands/skills.ts +130 -20
- package/src/cli/program.ts +2 -0
- package/src/cli.ts +1 -120
- package/src/config/bundled-skills/app-builder/SKILL.md +4 -1
- package/src/config/bundled-skills/gmail/SKILL.md +2 -2
- package/src/config/bundled-skills/messaging/SKILL.md +7 -0
- package/src/config/bundled-skills/schedule/SKILL.md +22 -2
- package/src/config/bundled-skills/schedule/TOOLS.json +8 -0
- package/src/config/bundled-skills/settings/tools/avatar-get.ts +3 -13
- package/src/config/bundled-skills/settings/tools/avatar-remove.ts +2 -4
- package/src/config/bundled-skills/settings/tools/avatar-update.ts +5 -2
- package/src/config/bundled-skills/slack/SKILL.md +2 -0
- package/src/config/bundled-skills/subagent/SKILL.md +43 -3
- package/src/config/bundled-skills/subagent/TOOLS.json +29 -4
- package/src/config/env-registry.ts +63 -0
- package/src/config/feature-flag-registry.json +17 -1
- package/src/config/schema.ts +8 -0
- package/src/config/schemas/filing.ts +51 -0
- package/src/config/schemas/heartbeat.ts +15 -12
- package/src/config/schemas/memory-lifecycle.ts +12 -0
- package/src/config/schemas/security.ts +14 -0
- package/src/daemon/app-source-watcher.ts +93 -0
- package/src/daemon/config-watcher.ts +79 -1
- package/src/daemon/conversation-agent-loop-handlers.ts +20 -0
- package/src/daemon/conversation-agent-loop.ts +158 -65
- package/src/daemon/conversation-history.ts +4 -19
- package/src/daemon/conversation-lifecycle.ts +8 -14
- package/src/daemon/conversation-process.ts +13 -7
- package/src/daemon/conversation-runtime-assembly.ts +300 -306
- package/src/daemon/conversation-tool-setup.ts +44 -14
- package/src/daemon/conversation-workspace.ts +1 -2
- package/src/daemon/conversation.ts +18 -0
- package/src/daemon/date-context.ts +26 -53
- package/src/daemon/first-greeting.ts +1 -1
- package/src/daemon/handlers/conversations.ts +4 -7
- package/src/daemon/handlers/shared.test.ts +143 -0
- package/src/daemon/handlers/shared.ts +63 -5
- package/src/daemon/handlers/skills.ts +11 -18
- package/src/daemon/lifecycle.ts +199 -157
- package/src/daemon/message-types/conversations.ts +25 -6
- package/src/daemon/message-types/messages.ts +9 -1
- package/src/daemon/message-types/schedules.ts +1 -0
- package/src/daemon/message-types/settings.ts +6 -0
- package/src/daemon/profiler-run-store.ts +557 -0
- package/src/daemon/server.ts +89 -9
- package/src/daemon/shutdown-handlers.ts +5 -0
- package/src/daemon/tool-side-effects.ts +23 -3
- package/src/export/transcript-formatter.ts +148 -0
- package/src/filing/filing-service.ts +228 -0
- package/src/heartbeat/heartbeat-service.ts +96 -7
- package/src/mcp/client.ts +6 -0
- package/src/mcp/mcp-oauth-provider.ts +149 -27
- package/src/memory/admin.ts +33 -32
- package/src/memory/app-store.ts +69 -0
- package/src/memory/conversation-bootstrap.ts +1 -1
- package/src/memory/conversation-crud.ts +136 -107
- package/src/memory/conversation-group-migration.ts +1 -1
- package/src/memory/conversation-queries.ts +58 -12
- package/src/memory/conversation-title-service.ts +1 -0
- package/src/memory/db-init.ts +182 -376
- package/src/memory/graph/bootstrap.ts +75 -66
- package/src/memory/graph/capability-seed.ts +167 -15
- package/src/memory/graph/consolidation.ts +38 -4
- package/src/memory/graph/conversation-graph-memory.ts +133 -104
- package/src/memory/graph/extraction-job.ts +9 -4
- package/src/memory/graph/extraction.ts +66 -23
- package/src/memory/graph/graph-memory-state-store.ts +37 -0
- package/src/memory/graph/graph-search.ts +29 -15
- package/src/memory/graph/injection.ts +38 -8
- package/src/memory/graph/inspect.ts +12 -3
- package/src/memory/graph/retriever.ts +365 -262
- package/src/memory/graph/store.test.ts +48 -0
- package/src/memory/graph/store.ts +150 -11
- package/src/memory/graph/tool-handlers.ts +84 -209
- package/src/memory/graph/tools.ts +8 -52
- package/src/memory/graph/types.ts +24 -0
- package/src/memory/job-handlers/cleanup.ts +44 -1
- package/src/memory/jobs-store.ts +70 -60
- package/src/memory/jobs-worker.ts +44 -28
- package/src/memory/llm-request-log-store.ts +96 -12
- package/src/memory/memory-recall-log-store.ts +49 -5
- package/src/memory/migrations/203-drop-memory-items-tables.ts +33 -1
- package/src/memory/migrations/206-memory-graph-node-edits.ts +19 -0
- package/src/memory/migrations/206-scrub-corrupted-image-attachments.ts +131 -0
- package/src/memory/migrations/207-conversation-graph-memory-state.ts +20 -0
- package/src/memory/migrations/208-conversations-last-message-at.ts +35 -0
- package/src/memory/migrations/209-strip-thinking-from-consolidated.ts +85 -0
- package/src/memory/migrations/210-schedule-reuse-conversation.ts +13 -0
- package/src/memory/migrations/211-memory-recall-logs-query-context.ts +21 -0
- package/src/memory/migrations/212-llm-request-logs-created-at-index.ts +19 -0
- package/src/memory/migrations/index.ts +8 -0
- package/src/memory/migrations/registry.ts +8 -0
- package/src/memory/schema/conversations.ts +14 -0
- package/src/memory/schema/infrastructure.ts +8 -1
- package/src/memory/schema/memory-core.ts +0 -51
- package/src/memory/schema/memory-graph.ts +15 -0
- package/src/memory/task-memory-cleanup.ts +30 -11
- package/src/notifications/copy-composer.ts +86 -0
- package/src/notifications/decision-engine.ts +35 -0
- package/src/permissions/checker.ts +12 -1
- package/src/permissions/permission-mode-store.ts +180 -0
- package/src/permissions/permission-mode.ts +31 -0
- package/src/permissions/workspace-policy.ts +9 -0
- package/src/prompts/system-prompt.ts +59 -7
- package/src/prompts/templates/BOOTSTRAP-REFERENCE.md +100 -0
- package/src/prompts/templates/BOOTSTRAP.md +70 -165
- package/src/prompts/templates/HEARTBEAT.md +3 -1
- package/src/prompts/templates/SOUL.md +25 -4
- package/src/prompts/templates/UPDATES.md +8 -0
- package/src/providers/anthropic/client.ts +107 -219
- package/src/runtime/auth/route-policy.ts +23 -0
- package/src/runtime/http-server.ts +32 -2
- package/src/runtime/http-types.ts +12 -1
- package/src/runtime/migrations/vbundle-builder.ts +389 -3
- package/src/runtime/migrations/vbundle-importer.ts +8 -6
- package/src/runtime/routes/__tests__/user-route-dispatcher.test.ts +378 -0
- package/src/runtime/routes/app-management-routes.ts +1 -11
- package/src/runtime/routes/approval-strategies/guardian-callback-strategy.ts +26 -0
- package/src/runtime/routes/archive-utils.ts +29 -0
- package/src/runtime/routes/avatar-routes.ts +2 -9
- package/src/runtime/routes/btw-routes.ts +14 -1
- package/src/runtime/routes/conversation-analysis-routes.ts +173 -0
- package/src/runtime/routes/conversation-management-routes.ts +1 -14
- package/src/runtime/routes/conversation-query-routes.ts +49 -3
- package/src/runtime/routes/conversation-routes.ts +264 -44
- package/src/runtime/routes/heartbeat-routes.ts +4 -10
- package/src/runtime/routes/identity-routes.ts +53 -18
- package/src/runtime/routes/llm-context-normalization.ts +14 -10
- package/src/runtime/routes/log-export-routes.ts +23 -275
- package/src/runtime/routes/memory-item-routes.test.ts +168 -233
- package/src/runtime/routes/migration-routes.ts +18 -7
- package/src/runtime/routes/profiler-routes.ts +350 -0
- package/src/runtime/routes/schedule-routes.ts +27 -12
- package/src/runtime/routes/settings-routes.ts +95 -8
- package/src/runtime/routes/subagents-routes.ts +28 -7
- package/src/runtime/routes/user-route-dispatcher.ts +223 -0
- package/src/runtime/routes/user-routes.ts +41 -0
- package/src/runtime/routes/workspace-routes.ts +0 -1
- package/src/schedule/schedule-store.ts +30 -0
- package/src/schedule/scheduler.ts +45 -18
- package/src/skills/catalog-install.ts +10 -2
- package/src/skills/managed-store.ts +2 -2
- package/src/skills/skill-memory.ts +1 -293
- package/src/subagent/index.ts +13 -3
- package/src/subagent/manager.ts +308 -29
- package/src/subagent/types.ts +68 -0
- package/src/tasks/task-runner.ts +4 -4
- package/src/tools/apps/executors.ts +29 -4
- package/src/tools/filesystem/list.ts +93 -0
- package/src/tools/permission-checker.ts +78 -0
- package/src/tools/registry.ts +4 -0
- package/src/tools/schedule/create.ts +3 -0
- package/src/tools/schedule/list.ts +1 -0
- package/src/tools/schedule/update.ts +6 -0
- package/src/tools/shared/filesystem/errors.ts +5 -0
- package/src/tools/shared/filesystem/file-ops-service.ts +90 -2
- package/src/tools/shared/filesystem/types.ts +17 -0
- package/src/tools/shared/shell-output.ts +31 -2
- package/src/tools/subagent/abort.ts +12 -2
- package/src/tools/subagent/message.ts +9 -2
- package/src/tools/subagent/notify-parent.ts +79 -0
- package/src/tools/subagent/read.ts +29 -8
- package/src/tools/subagent/resolve.ts +21 -0
- package/src/tools/subagent/spawn.ts +2 -0
- package/src/tools/subagent/status.ts +11 -1
- package/src/tools/system/avatar-generator.ts +3 -3
- package/src/tools/system/register.ts +23 -0
- package/src/tools/system/set-permission-mode.ts +103 -0
- package/src/tools/terminal/parser.ts +30 -5
- package/src/tools/terminal/safe-env.ts +16 -1
- package/src/tools/tool-manifest.ts +6 -0
- package/src/tools/types.ts +2 -0
- package/src/util/logger.ts +1 -1
- package/src/util/platform.ts +50 -17
- package/src/workspace/migrations/023-move-config-files-to-workspace.ts +2 -2
- package/src/workspace/migrations/024-move-runtime-files-to-workspace.ts +2 -2
- package/src/workspace/migrations/028-recover-conversations-from-disk-view.ts +270 -0
- package/src/workspace/migrations/029-seed-pkb.ts +84 -0
- package/src/workspace/migrations/registry.ts +4 -0
- package/src/workspace/top-level-renderer.ts +5 -9
- package/src/__tests__/cli-memory.test.ts +0 -377
- package/src/__tests__/clipboard.test.ts +0 -88
- package/src/cli/cli-memory.ts +0 -179
- package/src/util/clipboard.ts +0 -34
|
@@ -70,7 +70,7 @@ const interfaceIdSchema = z.enum(INTERFACE_IDS);
|
|
|
70
70
|
const subagentNotificationSchema = z.object({
|
|
71
71
|
subagentId: z.string(),
|
|
72
72
|
label: z.string(),
|
|
73
|
-
status: z.enum(["completed", "failed", "aborted"]),
|
|
73
|
+
status: z.enum(["running", "completed", "failed", "aborted"]),
|
|
74
74
|
error: z.string().optional(),
|
|
75
75
|
conversationId: z.string().optional(),
|
|
76
76
|
});
|
|
@@ -108,7 +108,7 @@ export type MessageMetadata = z.infer<typeof messageMetadataSchema>;
|
|
|
108
108
|
|
|
109
109
|
function cloneForkMessageMetadata(
|
|
110
110
|
metadata: string | null,
|
|
111
|
-
sourceMessageId: string
|
|
111
|
+
sourceMessageId: string,
|
|
112
112
|
): string {
|
|
113
113
|
if (!metadata) {
|
|
114
114
|
return JSON.stringify({ forkSourceMessageId: sourceMessageId });
|
|
@@ -141,7 +141,7 @@ function cloneForkMessageMetadata(
|
|
|
141
141
|
* callers with actual guardian trust should always supply a real context.
|
|
142
142
|
*/
|
|
143
143
|
export function provenanceFromTrustContext(
|
|
144
|
-
ctx: TrustContext | null | undefined
|
|
144
|
+
ctx: TrustContext | null | undefined,
|
|
145
145
|
): Record<string, unknown> {
|
|
146
146
|
if (!ctx) return { provenanceTrustClass: "unknown" };
|
|
147
147
|
return {
|
|
@@ -172,6 +172,7 @@ export interface ConversationRow {
|
|
|
172
172
|
forkParentMessageId: string | null;
|
|
173
173
|
isAutoTitle: number;
|
|
174
174
|
scheduleJobId: string | null;
|
|
175
|
+
lastMessageAt: number | null;
|
|
175
176
|
}
|
|
176
177
|
|
|
177
178
|
export const parseConversation = createRowMapper<
|
|
@@ -197,6 +198,7 @@ export const parseConversation = createRowMapper<
|
|
|
197
198
|
forkParentMessageId: "forkParentMessageId",
|
|
198
199
|
isAutoTitle: "isAutoTitle",
|
|
199
200
|
scheduleJobId: "scheduleJobId",
|
|
201
|
+
lastMessageAt: "lastMessageAt",
|
|
200
202
|
});
|
|
201
203
|
|
|
202
204
|
export interface MessageRow {
|
|
@@ -239,18 +241,18 @@ export function createConversation(
|
|
|
239
241
|
| string
|
|
240
242
|
| {
|
|
241
243
|
title?: string;
|
|
242
|
-
conversationType?: "standard" | "private" | "background";
|
|
244
|
+
conversationType?: "standard" | "private" | "background" | "scheduled";
|
|
243
245
|
source?: string;
|
|
244
246
|
scheduleJobId?: string;
|
|
245
247
|
groupId?: string;
|
|
246
|
-
}
|
|
248
|
+
},
|
|
247
249
|
) {
|
|
248
250
|
const db = getDb();
|
|
249
251
|
const now = Date.now();
|
|
250
252
|
const opts =
|
|
251
253
|
typeof titleOrOpts === "string"
|
|
252
254
|
? { title: titleOrOpts }
|
|
253
|
-
: titleOrOpts ?? {};
|
|
255
|
+
: (titleOrOpts ?? {});
|
|
254
256
|
const conversationType = opts.conversationType ?? "standard";
|
|
255
257
|
const source = opts.source ?? "user";
|
|
256
258
|
const groupId = opts.groupId;
|
|
@@ -301,7 +303,7 @@ export function createConversation(
|
|
|
301
303
|
) {
|
|
302
304
|
log.warn(
|
|
303
305
|
{ attempt, conversationId: id, code },
|
|
304
|
-
"createConversation: INSERT transient error, retrying"
|
|
306
|
+
"createConversation: INSERT transient error, retrying",
|
|
305
307
|
);
|
|
306
308
|
Bun.sleepSync(50 * (attempt + 1));
|
|
307
309
|
continue;
|
|
@@ -318,7 +320,7 @@ export function createConversation(
|
|
|
318
320
|
rawRun(
|
|
319
321
|
"UPDATE conversations SET group_id = ? WHERE id = ?",
|
|
320
322
|
groupId,
|
|
321
|
-
id
|
|
323
|
+
id,
|
|
322
324
|
);
|
|
323
325
|
break;
|
|
324
326
|
} catch (err) {
|
|
@@ -329,7 +331,7 @@ export function createConversation(
|
|
|
329
331
|
) {
|
|
330
332
|
log.warn(
|
|
331
333
|
{ attempt, conversationId: id, code },
|
|
332
|
-
"createConversation: group_id UPDATE transient error, retrying"
|
|
334
|
+
"createConversation: group_id UPDATE transient error, retrying",
|
|
333
335
|
);
|
|
334
336
|
Bun.sleepSync(50 * (attempt + 1));
|
|
335
337
|
continue;
|
|
@@ -360,18 +362,18 @@ export function getConversation(id: string): ConversationRow | null {
|
|
|
360
362
|
* (i.e. no other conversations still reference it).
|
|
361
363
|
*/
|
|
362
364
|
export function countConversationsByScheduleJobId(
|
|
363
|
-
scheduleJobId: string
|
|
365
|
+
scheduleJobId: string,
|
|
364
366
|
): number {
|
|
365
367
|
return (
|
|
366
368
|
rawGet<{ c: number }>(
|
|
367
369
|
"SELECT COUNT(*) AS c FROM conversations WHERE schedule_job_id = ?",
|
|
368
|
-
scheduleJobId
|
|
370
|
+
scheduleJobId,
|
|
369
371
|
)?.c ?? 0
|
|
370
372
|
);
|
|
371
373
|
}
|
|
372
374
|
|
|
373
375
|
export function getConversationType(
|
|
374
|
-
conversationId: string
|
|
376
|
+
conversationId: string,
|
|
375
377
|
): "standard" | "private" {
|
|
376
378
|
const conv = getConversation(conversationId);
|
|
377
379
|
const raw = conv?.conversationType;
|
|
@@ -392,7 +394,7 @@ export function getConversationGroupId(conversationId: string): string | null {
|
|
|
392
394
|
ensureGroupMigration();
|
|
393
395
|
const row = rawGet<{ group_id: string | null }>(
|
|
394
396
|
"SELECT group_id FROM conversations WHERE id = ?",
|
|
395
|
-
conversationId
|
|
397
|
+
conversationId,
|
|
396
398
|
);
|
|
397
399
|
return row?.group_id ?? null;
|
|
398
400
|
}
|
|
@@ -416,7 +418,7 @@ export function forkConversation(params: {
|
|
|
416
418
|
|
|
417
419
|
if (sourceMessages.length === 0) {
|
|
418
420
|
throw new UserError(
|
|
419
|
-
`Conversation ${conversationId} has no persisted messages to fork
|
|
421
|
+
`Conversation ${conversationId} has no persisted messages to fork`,
|
|
420
422
|
);
|
|
421
423
|
}
|
|
422
424
|
|
|
@@ -427,7 +429,7 @@ export function forkConversation(params: {
|
|
|
427
429
|
|
|
428
430
|
if (throughMessageId != null && copyBoundaryIndex === -1) {
|
|
429
431
|
throw new UserError(
|
|
430
|
-
`Message ${throughMessageId} does not belong to conversation ${conversationId}
|
|
432
|
+
`Message ${throughMessageId} does not belong to conversation ${conversationId}`,
|
|
431
433
|
);
|
|
432
434
|
}
|
|
433
435
|
|
|
@@ -435,8 +437,8 @@ export function forkConversation(params: {
|
|
|
435
437
|
0,
|
|
436
438
|
Math.min(
|
|
437
439
|
sourceConversation.contextCompactedMessageCount,
|
|
438
|
-
sourceMessages.length
|
|
439
|
-
)
|
|
440
|
+
sourceMessages.length,
|
|
441
|
+
),
|
|
440
442
|
);
|
|
441
443
|
const preserveSourceCompactionState =
|
|
442
444
|
copyBoundaryIndex >= visibleWindowStartIndex;
|
|
@@ -530,7 +532,7 @@ export function forkConversation(params: {
|
|
|
530
532
|
.orderBy(messageAttachments.position)
|
|
531
533
|
.all();
|
|
532
534
|
const uncachedAttachmentLinks = attachmentLinks.filter(
|
|
533
|
-
(link) => !attachmentIdMap.has(link.attachmentId)
|
|
535
|
+
(link) => !attachmentIdMap.has(link.attachmentId),
|
|
534
536
|
);
|
|
535
537
|
const stagingMessageId =
|
|
536
538
|
uncachedAttachmentLinks.length > 0 ? uuid() : null;
|
|
@@ -566,7 +568,7 @@ export function forkConversation(params: {
|
|
|
566
568
|
const scopedAttachmentId = linkAttachmentToMessage(
|
|
567
569
|
stagingMessageId ?? forkedMessageId,
|
|
568
570
|
link.attachmentId,
|
|
569
|
-
link.position
|
|
571
|
+
link.position,
|
|
570
572
|
);
|
|
571
573
|
attachmentIdMap.set(link.attachmentId, scopedAttachmentId);
|
|
572
574
|
}
|
|
@@ -583,6 +585,16 @@ export function forkConversation(params: {
|
|
|
583
585
|
});
|
|
584
586
|
}
|
|
585
587
|
|
|
588
|
+
// Set lastMessageAt to the max createdAt of copied messages so the
|
|
589
|
+
// forked conversation sorts correctly by message recency.
|
|
590
|
+
const lastCopiedMessage = messagesToCopy.at(-1);
|
|
591
|
+
if (lastCopiedMessage) {
|
|
592
|
+
db.update(conversations)
|
|
593
|
+
.set({ lastMessageAt: lastCopiedMessage.createdAt })
|
|
594
|
+
.where(eq(conversations.id, fc.id))
|
|
595
|
+
.run();
|
|
596
|
+
}
|
|
597
|
+
|
|
586
598
|
seedForkedConversationAttention({
|
|
587
599
|
conversationId: fc.id,
|
|
588
600
|
latestAssistantMessageId: latestForkedAssistant?.messageId ?? null,
|
|
@@ -601,7 +613,7 @@ export function forkConversation(params: {
|
|
|
601
613
|
const persistedFork = getConversation(forkedConversation.id);
|
|
602
614
|
if (!persistedFork) {
|
|
603
615
|
throw new Error(
|
|
604
|
-
`Failed to load forked conversation ${forkedConversation.id} after creation
|
|
616
|
+
`Failed to load forked conversation ${forkedConversation.id} after creation`,
|
|
605
617
|
);
|
|
606
618
|
}
|
|
607
619
|
|
|
@@ -610,14 +622,13 @@ export function forkConversation(params: {
|
|
|
610
622
|
|
|
611
623
|
/**
|
|
612
624
|
* Delete a conversation and all its messages, cleaning up orphaned memory
|
|
613
|
-
* artifacts (
|
|
614
|
-
*
|
|
625
|
+
* artifacts (embeddings). Returns segment IDs so callers can clean up
|
|
626
|
+
* the corresponding Qdrant vector entries.
|
|
615
627
|
*/
|
|
616
628
|
export function deleteConversation(id: string): DeletedMemoryIds {
|
|
617
629
|
const db = getDb();
|
|
618
630
|
const result: DeletedMemoryIds = {
|
|
619
631
|
segmentIds: [],
|
|
620
|
-
orphanedItemIds: [],
|
|
621
632
|
deletedSummaryIds: [],
|
|
622
633
|
};
|
|
623
634
|
|
|
@@ -662,8 +673,8 @@ export function deleteConversation(id: string): DeletedMemoryIds {
|
|
|
662
673
|
.where(
|
|
663
674
|
and(
|
|
664
675
|
eq(memoryEmbeddings.targetType, "segment"),
|
|
665
|
-
inArray(memoryEmbeddings.targetId, result.segmentIds)
|
|
666
|
-
)
|
|
676
|
+
inArray(memoryEmbeddings.targetId, result.segmentIds),
|
|
677
|
+
),
|
|
667
678
|
)
|
|
668
679
|
.run();
|
|
669
680
|
}
|
|
@@ -691,8 +702,8 @@ export function deleteConversation(id: string): DeletedMemoryIds {
|
|
|
691
702
|
.where(
|
|
692
703
|
and(
|
|
693
704
|
eq(memoryEmbeddings.targetType, "summary"),
|
|
694
|
-
inArray(memoryEmbeddings.targetId, scopeSummaryIds)
|
|
695
|
-
)
|
|
705
|
+
inArray(memoryEmbeddings.targetId, scopeSummaryIds),
|
|
706
|
+
),
|
|
696
707
|
)
|
|
697
708
|
.run();
|
|
698
709
|
tx.delete(memorySummaries)
|
|
@@ -723,14 +734,10 @@ export function deleteConversation(id: string): DeletedMemoryIds {
|
|
|
723
734
|
*
|
|
724
735
|
* Extends `deleteConversation` with:
|
|
725
736
|
* - Cancelling pending memory jobs before deletion
|
|
726
|
-
* - Restoring memory items that were explicitly superseded by items from this conversation
|
|
727
|
-
* - Restoring orphaned subject-match superseded items after deletion
|
|
728
737
|
* - Deleting conversation-scoped memory summaries and their embeddings
|
|
729
|
-
* - Enqueuing `embed_item` jobs for all restored items
|
|
730
738
|
*/
|
|
731
739
|
export function wipeConversation(id: string): WipeConversationResult {
|
|
732
740
|
const db = getDb();
|
|
733
|
-
const unsupersededItemIds: string[] = [];
|
|
734
741
|
const deletedSummaryIds: string[] = [];
|
|
735
742
|
|
|
736
743
|
// Step A — Cancel pending memory jobs (before deleting messages, since
|
|
@@ -744,8 +751,8 @@ export function wipeConversation(id: string): WipeConversationResult {
|
|
|
744
751
|
.where(
|
|
745
752
|
and(
|
|
746
753
|
eq(memorySummaries.scope, "conversation"),
|
|
747
|
-
eq(memorySummaries.scopeKey, id)
|
|
748
|
-
)
|
|
754
|
+
eq(memorySummaries.scopeKey, id),
|
|
755
|
+
),
|
|
749
756
|
)
|
|
750
757
|
.all();
|
|
751
758
|
const summaryIds = summaryRows.map((r) => r.id);
|
|
@@ -754,8 +761,8 @@ export function wipeConversation(id: string): WipeConversationResult {
|
|
|
754
761
|
.where(
|
|
755
762
|
and(
|
|
756
763
|
eq(memoryEmbeddings.targetType, "summary"),
|
|
757
|
-
inArray(memoryEmbeddings.targetId, summaryIds)
|
|
758
|
-
)
|
|
764
|
+
inArray(memoryEmbeddings.targetId, summaryIds),
|
|
765
|
+
),
|
|
759
766
|
)
|
|
760
767
|
.run();
|
|
761
768
|
db.delete(memorySummaries)
|
|
@@ -772,7 +779,6 @@ export function wipeConversation(id: string): WipeConversationResult {
|
|
|
772
779
|
// Step E — Return the combined result.
|
|
773
780
|
return {
|
|
774
781
|
...deletedMemoryIds,
|
|
775
|
-
unsupersededItemIds,
|
|
776
782
|
deletedSummaryIds: [
|
|
777
783
|
...deletedSummaryIds,
|
|
778
784
|
...deletedMemoryIds.deletedSummaryIds,
|
|
@@ -802,20 +808,17 @@ export function purgePrivateConversations(): {
|
|
|
802
808
|
count: 0,
|
|
803
809
|
deletedMemory: {
|
|
804
810
|
segmentIds: [],
|
|
805
|
-
orphanedItemIds: [],
|
|
806
811
|
deletedSummaryIds: [],
|
|
807
812
|
},
|
|
808
813
|
};
|
|
809
814
|
}
|
|
810
815
|
|
|
811
816
|
const allSegmentIds: string[] = [];
|
|
812
|
-
const allOrphanedItemIds: string[] = [];
|
|
813
817
|
const allDeletedSummaryIds: string[] = [];
|
|
814
818
|
|
|
815
819
|
for (const conv of privateConvs) {
|
|
816
820
|
const deleted = deleteConversation(conv.id);
|
|
817
821
|
allSegmentIds.push(...deleted.segmentIds);
|
|
818
|
-
allOrphanedItemIds.push(...deleted.orphanedItemIds);
|
|
819
822
|
allDeletedSummaryIds.push(...deleted.deletedSummaryIds);
|
|
820
823
|
}
|
|
821
824
|
|
|
@@ -823,7 +826,6 @@ export function purgePrivateConversations(): {
|
|
|
823
826
|
count: privateConvs.length,
|
|
824
827
|
deletedMemory: {
|
|
825
828
|
segmentIds: allSegmentIds,
|
|
826
|
-
orphanedItemIds: allOrphanedItemIds,
|
|
827
829
|
deletedSummaryIds: allDeletedSummaryIds,
|
|
828
830
|
},
|
|
829
831
|
};
|
|
@@ -834,7 +836,7 @@ export async function addMessage(
|
|
|
834
836
|
role: string,
|
|
835
837
|
content: string,
|
|
836
838
|
metadata?: Record<string, unknown>,
|
|
837
|
-
opts?: { skipIndexing?: boolean }
|
|
839
|
+
opts?: { skipIndexing?: boolean },
|
|
838
840
|
) {
|
|
839
841
|
const db = getDb();
|
|
840
842
|
const messageId = uuid();
|
|
@@ -844,7 +846,7 @@ export async function addMessage(
|
|
|
844
846
|
if (!result.success) {
|
|
845
847
|
log.warn(
|
|
846
848
|
{ conversationId, messageId, issues: result.error.issues },
|
|
847
|
-
"Invalid message metadata, storing as-is"
|
|
849
|
+
"Invalid message metadata, storing as-is",
|
|
848
850
|
);
|
|
849
851
|
}
|
|
850
852
|
}
|
|
@@ -879,13 +881,13 @@ export async function addMessage(
|
|
|
879
881
|
.where(
|
|
880
882
|
and(
|
|
881
883
|
eq(conversations.id, conversationId),
|
|
882
|
-
isNull(conversations.originChannel)
|
|
883
|
-
)
|
|
884
|
+
isNull(conversations.originChannel),
|
|
885
|
+
),
|
|
884
886
|
)
|
|
885
887
|
.run();
|
|
886
888
|
}
|
|
887
889
|
tx.update(conversations)
|
|
888
|
-
.set({ updatedAt: now })
|
|
890
|
+
.set({ updatedAt: now, lastMessageAt: now })
|
|
889
891
|
.where(eq(conversations.id, conversationId))
|
|
890
892
|
.run();
|
|
891
893
|
});
|
|
@@ -899,7 +901,7 @@ export async function addMessage(
|
|
|
899
901
|
) {
|
|
900
902
|
log.warn(
|
|
901
903
|
{ attempt, conversationId, code: errCode },
|
|
902
|
-
"addMessage: transient SQLite error, retrying"
|
|
904
|
+
"addMessage: transient SQLite error, retrying",
|
|
903
905
|
);
|
|
904
906
|
await Bun.sleep(50 * (attempt + 1));
|
|
905
907
|
continue;
|
|
@@ -938,12 +940,12 @@ export async function addMessage(
|
|
|
938
940
|
provenanceTrustClass,
|
|
939
941
|
automated,
|
|
940
942
|
},
|
|
941
|
-
config.memory
|
|
943
|
+
config.memory,
|
|
942
944
|
);
|
|
943
945
|
} catch (err) {
|
|
944
946
|
log.warn(
|
|
945
947
|
{ err, conversationId, messageId: message.id },
|
|
946
|
-
"Failed to index message for memory"
|
|
948
|
+
"Failed to index message for memory",
|
|
947
949
|
);
|
|
948
950
|
}
|
|
949
951
|
}
|
|
@@ -958,7 +960,7 @@ export async function addMessage(
|
|
|
958
960
|
} catch (err) {
|
|
959
961
|
log.warn(
|
|
960
962
|
{ err, conversationId, messageId: message.id },
|
|
961
|
-
"Failed to project assistant message for attention tracking"
|
|
963
|
+
"Failed to project assistant message for attention tracking",
|
|
962
964
|
);
|
|
963
965
|
}
|
|
964
966
|
}
|
|
@@ -985,7 +987,7 @@ export interface PaginatedMessagesResult {
|
|
|
985
987
|
export function getMessagesPaginated(
|
|
986
988
|
conversationId: string,
|
|
987
989
|
limit: number | undefined,
|
|
988
|
-
beforeTimestamp?: number
|
|
990
|
+
beforeTimestamp?: number,
|
|
989
991
|
): PaginatedMessagesResult {
|
|
990
992
|
const db = getDb();
|
|
991
993
|
|
|
@@ -1029,7 +1031,7 @@ export function getMessagesPaginated(
|
|
|
1029
1031
|
|
|
1030
1032
|
export function getLastAssistantTimestampBefore(
|
|
1031
1033
|
conversationId: string,
|
|
1032
|
-
beforeTimestamp: number
|
|
1034
|
+
beforeTimestamp: number,
|
|
1033
1035
|
): number {
|
|
1034
1036
|
const db = getDb();
|
|
1035
1037
|
const row = db
|
|
@@ -1039,8 +1041,8 @@ export function getLastAssistantTimestampBefore(
|
|
|
1039
1041
|
and(
|
|
1040
1042
|
eq(messages.conversationId, conversationId),
|
|
1041
1043
|
eq(messages.role, "assistant"),
|
|
1042
|
-
lt(messages.createdAt, beforeTimestamp)
|
|
1043
|
-
)
|
|
1044
|
+
lt(messages.createdAt, beforeTimestamp),
|
|
1045
|
+
),
|
|
1044
1046
|
)
|
|
1045
1047
|
.orderBy(desc(messages.createdAt))
|
|
1046
1048
|
.limit(1)
|
|
@@ -1051,7 +1053,7 @@ export function getLastAssistantTimestampBefore(
|
|
|
1051
1053
|
/** Fetch a single message by ID, optionally scoped to a specific conversation. */
|
|
1052
1054
|
export function getMessageById(
|
|
1053
1055
|
messageId: string,
|
|
1054
|
-
conversationId?: string
|
|
1056
|
+
conversationId?: string,
|
|
1055
1057
|
): MessageRow | null {
|
|
1056
1058
|
const db = getDb();
|
|
1057
1059
|
const conditions = [eq(messages.id, messageId)];
|
|
@@ -1069,7 +1071,7 @@ export function getMessageById(
|
|
|
1069
1071
|
export function updateConversationTitle(
|
|
1070
1072
|
id: string,
|
|
1071
1073
|
title: string,
|
|
1072
|
-
isAutoTitle?: number
|
|
1074
|
+
isAutoTitle?: number,
|
|
1073
1075
|
): void {
|
|
1074
1076
|
const db = getDb();
|
|
1075
1077
|
const set: Record<string, unknown> = { title, updatedAt: Date.now() };
|
|
@@ -1087,7 +1089,7 @@ export function updateConversationUsage(
|
|
|
1087
1089
|
id: string,
|
|
1088
1090
|
totalInputTokens: number,
|
|
1089
1091
|
totalOutputTokens: number,
|
|
1090
|
-
totalEstimatedCost: number
|
|
1092
|
+
totalEstimatedCost: number,
|
|
1091
1093
|
): void {
|
|
1092
1094
|
const db = getDb();
|
|
1093
1095
|
db.update(conversations)
|
|
@@ -1104,7 +1106,7 @@ export function updateConversationUsage(
|
|
|
1104
1106
|
export function updateConversationContextWindow(
|
|
1105
1107
|
id: string,
|
|
1106
1108
|
contextSummary: string,
|
|
1107
|
-
contextCompactedMessageCount: number
|
|
1109
|
+
contextCompactedMessageCount: number,
|
|
1108
1110
|
): void {
|
|
1109
1111
|
const db = getDb();
|
|
1110
1112
|
db.update(conversations)
|
|
@@ -1154,7 +1156,7 @@ export function clearAll(): { conversations: number; messages: number } {
|
|
|
1154
1156
|
} catch (err) {
|
|
1155
1157
|
log.warn(
|
|
1156
1158
|
{ err },
|
|
1157
|
-
"clearAll: failed to clear messages_fts — dropping triggers so base-table cleanup can proceed"
|
|
1159
|
+
"clearAll: failed to clear messages_fts — dropping triggers so base-table cleanup can proceed",
|
|
1158
1160
|
);
|
|
1159
1161
|
rawExec("DROP TRIGGER IF EXISTS messages_fts_ai");
|
|
1160
1162
|
rawExec("DROP TRIGGER IF EXISTS messages_fts_ad");
|
|
@@ -1170,7 +1172,7 @@ export function clearAll(): { conversations: number; messages: number } {
|
|
|
1170
1172
|
`INSERT INTO lifecycle_events (id, event_name, created_at) VALUES (?, ?, ?)`,
|
|
1171
1173
|
uuid(),
|
|
1172
1174
|
"conversations_clear_all",
|
|
1173
|
-
Date.now()
|
|
1175
|
+
Date.now(),
|
|
1174
1176
|
);
|
|
1175
1177
|
|
|
1176
1178
|
// Rebuild corrupted FTS tables and restore triggers after all base-table
|
|
@@ -1180,16 +1182,16 @@ export function clearAll(): { conversations: number; messages: number } {
|
|
|
1180
1182
|
if (messagesFtsCorrupted) {
|
|
1181
1183
|
rawExec("DROP TABLE IF EXISTS messages_fts");
|
|
1182
1184
|
rawExec(
|
|
1183
|
-
`CREATE VIRTUAL TABLE IF NOT EXISTS messages_fts USING fts5(message_id UNINDEXED, content)
|
|
1185
|
+
`CREATE VIRTUAL TABLE IF NOT EXISTS messages_fts USING fts5(message_id UNINDEXED, content)`,
|
|
1184
1186
|
);
|
|
1185
1187
|
rawExec(
|
|
1186
|
-
`CREATE TRIGGER IF NOT EXISTS messages_fts_ai AFTER INSERT ON messages BEGIN INSERT INTO messages_fts(message_id, content) VALUES (new.id, new.content); END
|
|
1188
|
+
`CREATE TRIGGER IF NOT EXISTS messages_fts_ai AFTER INSERT ON messages BEGIN INSERT INTO messages_fts(message_id, content) VALUES (new.id, new.content); END`,
|
|
1187
1189
|
);
|
|
1188
1190
|
rawExec(
|
|
1189
|
-
`CREATE TRIGGER IF NOT EXISTS messages_fts_ad AFTER DELETE ON messages BEGIN DELETE FROM messages_fts WHERE message_id = old.id; END
|
|
1191
|
+
`CREATE TRIGGER IF NOT EXISTS messages_fts_ad AFTER DELETE ON messages BEGIN DELETE FROM messages_fts WHERE message_id = old.id; END`,
|
|
1190
1192
|
);
|
|
1191
1193
|
rawExec(
|
|
1192
|
-
`CREATE TRIGGER IF NOT EXISTS messages_fts_au AFTER UPDATE ON messages BEGIN DELETE FROM messages_fts WHERE message_id = old.id; INSERT INTO messages_fts(message_id, content) VALUES (new.id, new.content); END
|
|
1194
|
+
`CREATE TRIGGER IF NOT EXISTS messages_fts_au AFTER UPDATE ON messages BEGIN DELETE FROM messages_fts WHERE message_id = old.id; INSERT INTO messages_fts(message_id, content) VALUES (new.id, new.content); END`,
|
|
1193
1195
|
);
|
|
1194
1196
|
}
|
|
1195
1197
|
|
|
@@ -1214,8 +1216,8 @@ export function deleteLastExchange(conversationId: string): number {
|
|
|
1214
1216
|
.where(
|
|
1215
1217
|
and(
|
|
1216
1218
|
eq(messages.conversationId, conversationId),
|
|
1217
|
-
eq(messages.role, "user")
|
|
1218
|
-
)
|
|
1219
|
+
eq(messages.role, "user"),
|
|
1220
|
+
),
|
|
1219
1221
|
)
|
|
1220
1222
|
.orderBy(sql`rowid DESC`)
|
|
1221
1223
|
.limit(1)
|
|
@@ -1229,7 +1231,7 @@ export function deleteLastExchange(conversationId: string): number {
|
|
|
1229
1231
|
const rowidSubquery = sql`(SELECT rowid FROM messages WHERE id = ${lastUserMsg.id})`;
|
|
1230
1232
|
const condition = and(
|
|
1231
1233
|
eq(messages.conversationId, conversationId),
|
|
1232
|
-
sql`rowid >= ${rowidSubquery}
|
|
1234
|
+
sql`rowid >= ${rowidSubquery}`,
|
|
1233
1235
|
);
|
|
1234
1236
|
|
|
1235
1237
|
const [{ deleted }] = db
|
|
@@ -1260,8 +1262,16 @@ export function deleteLastExchange(conversationId: string): number {
|
|
|
1260
1262
|
|
|
1261
1263
|
db.transaction((tx) => {
|
|
1262
1264
|
tx.delete(messages).where(condition).run();
|
|
1265
|
+
const maxResult = tx
|
|
1266
|
+
.select({ maxCreatedAt: sql<number | null>`MAX(${messages.createdAt})` })
|
|
1267
|
+
.from(messages)
|
|
1268
|
+
.where(eq(messages.conversationId, conversationId))
|
|
1269
|
+
.get();
|
|
1263
1270
|
tx.update(conversations)
|
|
1264
|
-
.set({
|
|
1271
|
+
.set({
|
|
1272
|
+
updatedAt: Date.now(),
|
|
1273
|
+
lastMessageAt: maxResult?.maxCreatedAt ?? null,
|
|
1274
|
+
})
|
|
1265
1275
|
.where(eq(conversations.id, conversationId))
|
|
1266
1276
|
.run();
|
|
1267
1277
|
});
|
|
@@ -1278,12 +1288,10 @@ export function deleteLastExchange(conversationId: string): number {
|
|
|
1278
1288
|
*/
|
|
1279
1289
|
export interface DeletedMemoryIds {
|
|
1280
1290
|
segmentIds: string[];
|
|
1281
|
-
orphanedItemIds: string[];
|
|
1282
1291
|
deletedSummaryIds: string[];
|
|
1283
1292
|
}
|
|
1284
1293
|
|
|
1285
1294
|
export interface WipeConversationResult extends DeletedMemoryIds {
|
|
1286
|
-
unsupersededItemIds: string[];
|
|
1287
1295
|
cancelledJobCount: number;
|
|
1288
1296
|
}
|
|
1289
1297
|
|
|
@@ -1293,7 +1301,7 @@ export interface WipeConversationResult extends DeletedMemoryIds {
|
|
|
1293
1301
|
*/
|
|
1294
1302
|
export function updateMessageContent(
|
|
1295
1303
|
messageId: string,
|
|
1296
|
-
newContent: string
|
|
1304
|
+
newContent: string,
|
|
1297
1305
|
): void {
|
|
1298
1306
|
const db = getDb();
|
|
1299
1307
|
db.update(messages)
|
|
@@ -1330,7 +1338,7 @@ export function updateMessageMetadata(
|
|
|
1330
1338
|
*/
|
|
1331
1339
|
export function relinkAttachments(
|
|
1332
1340
|
fromMessageIds: string[],
|
|
1333
|
-
toMessageId: string
|
|
1341
|
+
toMessageId: string,
|
|
1334
1342
|
): number {
|
|
1335
1343
|
if (fromMessageIds.length === 0) return 0;
|
|
1336
1344
|
const db = getDb();
|
|
@@ -1365,7 +1373,6 @@ export function deleteMessageById(messageId: string): DeletedMemoryIds {
|
|
|
1365
1373
|
const db = getDb();
|
|
1366
1374
|
const result: DeletedMemoryIds = {
|
|
1367
1375
|
segmentIds: [],
|
|
1368
|
-
orphanedItemIds: [],
|
|
1369
1376
|
deletedSummaryIds: [],
|
|
1370
1377
|
};
|
|
1371
1378
|
|
|
@@ -1379,6 +1386,13 @@ export function deleteMessageById(messageId: string): DeletedMemoryIds {
|
|
|
1379
1386
|
.map((r) => r.attachmentId)
|
|
1380
1387
|
.filter((id): id is string => id !== undefined);
|
|
1381
1388
|
|
|
1389
|
+
// Look up the conversation before the transaction so we can recalculate lastMessageAt.
|
|
1390
|
+
const msgRow = db
|
|
1391
|
+
.select({ conversationId: messages.conversationId })
|
|
1392
|
+
.from(messages)
|
|
1393
|
+
.where(eq(messages.id, messageId))
|
|
1394
|
+
.get();
|
|
1395
|
+
|
|
1382
1396
|
db.transaction((tx) => {
|
|
1383
1397
|
// Collect memory segment IDs linked to this message before cascade.
|
|
1384
1398
|
const linkedSegments = tx
|
|
@@ -1398,14 +1412,29 @@ export function deleteMessageById(messageId: string): DeletedMemoryIds {
|
|
|
1398
1412
|
// and message_attachments.
|
|
1399
1413
|
tx.delete(messages).where(eq(messages.id, messageId)).run();
|
|
1400
1414
|
|
|
1415
|
+
// Recalculate lastMessageAt after deletion.
|
|
1416
|
+
if (msgRow) {
|
|
1417
|
+
const maxResult = tx
|
|
1418
|
+
.select({
|
|
1419
|
+
maxCreatedAt: sql<number | null>`MAX(${messages.createdAt})`,
|
|
1420
|
+
})
|
|
1421
|
+
.from(messages)
|
|
1422
|
+
.where(eq(messages.conversationId, msgRow.conversationId))
|
|
1423
|
+
.get();
|
|
1424
|
+
tx.update(conversations)
|
|
1425
|
+
.set({ lastMessageAt: maxResult?.maxCreatedAt ?? null })
|
|
1426
|
+
.where(eq(conversations.id, msgRow.conversationId))
|
|
1427
|
+
.run();
|
|
1428
|
+
}
|
|
1429
|
+
|
|
1401
1430
|
// Clean up segment embeddings from SQLite (Qdrant cleanup is the caller's job).
|
|
1402
1431
|
if (result.segmentIds.length > 0) {
|
|
1403
1432
|
tx.delete(memoryEmbeddings)
|
|
1404
1433
|
.where(
|
|
1405
1434
|
and(
|
|
1406
1435
|
eq(memoryEmbeddings.targetType, "segment"),
|
|
1407
|
-
inArray(memoryEmbeddings.targetId, result.segmentIds)
|
|
1408
|
-
)
|
|
1436
|
+
inArray(memoryEmbeddings.targetId, result.segmentIds),
|
|
1437
|
+
),
|
|
1409
1438
|
)
|
|
1410
1439
|
.run();
|
|
1411
1440
|
}
|
|
@@ -1418,7 +1447,7 @@ export function deleteMessageById(messageId: string): DeletedMemoryIds {
|
|
|
1418
1447
|
|
|
1419
1448
|
export function setConversationOriginChannelIfUnset(
|
|
1420
1449
|
conversationId: string,
|
|
1421
|
-
channel: ChannelId
|
|
1450
|
+
channel: ChannelId,
|
|
1422
1451
|
): void {
|
|
1423
1452
|
const db = getDb();
|
|
1424
1453
|
db.update(conversations)
|
|
@@ -1426,14 +1455,14 @@ export function setConversationOriginChannelIfUnset(
|
|
|
1426
1455
|
.where(
|
|
1427
1456
|
and(
|
|
1428
1457
|
eq(conversations.id, conversationId),
|
|
1429
|
-
isNull(conversations.originChannel)
|
|
1430
|
-
)
|
|
1458
|
+
isNull(conversations.originChannel),
|
|
1459
|
+
),
|
|
1431
1460
|
)
|
|
1432
1461
|
.run();
|
|
1433
1462
|
}
|
|
1434
1463
|
|
|
1435
1464
|
export function getConversationOriginChannel(
|
|
1436
|
-
conversationId: string
|
|
1465
|
+
conversationId: string,
|
|
1437
1466
|
): ChannelId | null {
|
|
1438
1467
|
const db = getDb();
|
|
1439
1468
|
const row = db
|
|
@@ -1446,7 +1475,7 @@ export function getConversationOriginChannel(
|
|
|
1446
1475
|
|
|
1447
1476
|
export function setConversationOriginInterfaceIfUnset(
|
|
1448
1477
|
conversationId: string,
|
|
1449
|
-
interfaceId: InterfaceId
|
|
1478
|
+
interfaceId: InterfaceId,
|
|
1450
1479
|
): void {
|
|
1451
1480
|
const db = getDb();
|
|
1452
1481
|
db.update(conversations)
|
|
@@ -1454,14 +1483,14 @@ export function setConversationOriginInterfaceIfUnset(
|
|
|
1454
1483
|
.where(
|
|
1455
1484
|
and(
|
|
1456
1485
|
eq(conversations.id, conversationId),
|
|
1457
|
-
isNull(conversations.originInterface)
|
|
1458
|
-
)
|
|
1486
|
+
isNull(conversations.originInterface),
|
|
1487
|
+
),
|
|
1459
1488
|
)
|
|
1460
1489
|
.run();
|
|
1461
1490
|
}
|
|
1462
1491
|
|
|
1463
1492
|
export function getConversationOriginInterface(
|
|
1464
|
-
conversationId: string
|
|
1493
|
+
conversationId: string,
|
|
1465
1494
|
): InterfaceId | null {
|
|
1466
1495
|
const db = getDb();
|
|
1467
1496
|
const row = db
|
|
@@ -1481,13 +1510,13 @@ export function getConversationOriginInterface(
|
|
|
1481
1510
|
* conversation itself isn't a desktop-origin private conversation).
|
|
1482
1511
|
*/
|
|
1483
1512
|
export function getConversationRecentProvenanceTrustClass(
|
|
1484
|
-
conversationId: string
|
|
1513
|
+
conversationId: string,
|
|
1485
1514
|
): "guardian" | "trusted_contact" | "unknown" | undefined {
|
|
1486
1515
|
const row = rawGet<{ metadata: string | null }>(
|
|
1487
1516
|
`SELECT metadata FROM messages
|
|
1488
1517
|
WHERE conversation_id = ? AND role = 'user' AND metadata IS NOT NULL
|
|
1489
1518
|
ORDER BY created_at DESC LIMIT 1`,
|
|
1490
|
-
conversationId
|
|
1519
|
+
conversationId,
|
|
1491
1520
|
);
|
|
1492
1521
|
if (!row?.metadata) return undefined;
|
|
1493
1522
|
try {
|
|
@@ -1508,7 +1537,7 @@ export function batchSetDisplayOrders(
|
|
|
1508
1537
|
displayOrder: number | null;
|
|
1509
1538
|
isPinned: boolean;
|
|
1510
1539
|
groupId?: string | null;
|
|
1511
|
-
}
|
|
1540
|
+
}>,
|
|
1512
1541
|
): void {
|
|
1513
1542
|
ensureDisplayOrderMigration();
|
|
1514
1543
|
ensureGroupMigration();
|
|
@@ -1525,7 +1554,7 @@ export function batchSetDisplayOrders(
|
|
|
1525
1554
|
safeGroupId !== null &&
|
|
1526
1555
|
!rawGet<{ id: string }>(
|
|
1527
1556
|
"SELECT id FROM conversation_groups WHERE id = ?",
|
|
1528
|
-
safeGroupId
|
|
1557
|
+
safeGroupId,
|
|
1529
1558
|
)
|
|
1530
1559
|
) {
|
|
1531
1560
|
safeGroupId = null;
|
|
@@ -1535,7 +1564,7 @@ export function batchSetDisplayOrders(
|
|
|
1535
1564
|
update.displayOrder,
|
|
1536
1565
|
safeGroupId === "system:pinned" ? 1 : 0,
|
|
1537
1566
|
safeGroupId,
|
|
1538
|
-
update.id
|
|
1567
|
+
update.id,
|
|
1539
1568
|
);
|
|
1540
1569
|
} else {
|
|
1541
1570
|
// Old client: no groupId in payload
|
|
@@ -1546,7 +1575,7 @@ export function batchSetDisplayOrders(
|
|
|
1546
1575
|
rawRun(
|
|
1547
1576
|
"UPDATE conversations SET display_order = ?, is_pinned = 1, group_id = 'system:pinned' WHERE id = ?",
|
|
1548
1577
|
update.displayOrder,
|
|
1549
|
-
update.id
|
|
1578
|
+
update.id,
|
|
1550
1579
|
);
|
|
1551
1580
|
} else {
|
|
1552
1581
|
// Restore system group from source/conversationType when old clients
|
|
@@ -1563,7 +1592,7 @@ export function batchSetDisplayOrders(
|
|
|
1563
1592
|
ELSE group_id END
|
|
1564
1593
|
WHERE id = ?`,
|
|
1565
1594
|
update.displayOrder,
|
|
1566
|
-
update.id
|
|
1595
|
+
update.id,
|
|
1567
1596
|
);
|
|
1568
1597
|
}
|
|
1569
1598
|
}
|
|
@@ -1576,7 +1605,7 @@ export function batchSetDisplayOrders(
|
|
|
1576
1605
|
}
|
|
1577
1606
|
|
|
1578
1607
|
export function getDisplayMetaForConversations(
|
|
1579
|
-
conversationIds: string[]
|
|
1608
|
+
conversationIds: string[],
|
|
1580
1609
|
): Map<
|
|
1581
1610
|
string,
|
|
1582
1611
|
{ displayOrder: number | null; isPinned: boolean; groupId: string | null }
|
|
@@ -1595,7 +1624,7 @@ export function getDisplayMetaForConversations(
|
|
|
1595
1624
|
group_id: string | null;
|
|
1596
1625
|
}>(
|
|
1597
1626
|
"SELECT display_order, is_pinned, group_id FROM conversations WHERE id = ?",
|
|
1598
|
-
id
|
|
1627
|
+
id,
|
|
1599
1628
|
);
|
|
1600
1629
|
result.set(id, {
|
|
1601
1630
|
displayOrder: row?.display_order ?? null,
|
|
@@ -1624,7 +1653,7 @@ function isToolResultMessage(role: string, content: string): boolean {
|
|
|
1624
1653
|
(block: unknown) =>
|
|
1625
1654
|
block != null &&
|
|
1626
1655
|
typeof block === "object" &&
|
|
1627
|
-
(block as Record<string, unknown>).type === "tool_result"
|
|
1656
|
+
(block as Record<string, unknown>).type === "tool_result",
|
|
1628
1657
|
);
|
|
1629
1658
|
} catch {
|
|
1630
1659
|
return false;
|
|
@@ -1644,7 +1673,7 @@ function isToolResultMessage(role: string, content: string): boolean {
|
|
|
1644
1673
|
*/
|
|
1645
1674
|
export function getTurnTimeBounds(
|
|
1646
1675
|
conversationId: string,
|
|
1647
|
-
messageCreatedAt: number
|
|
1676
|
+
messageCreatedAt: number,
|
|
1648
1677
|
): { startTime: number; endTime: number } | null {
|
|
1649
1678
|
const db = getDb();
|
|
1650
1679
|
|
|
@@ -1666,8 +1695,8 @@ export function getTurnTimeBounds(
|
|
|
1666
1695
|
.where(
|
|
1667
1696
|
and(
|
|
1668
1697
|
eq(messages.conversationId, conversationId),
|
|
1669
|
-
sql`rowid <= ${rowidSubquery}
|
|
1670
|
-
)
|
|
1698
|
+
sql`rowid <= ${rowidSubquery}`,
|
|
1699
|
+
),
|
|
1671
1700
|
)
|
|
1672
1701
|
.orderBy(sql`rowid DESC`)
|
|
1673
1702
|
.limit(50)
|
|
@@ -1698,8 +1727,8 @@ export function getTurnTimeBounds(
|
|
|
1698
1727
|
.where(
|
|
1699
1728
|
and(
|
|
1700
1729
|
eq(messages.conversationId, conversationId),
|
|
1701
|
-
sql`rowid > ${forwardRowidSubquery}
|
|
1702
|
-
)
|
|
1730
|
+
sql`rowid > ${forwardRowidSubquery}`,
|
|
1731
|
+
),
|
|
1703
1732
|
)
|
|
1704
1733
|
.orderBy(sql`rowid ASC`)
|
|
1705
1734
|
.limit(50)
|
|
@@ -1740,8 +1769,8 @@ export function getTurnTimeBounds(
|
|
|
1740
1769
|
and(
|
|
1741
1770
|
eq(llmRequestLogs.conversationId, conversationId),
|
|
1742
1771
|
gte(llmRequestLogs.createdAt, startTime),
|
|
1743
|
-
lte(llmRequestLogs.createdAt, hardCeiling)
|
|
1744
|
-
)
|
|
1772
|
+
lte(llmRequestLogs.createdAt, hardCeiling),
|
|
1773
|
+
),
|
|
1745
1774
|
)
|
|
1746
1775
|
.orderBy(desc(llmRequestLogs.createdAt))
|
|
1747
1776
|
.limit(1)
|
|
@@ -1789,8 +1818,8 @@ export function getAssistantMessageIdsInTurn(messageId: string): string[] {
|
|
|
1789
1818
|
.where(
|
|
1790
1819
|
and(
|
|
1791
1820
|
eq(messages.conversationId, target.conversationId),
|
|
1792
|
-
lte(messages.createdAt, target.createdAt)
|
|
1793
|
-
)
|
|
1821
|
+
lte(messages.createdAt, target.createdAt),
|
|
1822
|
+
),
|
|
1794
1823
|
)
|
|
1795
1824
|
.orderBy(desc(messages.createdAt))
|
|
1796
1825
|
.limit(50)
|
|
@@ -1827,8 +1856,8 @@ export function getAssistantMessageIdsInTurn(messageId: string): string[] {
|
|
|
1827
1856
|
.where(
|
|
1828
1857
|
and(
|
|
1829
1858
|
eq(messages.conversationId, target.conversationId),
|
|
1830
|
-
gt(messages.createdAt, target.createdAt)
|
|
1831
|
-
)
|
|
1859
|
+
gt(messages.createdAt, target.createdAt),
|
|
1860
|
+
),
|
|
1832
1861
|
)
|
|
1833
1862
|
.orderBy(asc(messages.createdAt))
|
|
1834
1863
|
.limit(50)
|
|
@@ -1864,8 +1893,8 @@ export function getAssistantMessageIdsInTurn(messageId: string): string[] {
|
|
|
1864
1893
|
and(
|
|
1865
1894
|
eq(messages.conversationId, target.conversationId),
|
|
1866
1895
|
gt(messages.createdAt, boundaryCreatedAt),
|
|
1867
|
-
lte(messages.createdAt, target.createdAt)
|
|
1868
|
-
)
|
|
1896
|
+
lte(messages.createdAt, target.createdAt),
|
|
1897
|
+
),
|
|
1869
1898
|
)
|
|
1870
1899
|
.orderBy(asc(messages.createdAt))
|
|
1871
1900
|
.all();
|
|
@@ -1888,8 +1917,8 @@ export function getAssistantMessageIdsInTurn(messageId: string): string[] {
|
|
|
1888
1917
|
.where(
|
|
1889
1918
|
and(
|
|
1890
1919
|
eq(messages.conversationId, target.conversationId),
|
|
1891
|
-
inArray(messages.id, [...idSet])
|
|
1892
|
-
)
|
|
1920
|
+
inArray(messages.id, [...idSet]),
|
|
1921
|
+
),
|
|
1893
1922
|
)
|
|
1894
1923
|
.orderBy(asc(messages.createdAt))
|
|
1895
1924
|
.all();
|