vellum 0.2.0 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/__tests__/__snapshots__/ipc-snapshot.test.ts.snap +28 -0
- package/src/__tests__/app-bundler.test.ts +12 -33
- package/src/__tests__/browser-skill-endstate.test.ts +1 -5
- package/src/__tests__/call-orchestrator.test.ts +328 -0
- package/src/__tests__/call-state.test.ts +133 -0
- package/src/__tests__/call-store.test.ts +476 -0
- package/src/__tests__/commit-message-enrichment-service.test.ts +409 -0
- package/src/__tests__/config-schema.test.ts +49 -0
- package/src/__tests__/doordash-session.test.ts +9 -0
- package/src/__tests__/ipc-snapshot.test.ts +34 -0
- package/src/__tests__/registry.test.ts +13 -8
- package/src/__tests__/run-orchestrator-assistant-events.test.ts +218 -0
- package/src/__tests__/run-orchestrator.test.ts +3 -3
- package/src/__tests__/runtime-attachment-metadata.test.ts +17 -19
- package/src/__tests__/runtime-runs-http.test.ts +1 -19
- package/src/__tests__/runtime-runs.test.ts +7 -7
- package/src/__tests__/session-queue.test.ts +50 -0
- package/src/__tests__/turn-commit.test.ts +56 -0
- package/src/__tests__/workspace-git-service.test.ts +217 -0
- package/src/__tests__/workspace-heartbeat-service.test.ts +129 -0
- package/src/bundler/app-bundler.ts +29 -12
- package/src/calls/call-constants.ts +10 -0
- package/src/calls/call-orchestrator.ts +364 -0
- package/src/calls/call-state.ts +64 -0
- package/src/calls/call-store.ts +229 -0
- package/src/calls/relay-server.ts +298 -0
- package/src/calls/twilio-config.ts +34 -0
- package/src/calls/twilio-provider.ts +169 -0
- package/src/calls/twilio-routes.ts +236 -0
- package/src/calls/types.ts +37 -0
- package/src/calls/voice-provider.ts +14 -0
- package/src/cli/doordash.ts +5 -24
- package/src/config/bundled-skills/doordash/SKILL.md +104 -0
- package/src/config/bundled-skills/image-studio/TOOLS.json +2 -2
- package/src/config/bundled-skills/image-studio/tools/media-generate-image.ts +1 -1
- package/src/config/defaults.ts +11 -0
- package/src/config/schema.ts +57 -0
- package/src/config/system-prompt.ts +50 -1
- package/src/config/types.ts +1 -0
- package/src/daemon/handlers/config.ts +30 -0
- package/src/daemon/handlers/index.ts +6 -0
- package/src/daemon/handlers/work-items.ts +142 -2
- package/src/daemon/ipc-contract-inventory.json +12 -0
- package/src/daemon/ipc-contract.ts +52 -0
- package/src/daemon/lifecycle.ts +27 -5
- package/src/daemon/server.ts +10 -12
- package/src/daemon/session-tool-setup.ts +6 -0
- package/src/daemon/session.ts +40 -1
- package/src/index.ts +2 -0
- package/src/media/gemini-image-service.ts +1 -1
- package/src/memory/db.ts +266 -0
- package/src/memory/schema.ts +42 -0
- package/src/runtime/http-server.ts +189 -25
- package/src/runtime/http-types.ts +0 -2
- package/src/runtime/routes/attachment-routes.ts +6 -6
- package/src/runtime/routes/channel-routes.ts +16 -18
- package/src/runtime/routes/conversation-routes.ts +5 -9
- package/src/runtime/routes/run-routes.ts +4 -8
- package/src/runtime/run-orchestrator.ts +32 -5
- package/src/tools/calls/call-end.ts +117 -0
- package/src/tools/calls/call-start.ts +134 -0
- package/src/tools/calls/call-status.ts +97 -0
- package/src/tools/credentials/vault.ts +1 -1
- package/src/tools/registry.ts +2 -4
- package/src/tools/tasks/index.ts +2 -0
- package/src/tools/tasks/task-delete.ts +49 -8
- package/src/tools/tasks/task-run.ts +9 -1
- package/src/tools/tasks/work-item-enqueue.ts +93 -3
- package/src/tools/tasks/work-item-list.ts +10 -25
- package/src/tools/tasks/work-item-remove.ts +112 -0
- package/src/tools/tasks/work-item-update.ts +186 -0
- package/src/tools/tool-manifest.ts +39 -31
- package/src/tools/ui-surface/definitions.ts +3 -0
- package/src/work-items/work-item-store.ts +209 -0
- package/src/workspace/commit-message-enrichment-service.ts +260 -0
- package/src/workspace/commit-message-provider.ts +95 -0
- package/src/workspace/git-service.ts +187 -32
- package/src/workspace/heartbeat-service.ts +70 -13
- package/src/workspace/turn-commit.ts +39 -49
package/src/index.ts
CHANGED
|
@@ -230,10 +230,12 @@ program
|
|
|
230
230
|
|
|
231
231
|
log.info('Starting daemon in dev mode (Ctrl+C to stop)');
|
|
232
232
|
|
|
233
|
+
const repoRoot = join(import.meta.dirname, '..', '..');
|
|
233
234
|
const child = spawn('bun', ['--watch', 'run', mainPath], {
|
|
234
235
|
stdio: 'inherit',
|
|
235
236
|
env: {
|
|
236
237
|
...process.env,
|
|
238
|
+
BASE_DATA_DIR: repoRoot,
|
|
237
239
|
VELLUM_LOG_STDERR: '1',
|
|
238
240
|
VELLUM_DEBUG: '1',
|
|
239
241
|
},
|
|
@@ -27,7 +27,7 @@ export interface ImageGenerationResult {
|
|
|
27
27
|
// --- Constants ---
|
|
28
28
|
|
|
29
29
|
const DEFAULT_MODEL = 'gemini-2.5-flash-image';
|
|
30
|
-
const ALLOWED_MODELS = new Set(['gemini-2.5-flash-image', 'gemini-3-pro-image']);
|
|
30
|
+
const ALLOWED_MODELS = new Set(['gemini-2.5-flash-image', 'gemini-3-pro-image', 'gemini-3-pro-image-preview']);
|
|
31
31
|
const MAX_VARIANTS = 4;
|
|
32
32
|
|
|
33
33
|
// --- Error mapping ---
|
package/src/memory/db.ts
CHANGED
|
@@ -544,6 +544,7 @@ export function initializeDb(): void {
|
|
|
544
544
|
migrateMemoryEntityRelationDedup(database);
|
|
545
545
|
migrateMemoryItemsFingerprintScopeUnique(database);
|
|
546
546
|
migrateMemoryItemsScopeSaltedFingerprints(database);
|
|
547
|
+
migrateAssistantIdToSelf(database);
|
|
547
548
|
|
|
548
549
|
// Indexes for query performance on large datasets
|
|
549
550
|
database.run(/*sql*/ `CREATE INDEX IF NOT EXISTS idx_llm_request_logs_conv_created ON llm_request_logs(conversation_id, created_at)`);
|
|
@@ -685,6 +686,55 @@ export function initializeDb(): void {
|
|
|
685
686
|
database.run(/*sql*/ `CREATE INDEX IF NOT EXISTS idx_triage_results_sender ON triage_results(sender)`);
|
|
686
687
|
database.run(/*sql*/ `CREATE INDEX IF NOT EXISTS idx_triage_results_created_at ON triage_results(created_at DESC)`);
|
|
687
688
|
|
|
689
|
+
// ── Call Sessions (outgoing AI phone calls) ────────────────────────
|
|
690
|
+
|
|
691
|
+
database.run(/*sql*/ `
|
|
692
|
+
CREATE TABLE IF NOT EXISTS call_sessions (
|
|
693
|
+
id TEXT PRIMARY KEY,
|
|
694
|
+
conversation_id TEXT NOT NULL REFERENCES conversations(id) ON DELETE CASCADE,
|
|
695
|
+
provider TEXT NOT NULL,
|
|
696
|
+
provider_call_sid TEXT,
|
|
697
|
+
from_number TEXT NOT NULL,
|
|
698
|
+
to_number TEXT NOT NULL,
|
|
699
|
+
task TEXT,
|
|
700
|
+
status TEXT NOT NULL DEFAULT 'initiated',
|
|
701
|
+
started_at INTEGER,
|
|
702
|
+
ended_at INTEGER,
|
|
703
|
+
last_error TEXT,
|
|
704
|
+
created_at INTEGER NOT NULL,
|
|
705
|
+
updated_at INTEGER NOT NULL
|
|
706
|
+
)
|
|
707
|
+
`);
|
|
708
|
+
|
|
709
|
+
database.run(/*sql*/ `
|
|
710
|
+
CREATE TABLE IF NOT EXISTS call_events (
|
|
711
|
+
id TEXT PRIMARY KEY,
|
|
712
|
+
call_session_id TEXT NOT NULL REFERENCES call_sessions(id) ON DELETE CASCADE,
|
|
713
|
+
event_type TEXT NOT NULL,
|
|
714
|
+
payload_json TEXT NOT NULL DEFAULT '{}',
|
|
715
|
+
created_at INTEGER NOT NULL
|
|
716
|
+
)
|
|
717
|
+
`);
|
|
718
|
+
|
|
719
|
+
database.run(/*sql*/ `
|
|
720
|
+
CREATE TABLE IF NOT EXISTS call_pending_questions (
|
|
721
|
+
id TEXT PRIMARY KEY,
|
|
722
|
+
call_session_id TEXT NOT NULL REFERENCES call_sessions(id) ON DELETE CASCADE,
|
|
723
|
+
question_text TEXT NOT NULL,
|
|
724
|
+
status TEXT NOT NULL DEFAULT 'pending',
|
|
725
|
+
asked_at INTEGER NOT NULL,
|
|
726
|
+
answered_at INTEGER,
|
|
727
|
+
answer_text TEXT
|
|
728
|
+
)
|
|
729
|
+
`);
|
|
730
|
+
|
|
731
|
+
database.run(/*sql*/ `CREATE INDEX IF NOT EXISTS idx_call_sessions_conversation_id ON call_sessions(conversation_id)`);
|
|
732
|
+
database.run(/*sql*/ `CREATE INDEX IF NOT EXISTS idx_call_sessions_provider_call_sid ON call_sessions(provider_call_sid)`);
|
|
733
|
+
database.run(/*sql*/ `CREATE INDEX IF NOT EXISTS idx_call_sessions_status ON call_sessions(status)`);
|
|
734
|
+
database.run(/*sql*/ `CREATE INDEX IF NOT EXISTS idx_call_events_call_session_id ON call_events(call_session_id)`);
|
|
735
|
+
database.run(/*sql*/ `CREATE INDEX IF NOT EXISTS idx_call_pending_questions_call_session_id ON call_pending_questions(call_session_id)`);
|
|
736
|
+
database.run(/*sql*/ `CREATE INDEX IF NOT EXISTS idx_call_pending_questions_status ON call_pending_questions(status)`);
|
|
737
|
+
|
|
688
738
|
// ── Follow-ups ─────────────────────────────────────────────────────
|
|
689
739
|
|
|
690
740
|
database.run(/*sql*/ `
|
|
@@ -1103,3 +1153,219 @@ function migrateMemoryItemsScopeSaltedFingerprints(database: ReturnType<typeof d
|
|
|
1103
1153
|
throw e;
|
|
1104
1154
|
}
|
|
1105
1155
|
}
|
|
1156
|
+
|
|
1157
|
+
/**
|
|
1158
|
+
* One-shot migration: normalize all assistant_id values in assistant-scoped tables
|
|
1159
|
+
* to "self" so they are visible after the daemon switched to the implicit single-tenant
|
|
1160
|
+
* identity.
|
|
1161
|
+
*
|
|
1162
|
+
* Before this change, rows were keyed by the real assistantId string passed via the
|
|
1163
|
+
* HTTP route. After the route change, all lookups use the constant "self". Without this
|
|
1164
|
+
* migration an upgraded daemon would see empty history / attachment lists for existing
|
|
1165
|
+
* data that was stored under the old assistantId.
|
|
1166
|
+
*
|
|
1167
|
+
* Affected tables:
|
|
1168
|
+
* - conversation_keys UNIQUE (assistant_id, conversation_key)
|
|
1169
|
+
* - attachments UNIQUE (assistant_id, content_hash) WHERE content_hash IS NOT NULL
|
|
1170
|
+
* - channel_inbound_events UNIQUE (assistant_id, source_channel, external_chat_id, external_message_id)
|
|
1171
|
+
* - message_runs no unique constraint on assistant_id
|
|
1172
|
+
*
|
|
1173
|
+
* Data-safety guarantees:
|
|
1174
|
+
* - conversation_keys: when a key exists under both 'self' and a real assistantId, the
|
|
1175
|
+
* 'self' row is updated to point to the real-assistantId conversation (which holds the
|
|
1176
|
+
* historical message thread). The 'self' conversation may be orphaned but is not deleted.
|
|
1177
|
+
* - attachments: message_attachments links are remapped to the surviving attachment before
|
|
1178
|
+
* any duplicate row is deleted, so no message loses its attachment metadata.
|
|
1179
|
+
* - channel_inbound_events: only delivery-tracking metadata, not user content; dedup
|
|
1180
|
+
* keeps one row per unique (channel, chat, message) tuple.
|
|
1181
|
+
* - All conversations and messages remain untouched — only assistant_id index columns
|
|
1182
|
+
* and key-lookup rows are modified.
|
|
1183
|
+
*/
|
|
1184
|
+
function migrateAssistantIdToSelf(database: ReturnType<typeof drizzle<typeof schema>>): void {
|
|
1185
|
+
const raw = (database as unknown as { $client: Database }).$client;
|
|
1186
|
+
const checkpointKey = 'migration_normalize_assistant_id_to_self_v1';
|
|
1187
|
+
const checkpoint = raw.query(
|
|
1188
|
+
`SELECT 1 FROM memory_checkpoints WHERE key = ?`,
|
|
1189
|
+
).get(checkpointKey);
|
|
1190
|
+
if (checkpoint) return;
|
|
1191
|
+
|
|
1192
|
+
try {
|
|
1193
|
+
raw.exec('BEGIN');
|
|
1194
|
+
|
|
1195
|
+
// conversation_keys: UNIQUE (assistant_id, conversation_key)
|
|
1196
|
+
//
|
|
1197
|
+
// Step 1: Among non-self rows, keep only one per conversation_key so the
|
|
1198
|
+
// bulk UPDATE cannot hit a (non-self-A, key) + (non-self-B, key) collision.
|
|
1199
|
+
raw.exec(/*sql*/ `
|
|
1200
|
+
DELETE FROM conversation_keys
|
|
1201
|
+
WHERE assistant_id != 'self'
|
|
1202
|
+
AND rowid NOT IN (
|
|
1203
|
+
SELECT MIN(rowid) FROM conversation_keys
|
|
1204
|
+
WHERE assistant_id != 'self'
|
|
1205
|
+
GROUP BY conversation_key
|
|
1206
|
+
)
|
|
1207
|
+
`);
|
|
1208
|
+
// Step 2: For 'self' rows that have a non-self counterpart with the same
|
|
1209
|
+
// conversation_key, update the 'self' row to use the non-self row's
|
|
1210
|
+
// conversation_id. This preserves the historical conversation (which
|
|
1211
|
+
// has the message history from before the route change) rather than
|
|
1212
|
+
// discarding it in favour of a potentially-empty 'self' conversation.
|
|
1213
|
+
raw.exec(/*sql*/ `
|
|
1214
|
+
UPDATE conversation_keys
|
|
1215
|
+
SET conversation_id = (
|
|
1216
|
+
SELECT ck_ns.conversation_id
|
|
1217
|
+
FROM conversation_keys ck_ns
|
|
1218
|
+
WHERE ck_ns.assistant_id != 'self'
|
|
1219
|
+
AND ck_ns.conversation_key = conversation_keys.conversation_key
|
|
1220
|
+
ORDER BY ck_ns.rowid
|
|
1221
|
+
LIMIT 1
|
|
1222
|
+
)
|
|
1223
|
+
WHERE assistant_id = 'self'
|
|
1224
|
+
AND EXISTS (
|
|
1225
|
+
SELECT 1 FROM conversation_keys ck_ns
|
|
1226
|
+
WHERE ck_ns.assistant_id != 'self'
|
|
1227
|
+
AND ck_ns.conversation_key = conversation_keys.conversation_key
|
|
1228
|
+
)
|
|
1229
|
+
`);
|
|
1230
|
+
// Step 3: Delete the now-redundant non-self rows (their conversation_ids
|
|
1231
|
+
// have been preserved in the 'self' rows above).
|
|
1232
|
+
raw.exec(/*sql*/ `
|
|
1233
|
+
DELETE FROM conversation_keys
|
|
1234
|
+
WHERE assistant_id != 'self'
|
|
1235
|
+
AND EXISTS (
|
|
1236
|
+
SELECT 1 FROM conversation_keys ck2
|
|
1237
|
+
WHERE ck2.assistant_id = 'self'
|
|
1238
|
+
AND ck2.conversation_key = conversation_keys.conversation_key
|
|
1239
|
+
)
|
|
1240
|
+
`);
|
|
1241
|
+
// Step 4: Remaining non-self rows have no 'self' counterpart — safe to bulk-update.
|
|
1242
|
+
raw.exec(/*sql*/ `
|
|
1243
|
+
UPDATE conversation_keys SET assistant_id = 'self' WHERE assistant_id != 'self'
|
|
1244
|
+
`);
|
|
1245
|
+
|
|
1246
|
+
// attachments: UNIQUE (assistant_id, content_hash) WHERE content_hash IS NOT NULL
|
|
1247
|
+
//
|
|
1248
|
+
// message_attachments rows reference attachment IDs with ON DELETE CASCADE, so we
|
|
1249
|
+
// must remap links to the surviving row BEFORE deleting duplicates to avoid
|
|
1250
|
+
// silently dropping attachment metadata from messages.
|
|
1251
|
+
//
|
|
1252
|
+
// Step 1: Remap message_attachments from non-self duplicates to their survivor
|
|
1253
|
+
// (MIN rowid per content_hash group), then delete the duplicates.
|
|
1254
|
+
raw.exec(/*sql*/ `
|
|
1255
|
+
UPDATE message_attachments
|
|
1256
|
+
SET attachment_id = (
|
|
1257
|
+
SELECT a_survivor.id
|
|
1258
|
+
FROM attachments a_survivor
|
|
1259
|
+
WHERE a_survivor.assistant_id != 'self'
|
|
1260
|
+
AND a_survivor.content_hash = (
|
|
1261
|
+
SELECT a_dup.content_hash FROM attachments a_dup
|
|
1262
|
+
WHERE a_dup.id = message_attachments.attachment_id
|
|
1263
|
+
)
|
|
1264
|
+
ORDER BY a_survivor.rowid
|
|
1265
|
+
LIMIT 1
|
|
1266
|
+
)
|
|
1267
|
+
WHERE attachment_id IN (
|
|
1268
|
+
SELECT id FROM attachments
|
|
1269
|
+
WHERE assistant_id != 'self'
|
|
1270
|
+
AND content_hash IS NOT NULL
|
|
1271
|
+
AND rowid NOT IN (
|
|
1272
|
+
SELECT MIN(rowid) FROM attachments
|
|
1273
|
+
WHERE assistant_id != 'self' AND content_hash IS NOT NULL
|
|
1274
|
+
GROUP BY content_hash
|
|
1275
|
+
)
|
|
1276
|
+
)
|
|
1277
|
+
`);
|
|
1278
|
+
raw.exec(/*sql*/ `
|
|
1279
|
+
DELETE FROM attachments
|
|
1280
|
+
WHERE assistant_id != 'self'
|
|
1281
|
+
AND content_hash IS NOT NULL
|
|
1282
|
+
AND rowid NOT IN (
|
|
1283
|
+
SELECT MIN(rowid) FROM attachments
|
|
1284
|
+
WHERE assistant_id != 'self'
|
|
1285
|
+
AND content_hash IS NOT NULL
|
|
1286
|
+
GROUP BY content_hash
|
|
1287
|
+
)
|
|
1288
|
+
`);
|
|
1289
|
+
// Step 2: Remap message_attachments from non-self rows conflicting with a 'self'
|
|
1290
|
+
// row to the 'self' row, then delete the now-unlinked non-self rows.
|
|
1291
|
+
raw.exec(/*sql*/ `
|
|
1292
|
+
UPDATE message_attachments
|
|
1293
|
+
SET attachment_id = (
|
|
1294
|
+
SELECT a_self.id
|
|
1295
|
+
FROM attachments a_self
|
|
1296
|
+
WHERE a_self.assistant_id = 'self'
|
|
1297
|
+
AND a_self.content_hash = (
|
|
1298
|
+
SELECT a_ns.content_hash FROM attachments a_ns
|
|
1299
|
+
WHERE a_ns.id = message_attachments.attachment_id
|
|
1300
|
+
)
|
|
1301
|
+
LIMIT 1
|
|
1302
|
+
)
|
|
1303
|
+
WHERE attachment_id IN (
|
|
1304
|
+
SELECT id FROM attachments
|
|
1305
|
+
WHERE assistant_id != 'self'
|
|
1306
|
+
AND content_hash IS NOT NULL
|
|
1307
|
+
AND EXISTS (
|
|
1308
|
+
SELECT 1 FROM attachments a2
|
|
1309
|
+
WHERE a2.assistant_id = 'self'
|
|
1310
|
+
AND a2.content_hash = attachments.content_hash
|
|
1311
|
+
)
|
|
1312
|
+
)
|
|
1313
|
+
`);
|
|
1314
|
+
raw.exec(/*sql*/ `
|
|
1315
|
+
DELETE FROM attachments
|
|
1316
|
+
WHERE assistant_id != 'self'
|
|
1317
|
+
AND content_hash IS NOT NULL
|
|
1318
|
+
AND EXISTS (
|
|
1319
|
+
SELECT 1 FROM attachments a2
|
|
1320
|
+
WHERE a2.assistant_id = 'self'
|
|
1321
|
+
AND a2.content_hash = attachments.content_hash
|
|
1322
|
+
)
|
|
1323
|
+
`);
|
|
1324
|
+
// Step 3: Bulk-update remaining non-self rows.
|
|
1325
|
+
raw.exec(/*sql*/ `
|
|
1326
|
+
UPDATE attachments SET assistant_id = 'self' WHERE assistant_id != 'self'
|
|
1327
|
+
`);
|
|
1328
|
+
|
|
1329
|
+
// channel_inbound_events: UNIQUE (assistant_id, source_channel, external_chat_id, external_message_id)
|
|
1330
|
+
// Step 1: Dedup non-self rows sharing the same (source_channel, external_chat_id, external_message_id).
|
|
1331
|
+
raw.exec(/*sql*/ `
|
|
1332
|
+
DELETE FROM channel_inbound_events
|
|
1333
|
+
WHERE assistant_id != 'self'
|
|
1334
|
+
AND rowid NOT IN (
|
|
1335
|
+
SELECT MIN(rowid) FROM channel_inbound_events
|
|
1336
|
+
WHERE assistant_id != 'self'
|
|
1337
|
+
GROUP BY source_channel, external_chat_id, external_message_id
|
|
1338
|
+
)
|
|
1339
|
+
`);
|
|
1340
|
+
// Step 2: Delete non-self rows conflicting with existing 'self' rows.
|
|
1341
|
+
raw.exec(/*sql*/ `
|
|
1342
|
+
DELETE FROM channel_inbound_events
|
|
1343
|
+
WHERE assistant_id != 'self'
|
|
1344
|
+
AND EXISTS (
|
|
1345
|
+
SELECT 1 FROM channel_inbound_events e2
|
|
1346
|
+
WHERE e2.assistant_id = 'self'
|
|
1347
|
+
AND e2.source_channel = channel_inbound_events.source_channel
|
|
1348
|
+
AND e2.external_chat_id = channel_inbound_events.external_chat_id
|
|
1349
|
+
AND e2.external_message_id = channel_inbound_events.external_message_id
|
|
1350
|
+
)
|
|
1351
|
+
`);
|
|
1352
|
+
// Step 3: Bulk-update remaining non-self rows.
|
|
1353
|
+
raw.exec(/*sql*/ `
|
|
1354
|
+
UPDATE channel_inbound_events SET assistant_id = 'self' WHERE assistant_id != 'self'
|
|
1355
|
+
`);
|
|
1356
|
+
|
|
1357
|
+
// message_runs: no unique constraint on assistant_id — simple bulk update
|
|
1358
|
+
raw.exec(/*sql*/ `
|
|
1359
|
+
UPDATE message_runs SET assistant_id = 'self' WHERE assistant_id != 'self'
|
|
1360
|
+
`);
|
|
1361
|
+
|
|
1362
|
+
raw.query(
|
|
1363
|
+
`INSERT OR IGNORE INTO memory_checkpoints (key, value, updated_at) VALUES (?, '1', ?)`,
|
|
1364
|
+
).run(checkpointKey, Date.now());
|
|
1365
|
+
|
|
1366
|
+
raw.exec('COMMIT');
|
|
1367
|
+
} catch (e) {
|
|
1368
|
+
try { raw.exec('ROLLBACK'); } catch { /* no active transaction */ }
|
|
1369
|
+
throw e;
|
|
1370
|
+
}
|
|
1371
|
+
}
|
package/src/memory/schema.ts
CHANGED
|
@@ -527,3 +527,45 @@ export const llmUsageEvents = sqliteTable('llm_usage_events', {
|
|
|
527
527
|
pricingStatus: text('pricing_status').notNull(),
|
|
528
528
|
metadataJson: text('metadata_json'),
|
|
529
529
|
});
|
|
530
|
+
|
|
531
|
+
// ── Call Sessions (outgoing AI phone calls) ──────────────────────────
|
|
532
|
+
|
|
533
|
+
export const callSessions = sqliteTable('call_sessions', {
|
|
534
|
+
id: text('id').primaryKey(),
|
|
535
|
+
conversationId: text('conversation_id')
|
|
536
|
+
.notNull()
|
|
537
|
+
.references(() => conversations.id, { onDelete: 'cascade' }),
|
|
538
|
+
provider: text('provider').notNull(),
|
|
539
|
+
providerCallSid: text('provider_call_sid'),
|
|
540
|
+
fromNumber: text('from_number').notNull(),
|
|
541
|
+
toNumber: text('to_number').notNull(),
|
|
542
|
+
task: text('task'),
|
|
543
|
+
status: text('status').notNull().default('initiated'),
|
|
544
|
+
startedAt: integer('started_at'),
|
|
545
|
+
endedAt: integer('ended_at'),
|
|
546
|
+
lastError: text('last_error'),
|
|
547
|
+
createdAt: integer('created_at').notNull(),
|
|
548
|
+
updatedAt: integer('updated_at').notNull(),
|
|
549
|
+
});
|
|
550
|
+
|
|
551
|
+
export const callEvents = sqliteTable('call_events', {
|
|
552
|
+
id: text('id').primaryKey(),
|
|
553
|
+
callSessionId: text('call_session_id')
|
|
554
|
+
.notNull()
|
|
555
|
+
.references(() => callSessions.id, { onDelete: 'cascade' }),
|
|
556
|
+
eventType: text('event_type').notNull(),
|
|
557
|
+
payloadJson: text('payload_json').notNull().default('{}'),
|
|
558
|
+
createdAt: integer('created_at').notNull(),
|
|
559
|
+
});
|
|
560
|
+
|
|
561
|
+
export const callPendingQuestions = sqliteTable('call_pending_questions', {
|
|
562
|
+
id: text('id').primaryKey(),
|
|
563
|
+
callSessionId: text('call_session_id')
|
|
564
|
+
.notNull()
|
|
565
|
+
.references(() => callSessions.id, { onDelete: 'cascade' }),
|
|
566
|
+
questionText: text('question_text').notNull(),
|
|
567
|
+
status: text('status').notNull().default('pending'),
|
|
568
|
+
askedAt: integer('asked_at').notNull(),
|
|
569
|
+
answeredAt: integer('answered_at'),
|
|
570
|
+
answerText: text('answer_text'),
|
|
571
|
+
});
|