@vellumai/assistant 0.6.0 → 0.6.2
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 +42 -1
- package/docs/architecture/integrations.md +1 -1
- package/docs/architecture/memory.md +21 -24
- package/node_modules/@vellumai/ces-contracts/src/handles.ts +7 -9
- package/openapi.yaml +539 -4
- 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__/assistant-event-hub.test.ts +30 -0
- package/src/__tests__/checker.test.ts +138 -172
- package/src/__tests__/cli-command-risk-guard.test.ts +1 -1
- package/src/__tests__/config-schema.test.ts +5 -0
- package/src/__tests__/context-overflow-approval.test.ts +5 -5
- 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-analysis-routes.test.ts +169 -0
- package/src/__tests__/conversation-directories-parse.test.ts +105 -0
- 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__/credential-execution-approval-bridge.test.ts +0 -2
- 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__/init-feature-flag-overrides.test.ts +167 -0
- package/src/__tests__/injection-block.test.ts +24 -0
- package/src/__tests__/inline-command-runner.test.ts +7 -5
- 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 +257 -100
- package/src/__tests__/managed-credential-catalog-cli.test.ts +12 -14
- 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__/navigate-settings-tab.test.ts +14 -1
- package/src/__tests__/notification-broadcaster.test.ts +65 -0
- package/src/__tests__/notification-decision-recipient-context.test.ts +282 -0
- package/src/__tests__/onboarding-template-contract.test.ts +63 -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__/pkb-autoinject.test.ts +96 -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__/require-fresh-approval.test.ts +0 -2
- package/src/__tests__/sandbox-diagnostics.test.ts +1 -32
- 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-sandbox.test.ts +1 -1
- package/src/__tests__/terminal-tools.test.ts +16 -29
- package/src/__tests__/test-preload.ts +18 -0
- package/src/__tests__/tool-domain-event-publisher.test.ts +0 -1
- package/src/__tests__/tool-executor-lifecycle-events.test.ts +1 -8
- package/src/__tests__/tool-executor.test.ts +4 -27
- 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__/transport-hints-queue.test.ts +77 -0
- package/src/__tests__/trust-store.test.ts +4 -4
- 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/__tests__/workspace-migration-030-seed-pkb-autoinject.test.ts +168 -0
- package/src/__tests__/workspace-policy.test.ts +2 -7
- package/src/agent/loop.ts +6 -29
- package/src/approvals/guardian-request-resolvers.ts +24 -0
- package/src/avatar/traits-png-sync.ts +3 -3
- package/src/channels/types.ts +5 -0
- package/src/cli/__tests__/run-assistant-command.ts +56 -0
- package/src/cli/__tests__/unknown-command.test.ts +33 -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/default-action.ts +68 -1
- 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/__tests__/connect.test.ts +27 -0
- package/src/cli/commands/oauth/connect.ts +25 -5
- package/src/cli/commands/platform/__tests__/connect.test.ts +1 -1
- package/src/cli/commands/platform/__tests__/disconnect.test.ts +1 -1
- package/src/cli/commands/platform/__tests__/status.test.ts +1 -1
- package/src/cli/commands/routes.ts +396 -0
- package/src/cli/commands/skills.ts +130 -20
- package/src/cli/program.ts +11 -2
- package/src/cli.ts +1 -120
- package/src/config/assistant-feature-flags.ts +59 -55
- package/src/config/bundled-skills/app-builder/SKILL.md +91 -5
- package/src/config/bundled-skills/gmail/SKILL.md +13 -8
- package/src/config/bundled-skills/gmail/TOOLS.json +1 -1
- package/src/config/bundled-skills/gmail/tools/gmail-sender-digest.ts +2 -1
- 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.json +1 -1
- 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/settings/tools/navigate-settings-tab.ts +8 -3
- 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/config/schemas/services.ts +8 -0
- package/src/credential-execution/approval-bridge.ts +0 -1
- package/src/credential-execution/managed-catalog.ts +3 -7
- package/src/daemon/app-source-watcher.ts +93 -0
- package/src/daemon/config-watcher.ts +85 -3
- package/src/daemon/context-overflow-approval.ts +0 -1
- package/src/daemon/conversation-agent-loop-handlers.ts +20 -0
- package/src/daemon/conversation-agent-loop.ts +179 -65
- package/src/daemon/conversation-attachments.ts +0 -1
- package/src/daemon/conversation-history.ts +4 -19
- package/src/daemon/conversation-lifecycle.ts +8 -14
- package/src/daemon/conversation-messaging.ts +3 -0
- package/src/daemon/conversation-process.ts +30 -8
- package/src/daemon/conversation-queue-manager.ts +8 -0
- package/src/daemon/conversation-runtime-assembly.ts +359 -308
- package/src/daemon/conversation-surfaces.ts +65 -0
- package/src/daemon/conversation-tool-setup.ts +44 -17
- package/src/daemon/conversation-workspace.ts +1 -2
- package/src/daemon/conversation.ts +19 -3
- package/src/daemon/date-context.ts +26 -53
- package/src/daemon/first-greeting.ts +1 -1
- package/src/daemon/handlers/conversations.ts +5 -7
- package/src/daemon/handlers/shared.test.ts +143 -0
- package/src/daemon/handlers/shared.ts +70 -5
- package/src/daemon/handlers/skills.ts +11 -18
- package/src/daemon/lifecycle.ts +220 -158
- package/src/daemon/message-types/conversations.ts +29 -6
- package/src/daemon/message-types/messages.ts +9 -2
- package/src/daemon/message-types/notifications.ts +12 -0
- package/src/daemon/message-types/schedules.ts +1 -0
- package/src/daemon/message-types/settings.ts +18 -0
- package/src/daemon/profiler-run-store.ts +557 -0
- package/src/daemon/server.ts +87 -10
- package/src/daemon/shutdown-handlers.ts +5 -0
- package/src/daemon/tool-side-effects.ts +23 -3
- package/src/daemon/transport-hints.ts +33 -0
- 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/index.ts +1 -1
- 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 +151 -117
- package/src/memory/conversation-directories.ts +39 -0
- package/src/memory/conversation-group-migration.ts +66 -6
- 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/embedding-local.ts +1 -1
- package/src/memory/graph/bootstrap.ts +75 -66
- package/src/memory/graph/capability-seed.ts +167 -17
- 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/group-crud.ts +25 -9
- 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/messaging/provider.ts +1 -1
- package/src/notifications/broadcaster.ts +6 -0
- package/src/notifications/conversation-pairing.ts +12 -4
- package/src/notifications/copy-composer.ts +86 -0
- package/src/notifications/decision-engine.ts +35 -0
- package/src/notifications/emit-signal.ts +14 -0
- package/src/notifications/signal.ts +11 -0
- package/src/oauth/platform-connection.test.ts +2 -2
- package/src/oauth/seed-providers.ts +1 -0
- package/src/permissions/checker.ts +15 -4
- package/src/permissions/defaults.ts +7 -8
- package/src/permissions/permission-mode-store.ts +180 -0
- package/src/permissions/permission-mode.ts +31 -0
- package/src/permissions/prompter.ts +0 -2
- package/src/permissions/workspace-policy.ts +9 -0
- package/src/platform/client.ts +1 -1
- package/src/prompts/system-prompt.ts +59 -7
- package/src/prompts/templates/BOOTSTRAP-REFERENCE.md +100 -0
- package/src/prompts/templates/BOOTSTRAP.md +76 -162
- package/src/prompts/templates/HEARTBEAT.md +3 -1
- package/src/prompts/templates/SOUL.md +30 -9
- package/src/prompts/templates/UPDATES.md +8 -0
- package/src/providers/anthropic/client.ts +107 -219
- package/src/runtime/assistant-event-hub.ts +22 -0
- package/src/runtime/auth/route-policy.ts +23 -0
- package/src/runtime/auth/token-service.ts +8 -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 +185 -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 +270 -44
- package/src/runtime/routes/group-routes.ts +22 -8
- 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/AGENTS.md +104 -0
- package/src/runtime/routes/log-export/__tests__/workspace-allowlist-error-contract.test.ts +103 -0
- package/src/runtime/routes/log-export/__tests__/workspace-allowlist.test.ts +716 -0
- package/src/runtime/routes/log-export/workspace-allowlist.ts +458 -0
- package/src/runtime/routes/log-export-routes.ts +41 -278
- 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/inline-command-runner.ts +12 -14
- 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 -18
- 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/secret-detection-handler.ts +0 -1
- 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/skills/sandbox-runner.ts +3 -6
- 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/terminal/sandbox-diagnostics.ts +4 -4
- package/src/tools/terminal/sandbox.ts +4 -1
- package/src/tools/terminal/shell.ts +3 -5
- package/src/tools/tool-manifest.ts +6 -0
- package/src/tools/types.ts +2 -3
- package/src/util/logger.ts +1 -1
- package/src/util/platform.ts +50 -17
- package/src/watcher/provider-types.ts +1 -1
- 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 +85 -0
- package/src/workspace/migrations/030-seed-pkb-autoinject.ts +73 -0
- package/src/workspace/migrations/registry.ts +6 -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;
|
|
@@ -312,13 +314,16 @@ export function createConversation(
|
|
|
312
314
|
|
|
313
315
|
// group_id is NOT in the Drizzle schema (raw-query-only pattern).
|
|
314
316
|
// Set via raw SQL after the INSERT succeeds.
|
|
315
|
-
|
|
317
|
+
// Always set group_id — default to "system:all" when none provided.
|
|
318
|
+
{
|
|
319
|
+
const effectiveGroupId = groupId ?? "system:all";
|
|
316
320
|
for (let attempt = 0; ; attempt++) {
|
|
317
321
|
try {
|
|
318
322
|
rawRun(
|
|
319
|
-
"UPDATE conversations SET group_id = ? WHERE id = ?",
|
|
320
|
-
|
|
321
|
-
|
|
323
|
+
"UPDATE conversations SET group_id = ?, is_pinned = ? WHERE id = ?",
|
|
324
|
+
effectiveGroupId,
|
|
325
|
+
effectiveGroupId === "system:pinned" ? 1 : 0,
|
|
326
|
+
id,
|
|
322
327
|
);
|
|
323
328
|
break;
|
|
324
329
|
} catch (err) {
|
|
@@ -329,7 +334,7 @@ export function createConversation(
|
|
|
329
334
|
) {
|
|
330
335
|
log.warn(
|
|
331
336
|
{ attempt, conversationId: id, code },
|
|
332
|
-
"createConversation: group_id UPDATE transient error, retrying"
|
|
337
|
+
"createConversation: group_id UPDATE transient error, retrying",
|
|
333
338
|
);
|
|
334
339
|
Bun.sleepSync(50 * (attempt + 1));
|
|
335
340
|
continue;
|
|
@@ -360,18 +365,18 @@ export function getConversation(id: string): ConversationRow | null {
|
|
|
360
365
|
* (i.e. no other conversations still reference it).
|
|
361
366
|
*/
|
|
362
367
|
export function countConversationsByScheduleJobId(
|
|
363
|
-
scheduleJobId: string
|
|
368
|
+
scheduleJobId: string,
|
|
364
369
|
): number {
|
|
365
370
|
return (
|
|
366
371
|
rawGet<{ c: number }>(
|
|
367
372
|
"SELECT COUNT(*) AS c FROM conversations WHERE schedule_job_id = ?",
|
|
368
|
-
scheduleJobId
|
|
373
|
+
scheduleJobId,
|
|
369
374
|
)?.c ?? 0
|
|
370
375
|
);
|
|
371
376
|
}
|
|
372
377
|
|
|
373
378
|
export function getConversationType(
|
|
374
|
-
conversationId: string
|
|
379
|
+
conversationId: string,
|
|
375
380
|
): "standard" | "private" {
|
|
376
381
|
const conv = getConversation(conversationId);
|
|
377
382
|
const raw = conv?.conversationType;
|
|
@@ -392,7 +397,7 @@ export function getConversationGroupId(conversationId: string): string | null {
|
|
|
392
397
|
ensureGroupMigration();
|
|
393
398
|
const row = rawGet<{ group_id: string | null }>(
|
|
394
399
|
"SELECT group_id FROM conversations WHERE id = ?",
|
|
395
|
-
conversationId
|
|
400
|
+
conversationId,
|
|
396
401
|
);
|
|
397
402
|
return row?.group_id ?? null;
|
|
398
403
|
}
|
|
@@ -416,7 +421,7 @@ export function forkConversation(params: {
|
|
|
416
421
|
|
|
417
422
|
if (sourceMessages.length === 0) {
|
|
418
423
|
throw new UserError(
|
|
419
|
-
`Conversation ${conversationId} has no persisted messages to fork
|
|
424
|
+
`Conversation ${conversationId} has no persisted messages to fork`,
|
|
420
425
|
);
|
|
421
426
|
}
|
|
422
427
|
|
|
@@ -427,7 +432,7 @@ export function forkConversation(params: {
|
|
|
427
432
|
|
|
428
433
|
if (throughMessageId != null && copyBoundaryIndex === -1) {
|
|
429
434
|
throw new UserError(
|
|
430
|
-
`Message ${throughMessageId} does not belong to conversation ${conversationId}
|
|
435
|
+
`Message ${throughMessageId} does not belong to conversation ${conversationId}`,
|
|
431
436
|
);
|
|
432
437
|
}
|
|
433
438
|
|
|
@@ -435,8 +440,8 @@ export function forkConversation(params: {
|
|
|
435
440
|
0,
|
|
436
441
|
Math.min(
|
|
437
442
|
sourceConversation.contextCompactedMessageCount,
|
|
438
|
-
sourceMessages.length
|
|
439
|
-
)
|
|
443
|
+
sourceMessages.length,
|
|
444
|
+
),
|
|
440
445
|
);
|
|
441
446
|
const preserveSourceCompactionState =
|
|
442
447
|
copyBoundaryIndex >= visibleWindowStartIndex;
|
|
@@ -467,7 +472,7 @@ export function forkConversation(params: {
|
|
|
467
472
|
const fc = createConversation({
|
|
468
473
|
title: forkTitle,
|
|
469
474
|
conversationType: "standard",
|
|
470
|
-
groupId: parentGroupId ??
|
|
475
|
+
groupId: parentGroupId ?? "system:all",
|
|
471
476
|
});
|
|
472
477
|
|
|
473
478
|
db.update(conversations)
|
|
@@ -530,7 +535,7 @@ export function forkConversation(params: {
|
|
|
530
535
|
.orderBy(messageAttachments.position)
|
|
531
536
|
.all();
|
|
532
537
|
const uncachedAttachmentLinks = attachmentLinks.filter(
|
|
533
|
-
(link) => !attachmentIdMap.has(link.attachmentId)
|
|
538
|
+
(link) => !attachmentIdMap.has(link.attachmentId),
|
|
534
539
|
);
|
|
535
540
|
const stagingMessageId =
|
|
536
541
|
uncachedAttachmentLinks.length > 0 ? uuid() : null;
|
|
@@ -566,7 +571,7 @@ export function forkConversation(params: {
|
|
|
566
571
|
const scopedAttachmentId = linkAttachmentToMessage(
|
|
567
572
|
stagingMessageId ?? forkedMessageId,
|
|
568
573
|
link.attachmentId,
|
|
569
|
-
link.position
|
|
574
|
+
link.position,
|
|
570
575
|
);
|
|
571
576
|
attachmentIdMap.set(link.attachmentId, scopedAttachmentId);
|
|
572
577
|
}
|
|
@@ -583,6 +588,16 @@ export function forkConversation(params: {
|
|
|
583
588
|
});
|
|
584
589
|
}
|
|
585
590
|
|
|
591
|
+
// Set lastMessageAt to the max createdAt of copied messages so the
|
|
592
|
+
// forked conversation sorts correctly by message recency.
|
|
593
|
+
const lastCopiedMessage = messagesToCopy.at(-1);
|
|
594
|
+
if (lastCopiedMessage) {
|
|
595
|
+
db.update(conversations)
|
|
596
|
+
.set({ lastMessageAt: lastCopiedMessage.createdAt })
|
|
597
|
+
.where(eq(conversations.id, fc.id))
|
|
598
|
+
.run();
|
|
599
|
+
}
|
|
600
|
+
|
|
586
601
|
seedForkedConversationAttention({
|
|
587
602
|
conversationId: fc.id,
|
|
588
603
|
latestAssistantMessageId: latestForkedAssistant?.messageId ?? null,
|
|
@@ -601,7 +616,7 @@ export function forkConversation(params: {
|
|
|
601
616
|
const persistedFork = getConversation(forkedConversation.id);
|
|
602
617
|
if (!persistedFork) {
|
|
603
618
|
throw new Error(
|
|
604
|
-
`Failed to load forked conversation ${forkedConversation.id} after creation
|
|
619
|
+
`Failed to load forked conversation ${forkedConversation.id} after creation`,
|
|
605
620
|
);
|
|
606
621
|
}
|
|
607
622
|
|
|
@@ -610,14 +625,13 @@ export function forkConversation(params: {
|
|
|
610
625
|
|
|
611
626
|
/**
|
|
612
627
|
* Delete a conversation and all its messages, cleaning up orphaned memory
|
|
613
|
-
* artifacts (
|
|
614
|
-
*
|
|
628
|
+
* artifacts (embeddings). Returns segment IDs so callers can clean up
|
|
629
|
+
* the corresponding Qdrant vector entries.
|
|
615
630
|
*/
|
|
616
631
|
export function deleteConversation(id: string): DeletedMemoryIds {
|
|
617
632
|
const db = getDb();
|
|
618
633
|
const result: DeletedMemoryIds = {
|
|
619
634
|
segmentIds: [],
|
|
620
|
-
orphanedItemIds: [],
|
|
621
635
|
deletedSummaryIds: [],
|
|
622
636
|
};
|
|
623
637
|
|
|
@@ -662,8 +676,8 @@ export function deleteConversation(id: string): DeletedMemoryIds {
|
|
|
662
676
|
.where(
|
|
663
677
|
and(
|
|
664
678
|
eq(memoryEmbeddings.targetType, "segment"),
|
|
665
|
-
inArray(memoryEmbeddings.targetId, result.segmentIds)
|
|
666
|
-
)
|
|
679
|
+
inArray(memoryEmbeddings.targetId, result.segmentIds),
|
|
680
|
+
),
|
|
667
681
|
)
|
|
668
682
|
.run();
|
|
669
683
|
}
|
|
@@ -691,8 +705,8 @@ export function deleteConversation(id: string): DeletedMemoryIds {
|
|
|
691
705
|
.where(
|
|
692
706
|
and(
|
|
693
707
|
eq(memoryEmbeddings.targetType, "summary"),
|
|
694
|
-
inArray(memoryEmbeddings.targetId, scopeSummaryIds)
|
|
695
|
-
)
|
|
708
|
+
inArray(memoryEmbeddings.targetId, scopeSummaryIds),
|
|
709
|
+
),
|
|
696
710
|
)
|
|
697
711
|
.run();
|
|
698
712
|
tx.delete(memorySummaries)
|
|
@@ -723,14 +737,10 @@ export function deleteConversation(id: string): DeletedMemoryIds {
|
|
|
723
737
|
*
|
|
724
738
|
* Extends `deleteConversation` with:
|
|
725
739
|
* - 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
740
|
* - Deleting conversation-scoped memory summaries and their embeddings
|
|
729
|
-
* - Enqueuing `embed_item` jobs for all restored items
|
|
730
741
|
*/
|
|
731
742
|
export function wipeConversation(id: string): WipeConversationResult {
|
|
732
743
|
const db = getDb();
|
|
733
|
-
const unsupersededItemIds: string[] = [];
|
|
734
744
|
const deletedSummaryIds: string[] = [];
|
|
735
745
|
|
|
736
746
|
// Step A — Cancel pending memory jobs (before deleting messages, since
|
|
@@ -744,8 +754,8 @@ export function wipeConversation(id: string): WipeConversationResult {
|
|
|
744
754
|
.where(
|
|
745
755
|
and(
|
|
746
756
|
eq(memorySummaries.scope, "conversation"),
|
|
747
|
-
eq(memorySummaries.scopeKey, id)
|
|
748
|
-
)
|
|
757
|
+
eq(memorySummaries.scopeKey, id),
|
|
758
|
+
),
|
|
749
759
|
)
|
|
750
760
|
.all();
|
|
751
761
|
const summaryIds = summaryRows.map((r) => r.id);
|
|
@@ -754,8 +764,8 @@ export function wipeConversation(id: string): WipeConversationResult {
|
|
|
754
764
|
.where(
|
|
755
765
|
and(
|
|
756
766
|
eq(memoryEmbeddings.targetType, "summary"),
|
|
757
|
-
inArray(memoryEmbeddings.targetId, summaryIds)
|
|
758
|
-
)
|
|
767
|
+
inArray(memoryEmbeddings.targetId, summaryIds),
|
|
768
|
+
),
|
|
759
769
|
)
|
|
760
770
|
.run();
|
|
761
771
|
db.delete(memorySummaries)
|
|
@@ -772,7 +782,6 @@ export function wipeConversation(id: string): WipeConversationResult {
|
|
|
772
782
|
// Step E — Return the combined result.
|
|
773
783
|
return {
|
|
774
784
|
...deletedMemoryIds,
|
|
775
|
-
unsupersededItemIds,
|
|
776
785
|
deletedSummaryIds: [
|
|
777
786
|
...deletedSummaryIds,
|
|
778
787
|
...deletedMemoryIds.deletedSummaryIds,
|
|
@@ -802,20 +811,17 @@ export function purgePrivateConversations(): {
|
|
|
802
811
|
count: 0,
|
|
803
812
|
deletedMemory: {
|
|
804
813
|
segmentIds: [],
|
|
805
|
-
orphanedItemIds: [],
|
|
806
814
|
deletedSummaryIds: [],
|
|
807
815
|
},
|
|
808
816
|
};
|
|
809
817
|
}
|
|
810
818
|
|
|
811
819
|
const allSegmentIds: string[] = [];
|
|
812
|
-
const allOrphanedItemIds: string[] = [];
|
|
813
820
|
const allDeletedSummaryIds: string[] = [];
|
|
814
821
|
|
|
815
822
|
for (const conv of privateConvs) {
|
|
816
823
|
const deleted = deleteConversation(conv.id);
|
|
817
824
|
allSegmentIds.push(...deleted.segmentIds);
|
|
818
|
-
allOrphanedItemIds.push(...deleted.orphanedItemIds);
|
|
819
825
|
allDeletedSummaryIds.push(...deleted.deletedSummaryIds);
|
|
820
826
|
}
|
|
821
827
|
|
|
@@ -823,7 +829,6 @@ export function purgePrivateConversations(): {
|
|
|
823
829
|
count: privateConvs.length,
|
|
824
830
|
deletedMemory: {
|
|
825
831
|
segmentIds: allSegmentIds,
|
|
826
|
-
orphanedItemIds: allOrphanedItemIds,
|
|
827
832
|
deletedSummaryIds: allDeletedSummaryIds,
|
|
828
833
|
},
|
|
829
834
|
};
|
|
@@ -834,7 +839,7 @@ export async function addMessage(
|
|
|
834
839
|
role: string,
|
|
835
840
|
content: string,
|
|
836
841
|
metadata?: Record<string, unknown>,
|
|
837
|
-
opts?: { skipIndexing?: boolean }
|
|
842
|
+
opts?: { skipIndexing?: boolean },
|
|
838
843
|
) {
|
|
839
844
|
const db = getDb();
|
|
840
845
|
const messageId = uuid();
|
|
@@ -844,7 +849,7 @@ export async function addMessage(
|
|
|
844
849
|
if (!result.success) {
|
|
845
850
|
log.warn(
|
|
846
851
|
{ conversationId, messageId, issues: result.error.issues },
|
|
847
|
-
"Invalid message metadata, storing as-is"
|
|
852
|
+
"Invalid message metadata, storing as-is",
|
|
848
853
|
);
|
|
849
854
|
}
|
|
850
855
|
}
|
|
@@ -879,13 +884,13 @@ export async function addMessage(
|
|
|
879
884
|
.where(
|
|
880
885
|
and(
|
|
881
886
|
eq(conversations.id, conversationId),
|
|
882
|
-
isNull(conversations.originChannel)
|
|
883
|
-
)
|
|
887
|
+
isNull(conversations.originChannel),
|
|
888
|
+
),
|
|
884
889
|
)
|
|
885
890
|
.run();
|
|
886
891
|
}
|
|
887
892
|
tx.update(conversations)
|
|
888
|
-
.set({ updatedAt: now })
|
|
893
|
+
.set({ updatedAt: now, lastMessageAt: now })
|
|
889
894
|
.where(eq(conversations.id, conversationId))
|
|
890
895
|
.run();
|
|
891
896
|
});
|
|
@@ -899,7 +904,7 @@ export async function addMessage(
|
|
|
899
904
|
) {
|
|
900
905
|
log.warn(
|
|
901
906
|
{ attempt, conversationId, code: errCode },
|
|
902
|
-
"addMessage: transient SQLite error, retrying"
|
|
907
|
+
"addMessage: transient SQLite error, retrying",
|
|
903
908
|
);
|
|
904
909
|
await Bun.sleep(50 * (attempt + 1));
|
|
905
910
|
continue;
|
|
@@ -938,12 +943,12 @@ export async function addMessage(
|
|
|
938
943
|
provenanceTrustClass,
|
|
939
944
|
automated,
|
|
940
945
|
},
|
|
941
|
-
config.memory
|
|
946
|
+
config.memory,
|
|
942
947
|
);
|
|
943
948
|
} catch (err) {
|
|
944
949
|
log.warn(
|
|
945
950
|
{ err, conversationId, messageId: message.id },
|
|
946
|
-
"Failed to index message for memory"
|
|
951
|
+
"Failed to index message for memory",
|
|
947
952
|
);
|
|
948
953
|
}
|
|
949
954
|
}
|
|
@@ -958,7 +963,7 @@ export async function addMessage(
|
|
|
958
963
|
} catch (err) {
|
|
959
964
|
log.warn(
|
|
960
965
|
{ err, conversationId, messageId: message.id },
|
|
961
|
-
"Failed to project assistant message for attention tracking"
|
|
966
|
+
"Failed to project assistant message for attention tracking",
|
|
962
967
|
);
|
|
963
968
|
}
|
|
964
969
|
}
|
|
@@ -985,7 +990,7 @@ export interface PaginatedMessagesResult {
|
|
|
985
990
|
export function getMessagesPaginated(
|
|
986
991
|
conversationId: string,
|
|
987
992
|
limit: number | undefined,
|
|
988
|
-
beforeTimestamp?: number
|
|
993
|
+
beforeTimestamp?: number,
|
|
989
994
|
): PaginatedMessagesResult {
|
|
990
995
|
const db = getDb();
|
|
991
996
|
|
|
@@ -1029,7 +1034,7 @@ export function getMessagesPaginated(
|
|
|
1029
1034
|
|
|
1030
1035
|
export function getLastAssistantTimestampBefore(
|
|
1031
1036
|
conversationId: string,
|
|
1032
|
-
beforeTimestamp: number
|
|
1037
|
+
beforeTimestamp: number,
|
|
1033
1038
|
): number {
|
|
1034
1039
|
const db = getDb();
|
|
1035
1040
|
const row = db
|
|
@@ -1039,8 +1044,8 @@ export function getLastAssistantTimestampBefore(
|
|
|
1039
1044
|
and(
|
|
1040
1045
|
eq(messages.conversationId, conversationId),
|
|
1041
1046
|
eq(messages.role, "assistant"),
|
|
1042
|
-
lt(messages.createdAt, beforeTimestamp)
|
|
1043
|
-
)
|
|
1047
|
+
lt(messages.createdAt, beforeTimestamp),
|
|
1048
|
+
),
|
|
1044
1049
|
)
|
|
1045
1050
|
.orderBy(desc(messages.createdAt))
|
|
1046
1051
|
.limit(1)
|
|
@@ -1051,7 +1056,7 @@ export function getLastAssistantTimestampBefore(
|
|
|
1051
1056
|
/** Fetch a single message by ID, optionally scoped to a specific conversation. */
|
|
1052
1057
|
export function getMessageById(
|
|
1053
1058
|
messageId: string,
|
|
1054
|
-
conversationId?: string
|
|
1059
|
+
conversationId?: string,
|
|
1055
1060
|
): MessageRow | null {
|
|
1056
1061
|
const db = getDb();
|
|
1057
1062
|
const conditions = [eq(messages.id, messageId)];
|
|
@@ -1069,7 +1074,7 @@ export function getMessageById(
|
|
|
1069
1074
|
export function updateConversationTitle(
|
|
1070
1075
|
id: string,
|
|
1071
1076
|
title: string,
|
|
1072
|
-
isAutoTitle?: number
|
|
1077
|
+
isAutoTitle?: number,
|
|
1073
1078
|
): void {
|
|
1074
1079
|
const db = getDb();
|
|
1075
1080
|
const set: Record<string, unknown> = { title, updatedAt: Date.now() };
|
|
@@ -1087,7 +1092,7 @@ export function updateConversationUsage(
|
|
|
1087
1092
|
id: string,
|
|
1088
1093
|
totalInputTokens: number,
|
|
1089
1094
|
totalOutputTokens: number,
|
|
1090
|
-
totalEstimatedCost: number
|
|
1095
|
+
totalEstimatedCost: number,
|
|
1091
1096
|
): void {
|
|
1092
1097
|
const db = getDb();
|
|
1093
1098
|
db.update(conversations)
|
|
@@ -1104,7 +1109,7 @@ export function updateConversationUsage(
|
|
|
1104
1109
|
export function updateConversationContextWindow(
|
|
1105
1110
|
id: string,
|
|
1106
1111
|
contextSummary: string,
|
|
1107
|
-
contextCompactedMessageCount: number
|
|
1112
|
+
contextCompactedMessageCount: number,
|
|
1108
1113
|
): void {
|
|
1109
1114
|
const db = getDb();
|
|
1110
1115
|
db.update(conversations)
|
|
@@ -1154,7 +1159,7 @@ export function clearAll(): { conversations: number; messages: number } {
|
|
|
1154
1159
|
} catch (err) {
|
|
1155
1160
|
log.warn(
|
|
1156
1161
|
{ err },
|
|
1157
|
-
"clearAll: failed to clear messages_fts — dropping triggers so base-table cleanup can proceed"
|
|
1162
|
+
"clearAll: failed to clear messages_fts — dropping triggers so base-table cleanup can proceed",
|
|
1158
1163
|
);
|
|
1159
1164
|
rawExec("DROP TRIGGER IF EXISTS messages_fts_ai");
|
|
1160
1165
|
rawExec("DROP TRIGGER IF EXISTS messages_fts_ad");
|
|
@@ -1170,7 +1175,7 @@ export function clearAll(): { conversations: number; messages: number } {
|
|
|
1170
1175
|
`INSERT INTO lifecycle_events (id, event_name, created_at) VALUES (?, ?, ?)`,
|
|
1171
1176
|
uuid(),
|
|
1172
1177
|
"conversations_clear_all",
|
|
1173
|
-
Date.now()
|
|
1178
|
+
Date.now(),
|
|
1174
1179
|
);
|
|
1175
1180
|
|
|
1176
1181
|
// Rebuild corrupted FTS tables and restore triggers after all base-table
|
|
@@ -1180,16 +1185,16 @@ export function clearAll(): { conversations: number; messages: number } {
|
|
|
1180
1185
|
if (messagesFtsCorrupted) {
|
|
1181
1186
|
rawExec("DROP TABLE IF EXISTS messages_fts");
|
|
1182
1187
|
rawExec(
|
|
1183
|
-
`CREATE VIRTUAL TABLE IF NOT EXISTS messages_fts USING fts5(message_id UNINDEXED, content)
|
|
1188
|
+
`CREATE VIRTUAL TABLE IF NOT EXISTS messages_fts USING fts5(message_id UNINDEXED, content)`,
|
|
1184
1189
|
);
|
|
1185
1190
|
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
|
|
1191
|
+
`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
1192
|
);
|
|
1188
1193
|
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
|
|
1194
|
+
`CREATE TRIGGER IF NOT EXISTS messages_fts_ad AFTER DELETE ON messages BEGIN DELETE FROM messages_fts WHERE message_id = old.id; END`,
|
|
1190
1195
|
);
|
|
1191
1196
|
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
|
|
1197
|
+
`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
1198
|
);
|
|
1194
1199
|
}
|
|
1195
1200
|
|
|
@@ -1214,8 +1219,8 @@ export function deleteLastExchange(conversationId: string): number {
|
|
|
1214
1219
|
.where(
|
|
1215
1220
|
and(
|
|
1216
1221
|
eq(messages.conversationId, conversationId),
|
|
1217
|
-
eq(messages.role, "user")
|
|
1218
|
-
)
|
|
1222
|
+
eq(messages.role, "user"),
|
|
1223
|
+
),
|
|
1219
1224
|
)
|
|
1220
1225
|
.orderBy(sql`rowid DESC`)
|
|
1221
1226
|
.limit(1)
|
|
@@ -1229,7 +1234,7 @@ export function deleteLastExchange(conversationId: string): number {
|
|
|
1229
1234
|
const rowidSubquery = sql`(SELECT rowid FROM messages WHERE id = ${lastUserMsg.id})`;
|
|
1230
1235
|
const condition = and(
|
|
1231
1236
|
eq(messages.conversationId, conversationId),
|
|
1232
|
-
sql`rowid >= ${rowidSubquery}
|
|
1237
|
+
sql`rowid >= ${rowidSubquery}`,
|
|
1233
1238
|
);
|
|
1234
1239
|
|
|
1235
1240
|
const [{ deleted }] = db
|
|
@@ -1260,8 +1265,16 @@ export function deleteLastExchange(conversationId: string): number {
|
|
|
1260
1265
|
|
|
1261
1266
|
db.transaction((tx) => {
|
|
1262
1267
|
tx.delete(messages).where(condition).run();
|
|
1268
|
+
const maxResult = tx
|
|
1269
|
+
.select({ maxCreatedAt: sql<number | null>`MAX(${messages.createdAt})` })
|
|
1270
|
+
.from(messages)
|
|
1271
|
+
.where(eq(messages.conversationId, conversationId))
|
|
1272
|
+
.get();
|
|
1263
1273
|
tx.update(conversations)
|
|
1264
|
-
.set({
|
|
1274
|
+
.set({
|
|
1275
|
+
updatedAt: Date.now(),
|
|
1276
|
+
lastMessageAt: maxResult?.maxCreatedAt ?? null,
|
|
1277
|
+
})
|
|
1265
1278
|
.where(eq(conversations.id, conversationId))
|
|
1266
1279
|
.run();
|
|
1267
1280
|
});
|
|
@@ -1278,12 +1291,10 @@ export function deleteLastExchange(conversationId: string): number {
|
|
|
1278
1291
|
*/
|
|
1279
1292
|
export interface DeletedMemoryIds {
|
|
1280
1293
|
segmentIds: string[];
|
|
1281
|
-
orphanedItemIds: string[];
|
|
1282
1294
|
deletedSummaryIds: string[];
|
|
1283
1295
|
}
|
|
1284
1296
|
|
|
1285
1297
|
export interface WipeConversationResult extends DeletedMemoryIds {
|
|
1286
|
-
unsupersededItemIds: string[];
|
|
1287
1298
|
cancelledJobCount: number;
|
|
1288
1299
|
}
|
|
1289
1300
|
|
|
@@ -1293,7 +1304,7 @@ export interface WipeConversationResult extends DeletedMemoryIds {
|
|
|
1293
1304
|
*/
|
|
1294
1305
|
export function updateMessageContent(
|
|
1295
1306
|
messageId: string,
|
|
1296
|
-
newContent: string
|
|
1307
|
+
newContent: string,
|
|
1297
1308
|
): void {
|
|
1298
1309
|
const db = getDb();
|
|
1299
1310
|
db.update(messages)
|
|
@@ -1330,7 +1341,7 @@ export function updateMessageMetadata(
|
|
|
1330
1341
|
*/
|
|
1331
1342
|
export function relinkAttachments(
|
|
1332
1343
|
fromMessageIds: string[],
|
|
1333
|
-
toMessageId: string
|
|
1344
|
+
toMessageId: string,
|
|
1334
1345
|
): number {
|
|
1335
1346
|
if (fromMessageIds.length === 0) return 0;
|
|
1336
1347
|
const db = getDb();
|
|
@@ -1365,7 +1376,6 @@ export function deleteMessageById(messageId: string): DeletedMemoryIds {
|
|
|
1365
1376
|
const db = getDb();
|
|
1366
1377
|
const result: DeletedMemoryIds = {
|
|
1367
1378
|
segmentIds: [],
|
|
1368
|
-
orphanedItemIds: [],
|
|
1369
1379
|
deletedSummaryIds: [],
|
|
1370
1380
|
};
|
|
1371
1381
|
|
|
@@ -1379,6 +1389,13 @@ export function deleteMessageById(messageId: string): DeletedMemoryIds {
|
|
|
1379
1389
|
.map((r) => r.attachmentId)
|
|
1380
1390
|
.filter((id): id is string => id !== undefined);
|
|
1381
1391
|
|
|
1392
|
+
// Look up the conversation before the transaction so we can recalculate lastMessageAt.
|
|
1393
|
+
const msgRow = db
|
|
1394
|
+
.select({ conversationId: messages.conversationId })
|
|
1395
|
+
.from(messages)
|
|
1396
|
+
.where(eq(messages.id, messageId))
|
|
1397
|
+
.get();
|
|
1398
|
+
|
|
1382
1399
|
db.transaction((tx) => {
|
|
1383
1400
|
// Collect memory segment IDs linked to this message before cascade.
|
|
1384
1401
|
const linkedSegments = tx
|
|
@@ -1398,14 +1415,29 @@ export function deleteMessageById(messageId: string): DeletedMemoryIds {
|
|
|
1398
1415
|
// and message_attachments.
|
|
1399
1416
|
tx.delete(messages).where(eq(messages.id, messageId)).run();
|
|
1400
1417
|
|
|
1418
|
+
// Recalculate lastMessageAt after deletion.
|
|
1419
|
+
if (msgRow) {
|
|
1420
|
+
const maxResult = tx
|
|
1421
|
+
.select({
|
|
1422
|
+
maxCreatedAt: sql<number | null>`MAX(${messages.createdAt})`,
|
|
1423
|
+
})
|
|
1424
|
+
.from(messages)
|
|
1425
|
+
.where(eq(messages.conversationId, msgRow.conversationId))
|
|
1426
|
+
.get();
|
|
1427
|
+
tx.update(conversations)
|
|
1428
|
+
.set({ lastMessageAt: maxResult?.maxCreatedAt ?? null })
|
|
1429
|
+
.where(eq(conversations.id, msgRow.conversationId))
|
|
1430
|
+
.run();
|
|
1431
|
+
}
|
|
1432
|
+
|
|
1401
1433
|
// Clean up segment embeddings from SQLite (Qdrant cleanup is the caller's job).
|
|
1402
1434
|
if (result.segmentIds.length > 0) {
|
|
1403
1435
|
tx.delete(memoryEmbeddings)
|
|
1404
1436
|
.where(
|
|
1405
1437
|
and(
|
|
1406
1438
|
eq(memoryEmbeddings.targetType, "segment"),
|
|
1407
|
-
inArray(memoryEmbeddings.targetId, result.segmentIds)
|
|
1408
|
-
)
|
|
1439
|
+
inArray(memoryEmbeddings.targetId, result.segmentIds),
|
|
1440
|
+
),
|
|
1409
1441
|
)
|
|
1410
1442
|
.run();
|
|
1411
1443
|
}
|
|
@@ -1418,7 +1450,7 @@ export function deleteMessageById(messageId: string): DeletedMemoryIds {
|
|
|
1418
1450
|
|
|
1419
1451
|
export function setConversationOriginChannelIfUnset(
|
|
1420
1452
|
conversationId: string,
|
|
1421
|
-
channel: ChannelId
|
|
1453
|
+
channel: ChannelId,
|
|
1422
1454
|
): void {
|
|
1423
1455
|
const db = getDb();
|
|
1424
1456
|
db.update(conversations)
|
|
@@ -1426,14 +1458,14 @@ export function setConversationOriginChannelIfUnset(
|
|
|
1426
1458
|
.where(
|
|
1427
1459
|
and(
|
|
1428
1460
|
eq(conversations.id, conversationId),
|
|
1429
|
-
isNull(conversations.originChannel)
|
|
1430
|
-
)
|
|
1461
|
+
isNull(conversations.originChannel),
|
|
1462
|
+
),
|
|
1431
1463
|
)
|
|
1432
1464
|
.run();
|
|
1433
1465
|
}
|
|
1434
1466
|
|
|
1435
1467
|
export function getConversationOriginChannel(
|
|
1436
|
-
conversationId: string
|
|
1468
|
+
conversationId: string,
|
|
1437
1469
|
): ChannelId | null {
|
|
1438
1470
|
const db = getDb();
|
|
1439
1471
|
const row = db
|
|
@@ -1446,7 +1478,7 @@ export function getConversationOriginChannel(
|
|
|
1446
1478
|
|
|
1447
1479
|
export function setConversationOriginInterfaceIfUnset(
|
|
1448
1480
|
conversationId: string,
|
|
1449
|
-
interfaceId: InterfaceId
|
|
1481
|
+
interfaceId: InterfaceId,
|
|
1450
1482
|
): void {
|
|
1451
1483
|
const db = getDb();
|
|
1452
1484
|
db.update(conversations)
|
|
@@ -1454,14 +1486,14 @@ export function setConversationOriginInterfaceIfUnset(
|
|
|
1454
1486
|
.where(
|
|
1455
1487
|
and(
|
|
1456
1488
|
eq(conversations.id, conversationId),
|
|
1457
|
-
isNull(conversations.originInterface)
|
|
1458
|
-
)
|
|
1489
|
+
isNull(conversations.originInterface),
|
|
1490
|
+
),
|
|
1459
1491
|
)
|
|
1460
1492
|
.run();
|
|
1461
1493
|
}
|
|
1462
1494
|
|
|
1463
1495
|
export function getConversationOriginInterface(
|
|
1464
|
-
conversationId: string
|
|
1496
|
+
conversationId: string,
|
|
1465
1497
|
): InterfaceId | null {
|
|
1466
1498
|
const db = getDb();
|
|
1467
1499
|
const row = db
|
|
@@ -1481,13 +1513,13 @@ export function getConversationOriginInterface(
|
|
|
1481
1513
|
* conversation itself isn't a desktop-origin private conversation).
|
|
1482
1514
|
*/
|
|
1483
1515
|
export function getConversationRecentProvenanceTrustClass(
|
|
1484
|
-
conversationId: string
|
|
1516
|
+
conversationId: string,
|
|
1485
1517
|
): "guardian" | "trusted_contact" | "unknown" | undefined {
|
|
1486
1518
|
const row = rawGet<{ metadata: string | null }>(
|
|
1487
1519
|
`SELECT metadata FROM messages
|
|
1488
1520
|
WHERE conversation_id = ? AND role = 'user' AND metadata IS NOT NULL
|
|
1489
1521
|
ORDER BY created_at DESC LIMIT 1`,
|
|
1490
|
-
conversationId
|
|
1522
|
+
conversationId,
|
|
1491
1523
|
);
|
|
1492
1524
|
if (!row?.metadata) return undefined;
|
|
1493
1525
|
try {
|
|
@@ -1508,7 +1540,7 @@ export function batchSetDisplayOrders(
|
|
|
1508
1540
|
displayOrder: number | null;
|
|
1509
1541
|
isPinned: boolean;
|
|
1510
1542
|
groupId?: string | null;
|
|
1511
|
-
}
|
|
1543
|
+
}>,
|
|
1512
1544
|
): void {
|
|
1513
1545
|
ensureDisplayOrderMigration();
|
|
1514
1546
|
ensureGroupMigration();
|
|
@@ -1518,24 +1550,26 @@ export function batchSetDisplayOrders(
|
|
|
1518
1550
|
if (update.groupId !== undefined) {
|
|
1519
1551
|
// New client: groupId is authoritative.
|
|
1520
1552
|
// Derive is_pinned from groupId.
|
|
1521
|
-
// Sanitize: if groupId references a deleted/unknown group,
|
|
1522
|
-
// to
|
|
1553
|
+
// Sanitize: if groupId is null or references a deleted/unknown group,
|
|
1554
|
+
// fall back to "system:all" to avoid FK violation that would roll back
|
|
1555
|
+
// the entire batch.
|
|
1523
1556
|
let safeGroupId = update.groupId;
|
|
1524
|
-
if (
|
|
1525
|
-
safeGroupId
|
|
1557
|
+
if (safeGroupId === null) {
|
|
1558
|
+
safeGroupId = "system:all";
|
|
1559
|
+
} else if (
|
|
1526
1560
|
!rawGet<{ id: string }>(
|
|
1527
1561
|
"SELECT id FROM conversation_groups WHERE id = ?",
|
|
1528
|
-
safeGroupId
|
|
1562
|
+
safeGroupId,
|
|
1529
1563
|
)
|
|
1530
1564
|
) {
|
|
1531
|
-
safeGroupId =
|
|
1565
|
+
safeGroupId = "system:all";
|
|
1532
1566
|
}
|
|
1533
1567
|
rawRun(
|
|
1534
1568
|
"UPDATE conversations SET display_order = ?, is_pinned = ?, group_id = ? WHERE id = ?",
|
|
1535
1569
|
update.displayOrder,
|
|
1536
1570
|
safeGroupId === "system:pinned" ? 1 : 0,
|
|
1537
1571
|
safeGroupId,
|
|
1538
|
-
update.id
|
|
1572
|
+
update.id,
|
|
1539
1573
|
);
|
|
1540
1574
|
} else {
|
|
1541
1575
|
// Old client: no groupId in payload
|
|
@@ -1546,7 +1580,7 @@ export function batchSetDisplayOrders(
|
|
|
1546
1580
|
rawRun(
|
|
1547
1581
|
"UPDATE conversations SET display_order = ?, is_pinned = 1, group_id = 'system:pinned' WHERE id = ?",
|
|
1548
1582
|
update.displayOrder,
|
|
1549
|
-
update.id
|
|
1583
|
+
update.id,
|
|
1550
1584
|
);
|
|
1551
1585
|
} else {
|
|
1552
1586
|
// Restore system group from source/conversationType when old clients
|
|
@@ -1558,12 +1592,12 @@ export function batchSetDisplayOrders(
|
|
|
1558
1592
|
WHEN source IN ('schedule', 'reminder') THEN 'system:scheduled'
|
|
1559
1593
|
WHEN source IN ('heartbeat', 'task') THEN 'system:background'
|
|
1560
1594
|
WHEN conversation_type = 'background' AND COALESCE(source, '') != 'notification' THEN 'system:background'
|
|
1561
|
-
ELSE
|
|
1595
|
+
ELSE 'system:all'
|
|
1562
1596
|
END
|
|
1563
1597
|
ELSE group_id END
|
|
1564
1598
|
WHERE id = ?`,
|
|
1565
1599
|
update.displayOrder,
|
|
1566
|
-
update.id
|
|
1600
|
+
update.id,
|
|
1567
1601
|
);
|
|
1568
1602
|
}
|
|
1569
1603
|
}
|
|
@@ -1576,7 +1610,7 @@ export function batchSetDisplayOrders(
|
|
|
1576
1610
|
}
|
|
1577
1611
|
|
|
1578
1612
|
export function getDisplayMetaForConversations(
|
|
1579
|
-
conversationIds: string[]
|
|
1613
|
+
conversationIds: string[],
|
|
1580
1614
|
): Map<
|
|
1581
1615
|
string,
|
|
1582
1616
|
{ displayOrder: number | null; isPinned: boolean; groupId: string | null }
|
|
@@ -1595,7 +1629,7 @@ export function getDisplayMetaForConversations(
|
|
|
1595
1629
|
group_id: string | null;
|
|
1596
1630
|
}>(
|
|
1597
1631
|
"SELECT display_order, is_pinned, group_id FROM conversations WHERE id = ?",
|
|
1598
|
-
id
|
|
1632
|
+
id,
|
|
1599
1633
|
);
|
|
1600
1634
|
result.set(id, {
|
|
1601
1635
|
displayOrder: row?.display_order ?? null,
|
|
@@ -1624,7 +1658,7 @@ function isToolResultMessage(role: string, content: string): boolean {
|
|
|
1624
1658
|
(block: unknown) =>
|
|
1625
1659
|
block != null &&
|
|
1626
1660
|
typeof block === "object" &&
|
|
1627
|
-
(block as Record<string, unknown>).type === "tool_result"
|
|
1661
|
+
(block as Record<string, unknown>).type === "tool_result",
|
|
1628
1662
|
);
|
|
1629
1663
|
} catch {
|
|
1630
1664
|
return false;
|
|
@@ -1644,7 +1678,7 @@ function isToolResultMessage(role: string, content: string): boolean {
|
|
|
1644
1678
|
*/
|
|
1645
1679
|
export function getTurnTimeBounds(
|
|
1646
1680
|
conversationId: string,
|
|
1647
|
-
messageCreatedAt: number
|
|
1681
|
+
messageCreatedAt: number,
|
|
1648
1682
|
): { startTime: number; endTime: number } | null {
|
|
1649
1683
|
const db = getDb();
|
|
1650
1684
|
|
|
@@ -1666,8 +1700,8 @@ export function getTurnTimeBounds(
|
|
|
1666
1700
|
.where(
|
|
1667
1701
|
and(
|
|
1668
1702
|
eq(messages.conversationId, conversationId),
|
|
1669
|
-
sql`rowid <= ${rowidSubquery}
|
|
1670
|
-
)
|
|
1703
|
+
sql`rowid <= ${rowidSubquery}`,
|
|
1704
|
+
),
|
|
1671
1705
|
)
|
|
1672
1706
|
.orderBy(sql`rowid DESC`)
|
|
1673
1707
|
.limit(50)
|
|
@@ -1698,8 +1732,8 @@ export function getTurnTimeBounds(
|
|
|
1698
1732
|
.where(
|
|
1699
1733
|
and(
|
|
1700
1734
|
eq(messages.conversationId, conversationId),
|
|
1701
|
-
sql`rowid > ${forwardRowidSubquery}
|
|
1702
|
-
)
|
|
1735
|
+
sql`rowid > ${forwardRowidSubquery}`,
|
|
1736
|
+
),
|
|
1703
1737
|
)
|
|
1704
1738
|
.orderBy(sql`rowid ASC`)
|
|
1705
1739
|
.limit(50)
|
|
@@ -1740,8 +1774,8 @@ export function getTurnTimeBounds(
|
|
|
1740
1774
|
and(
|
|
1741
1775
|
eq(llmRequestLogs.conversationId, conversationId),
|
|
1742
1776
|
gte(llmRequestLogs.createdAt, startTime),
|
|
1743
|
-
lte(llmRequestLogs.createdAt, hardCeiling)
|
|
1744
|
-
)
|
|
1777
|
+
lte(llmRequestLogs.createdAt, hardCeiling),
|
|
1778
|
+
),
|
|
1745
1779
|
)
|
|
1746
1780
|
.orderBy(desc(llmRequestLogs.createdAt))
|
|
1747
1781
|
.limit(1)
|
|
@@ -1789,8 +1823,8 @@ export function getAssistantMessageIdsInTurn(messageId: string): string[] {
|
|
|
1789
1823
|
.where(
|
|
1790
1824
|
and(
|
|
1791
1825
|
eq(messages.conversationId, target.conversationId),
|
|
1792
|
-
lte(messages.createdAt, target.createdAt)
|
|
1793
|
-
)
|
|
1826
|
+
lte(messages.createdAt, target.createdAt),
|
|
1827
|
+
),
|
|
1794
1828
|
)
|
|
1795
1829
|
.orderBy(desc(messages.createdAt))
|
|
1796
1830
|
.limit(50)
|
|
@@ -1827,8 +1861,8 @@ export function getAssistantMessageIdsInTurn(messageId: string): string[] {
|
|
|
1827
1861
|
.where(
|
|
1828
1862
|
and(
|
|
1829
1863
|
eq(messages.conversationId, target.conversationId),
|
|
1830
|
-
gt(messages.createdAt, target.createdAt)
|
|
1831
|
-
)
|
|
1864
|
+
gt(messages.createdAt, target.createdAt),
|
|
1865
|
+
),
|
|
1832
1866
|
)
|
|
1833
1867
|
.orderBy(asc(messages.createdAt))
|
|
1834
1868
|
.limit(50)
|
|
@@ -1864,8 +1898,8 @@ export function getAssistantMessageIdsInTurn(messageId: string): string[] {
|
|
|
1864
1898
|
and(
|
|
1865
1899
|
eq(messages.conversationId, target.conversationId),
|
|
1866
1900
|
gt(messages.createdAt, boundaryCreatedAt),
|
|
1867
|
-
lte(messages.createdAt, target.createdAt)
|
|
1868
|
-
)
|
|
1901
|
+
lte(messages.createdAt, target.createdAt),
|
|
1902
|
+
),
|
|
1869
1903
|
)
|
|
1870
1904
|
.orderBy(asc(messages.createdAt))
|
|
1871
1905
|
.all();
|
|
@@ -1888,8 +1922,8 @@ export function getAssistantMessageIdsInTurn(messageId: string): string[] {
|
|
|
1888
1922
|
.where(
|
|
1889
1923
|
and(
|
|
1890
1924
|
eq(messages.conversationId, target.conversationId),
|
|
1891
|
-
inArray(messages.id, [...idSet])
|
|
1892
|
-
)
|
|
1925
|
+
inArray(messages.id, [...idSet]),
|
|
1926
|
+
),
|
|
1893
1927
|
)
|
|
1894
1928
|
.orderBy(asc(messages.createdAt))
|
|
1895
1929
|
.all();
|