vellum 0.2.0 → 0.2.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/README.md +15 -2
- package/bun.lock +5 -2
- package/package.json +4 -2
- package/scripts/capture-x-graphql.ts +562 -0
- package/scripts/ipc/check-swift-decoder-drift.ts +2 -1
- package/scripts/test.sh +5 -0
- package/src/__tests__/__snapshots__/ipc-snapshot.test.ts.snap +161 -34
- package/src/__tests__/account-registry.test.ts +2 -1
- package/src/__tests__/agent-heartbeat-service.test.ts +250 -0
- package/src/__tests__/app-bundler.test.ts +12 -33
- package/src/__tests__/asset-materialize-tool.test.ts +16 -15
- package/src/__tests__/asset-search-tool.test.ts +23 -22
- package/src/__tests__/attachments-store.test.ts +56 -127
- package/src/__tests__/browser-skill-baseline-tool-payload.test.ts +5 -4
- package/src/__tests__/browser-skill-endstate.test.ts +5 -8
- package/src/__tests__/call-bridge.test.ts +385 -0
- package/src/__tests__/call-constants.test.ts +40 -0
- package/src/__tests__/call-orchestrator.test.ts +454 -0
- package/src/__tests__/call-recovery.test.ts +518 -0
- package/src/__tests__/call-routes-http.test.ts +459 -0
- package/src/__tests__/call-state-machine.test.ts +143 -0
- package/src/__tests__/call-state.test.ts +133 -0
- package/src/__tests__/call-store.test.ts +691 -0
- package/src/__tests__/cli-discover.test.ts +1 -1
- package/src/__tests__/commit-message-enrichment-service.test.ts +550 -0
- package/src/__tests__/compaction.benchmark.test.ts +176 -0
- package/src/__tests__/computer-use-tools.test.ts +250 -0
- package/src/__tests__/config-schema.test.ts +348 -3
- package/src/__tests__/conflict-store.test.ts +2 -1
- package/src/__tests__/contacts-tools.test.ts +331 -0
- package/src/__tests__/conversation-store.test.ts +30 -32
- package/src/__tests__/credential-security-invariants.test.ts +4 -0
- package/src/__tests__/date-context.test.ts +373 -0
- package/src/__tests__/db-schedule-syntax-migration.test.ts +129 -0
- package/src/__tests__/doordash-session.test.ts +9 -0
- package/src/__tests__/fixtures/media-reuse-fixtures.ts +3 -3
- package/src/__tests__/followup-tools.test.ts +303 -0
- package/src/__tests__/handlers-twitter-config.test.ts +718 -0
- package/src/__tests__/intent-routing.test.ts +64 -57
- package/src/__tests__/ipc-roundtrip.benchmark.test.ts +237 -0
- package/src/__tests__/ipc-snapshot.test.ts +96 -28
- package/src/__tests__/llm-usage-store.test.ts +3 -8
- package/src/__tests__/media-generate-image.test.ts +1 -1
- package/src/__tests__/media-reuse-story.e2e.test.ts +7 -7
- package/src/__tests__/memory-retrieval.benchmark.test.ts +430 -0
- package/src/__tests__/parallel-tool.benchmark.test.ts +294 -0
- package/src/__tests__/playbook-tools.test.ts +342 -0
- package/src/__tests__/profile-compiler.test.ts +2 -1
- package/src/__tests__/provider-streaming.benchmark.test.ts +773 -0
- package/src/__tests__/recurrence-engine-rruleset.test.ts +78 -0
- package/src/__tests__/recurrence-engine.test.ts +69 -0
- package/src/__tests__/recurrence-types.test.ts +71 -0
- package/src/__tests__/registry.test.ts +17 -10
- package/src/__tests__/relay-server.test.ts +633 -0
- package/src/__tests__/reminder-store.test.ts +6 -3
- package/src/__tests__/reminder.test.ts +43 -77
- package/src/__tests__/run-orchestrator-assistant-events.test.ts +222 -0
- package/src/__tests__/run-orchestrator.test.ts +7 -7
- package/src/__tests__/runtime-attachment-metadata.test.ts +19 -20
- package/src/__tests__/runtime-runs-http.test.ts +5 -23
- package/src/__tests__/runtime-runs.test.ts +11 -11
- package/src/__tests__/schedule-store.test.ts +482 -0
- package/src/__tests__/schedule-tools.test.ts +700 -0
- package/src/__tests__/scheduler-recurrence.test.ts +329 -0
- package/src/__tests__/server-history-render.test.ts +14 -13
- package/src/__tests__/session-error.test.ts +28 -0
- package/src/__tests__/session-init.benchmark.test.ts +462 -0
- package/src/__tests__/session-queue.test.ts +89 -16
- package/src/__tests__/session-runtime-assembly.test.ts +161 -0
- package/src/__tests__/session-surfaces-task-progress.test.ts +104 -0
- package/src/__tests__/signup-e2e.test.ts +2 -1
- package/src/__tests__/skill-projection.benchmark.test.ts +328 -0
- package/src/__tests__/skill-script-runner.test.ts +159 -0
- package/src/__tests__/speaker-identification.test.ts +52 -0
- package/src/__tests__/subagent-manager-notify.test.ts +42 -10
- package/src/__tests__/subagent-tools.test.ts +141 -41
- package/src/__tests__/task-compiler.test.ts +2 -1
- package/src/__tests__/task-runner.test.ts +2 -1
- package/src/__tests__/task-scheduler.test.ts +2 -1
- package/src/__tests__/task-tools.test.ts +49 -56
- package/src/__tests__/tool-audit-listener.test.ts +1 -0
- package/src/__tests__/tool-domain-event-publisher.test.ts +2 -0
- package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +500 -0
- package/src/__tests__/tool-executor.test.ts +13 -17
- package/src/__tests__/turn-commit.test.ts +273 -2
- package/src/__tests__/twilio-provider.test.ts +143 -0
- package/src/__tests__/twilio-routes.test.ts +789 -0
- package/src/__tests__/twitter-auth-handler.test.ts +581 -0
- package/src/__tests__/view-image-tool.test.ts +217 -0
- package/src/__tests__/workspace-git-service.test.ts +403 -0
- package/src/__tests__/workspace-heartbeat-service.test.ts +141 -2
- package/src/agent-heartbeat/agent-heartbeat-service.ts +155 -0
- package/src/bundler/app-bundler.ts +35 -14
- package/src/calls/call-bridge.ts +95 -0
- package/src/calls/call-constants.ts +48 -0
- package/src/calls/call-domain.ts +276 -0
- package/src/calls/call-orchestrator.ts +390 -0
- package/src/calls/call-recovery.ts +207 -0
- package/src/calls/call-state-machine.ts +68 -0
- package/src/calls/call-state.ts +64 -0
- package/src/calls/call-store.ts +416 -0
- package/src/calls/relay-server.ts +335 -0
- package/src/calls/speaker-identification.ts +213 -0
- package/src/calls/twilio-config.ts +34 -0
- package/src/calls/twilio-provider.ts +173 -0
- package/src/calls/twilio-routes.ts +250 -0
- package/src/calls/types.ts +37 -0
- package/src/calls/voice-provider.ts +14 -0
- package/src/cli/config-commands.ts +334 -0
- package/src/cli/core-commands.ts +776 -0
- package/src/cli/doordash.ts +256 -25
- package/src/cli/ipc-client.ts +82 -0
- package/src/cli/map.ts +246 -0
- package/src/cli/twitter.ts +575 -0
- package/src/cli.ts +7 -5
- package/src/commands/__tests__/cc-command-registry.test.ts +319 -0
- package/src/commands/cc-command-registry.ts +209 -0
- package/src/config/bundled-skills/contacts/SKILL.md +39 -0
- package/src/config/bundled-skills/contacts/TOOLS.json +122 -0
- package/src/config/bundled-skills/contacts/tools/contact-merge.ts +9 -0
- package/src/config/bundled-skills/contacts/tools/contact-search.ts +9 -0
- package/src/config/bundled-skills/contacts/tools/contact-upsert.ts +9 -0
- package/src/config/bundled-skills/document/SKILL.md +18 -0
- package/src/config/bundled-skills/document/TOOLS.json +53 -0
- package/src/config/bundled-skills/document/tools/document-create.ts +9 -0
- package/src/config/bundled-skills/document/tools/document-update.ts +9 -0
- package/src/config/bundled-skills/doordash/SKILL.md +163 -0
- package/src/config/bundled-skills/followups/SKILL.md +32 -0
- package/src/config/bundled-skills/followups/TOOLS.json +100 -0
- package/src/config/bundled-skills/followups/tools/followup-create.ts +9 -0
- package/src/config/bundled-skills/followups/tools/followup-list.ts +9 -0
- package/src/config/bundled-skills/followups/tools/followup-resolve.ts +9 -0
- package/src/config/bundled-skills/image-studio/TOOLS.json +2 -2
- package/src/config/bundled-skills/image-studio/tools/media-generate-image.ts +2 -24
- package/src/config/bundled-skills/messaging/tools/messaging-analyze-style.ts +2 -1
- package/src/config/bundled-skills/playbooks/SKILL.md +31 -0
- package/src/config/bundled-skills/playbooks/TOOLS.json +126 -0
- package/src/config/bundled-skills/playbooks/tools/playbook-create.ts +9 -0
- package/src/config/bundled-skills/playbooks/tools/playbook-delete.ts +9 -0
- package/src/config/bundled-skills/playbooks/tools/playbook-list.ts +9 -0
- package/src/config/bundled-skills/playbooks/tools/playbook-update.ts +9 -0
- package/src/config/bundled-skills/reminder/SKILL.md +20 -0
- package/src/config/bundled-skills/reminder/TOOLS.json +67 -0
- package/src/config/bundled-skills/reminder/tools/reminder-cancel.ts +9 -0
- package/src/config/bundled-skills/reminder/tools/reminder-create.ts +9 -0
- package/src/config/bundled-skills/reminder/tools/reminder-list.ts +9 -0
- package/src/config/bundled-skills/schedule/SKILL.md +74 -0
- package/src/config/bundled-skills/schedule/TOOLS.json +135 -0
- package/src/config/bundled-skills/schedule/tools/schedule-create.ts +9 -0
- package/src/config/bundled-skills/schedule/tools/schedule-delete.ts +9 -0
- package/src/config/bundled-skills/schedule/tools/schedule-list.ts +9 -0
- package/src/config/bundled-skills/schedule/tools/schedule-update.ts +9 -0
- package/src/config/bundled-skills/subagent/SKILL.md +25 -0
- package/src/config/bundled-skills/subagent/TOOLS.json +107 -0
- package/src/config/bundled-skills/subagent/tools/subagent-abort.ts +9 -0
- package/src/config/bundled-skills/subagent/tools/subagent-message.ts +9 -0
- package/src/config/bundled-skills/subagent/tools/subagent-read.ts +9 -0
- package/src/config/bundled-skills/subagent/tools/subagent-spawn.ts +9 -0
- package/src/config/bundled-skills/subagent/tools/subagent-status.ts +9 -0
- package/src/config/bundled-skills/tasks/SKILL.md +28 -0
- package/src/config/bundled-skills/tasks/TOOLS.json +256 -0
- package/src/config/bundled-skills/tasks/tools/task-delete.ts +9 -0
- package/src/config/bundled-skills/tasks/tools/task-list-add.ts +9 -0
- package/src/config/bundled-skills/tasks/tools/task-list-remove.ts +9 -0
- package/src/config/bundled-skills/tasks/tools/task-list-show.ts +9 -0
- package/src/config/bundled-skills/tasks/tools/task-list-update.ts +9 -0
- package/src/config/bundled-skills/tasks/tools/task-list.ts +9 -0
- package/src/config/bundled-skills/tasks/tools/task-run.ts +9 -0
- package/src/config/bundled-skills/tasks/tools/task-save.ts +9 -0
- package/src/config/bundled-skills/twitter/SKILL.md +134 -0
- package/src/config/bundled-skills/watcher/SKILL.md +27 -0
- package/src/config/bundled-skills/watcher/TOOLS.json +147 -0
- package/src/config/bundled-skills/watcher/tools/watcher-create.ts +9 -0
- package/src/config/bundled-skills/watcher/tools/watcher-delete.ts +9 -0
- package/src/config/bundled-skills/watcher/tools/watcher-digest.ts +9 -0
- package/src/config/bundled-skills/watcher/tools/watcher-list.ts +9 -0
- package/src/config/bundled-skills/watcher/tools/watcher-update.ts +9 -0
- package/src/config/defaults.ts +44 -0
- package/src/config/loader.ts +4 -1
- package/src/config/schema.ts +218 -1
- package/src/config/system-prompt.ts +100 -6
- package/src/config/templates/IDENTITY.md +7 -0
- package/src/config/types.ts +5 -0
- package/src/contacts/contact-store.ts +4 -4
- package/src/daemon/assistant-attachments.ts +10 -0
- package/src/daemon/classifier.ts +3 -1
- package/src/daemon/computer-use-session.ts +3 -1
- package/src/daemon/date-context.ts +136 -0
- package/src/daemon/handlers/apps.ts +16 -1
- package/src/daemon/handlers/browser.ts +54 -0
- package/src/daemon/handlers/computer-use.ts +7 -1
- package/src/daemon/handlers/config.ts +192 -4
- package/src/daemon/handlers/diagnostics.ts +5 -1
- package/src/daemon/handlers/documents.ts +18 -29
- package/src/daemon/handlers/home-base.ts +5 -1
- package/src/daemon/handlers/index.ts +40 -271
- package/src/daemon/handlers/misc.ts +9 -1
- package/src/daemon/handlers/publish.ts +6 -1
- package/src/daemon/handlers/sessions.ts +65 -12
- package/src/daemon/handlers/shared.ts +36 -1
- package/src/daemon/handlers/signing.ts +37 -0
- package/src/daemon/handlers/skills.ts +20 -6
- package/src/daemon/handlers/subagents.ts +8 -3
- package/src/daemon/handlers/twitter-auth.ts +169 -0
- package/src/daemon/handlers/work-items.ts +495 -39
- package/src/daemon/ipc-contract-inventory.json +40 -4
- package/src/daemon/ipc-contract.ts +185 -37
- package/src/daemon/ipc-protocol.ts +7 -2
- package/src/daemon/lifecycle.ts +48 -5
- package/src/daemon/main.ts +10 -4
- package/src/daemon/ride-shotgun-handler.ts +74 -10
- package/src/daemon/server.ts +144 -29
- package/src/daemon/session-agent-loop.ts +887 -0
- package/src/daemon/session-attachments.ts +28 -5
- package/src/daemon/session-error.ts +24 -3
- package/src/daemon/session-lifecycle.ts +147 -0
- package/src/daemon/session-media-retry.ts +147 -0
- package/src/daemon/session-messaging.ts +145 -0
- package/src/daemon/session-notifiers.ts +164 -0
- package/src/daemon/session-process.ts +2 -2
- package/src/daemon/session-queue-manager.ts +1 -0
- package/src/daemon/session-runtime-assembly.ts +52 -0
- package/src/daemon/session-skill-tools.ts +124 -5
- package/src/daemon/session-slash.ts +3 -0
- package/src/daemon/session-surfaces.ts +77 -2
- package/src/daemon/session-tool-setup.ts +222 -2
- package/src/daemon/session-usage.ts +0 -2
- package/src/daemon/session.ts +114 -1365
- package/src/daemon/video-thumbnail.ts +60 -0
- package/src/doordash/client.ts +121 -27
- package/src/doordash/queries.ts +1 -2
- package/src/export/formatter.ts +3 -1
- package/src/followups/followup-store.ts +4 -2
- package/src/followups/types.ts +6 -0
- package/src/hooks/templates.ts +1 -1
- package/src/index.ts +32 -1151
- package/src/media/gemini-image-service.ts +1 -1
- package/src/memory/attachments-store.ts +28 -83
- package/src/memory/channel-delivery-store.ts +7 -21
- package/src/memory/clarification-resolver.ts +6 -5
- package/src/memory/contradiction-checker.ts +3 -2
- package/src/memory/conversation-key-store.ts +10 -29
- package/src/memory/conversation-store.ts +2 -1
- package/src/memory/db.ts +362 -2
- package/src/memory/entity-extractor.ts +6 -3
- package/src/memory/items-extractor.ts +5 -4
- package/src/memory/jobs-store.ts +3 -2
- package/src/memory/llm-usage-store.ts +1 -2
- package/src/memory/runs-store.ts +1 -2
- package/src/memory/schema.ts +65 -2
- package/src/messaging/style-analyzer.ts +3 -2
- package/src/messaging/thread-summarizer.ts +8 -12
- package/src/messaging/triage-engine.ts +4 -2
- package/src/providers/openrouter/client.ts +20 -0
- package/src/providers/registry.ts +8 -0
- package/src/runtime/http-server.ts +277 -25
- package/src/runtime/http-types.ts +0 -2
- package/src/runtime/routes/attachment-routes.ts +5 -6
- package/src/runtime/routes/call-routes.ts +140 -0
- package/src/runtime/routes/channel-routes.ts +12 -19
- package/src/runtime/routes/conversation-routes.ts +5 -9
- package/src/runtime/routes/run-routes.ts +4 -8
- package/src/runtime/run-orchestrator.ts +39 -6
- package/src/schedule/recurrence-engine.ts +138 -0
- package/src/schedule/recurrence-types.ts +67 -0
- package/src/schedule/schedule-store.ts +102 -57
- package/src/schedule/scheduler.ts +9 -6
- package/src/security/oauth2.ts +29 -4
- package/src/security/secret-allowlist.ts +46 -0
- package/src/skills/clawhub.ts +1 -1
- package/src/subagent/manager.ts +40 -8
- package/src/swarm/backend-claude-code.ts +64 -9
- package/src/swarm/worker-prompts.ts +2 -1
- package/src/tasks/SPEC.md +34 -28
- package/src/tasks/ephemeral-permissions.ts +16 -7
- package/src/tasks/task-compiler.ts +5 -4
- package/src/tasks/task-runner.ts +10 -5
- package/src/tasks/task-scheduler.ts +1 -1
- package/src/tasks/tool-sanitizer.ts +36 -0
- package/src/tools/assets/search.ts +4 -4
- package/src/tools/browser/api-map.ts +220 -0
- package/src/tools/browser/auto-navigate.ts +270 -0
- package/src/tools/browser/browser-execution.ts +2 -1
- package/src/tools/browser/browser-manager.ts +2 -2
- package/src/tools/browser/network-recorder.ts +5 -4
- package/src/tools/browser/x-auto-navigate.ts +207 -0
- package/src/tools/calls/call-end.ts +67 -0
- package/src/tools/calls/call-start.ts +73 -0
- package/src/tools/calls/call-status.ts +81 -0
- package/src/tools/claude-code/claude-code.ts +77 -11
- package/src/tools/contacts/contact-merge.ts +46 -78
- package/src/tools/contacts/contact-search.ts +35 -79
- package/src/tools/contacts/contact-upsert.ts +35 -108
- package/src/tools/credentials/vault.ts +21 -5
- package/src/tools/document/document-tool.ts +71 -144
- package/src/tools/executor.ts +129 -10
- package/src/tools/followups/followup_create.ts +46 -88
- package/src/tools/followups/followup_list.ts +34 -74
- package/src/tools/followups/followup_resolve.ts +31 -66
- package/src/tools/host-terminal/cli-discover.ts +2 -1
- package/src/tools/host-terminal/host-shell.ts +10 -0
- package/src/tools/memory/handlers.ts +5 -4
- package/src/tools/network/__tests__/web-search.test.ts +427 -0
- package/src/tools/network/script-proxy/__tests__/logging.test.ts +248 -0
- package/src/tools/network/script-proxy/__tests__/policy.test.ts +234 -0
- package/src/tools/network/script-proxy/__tests__/router.test.ts +76 -0
- package/src/tools/network/web-fetch.ts +18 -6
- package/src/tools/playbooks/index.ts +4 -5
- package/src/tools/playbooks/playbook-create.ts +3 -47
- package/src/tools/playbooks/playbook-delete.ts +1 -25
- package/src/tools/playbooks/playbook-list.ts +1 -28
- package/src/tools/playbooks/playbook-update.ts +3 -51
- package/src/tools/registry.ts +2 -4
- package/src/tools/reminder/reminder.ts +5 -78
- package/src/tools/schedule/create.ts +69 -74
- package/src/tools/schedule/delete.ts +21 -47
- package/src/tools/schedule/list.ts +55 -74
- package/src/tools/schedule/update.ts +77 -84
- package/src/tools/subagent/abort.ts +29 -58
- package/src/tools/subagent/message.ts +30 -63
- package/src/tools/subagent/read.ts +53 -84
- package/src/tools/subagent/spawn.ts +43 -82
- package/src/tools/subagent/status.ts +42 -71
- package/src/tools/swarm/delegate.ts +2 -1
- package/src/tools/tasks/index.ts +8 -6
- package/src/tools/tasks/task-delete.ts +69 -56
- package/src/tools/tasks/task-list.ts +31 -52
- package/src/tools/tasks/task-run.ts +74 -102
- package/src/tools/tasks/task-save.ts +33 -65
- package/src/tools/tasks/work-item-enqueue.ts +192 -134
- package/src/tools/tasks/work-item-list.ts +33 -78
- package/src/tools/tasks/work-item-remove.ts +60 -0
- package/src/tools/tasks/work-item-update.ts +114 -0
- package/src/tools/terminal/backends/native.ts +3 -1
- package/src/tools/tool-manifest.ts +20 -74
- package/src/tools/types.ts +6 -0
- package/src/tools/ui-surface/definitions.ts +6 -1
- package/src/tools/watch/screen-watch.ts +3 -1
- package/src/tools/watcher/create.ts +52 -98
- package/src/tools/watcher/delete.ts +20 -46
- package/src/tools/watcher/digest.ts +36 -70
- package/src/tools/watcher/list.ts +49 -79
- package/src/tools/watcher/update.ts +45 -91
- package/src/twitter/client.ts +690 -0
- package/src/twitter/session.ts +91 -0
- package/src/usage/types.ts +0 -1
- package/src/util/truncate.ts +6 -0
- package/src/watcher/providers/slack.ts +2 -1
- package/src/watcher/watcher-store.ts +3 -2
- package/src/work-items/work-item-store.ts +236 -2
- package/src/workspace/commit-message-enrichment-service.ts +284 -0
- package/src/workspace/commit-message-provider.ts +95 -0
- package/src/workspace/git-service.ts +272 -52
- package/src/workspace/heartbeat-service.ts +70 -13
- package/src/workspace/provider-commit-message-generator.ts +242 -0
- package/src/workspace/turn-commit.ts +100 -51
- package/src/tools/contacts/index.ts +0 -4
- package/src/tools/document/index.ts +0 -5
- package/src/tools/followups/index.ts +0 -3
- package/src/tools/subagent/index.ts +0 -5
- /package/src/__tests__/{memory-context-benchmark.test.ts → memory-context-benchmark.benchmark.test.ts} +0 -0
package/src/memory/db.ts
CHANGED
|
@@ -3,6 +3,9 @@ import { drizzle } from 'drizzle-orm/bun-sqlite';
|
|
|
3
3
|
import { computeMemoryFingerprint } from './fingerprint.js';
|
|
4
4
|
import * as schema from './schema.js';
|
|
5
5
|
import { getDbPath, ensureDataDir, migrateToDataLayout, migrateToWorkspaceLayout } from '../util/platform.js';
|
|
6
|
+
import { getLogger } from '../util/logger.js';
|
|
7
|
+
|
|
8
|
+
const log = getLogger('memory-db');
|
|
6
9
|
|
|
7
10
|
let db: ReturnType<typeof drizzle<typeof schema>> | null = null;
|
|
8
11
|
|
|
@@ -271,6 +274,7 @@ export function initializeDb(): void {
|
|
|
271
274
|
name TEXT NOT NULL,
|
|
272
275
|
enabled INTEGER NOT NULL DEFAULT 1,
|
|
273
276
|
cron_expression TEXT NOT NULL,
|
|
277
|
+
schedule_syntax TEXT NOT NULL DEFAULT 'cron',
|
|
274
278
|
timezone TEXT,
|
|
275
279
|
message TEXT NOT NULL,
|
|
276
280
|
next_run_at INTEGER NOT NULL,
|
|
@@ -337,8 +341,8 @@ export function initializeDb(): void {
|
|
|
337
341
|
)
|
|
338
342
|
`);
|
|
339
343
|
|
|
340
|
-
try { database.run(/*sql*/ `ALTER TABLE published_pages ADD COLUMN app_id TEXT`); } catch {}
|
|
341
|
-
try { database.run(/*sql*/ `ALTER TABLE published_pages ADD COLUMN project_slug TEXT`); } catch {}
|
|
344
|
+
try { database.run(/*sql*/ `ALTER TABLE published_pages ADD COLUMN app_id TEXT`); } catch (e) { log.debug({ err: e }, 'ALTER TABLE published_pages ADD COLUMN app_id (likely already exists)'); }
|
|
345
|
+
try { database.run(/*sql*/ `ALTER TABLE published_pages ADD COLUMN project_slug TEXT`); } catch (e) { log.debug({ err: e }, 'ALTER TABLE published_pages ADD COLUMN project_slug (likely already exists)'); }
|
|
342
346
|
|
|
343
347
|
database.run(/*sql*/ `
|
|
344
348
|
CREATE TABLE IF NOT EXISTS shared_app_links (
|
|
@@ -538,12 +542,16 @@ export function initializeDb(): void {
|
|
|
538
542
|
try { database.run(/*sql*/ `ALTER TABLE channel_inbound_events ADD COLUMN raw_payload TEXT`); } catch { /* already exists */ }
|
|
539
543
|
try { database.run(/*sql*/ `ALTER TABLE conversations ADD COLUMN thread_type TEXT NOT NULL DEFAULT 'standard'`); } catch { /* already exists */ }
|
|
540
544
|
try { database.run(/*sql*/ `ALTER TABLE conversations ADD COLUMN memory_scope_id TEXT NOT NULL DEFAULT 'default'`); } catch { /* already exists */ }
|
|
545
|
+
try { database.run(/*sql*/ `ALTER TABLE attachments ADD COLUMN thumbnail_base64 TEXT`); } catch { /* already exists */ }
|
|
546
|
+
try { database.run(/*sql*/ `ALTER TABLE cron_jobs ADD COLUMN schedule_syntax TEXT NOT NULL DEFAULT 'cron'`); } catch { /* already exists */ }
|
|
547
|
+
try { database.run(/*sql*/ `ALTER TABLE messages ADD COLUMN metadata TEXT`); } catch { /* already exists */ }
|
|
541
548
|
|
|
542
549
|
migrateJobDeferrals(database);
|
|
543
550
|
migrateToolInvocationsFk(database);
|
|
544
551
|
migrateMemoryEntityRelationDedup(database);
|
|
545
552
|
migrateMemoryItemsFingerprintScopeUnique(database);
|
|
546
553
|
migrateMemoryItemsScopeSaltedFingerprints(database);
|
|
554
|
+
migrateAssistantIdToSelf(database);
|
|
547
555
|
|
|
548
556
|
// Indexes for query performance on large datasets
|
|
549
557
|
database.run(/*sql*/ `CREATE INDEX IF NOT EXISTS idx_llm_request_logs_conv_created ON llm_request_logs(conversation_id, created_at)`);
|
|
@@ -600,6 +608,7 @@ export function initializeDb(): void {
|
|
|
600
608
|
database.run(/*sql*/ `CREATE INDEX IF NOT EXISTS idx_reminders_status_fire_at ON reminders(status, fire_at)`);
|
|
601
609
|
|
|
602
610
|
database.run(/*sql*/ `CREATE INDEX IF NOT EXISTS idx_cron_jobs_enabled_next_run ON cron_jobs(enabled, next_run_at)`);
|
|
611
|
+
database.run(/*sql*/ `CREATE INDEX IF NOT EXISTS idx_cron_jobs_syntax_enabled_next_run ON cron_jobs(schedule_syntax, enabled, next_run_at)`);
|
|
603
612
|
database.run(/*sql*/ `CREATE INDEX IF NOT EXISTS idx_cron_runs_job_id ON cron_runs(job_id)`);
|
|
604
613
|
|
|
605
614
|
database.run(/*sql*/ `CREATE INDEX IF NOT EXISTS idx_accounts_service ON accounts(service)`);
|
|
@@ -685,6 +694,75 @@ export function initializeDb(): void {
|
|
|
685
694
|
database.run(/*sql*/ `CREATE INDEX IF NOT EXISTS idx_triage_results_sender ON triage_results(sender)`);
|
|
686
695
|
database.run(/*sql*/ `CREATE INDEX IF NOT EXISTS idx_triage_results_created_at ON triage_results(created_at DESC)`);
|
|
687
696
|
|
|
697
|
+
// ── Call Sessions (outgoing AI phone calls) ────────────────────────
|
|
698
|
+
|
|
699
|
+
database.run(/*sql*/ `
|
|
700
|
+
CREATE TABLE IF NOT EXISTS call_sessions (
|
|
701
|
+
id TEXT PRIMARY KEY,
|
|
702
|
+
conversation_id TEXT NOT NULL REFERENCES conversations(id) ON DELETE CASCADE,
|
|
703
|
+
provider TEXT NOT NULL,
|
|
704
|
+
provider_call_sid TEXT,
|
|
705
|
+
from_number TEXT NOT NULL,
|
|
706
|
+
to_number TEXT NOT NULL,
|
|
707
|
+
task TEXT,
|
|
708
|
+
status TEXT NOT NULL DEFAULT 'initiated',
|
|
709
|
+
started_at INTEGER,
|
|
710
|
+
ended_at INTEGER,
|
|
711
|
+
last_error TEXT,
|
|
712
|
+
created_at INTEGER NOT NULL,
|
|
713
|
+
updated_at INTEGER NOT NULL
|
|
714
|
+
)
|
|
715
|
+
`);
|
|
716
|
+
|
|
717
|
+
database.run(/*sql*/ `
|
|
718
|
+
CREATE TABLE IF NOT EXISTS call_events (
|
|
719
|
+
id TEXT PRIMARY KEY,
|
|
720
|
+
call_session_id TEXT NOT NULL REFERENCES call_sessions(id) ON DELETE CASCADE,
|
|
721
|
+
event_type TEXT NOT NULL,
|
|
722
|
+
payload_json TEXT NOT NULL DEFAULT '{}',
|
|
723
|
+
created_at INTEGER NOT NULL
|
|
724
|
+
)
|
|
725
|
+
`);
|
|
726
|
+
|
|
727
|
+
database.run(/*sql*/ `
|
|
728
|
+
CREATE TABLE IF NOT EXISTS call_pending_questions (
|
|
729
|
+
id TEXT PRIMARY KEY,
|
|
730
|
+
call_session_id TEXT NOT NULL REFERENCES call_sessions(id) ON DELETE CASCADE,
|
|
731
|
+
question_text TEXT NOT NULL,
|
|
732
|
+
status TEXT NOT NULL DEFAULT 'pending',
|
|
733
|
+
asked_at INTEGER NOT NULL,
|
|
734
|
+
answered_at INTEGER,
|
|
735
|
+
answer_text TEXT
|
|
736
|
+
)
|
|
737
|
+
`);
|
|
738
|
+
|
|
739
|
+
database.run(/*sql*/ `
|
|
740
|
+
CREATE TABLE IF NOT EXISTS processed_callbacks (
|
|
741
|
+
id TEXT PRIMARY KEY,
|
|
742
|
+
dedupe_key TEXT NOT NULL UNIQUE,
|
|
743
|
+
call_session_id TEXT NOT NULL REFERENCES call_sessions(id) ON DELETE CASCADE,
|
|
744
|
+
created_at INTEGER NOT NULL
|
|
745
|
+
)
|
|
746
|
+
`);
|
|
747
|
+
|
|
748
|
+
database.run(/*sql*/ `CREATE INDEX IF NOT EXISTS idx_call_sessions_conversation_id ON call_sessions(conversation_id)`);
|
|
749
|
+
database.run(/*sql*/ `CREATE INDEX IF NOT EXISTS idx_call_sessions_provider_call_sid ON call_sessions(provider_call_sid)`);
|
|
750
|
+
database.run(/*sql*/ `CREATE INDEX IF NOT EXISTS idx_call_sessions_status ON call_sessions(status)`);
|
|
751
|
+
database.run(/*sql*/ `CREATE INDEX IF NOT EXISTS idx_call_events_call_session_id ON call_events(call_session_id)`);
|
|
752
|
+
database.run(/*sql*/ `CREATE INDEX IF NOT EXISTS idx_call_pending_questions_call_session_id ON call_pending_questions(call_session_id)`);
|
|
753
|
+
database.run(/*sql*/ `CREATE INDEX IF NOT EXISTS idx_call_pending_questions_status ON call_pending_questions(status)`);
|
|
754
|
+
database.run(/*sql*/ `CREATE INDEX IF NOT EXISTS idx_processed_callbacks_dedupe_key ON processed_callbacks(dedupe_key)`);
|
|
755
|
+
database.run(/*sql*/ `CREATE INDEX IF NOT EXISTS idx_processed_callbacks_call_session_id ON processed_callbacks(call_session_id)`);
|
|
756
|
+
|
|
757
|
+
// Add claim ownership token to prevent cross-handler claim interference
|
|
758
|
+
try { database.run(/*sql*/ `ALTER TABLE processed_callbacks ADD COLUMN claim_id TEXT`); } catch { /* already exists */ }
|
|
759
|
+
|
|
760
|
+
// Unique constraint: at most one non-null provider_call_sid per (provider, provider_call_sid).
|
|
761
|
+
// On upgraded databases that pre-date this constraint, duplicate rows may exist; deduplicate
|
|
762
|
+
// them first to avoid a UNIQUE constraint failure that would prevent startup.
|
|
763
|
+
migrateCallSessionsProviderSidDedup(database);
|
|
764
|
+
database.run(/*sql*/ `CREATE UNIQUE INDEX IF NOT EXISTS idx_call_sessions_provider_sid_unique ON call_sessions(provider, provider_call_sid) WHERE provider_call_sid IS NOT NULL`);
|
|
765
|
+
|
|
688
766
|
// ── Follow-ups ─────────────────────────────────────────────────────
|
|
689
767
|
|
|
690
768
|
database.run(/*sql*/ `
|
|
@@ -773,6 +851,13 @@ export function initializeDb(): void {
|
|
|
773
851
|
)
|
|
774
852
|
`);
|
|
775
853
|
|
|
854
|
+
// Work item run contract snapshot
|
|
855
|
+
try { database.run(/*sql*/ `ALTER TABLE work_items ADD COLUMN required_tools TEXT`); } catch (e) { log.debug({ err: e }, 'ALTER TABLE work_items ADD COLUMN required_tools (likely already exists)'); }
|
|
856
|
+
|
|
857
|
+
// Work item permission preflight columns
|
|
858
|
+
try { database.run(/*sql*/ `ALTER TABLE work_items ADD COLUMN approved_tools TEXT`); } catch (e) { log.debug({ err: e }, 'ALTER TABLE work_items ADD COLUMN approved_tools (likely already exists)'); }
|
|
859
|
+
try { database.run(/*sql*/ `ALTER TABLE work_items ADD COLUMN approval_status TEXT DEFAULT 'none'`); } catch (e) { log.debug({ err: e }, 'ALTER TABLE work_items ADD COLUMN approval_status (likely already exists)'); }
|
|
860
|
+
|
|
776
861
|
database.run(/*sql*/ `CREATE INDEX IF NOT EXISTS idx_work_items_status ON work_items(status)`);
|
|
777
862
|
database.run(/*sql*/ `CREATE INDEX IF NOT EXISTS idx_work_items_task_id ON work_items(task_id)`);
|
|
778
863
|
database.run(/*sql*/ `CREATE INDEX IF NOT EXISTS idx_work_items_priority_sort ON work_items(priority_tier, sort_index)`);
|
|
@@ -1103,3 +1188,278 @@ function migrateMemoryItemsScopeSaltedFingerprints(database: ReturnType<typeof d
|
|
|
1103
1188
|
throw e;
|
|
1104
1189
|
}
|
|
1105
1190
|
}
|
|
1191
|
+
|
|
1192
|
+
/**
|
|
1193
|
+
* One-shot migration: normalize all assistant_id values in assistant-scoped tables
|
|
1194
|
+
* to "self" so they are visible after the daemon switched to the implicit single-tenant
|
|
1195
|
+
* identity.
|
|
1196
|
+
*
|
|
1197
|
+
* Before this change, rows were keyed by the real assistantId string passed via the
|
|
1198
|
+
* HTTP route. After the route change, all lookups use the constant "self". Without this
|
|
1199
|
+
* migration an upgraded daemon would see empty history / attachment lists for existing
|
|
1200
|
+
* data that was stored under the old assistantId.
|
|
1201
|
+
*
|
|
1202
|
+
* Affected tables:
|
|
1203
|
+
* - conversation_keys UNIQUE (assistant_id, conversation_key)
|
|
1204
|
+
* - attachments UNIQUE (assistant_id, content_hash) WHERE content_hash IS NOT NULL
|
|
1205
|
+
* - channel_inbound_events UNIQUE (assistant_id, source_channel, external_chat_id, external_message_id)
|
|
1206
|
+
* - message_runs no unique constraint on assistant_id
|
|
1207
|
+
*
|
|
1208
|
+
* Data-safety guarantees:
|
|
1209
|
+
* - conversation_keys: when a key exists under both 'self' and a real assistantId, the
|
|
1210
|
+
* 'self' row is updated to point to the real-assistantId conversation (which holds the
|
|
1211
|
+
* historical message thread). The 'self' conversation may be orphaned but is not deleted.
|
|
1212
|
+
* - attachments: message_attachments links are remapped to the surviving attachment before
|
|
1213
|
+
* any duplicate row is deleted, so no message loses its attachment metadata.
|
|
1214
|
+
* - channel_inbound_events: only delivery-tracking metadata, not user content; dedup
|
|
1215
|
+
* keeps one row per unique (channel, chat, message) tuple.
|
|
1216
|
+
* - All conversations and messages remain untouched — only assistant_id index columns
|
|
1217
|
+
* and key-lookup rows are modified.
|
|
1218
|
+
*/
|
|
1219
|
+
function migrateAssistantIdToSelf(database: ReturnType<typeof drizzle<typeof schema>>): void {
|
|
1220
|
+
const raw = (database as unknown as { $client: Database }).$client;
|
|
1221
|
+
const checkpointKey = 'migration_normalize_assistant_id_to_self_v1';
|
|
1222
|
+
const checkpoint = raw.query(
|
|
1223
|
+
`SELECT 1 FROM memory_checkpoints WHERE key = ?`,
|
|
1224
|
+
).get(checkpointKey);
|
|
1225
|
+
if (checkpoint) return;
|
|
1226
|
+
|
|
1227
|
+
try {
|
|
1228
|
+
raw.exec('BEGIN');
|
|
1229
|
+
|
|
1230
|
+
// conversation_keys: UNIQUE (assistant_id, conversation_key)
|
|
1231
|
+
//
|
|
1232
|
+
// Step 1: Among non-self rows, keep only one per conversation_key so the
|
|
1233
|
+
// bulk UPDATE cannot hit a (non-self-A, key) + (non-self-B, key) collision.
|
|
1234
|
+
raw.exec(/*sql*/ `
|
|
1235
|
+
DELETE FROM conversation_keys
|
|
1236
|
+
WHERE assistant_id != 'self'
|
|
1237
|
+
AND rowid NOT IN (
|
|
1238
|
+
SELECT MIN(rowid) FROM conversation_keys
|
|
1239
|
+
WHERE assistant_id != 'self'
|
|
1240
|
+
GROUP BY conversation_key
|
|
1241
|
+
)
|
|
1242
|
+
`);
|
|
1243
|
+
// Step 2: For 'self' rows that have a non-self counterpart with the same
|
|
1244
|
+
// conversation_key, update the 'self' row to use the non-self row's
|
|
1245
|
+
// conversation_id. This preserves the historical conversation (which
|
|
1246
|
+
// has the message history from before the route change) rather than
|
|
1247
|
+
// discarding it in favour of a potentially-empty 'self' conversation.
|
|
1248
|
+
raw.exec(/*sql*/ `
|
|
1249
|
+
UPDATE conversation_keys
|
|
1250
|
+
SET conversation_id = (
|
|
1251
|
+
SELECT ck_ns.conversation_id
|
|
1252
|
+
FROM conversation_keys ck_ns
|
|
1253
|
+
WHERE ck_ns.assistant_id != 'self'
|
|
1254
|
+
AND ck_ns.conversation_key = conversation_keys.conversation_key
|
|
1255
|
+
ORDER BY ck_ns.rowid
|
|
1256
|
+
LIMIT 1
|
|
1257
|
+
)
|
|
1258
|
+
WHERE assistant_id = 'self'
|
|
1259
|
+
AND EXISTS (
|
|
1260
|
+
SELECT 1 FROM conversation_keys ck_ns
|
|
1261
|
+
WHERE ck_ns.assistant_id != 'self'
|
|
1262
|
+
AND ck_ns.conversation_key = conversation_keys.conversation_key
|
|
1263
|
+
)
|
|
1264
|
+
`);
|
|
1265
|
+
// Step 3: Delete the now-redundant non-self rows (their conversation_ids
|
|
1266
|
+
// have been preserved in the 'self' rows above).
|
|
1267
|
+
raw.exec(/*sql*/ `
|
|
1268
|
+
DELETE FROM conversation_keys
|
|
1269
|
+
WHERE assistant_id != 'self'
|
|
1270
|
+
AND EXISTS (
|
|
1271
|
+
SELECT 1 FROM conversation_keys ck2
|
|
1272
|
+
WHERE ck2.assistant_id = 'self'
|
|
1273
|
+
AND ck2.conversation_key = conversation_keys.conversation_key
|
|
1274
|
+
)
|
|
1275
|
+
`);
|
|
1276
|
+
// Step 4: Remaining non-self rows have no 'self' counterpart — safe to bulk-update.
|
|
1277
|
+
raw.exec(/*sql*/ `
|
|
1278
|
+
UPDATE conversation_keys SET assistant_id = 'self' WHERE assistant_id != 'self'
|
|
1279
|
+
`);
|
|
1280
|
+
|
|
1281
|
+
// attachments: UNIQUE (assistant_id, content_hash) WHERE content_hash IS NOT NULL
|
|
1282
|
+
//
|
|
1283
|
+
// message_attachments rows reference attachment IDs with ON DELETE CASCADE, so we
|
|
1284
|
+
// must remap links to the surviving row BEFORE deleting duplicates to avoid
|
|
1285
|
+
// silently dropping attachment metadata from messages.
|
|
1286
|
+
//
|
|
1287
|
+
// Step 1: Remap message_attachments from non-self duplicates to their survivor
|
|
1288
|
+
// (MIN rowid per content_hash group), then delete the duplicates.
|
|
1289
|
+
raw.exec(/*sql*/ `
|
|
1290
|
+
UPDATE message_attachments
|
|
1291
|
+
SET attachment_id = (
|
|
1292
|
+
SELECT a_survivor.id
|
|
1293
|
+
FROM attachments a_survivor
|
|
1294
|
+
WHERE a_survivor.assistant_id != 'self'
|
|
1295
|
+
AND a_survivor.content_hash = (
|
|
1296
|
+
SELECT a_dup.content_hash FROM attachments a_dup
|
|
1297
|
+
WHERE a_dup.id = message_attachments.attachment_id
|
|
1298
|
+
)
|
|
1299
|
+
ORDER BY a_survivor.rowid
|
|
1300
|
+
LIMIT 1
|
|
1301
|
+
)
|
|
1302
|
+
WHERE attachment_id IN (
|
|
1303
|
+
SELECT id FROM attachments
|
|
1304
|
+
WHERE assistant_id != 'self'
|
|
1305
|
+
AND content_hash IS NOT NULL
|
|
1306
|
+
AND rowid NOT IN (
|
|
1307
|
+
SELECT MIN(rowid) FROM attachments
|
|
1308
|
+
WHERE assistant_id != 'self' AND content_hash IS NOT NULL
|
|
1309
|
+
GROUP BY content_hash
|
|
1310
|
+
)
|
|
1311
|
+
)
|
|
1312
|
+
`);
|
|
1313
|
+
raw.exec(/*sql*/ `
|
|
1314
|
+
DELETE FROM attachments
|
|
1315
|
+
WHERE assistant_id != 'self'
|
|
1316
|
+
AND content_hash IS NOT NULL
|
|
1317
|
+
AND rowid NOT IN (
|
|
1318
|
+
SELECT MIN(rowid) FROM attachments
|
|
1319
|
+
WHERE assistant_id != 'self'
|
|
1320
|
+
AND content_hash IS NOT NULL
|
|
1321
|
+
GROUP BY content_hash
|
|
1322
|
+
)
|
|
1323
|
+
`);
|
|
1324
|
+
// Step 2: Remap message_attachments from non-self rows conflicting with a 'self'
|
|
1325
|
+
// row to the 'self' row, then delete the now-unlinked non-self rows.
|
|
1326
|
+
raw.exec(/*sql*/ `
|
|
1327
|
+
UPDATE message_attachments
|
|
1328
|
+
SET attachment_id = (
|
|
1329
|
+
SELECT a_self.id
|
|
1330
|
+
FROM attachments a_self
|
|
1331
|
+
WHERE a_self.assistant_id = 'self'
|
|
1332
|
+
AND a_self.content_hash = (
|
|
1333
|
+
SELECT a_ns.content_hash FROM attachments a_ns
|
|
1334
|
+
WHERE a_ns.id = message_attachments.attachment_id
|
|
1335
|
+
)
|
|
1336
|
+
LIMIT 1
|
|
1337
|
+
)
|
|
1338
|
+
WHERE attachment_id IN (
|
|
1339
|
+
SELECT id FROM attachments
|
|
1340
|
+
WHERE assistant_id != 'self'
|
|
1341
|
+
AND content_hash IS NOT NULL
|
|
1342
|
+
AND EXISTS (
|
|
1343
|
+
SELECT 1 FROM attachments a2
|
|
1344
|
+
WHERE a2.assistant_id = 'self'
|
|
1345
|
+
AND a2.content_hash = attachments.content_hash
|
|
1346
|
+
)
|
|
1347
|
+
)
|
|
1348
|
+
`);
|
|
1349
|
+
raw.exec(/*sql*/ `
|
|
1350
|
+
DELETE FROM attachments
|
|
1351
|
+
WHERE assistant_id != 'self'
|
|
1352
|
+
AND content_hash IS NOT NULL
|
|
1353
|
+
AND EXISTS (
|
|
1354
|
+
SELECT 1 FROM attachments a2
|
|
1355
|
+
WHERE a2.assistant_id = 'self'
|
|
1356
|
+
AND a2.content_hash = attachments.content_hash
|
|
1357
|
+
)
|
|
1358
|
+
`);
|
|
1359
|
+
// Step 3: Bulk-update remaining non-self rows.
|
|
1360
|
+
raw.exec(/*sql*/ `
|
|
1361
|
+
UPDATE attachments SET assistant_id = 'self' WHERE assistant_id != 'self'
|
|
1362
|
+
`);
|
|
1363
|
+
|
|
1364
|
+
// channel_inbound_events: UNIQUE (assistant_id, source_channel, external_chat_id, external_message_id)
|
|
1365
|
+
// Step 1: Dedup non-self rows sharing the same (source_channel, external_chat_id, external_message_id).
|
|
1366
|
+
raw.exec(/*sql*/ `
|
|
1367
|
+
DELETE FROM channel_inbound_events
|
|
1368
|
+
WHERE assistant_id != 'self'
|
|
1369
|
+
AND rowid NOT IN (
|
|
1370
|
+
SELECT MIN(rowid) FROM channel_inbound_events
|
|
1371
|
+
WHERE assistant_id != 'self'
|
|
1372
|
+
GROUP BY source_channel, external_chat_id, external_message_id
|
|
1373
|
+
)
|
|
1374
|
+
`);
|
|
1375
|
+
// Step 2: Delete non-self rows conflicting with existing 'self' rows.
|
|
1376
|
+
raw.exec(/*sql*/ `
|
|
1377
|
+
DELETE FROM channel_inbound_events
|
|
1378
|
+
WHERE assistant_id != 'self'
|
|
1379
|
+
AND EXISTS (
|
|
1380
|
+
SELECT 1 FROM channel_inbound_events e2
|
|
1381
|
+
WHERE e2.assistant_id = 'self'
|
|
1382
|
+
AND e2.source_channel = channel_inbound_events.source_channel
|
|
1383
|
+
AND e2.external_chat_id = channel_inbound_events.external_chat_id
|
|
1384
|
+
AND e2.external_message_id = channel_inbound_events.external_message_id
|
|
1385
|
+
)
|
|
1386
|
+
`);
|
|
1387
|
+
// Step 3: Bulk-update remaining non-self rows.
|
|
1388
|
+
raw.exec(/*sql*/ `
|
|
1389
|
+
UPDATE channel_inbound_events SET assistant_id = 'self' WHERE assistant_id != 'self'
|
|
1390
|
+
`);
|
|
1391
|
+
|
|
1392
|
+
// message_runs: no unique constraint on assistant_id — simple bulk update
|
|
1393
|
+
raw.exec(/*sql*/ `
|
|
1394
|
+
UPDATE message_runs SET assistant_id = 'self' WHERE assistant_id != 'self'
|
|
1395
|
+
`);
|
|
1396
|
+
|
|
1397
|
+
raw.query(
|
|
1398
|
+
`INSERT OR IGNORE INTO memory_checkpoints (key, value, updated_at) VALUES (?, '1', ?)`,
|
|
1399
|
+
).run(checkpointKey, Date.now());
|
|
1400
|
+
|
|
1401
|
+
raw.exec('COMMIT');
|
|
1402
|
+
} catch (e) {
|
|
1403
|
+
try { raw.exec('ROLLBACK'); } catch { /* no active transaction */ }
|
|
1404
|
+
throw e;
|
|
1405
|
+
}
|
|
1406
|
+
}
|
|
1407
|
+
|
|
1408
|
+
/**
|
|
1409
|
+
* One-shot migration: remove duplicate (provider, provider_call_sid) rows from
|
|
1410
|
+
* call_sessions so that the unique index can be created safely on upgraded databases
|
|
1411
|
+
* that pre-date the constraint.
|
|
1412
|
+
*
|
|
1413
|
+
* For each set of duplicates, the most recently updated row is kept.
|
|
1414
|
+
*/
|
|
1415
|
+
function migrateCallSessionsProviderSidDedup(database: ReturnType<typeof drizzle<typeof schema>>): void {
|
|
1416
|
+
const raw = (database as unknown as { $client: Database }).$client;
|
|
1417
|
+
|
|
1418
|
+
// Quick check: if the unique index already exists, no dedup is needed.
|
|
1419
|
+
const idxExists = raw.query(
|
|
1420
|
+
`SELECT 1 FROM sqlite_master WHERE type = 'index' AND name = 'idx_call_sessions_provider_sid_unique'`,
|
|
1421
|
+
).get();
|
|
1422
|
+
if (idxExists) return;
|
|
1423
|
+
|
|
1424
|
+
// Check if the table even exists yet (first boot).
|
|
1425
|
+
const tableExists = raw.query(
|
|
1426
|
+
`SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = 'call_sessions'`,
|
|
1427
|
+
).get();
|
|
1428
|
+
if (!tableExists) return;
|
|
1429
|
+
|
|
1430
|
+
// Count duplicates before doing any work.
|
|
1431
|
+
const dupCount = raw.query(/*sql*/ `
|
|
1432
|
+
SELECT COUNT(*) AS c FROM (
|
|
1433
|
+
SELECT provider, provider_call_sid
|
|
1434
|
+
FROM call_sessions
|
|
1435
|
+
WHERE provider_call_sid IS NOT NULL
|
|
1436
|
+
GROUP BY provider, provider_call_sid
|
|
1437
|
+
HAVING COUNT(*) > 1
|
|
1438
|
+
)
|
|
1439
|
+
`).get() as { c: number } | null;
|
|
1440
|
+
|
|
1441
|
+
if (!dupCount || dupCount.c === 0) return;
|
|
1442
|
+
|
|
1443
|
+
log.warn({ duplicateGroups: dupCount.c }, 'Deduplicating call_sessions with duplicate provider_call_sid before creating unique index');
|
|
1444
|
+
|
|
1445
|
+
try {
|
|
1446
|
+
raw.exec('BEGIN');
|
|
1447
|
+
|
|
1448
|
+
// Keep the most recently updated row per (provider, provider_call_sid);
|
|
1449
|
+
// delete the rest.
|
|
1450
|
+
raw.exec(/*sql*/ `
|
|
1451
|
+
DELETE FROM call_sessions
|
|
1452
|
+
WHERE provider_call_sid IS NOT NULL
|
|
1453
|
+
AND rowid NOT IN (
|
|
1454
|
+
SELECT MAX(rowid) FROM call_sessions
|
|
1455
|
+
WHERE provider_call_sid IS NOT NULL
|
|
1456
|
+
GROUP BY provider, provider_call_sid
|
|
1457
|
+
)
|
|
1458
|
+
`);
|
|
1459
|
+
|
|
1460
|
+
raw.exec('COMMIT');
|
|
1461
|
+
} catch (e) {
|
|
1462
|
+
try { raw.exec('ROLLBACK'); } catch { /* no active transaction */ }
|
|
1463
|
+
throw e;
|
|
1464
|
+
}
|
|
1465
|
+
}
|
|
@@ -3,11 +3,14 @@ import { eq, sql } from 'drizzle-orm';
|
|
|
3
3
|
import { getConfig } from '../config/loader.js';
|
|
4
4
|
import type { MemoryEntityConfig } from '../config/types.js';
|
|
5
5
|
import { getLogger } from '../util/logger.js';
|
|
6
|
+
import { truncate } from '../util/truncate.js';
|
|
6
7
|
import { getDb } from './db.js';
|
|
7
8
|
import { memoryEntities, memoryEntityRelations, memoryItemEntities } from './schema.js';
|
|
8
9
|
|
|
9
10
|
const log = getLogger('memory-entity-extractor');
|
|
10
11
|
|
|
12
|
+
const ENTITY_EXTRACTION_TIMEOUT_MS = 15_000;
|
|
13
|
+
|
|
11
14
|
export type EntityType =
|
|
12
15
|
| 'person'
|
|
13
16
|
| 'project'
|
|
@@ -145,7 +148,7 @@ export async function extractEntitiesWithLLM(
|
|
|
145
148
|
messages: [{ role: 'user' as const, content: text }],
|
|
146
149
|
}) as Promise<Anthropic.Message>,
|
|
147
150
|
new Promise<never>((_, reject) =>
|
|
148
|
-
setTimeout(() => reject(new Error('Entity extraction LLM timeout')),
|
|
151
|
+
setTimeout(() => reject(new Error('Entity extraction LLM timeout')), ENTITY_EXTRACTION_TIMEOUT_MS),
|
|
149
152
|
),
|
|
150
153
|
]) as Anthropic.Message;
|
|
151
154
|
|
|
@@ -460,12 +463,12 @@ function dedupeAliasList(rawAliases: string[], canonicalName: string): string[]
|
|
|
460
463
|
|
|
461
464
|
function normalizeEntityName(value: string | null | undefined): string | null {
|
|
462
465
|
if (!value) return null;
|
|
463
|
-
const normalized = String(value).trim()
|
|
466
|
+
const normalized = truncate(String(value).trim(), 200, '');
|
|
464
467
|
return normalized.length > 0 ? normalized : null;
|
|
465
468
|
}
|
|
466
469
|
|
|
467
470
|
function normalizeEvidence(value: string | null | undefined): string | null {
|
|
468
471
|
if (!value) return null;
|
|
469
|
-
const normalized = String(value).trim()
|
|
472
|
+
const normalized = truncate(String(value).trim(), 500, '');
|
|
470
473
|
return normalized.length > 0 ? normalized : null;
|
|
471
474
|
}
|
|
@@ -4,6 +4,7 @@ import { v4 as uuid } from 'uuid';
|
|
|
4
4
|
import { getConfig } from '../config/loader.js';
|
|
5
5
|
import type { MemoryExtractionConfig } from '../config/types.js';
|
|
6
6
|
import { getLogger } from '../util/logger.js';
|
|
7
|
+
import { truncate } from '../util/truncate.js';
|
|
7
8
|
import { computeMemoryFingerprint } from './fingerprint.js';
|
|
8
9
|
import { enqueueMemoryJob } from './jobs-store.js';
|
|
9
10
|
import { extractTextFromStoredMessageContent } from './message-content.js';
|
|
@@ -199,8 +200,8 @@ async function extractItemsWithLLM(
|
|
|
199
200
|
for (const raw of input.items) {
|
|
200
201
|
if (!VALID_KINDS.has(raw.kind)) continue;
|
|
201
202
|
if (!raw.subject || !raw.statement) continue;
|
|
202
|
-
const subject = String(raw.subject)
|
|
203
|
-
const statement = String(raw.statement)
|
|
203
|
+
const subject = truncate(String(raw.subject), 80, '');
|
|
204
|
+
const statement = truncate(String(raw.statement), 500, '');
|
|
204
205
|
const confidence = clamp(parseScore(raw.confidence, 0.5), 0, 1);
|
|
205
206
|
const importance = clamp(parseScore(raw.importance, 0.5), 0, 1);
|
|
206
207
|
const fingerprint = computeMemoryFingerprint(scopeId, raw.kind, subject, statement);
|
|
@@ -333,7 +334,7 @@ export async function extractAndUpsertMemoryItemsForMessage(messageId: string, s
|
|
|
333
334
|
db.insert(memoryItemSources).values({
|
|
334
335
|
memoryItemId,
|
|
335
336
|
messageId,
|
|
336
|
-
evidence: item.statement
|
|
337
|
+
evidence: truncate(item.statement, 500, ''),
|
|
337
338
|
createdAt: now,
|
|
338
339
|
}).onConflictDoNothing().run();
|
|
339
340
|
|
|
@@ -410,7 +411,7 @@ function inferSubject(sentence: string, kind: MemoryItemKind): string {
|
|
|
410
411
|
if (match) return match[1];
|
|
411
412
|
}
|
|
412
413
|
const words = trimmed.split(/\s+/).slice(0, 6).join(' ');
|
|
413
|
-
return words
|
|
414
|
+
return truncate(words, 80, '');
|
|
414
415
|
}
|
|
415
416
|
|
|
416
417
|
function includesAny(text: string, needles: string[]): boolean {
|
package/src/memory/jobs-store.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { and, asc, eq, lte, notInArray, inArray } from 'drizzle-orm';
|
|
|
2
2
|
import { v4 as uuid } from 'uuid';
|
|
3
3
|
import { getDb } from './db.js';
|
|
4
4
|
import { memoryJobs } from './schema.js';
|
|
5
|
+
import { truncate } from '../util/truncate.js';
|
|
5
6
|
|
|
6
7
|
export type MemoryJobType =
|
|
7
8
|
| 'embed_segment'
|
|
@@ -331,7 +332,7 @@ export function failMemoryJob(
|
|
|
331
332
|
status: 'failed',
|
|
332
333
|
attempts,
|
|
333
334
|
updatedAt: now,
|
|
334
|
-
lastError: error
|
|
335
|
+
lastError: truncate(error, 2000, ''),
|
|
335
336
|
})
|
|
336
337
|
.where(eq(memoryJobs.id, id))
|
|
337
338
|
.run();
|
|
@@ -343,7 +344,7 @@ export function failMemoryJob(
|
|
|
343
344
|
attempts,
|
|
344
345
|
runAfter: now + retryDelayMs,
|
|
345
346
|
updatedAt: now,
|
|
346
|
-
lastError: error
|
|
347
|
+
lastError: truncate(error, 2000, ''),
|
|
347
348
|
})
|
|
348
349
|
.where(eq(memoryJobs.id, id))
|
|
349
350
|
.run();
|
|
@@ -16,7 +16,7 @@ export function recordUsageEvent(input: UsageEventInput, pricing: PricingResult)
|
|
|
16
16
|
db.insert(llmUsageEvents).values({
|
|
17
17
|
id: event.id,
|
|
18
18
|
createdAt: event.createdAt,
|
|
19
|
-
assistantId:
|
|
19
|
+
assistantId: 'self',
|
|
20
20
|
conversationId: event.conversationId,
|
|
21
21
|
runId: event.runId,
|
|
22
22
|
requestId: event.requestId,
|
|
@@ -45,7 +45,6 @@ export function listUsageEvents(options?: { limit?: number }): UsageEvent[] {
|
|
|
45
45
|
return rows.map(row => ({
|
|
46
46
|
id: row.id,
|
|
47
47
|
createdAt: row.createdAt,
|
|
48
|
-
assistantId: row.assistantId,
|
|
49
48
|
conversationId: row.conversationId,
|
|
50
49
|
runId: row.runId,
|
|
51
50
|
requestId: row.requestId,
|
package/src/memory/runs-store.ts
CHANGED
|
@@ -85,7 +85,6 @@ function rowToRun(row: typeof messageRuns.$inferSelect): Run {
|
|
|
85
85
|
// ---------------------------------------------------------------------------
|
|
86
86
|
|
|
87
87
|
export function createRun(
|
|
88
|
-
assistantId: string,
|
|
89
88
|
conversationId: string,
|
|
90
89
|
messageId?: string,
|
|
91
90
|
): Run {
|
|
@@ -95,7 +94,7 @@ export function createRun(
|
|
|
95
94
|
|
|
96
95
|
const row = {
|
|
97
96
|
id,
|
|
98
|
-
assistantId,
|
|
97
|
+
assistantId: 'self',
|
|
99
98
|
conversationId,
|
|
100
99
|
messageId: messageId ?? null,
|
|
101
100
|
status: 'running' as const,
|
package/src/memory/schema.ts
CHANGED
|
@@ -23,6 +23,7 @@ export const messages = sqliteTable('messages', {
|
|
|
23
23
|
role: text('role').notNull(),
|
|
24
24
|
content: text('content').notNull(),
|
|
25
25
|
createdAt: integer('created_at').notNull(),
|
|
26
|
+
metadata: text('metadata'),
|
|
26
27
|
});
|
|
27
28
|
|
|
28
29
|
export const toolInvocations = sqliteTable('tool_invocations', {
|
|
@@ -164,6 +165,7 @@ export const attachments = sqliteTable('attachments', {
|
|
|
164
165
|
kind: text('kind').notNull(),
|
|
165
166
|
dataBase64: text('data_base64').notNull(),
|
|
166
167
|
contentHash: text('content_hash'),
|
|
168
|
+
thumbnailBase64: text('thumbnail_base64'),
|
|
167
169
|
createdAt: integer('created_at').notNull(),
|
|
168
170
|
});
|
|
169
171
|
|
|
@@ -242,13 +244,14 @@ export const reminders = sqliteTable('reminders', {
|
|
|
242
244
|
updatedAt: integer('updated_at').notNull(),
|
|
243
245
|
});
|
|
244
246
|
|
|
245
|
-
// ──
|
|
247
|
+
// ── Recurrence Schedules ─────────────────────────────────────────────
|
|
246
248
|
|
|
247
249
|
export const cronJobs = sqliteTable('cron_jobs', {
|
|
248
250
|
id: text('id').primaryKey(),
|
|
249
251
|
name: text('name').notNull(),
|
|
250
252
|
enabled: integer('enabled', { mode: 'boolean' }).notNull().default(true),
|
|
251
253
|
cronExpression: text('cron_expression').notNull(), // e.g. '0 9 * * 1-5'
|
|
254
|
+
scheduleSyntax: text('schedule_syntax').notNull().default('cron'), // 'cron' | 'rrule'
|
|
252
255
|
timezone: text('timezone'), // e.g. 'America/Los_Angeles'
|
|
253
256
|
message: text('message').notNull(),
|
|
254
257
|
nextRunAt: integer('next_run_at').notNull(),
|
|
@@ -288,6 +291,11 @@ export const cronRuns = sqliteTable('cron_runs', {
|
|
|
288
291
|
createdAt: integer('created_at').notNull(),
|
|
289
292
|
});
|
|
290
293
|
|
|
294
|
+
// Recurrence-centric aliases — prefer these in new code.
|
|
295
|
+
// Physical table names remain `cron_jobs` / `cron_runs` for migration compatibility.
|
|
296
|
+
export const scheduleJobs = cronJobs;
|
|
297
|
+
export const scheduleRuns = cronRuns;
|
|
298
|
+
|
|
291
299
|
// ── LLM Usage Events (cost tracking ledger) ─────────────────────────
|
|
292
300
|
|
|
293
301
|
// ── Entity Graph ─────────────────────────────────────────────────────
|
|
@@ -432,7 +440,7 @@ export const workItems = sqliteTable('work_items', {
|
|
|
432
440
|
taskId: text('task_id').notNull().references(() => tasks.id),
|
|
433
441
|
title: text('title').notNull(),
|
|
434
442
|
notes: text('notes'),
|
|
435
|
-
status: text('status').notNull().default('queued'), // queued | running | awaiting_review | failed | done | archived
|
|
443
|
+
status: text('status').notNull().default('queued'), // queued | running | awaiting_review | failed | cancelled | done | archived
|
|
436
444
|
priorityTier: integer('priority_tier').notNull().default(1), // 0=high, 1=medium, 2=low
|
|
437
445
|
sortIndex: integer('sort_index'), // manual ordering within same priority tier; null = fall back to updated_at
|
|
438
446
|
lastRunId: text('last_run_id'),
|
|
@@ -440,6 +448,9 @@ export const workItems = sqliteTable('work_items', {
|
|
|
440
448
|
lastRunStatus: text('last_run_status'), // 'completed' | 'failed' | null
|
|
441
449
|
sourceType: text('source_type'), // reserved for future bridge (e.g. 'followup', 'triage')
|
|
442
450
|
sourceId: text('source_id'), // reserved for future bridge
|
|
451
|
+
requiredTools: text('required_tools'), // JSON array snapshot of tools needed for this run (null=unknown, []=none, ["bash",...]=specific)
|
|
452
|
+
approvedTools: text('approved_tools'), // JSON array of pre-approved tool names
|
|
453
|
+
approvalStatus: text('approval_status').default('none'), // 'none' | 'approved' | 'denied'
|
|
443
454
|
createdAt: integer('created_at').notNull(),
|
|
444
455
|
updatedAt: integer('updated_at').notNull(),
|
|
445
456
|
});
|
|
@@ -527,3 +538,55 @@ export const llmUsageEvents = sqliteTable('llm_usage_events', {
|
|
|
527
538
|
pricingStatus: text('pricing_status').notNull(),
|
|
528
539
|
metadataJson: text('metadata_json'),
|
|
529
540
|
});
|
|
541
|
+
|
|
542
|
+
// ── Call Sessions (outgoing AI phone calls) ──────────────────────────
|
|
543
|
+
|
|
544
|
+
export const callSessions = sqliteTable('call_sessions', {
|
|
545
|
+
id: text('id').primaryKey(),
|
|
546
|
+
conversationId: text('conversation_id')
|
|
547
|
+
.notNull()
|
|
548
|
+
.references(() => conversations.id, { onDelete: 'cascade' }),
|
|
549
|
+
provider: text('provider').notNull(),
|
|
550
|
+
providerCallSid: text('provider_call_sid'),
|
|
551
|
+
fromNumber: text('from_number').notNull(),
|
|
552
|
+
toNumber: text('to_number').notNull(),
|
|
553
|
+
task: text('task'),
|
|
554
|
+
status: text('status').notNull().default('initiated'),
|
|
555
|
+
startedAt: integer('started_at'),
|
|
556
|
+
endedAt: integer('ended_at'),
|
|
557
|
+
lastError: text('last_error'),
|
|
558
|
+
createdAt: integer('created_at').notNull(),
|
|
559
|
+
updatedAt: integer('updated_at').notNull(),
|
|
560
|
+
});
|
|
561
|
+
|
|
562
|
+
export const callEvents = sqliteTable('call_events', {
|
|
563
|
+
id: text('id').primaryKey(),
|
|
564
|
+
callSessionId: text('call_session_id')
|
|
565
|
+
.notNull()
|
|
566
|
+
.references(() => callSessions.id, { onDelete: 'cascade' }),
|
|
567
|
+
eventType: text('event_type').notNull(),
|
|
568
|
+
payloadJson: text('payload_json').notNull().default('{}'),
|
|
569
|
+
createdAt: integer('created_at').notNull(),
|
|
570
|
+
});
|
|
571
|
+
|
|
572
|
+
export const callPendingQuestions = sqliteTable('call_pending_questions', {
|
|
573
|
+
id: text('id').primaryKey(),
|
|
574
|
+
callSessionId: text('call_session_id')
|
|
575
|
+
.notNull()
|
|
576
|
+
.references(() => callSessions.id, { onDelete: 'cascade' }),
|
|
577
|
+
questionText: text('question_text').notNull(),
|
|
578
|
+
status: text('status').notNull().default('pending'),
|
|
579
|
+
askedAt: integer('asked_at').notNull(),
|
|
580
|
+
answeredAt: integer('answered_at'),
|
|
581
|
+
answerText: text('answer_text'),
|
|
582
|
+
});
|
|
583
|
+
|
|
584
|
+
export const processedCallbacks = sqliteTable('processed_callbacks', {
|
|
585
|
+
id: text('id').primaryKey(),
|
|
586
|
+
dedupeKey: text('dedupe_key').notNull().unique(),
|
|
587
|
+
callSessionId: text('call_session_id')
|
|
588
|
+
.notNull()
|
|
589
|
+
.references(() => callSessions.id, { onDelete: 'cascade' }),
|
|
590
|
+
claimId: text('claim_id'),
|
|
591
|
+
createdAt: integer('created_at').notNull(),
|
|
592
|
+
});
|