@vellumai/assistant 0.4.21 → 0.4.23

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.
@@ -1,304 +1,358 @@
1
- import { blob, index,integer, real, sqliteTable, text, uniqueIndex } from 'drizzle-orm/sqlite-core';
2
-
3
- import { DAEMON_INTERNAL_ASSISTANT_ID } from '../runtime/assistant-scope.js';
4
-
5
- export const conversations = sqliteTable('conversations', {
6
- id: text('id').primaryKey(),
7
- title: text('title'),
8
- createdAt: integer('created_at').notNull(),
9
- updatedAt: integer('updated_at').notNull(),
10
- totalInputTokens: integer('total_input_tokens').notNull().default(0),
11
- totalOutputTokens: integer('total_output_tokens').notNull().default(0),
12
- totalEstimatedCost: real('total_estimated_cost').notNull().default(0),
13
- contextSummary: text('context_summary'),
14
- contextCompactedMessageCount: integer('context_compacted_message_count').notNull().default(0),
15
- contextCompactedAt: integer('context_compacted_at'),
16
- threadType: text('thread_type').notNull().default('standard'),
17
- source: text('source').notNull().default('user'),
18
- memoryScopeId: text('memory_scope_id').notNull().default('default'),
19
- originChannel: text('origin_channel'),
20
- originInterface: text('origin_interface'),
21
- isAutoTitle: integer('is_auto_title').notNull().default(1),
22
- }, (table) => [
23
- index('idx_conversations_updated_at').on(table.updatedAt),
24
- index('idx_conversations_thread_type').on(table.threadType),
25
- ]);
26
-
27
- export const messages = sqliteTable('messages', {
28
- id: text('id').primaryKey(),
29
- conversationId: text('conversation_id')
30
- .notNull()
31
- .references(() => conversations.id, { onDelete: 'cascade' }),
32
- role: text('role').notNull(),
33
- content: text('content').notNull(),
34
- createdAt: integer('created_at').notNull(),
35
- metadata: text('metadata'),
36
- }, (table) => [
37
- index('idx_messages_conversation_id').on(table.conversationId),
38
- ]);
39
-
40
- export const toolInvocations = sqliteTable('tool_invocations', {
41
- id: text('id').primaryKey(),
42
- conversationId: text('conversation_id')
43
- .notNull()
44
- .references(() => conversations.id),
45
- toolName: text('tool_name').notNull(),
46
- input: text('input').notNull(),
47
- result: text('result').notNull(),
48
- decision: text('decision').notNull(),
49
- riskLevel: text('risk_level').notNull(),
50
- durationMs: integer('duration_ms').notNull(),
51
- createdAt: integer('created_at').notNull(),
52
- }, (table) => [
53
- index('idx_tool_invocations_conversation_id').on(table.conversationId),
54
- ]);
55
-
56
- export const memorySegments = sqliteTable('memory_segments', {
57
- id: text('id').primaryKey(),
58
- messageId: text('message_id')
59
- .notNull()
60
- .references(() => messages.id, { onDelete: 'cascade' }),
61
- conversationId: text('conversation_id')
62
- .notNull()
63
- .references(() => conversations.id, { onDelete: 'cascade' }),
64
- role: text('role').notNull(),
65
- segmentIndex: integer('segment_index').notNull(),
66
- text: text('text').notNull(),
67
- tokenEstimate: integer('token_estimate').notNull(),
68
- scopeId: text('scope_id').notNull().default('default'),
69
- contentHash: text('content_hash'),
70
- createdAt: integer('created_at').notNull(),
71
- updatedAt: integer('updated_at').notNull(),
72
- }, (table) => [
73
- index('idx_memory_segments_scope_id').on(table.scopeId),
74
- ]);
75
-
76
- export const memoryItems = sqliteTable('memory_items', {
77
- id: text('id').primaryKey(),
78
- kind: text('kind').notNull(),
79
- subject: text('subject').notNull(),
80
- statement: text('statement').notNull(),
81
- status: text('status').notNull(),
82
- confidence: real('confidence').notNull(),
83
- importance: real('importance'),
84
- accessCount: integer('access_count').notNull().default(0),
85
- fingerprint: text('fingerprint').notNull(),
86
- verificationState: text('verification_state').notNull().default('assistant_inferred'),
87
- scopeId: text('scope_id').notNull().default('default'),
88
- firstSeenAt: integer('first_seen_at').notNull(),
89
- lastSeenAt: integer('last_seen_at').notNull(),
90
- lastUsedAt: integer('last_used_at'),
91
- validFrom: integer('valid_from'),
92
- invalidAt: integer('invalid_at'),
93
- }, (table) => [
94
- index('idx_memory_items_scope_id').on(table.scopeId),
95
- index('idx_memory_items_fingerprint').on(table.fingerprint),
96
- ]);
97
-
98
- export const memoryItemSources = sqliteTable('memory_item_sources', {
99
- memoryItemId: text('memory_item_id')
100
- .notNull()
101
- .references(() => memoryItems.id, { onDelete: 'cascade' }),
102
- messageId: text('message_id')
103
- .notNull()
104
- .references(() => messages.id, { onDelete: 'cascade' }),
105
- evidence: text('evidence'),
106
- createdAt: integer('created_at').notNull(),
107
- }, (table) => [
108
- index('idx_memory_item_sources_memory_item_id').on(table.memoryItemId),
109
- ]);
110
-
111
- export const memoryItemConflicts = sqliteTable('memory_item_conflicts', {
112
- id: text('id').primaryKey(),
113
- scopeId: text('scope_id').notNull().default('default'),
114
- existingItemId: text('existing_item_id')
115
- .notNull()
116
- .references(() => memoryItems.id, { onDelete: 'cascade' }),
117
- candidateItemId: text('candidate_item_id')
118
- .notNull()
119
- .references(() => memoryItems.id, { onDelete: 'cascade' }),
120
- relationship: text('relationship').notNull(),
121
- status: text('status').notNull(),
122
- clarificationQuestion: text('clarification_question'),
123
- resolutionNote: text('resolution_note'),
124
- lastAskedAt: integer('last_asked_at'),
125
- resolvedAt: integer('resolved_at'),
126
- createdAt: integer('created_at').notNull(),
127
- updatedAt: integer('updated_at').notNull(),
128
- }, (table) => [
129
- index('idx_memory_item_conflicts_scope_id').on(table.scopeId),
130
- ]);
131
-
132
- export const memorySummaries = sqliteTable('memory_summaries', {
133
- id: text('id').primaryKey(),
134
- scope: text('scope').notNull(),
135
- scopeKey: text('scope_key').notNull(),
136
- summary: text('summary').notNull(),
137
- tokenEstimate: integer('token_estimate').notNull(),
138
- version: integer('version').notNull().default(1),
139
- scopeId: text('scope_id').notNull().default('default'),
140
- startAt: integer('start_at').notNull(),
141
- endAt: integer('end_at').notNull(),
142
- createdAt: integer('created_at').notNull(),
143
- updatedAt: integer('updated_at').notNull(),
144
- }, (table) => [
145
- index('idx_memory_summaries_scope_id').on(table.scopeId),
146
- uniqueIndex('idx_memory_summaries_scope_scope_key').on(table.scope, table.scopeKey),
147
- ]);
148
-
149
- export const memoryEmbeddings = sqliteTable('memory_embeddings', {
150
- id: text('id').primaryKey(),
151
- targetType: text('target_type').notNull(),
152
- targetId: text('target_id').notNull(),
153
- provider: text('provider').notNull(),
154
- model: text('model').notNull(),
155
- dimensions: integer('dimensions').notNull(),
156
- vectorJson: text('vector_json'),
157
- vectorBlob: blob('vector_blob'),
158
- contentHash: text('content_hash'),
159
- createdAt: integer('created_at').notNull(),
160
- updatedAt: integer('updated_at').notNull(),
161
- }, (table) => [
162
- uniqueIndex('idx_memory_embeddings_target_provider_model').on(table.targetType, table.targetId, table.provider, table.model),
163
- ]);
164
-
165
- export const memoryJobs = sqliteTable('memory_jobs', {
166
- id: text('id').primaryKey(),
167
- type: text('type').notNull(),
168
- payload: text('payload').notNull(),
169
- status: text('status').notNull(),
170
- attempts: integer('attempts').notNull().default(0),
171
- deferrals: integer('deferrals').notNull().default(0),
172
- runAfter: integer('run_after').notNull(),
173
- lastError: text('last_error'),
174
- startedAt: integer('started_at'),
175
- createdAt: integer('created_at').notNull(),
176
- updatedAt: integer('updated_at').notNull(),
1
+ import {
2
+ blob,
3
+ index,
4
+ integer,
5
+ real,
6
+ sqliteTable,
7
+ text,
8
+ uniqueIndex,
9
+ } from "drizzle-orm/sqlite-core";
10
+
11
+ import { DAEMON_INTERNAL_ASSISTANT_ID } from "../runtime/assistant-scope.js";
12
+
13
+ export const conversations = sqliteTable(
14
+ "conversations",
15
+ {
16
+ id: text("id").primaryKey(),
17
+ title: text("title"),
18
+ createdAt: integer("created_at").notNull(),
19
+ updatedAt: integer("updated_at").notNull(),
20
+ totalInputTokens: integer("total_input_tokens").notNull().default(0),
21
+ totalOutputTokens: integer("total_output_tokens").notNull().default(0),
22
+ totalEstimatedCost: real("total_estimated_cost").notNull().default(0),
23
+ contextSummary: text("context_summary"),
24
+ contextCompactedMessageCount: integer("context_compacted_message_count")
25
+ .notNull()
26
+ .default(0),
27
+ contextCompactedAt: integer("context_compacted_at"),
28
+ threadType: text("thread_type").notNull().default("standard"),
29
+ source: text("source").notNull().default("user"),
30
+ memoryScopeId: text("memory_scope_id").notNull().default("default"),
31
+ originChannel: text("origin_channel"),
32
+ originInterface: text("origin_interface"),
33
+ isAutoTitle: integer("is_auto_title").notNull().default(1),
34
+ scheduleJobId: text("schedule_job_id"),
35
+ },
36
+ (table) => [
37
+ index("idx_conversations_updated_at").on(table.updatedAt),
38
+ index("idx_conversations_thread_type").on(table.threadType),
39
+ ],
40
+ );
41
+
42
+ export const messages = sqliteTable(
43
+ "messages",
44
+ {
45
+ id: text("id").primaryKey(),
46
+ conversationId: text("conversation_id")
47
+ .notNull()
48
+ .references(() => conversations.id, { onDelete: "cascade" }),
49
+ role: text("role").notNull(),
50
+ content: text("content").notNull(),
51
+ createdAt: integer("created_at").notNull(),
52
+ metadata: text("metadata"),
53
+ },
54
+ (table) => [index("idx_messages_conversation_id").on(table.conversationId)],
55
+ );
56
+
57
+ export const toolInvocations = sqliteTable(
58
+ "tool_invocations",
59
+ {
60
+ id: text("id").primaryKey(),
61
+ conversationId: text("conversation_id")
62
+ .notNull()
63
+ .references(() => conversations.id),
64
+ toolName: text("tool_name").notNull(),
65
+ input: text("input").notNull(),
66
+ result: text("result").notNull(),
67
+ decision: text("decision").notNull(),
68
+ riskLevel: text("risk_level").notNull(),
69
+ durationMs: integer("duration_ms").notNull(),
70
+ createdAt: integer("created_at").notNull(),
71
+ },
72
+ (table) => [
73
+ index("idx_tool_invocations_conversation_id").on(table.conversationId),
74
+ ],
75
+ );
76
+
77
+ export const memorySegments = sqliteTable(
78
+ "memory_segments",
79
+ {
80
+ id: text("id").primaryKey(),
81
+ messageId: text("message_id")
82
+ .notNull()
83
+ .references(() => messages.id, { onDelete: "cascade" }),
84
+ conversationId: text("conversation_id")
85
+ .notNull()
86
+ .references(() => conversations.id, { onDelete: "cascade" }),
87
+ role: text("role").notNull(),
88
+ segmentIndex: integer("segment_index").notNull(),
89
+ text: text("text").notNull(),
90
+ tokenEstimate: integer("token_estimate").notNull(),
91
+ scopeId: text("scope_id").notNull().default("default"),
92
+ contentHash: text("content_hash"),
93
+ createdAt: integer("created_at").notNull(),
94
+ updatedAt: integer("updated_at").notNull(),
95
+ },
96
+ (table) => [index("idx_memory_segments_scope_id").on(table.scopeId)],
97
+ );
98
+
99
+ export const memoryItems = sqliteTable(
100
+ "memory_items",
101
+ {
102
+ id: text("id").primaryKey(),
103
+ kind: text("kind").notNull(),
104
+ subject: text("subject").notNull(),
105
+ statement: text("statement").notNull(),
106
+ status: text("status").notNull(),
107
+ confidence: real("confidence").notNull(),
108
+ importance: real("importance"),
109
+ accessCount: integer("access_count").notNull().default(0),
110
+ fingerprint: text("fingerprint").notNull(),
111
+ verificationState: text("verification_state")
112
+ .notNull()
113
+ .default("assistant_inferred"),
114
+ scopeId: text("scope_id").notNull().default("default"),
115
+ firstSeenAt: integer("first_seen_at").notNull(),
116
+ lastSeenAt: integer("last_seen_at").notNull(),
117
+ lastUsedAt: integer("last_used_at"),
118
+ validFrom: integer("valid_from"),
119
+ invalidAt: integer("invalid_at"),
120
+ },
121
+ (table) => [
122
+ index("idx_memory_items_scope_id").on(table.scopeId),
123
+ index("idx_memory_items_fingerprint").on(table.fingerprint),
124
+ ],
125
+ );
126
+
127
+ export const memoryItemSources = sqliteTable(
128
+ "memory_item_sources",
129
+ {
130
+ memoryItemId: text("memory_item_id")
131
+ .notNull()
132
+ .references(() => memoryItems.id, { onDelete: "cascade" }),
133
+ messageId: text("message_id")
134
+ .notNull()
135
+ .references(() => messages.id, { onDelete: "cascade" }),
136
+ evidence: text("evidence"),
137
+ createdAt: integer("created_at").notNull(),
138
+ },
139
+ (table) => [
140
+ index("idx_memory_item_sources_memory_item_id").on(table.memoryItemId),
141
+ ],
142
+ );
143
+
144
+ export const memoryItemConflicts = sqliteTable(
145
+ "memory_item_conflicts",
146
+ {
147
+ id: text("id").primaryKey(),
148
+ scopeId: text("scope_id").notNull().default("default"),
149
+ existingItemId: text("existing_item_id")
150
+ .notNull()
151
+ .references(() => memoryItems.id, { onDelete: "cascade" }),
152
+ candidateItemId: text("candidate_item_id")
153
+ .notNull()
154
+ .references(() => memoryItems.id, { onDelete: "cascade" }),
155
+ relationship: text("relationship").notNull(),
156
+ status: text("status").notNull(),
157
+ clarificationQuestion: text("clarification_question"),
158
+ resolutionNote: text("resolution_note"),
159
+ lastAskedAt: integer("last_asked_at"),
160
+ resolvedAt: integer("resolved_at"),
161
+ createdAt: integer("created_at").notNull(),
162
+ updatedAt: integer("updated_at").notNull(),
163
+ },
164
+ (table) => [index("idx_memory_item_conflicts_scope_id").on(table.scopeId)],
165
+ );
166
+
167
+ export const memorySummaries = sqliteTable(
168
+ "memory_summaries",
169
+ {
170
+ id: text("id").primaryKey(),
171
+ scope: text("scope").notNull(),
172
+ scopeKey: text("scope_key").notNull(),
173
+ summary: text("summary").notNull(),
174
+ tokenEstimate: integer("token_estimate").notNull(),
175
+ version: integer("version").notNull().default(1),
176
+ scopeId: text("scope_id").notNull().default("default"),
177
+ startAt: integer("start_at").notNull(),
178
+ endAt: integer("end_at").notNull(),
179
+ createdAt: integer("created_at").notNull(),
180
+ updatedAt: integer("updated_at").notNull(),
181
+ },
182
+ (table) => [
183
+ index("idx_memory_summaries_scope_id").on(table.scopeId),
184
+ uniqueIndex("idx_memory_summaries_scope_scope_key").on(
185
+ table.scope,
186
+ table.scopeKey,
187
+ ),
188
+ ],
189
+ );
190
+
191
+ export const memoryEmbeddings = sqliteTable(
192
+ "memory_embeddings",
193
+ {
194
+ id: text("id").primaryKey(),
195
+ targetType: text("target_type").notNull(),
196
+ targetId: text("target_id").notNull(),
197
+ provider: text("provider").notNull(),
198
+ model: text("model").notNull(),
199
+ dimensions: integer("dimensions").notNull(),
200
+ vectorJson: text("vector_json"),
201
+ vectorBlob: blob("vector_blob"),
202
+ contentHash: text("content_hash"),
203
+ createdAt: integer("created_at").notNull(),
204
+ updatedAt: integer("updated_at").notNull(),
205
+ },
206
+ (table) => [
207
+ uniqueIndex("idx_memory_embeddings_target_provider_model").on(
208
+ table.targetType,
209
+ table.targetId,
210
+ table.provider,
211
+ table.model,
212
+ ),
213
+ ],
214
+ );
215
+
216
+ export const memoryJobs = sqliteTable("memory_jobs", {
217
+ id: text("id").primaryKey(),
218
+ type: text("type").notNull(),
219
+ payload: text("payload").notNull(),
220
+ status: text("status").notNull(),
221
+ attempts: integer("attempts").notNull().default(0),
222
+ deferrals: integer("deferrals").notNull().default(0),
223
+ runAfter: integer("run_after").notNull(),
224
+ lastError: text("last_error"),
225
+ startedAt: integer("started_at"),
226
+ createdAt: integer("created_at").notNull(),
227
+ updatedAt: integer("updated_at").notNull(),
177
228
  });
178
229
 
179
- export const conversationKeys = sqliteTable('conversation_keys', {
180
- id: text('id').primaryKey(),
181
- conversationKey: text('conversation_key').notNull(),
182
- conversationId: text('conversation_id')
230
+ export const conversationKeys = sqliteTable("conversation_keys", {
231
+ id: text("id").primaryKey(),
232
+ conversationKey: text("conversation_key").notNull(),
233
+ conversationId: text("conversation_id")
183
234
  .notNull()
184
- .references(() => conversations.id, { onDelete: 'cascade' }),
185
- createdAt: integer('created_at').notNull(),
235
+ .references(() => conversations.id, { onDelete: "cascade" }),
236
+ createdAt: integer("created_at").notNull(),
186
237
  });
187
238
 
188
- export const attachments = sqliteTable('attachments', {
189
- id: text('id').primaryKey(),
190
- originalFilename: text('original_filename').notNull(),
191
- mimeType: text('mime_type').notNull(),
192
- sizeBytes: integer('size_bytes').notNull(),
193
- kind: text('kind').notNull(),
194
- dataBase64: text('data_base64').notNull(),
195
- contentHash: text('content_hash'),
196
- thumbnailBase64: text('thumbnail_base64'),
197
- createdAt: integer('created_at').notNull(),
239
+ export const attachments = sqliteTable("attachments", {
240
+ id: text("id").primaryKey(),
241
+ originalFilename: text("original_filename").notNull(),
242
+ mimeType: text("mime_type").notNull(),
243
+ sizeBytes: integer("size_bytes").notNull(),
244
+ kind: text("kind").notNull(),
245
+ dataBase64: text("data_base64").notNull(),
246
+ contentHash: text("content_hash"),
247
+ thumbnailBase64: text("thumbnail_base64"),
248
+ createdAt: integer("created_at").notNull(),
198
249
  });
199
250
 
200
- export const messageAttachments = sqliteTable('message_attachments', {
201
- id: text('id').primaryKey(),
202
- messageId: text('message_id')
251
+ export const messageAttachments = sqliteTable("message_attachments", {
252
+ id: text("id").primaryKey(),
253
+ messageId: text("message_id")
203
254
  .notNull()
204
- .references(() => messages.id, { onDelete: 'cascade' }),
205
- attachmentId: text('attachment_id')
255
+ .references(() => messages.id, { onDelete: "cascade" }),
256
+ attachmentId: text("attachment_id")
206
257
  .notNull()
207
- .references(() => attachments.id, { onDelete: 'cascade' }),
208
- position: integer('position').notNull().default(0),
209
- createdAt: integer('created_at').notNull(),
258
+ .references(() => attachments.id, { onDelete: "cascade" }),
259
+ position: integer("position").notNull().default(0),
260
+ createdAt: integer("created_at").notNull(),
210
261
  });
211
262
 
212
- export const channelInboundEvents = sqliteTable('channel_inbound_events', {
213
- id: text('id').primaryKey(),
214
- sourceChannel: text('source_channel').notNull(),
215
- externalChatId: text('external_chat_id').notNull(),
216
- externalMessageId: text('external_message_id').notNull(),
217
- sourceMessageId: text('source_message_id'),
218
- conversationId: text('conversation_id')
263
+ export const channelInboundEvents = sqliteTable("channel_inbound_events", {
264
+ id: text("id").primaryKey(),
265
+ sourceChannel: text("source_channel").notNull(),
266
+ externalChatId: text("external_chat_id").notNull(),
267
+ externalMessageId: text("external_message_id").notNull(),
268
+ sourceMessageId: text("source_message_id"),
269
+ conversationId: text("conversation_id")
219
270
  .notNull()
220
- .references(() => conversations.id, { onDelete: 'cascade' }),
221
- messageId: text('message_id')
222
- .references(() => messages.id, { onDelete: 'cascade' }),
223
- deliveryStatus: text('delivery_status').notNull().default('pending'),
224
- processingStatus: text('processing_status').notNull().default('pending'),
225
- processingAttempts: integer('processing_attempts').notNull().default(0),
226
- lastProcessingError: text('last_processing_error'),
227
- retryAfter: integer('retry_after'),
228
- rawPayload: text('raw_payload'),
229
- deliveredSegmentCount: integer('delivered_segment_count').notNull().default(0),
230
- createdAt: integer('created_at').notNull(),
231
- updatedAt: integer('updated_at').notNull(),
271
+ .references(() => conversations.id, { onDelete: "cascade" }),
272
+ messageId: text("message_id").references(() => messages.id, {
273
+ onDelete: "cascade",
274
+ }),
275
+ deliveryStatus: text("delivery_status").notNull().default("pending"),
276
+ processingStatus: text("processing_status").notNull().default("pending"),
277
+ processingAttempts: integer("processing_attempts").notNull().default(0),
278
+ lastProcessingError: text("last_processing_error"),
279
+ retryAfter: integer("retry_after"),
280
+ rawPayload: text("raw_payload"),
281
+ deliveredSegmentCount: integer("delivered_segment_count")
282
+ .notNull()
283
+ .default(0),
284
+ createdAt: integer("created_at").notNull(),
285
+ updatedAt: integer("updated_at").notNull(),
232
286
  });
233
287
 
234
- export const memoryCheckpoints = sqliteTable('memory_checkpoints', {
235
- key: text('key').primaryKey(),
236
- value: text('value').notNull(),
237
- updatedAt: integer('updated_at').notNull(),
288
+ export const memoryCheckpoints = sqliteTable("memory_checkpoints", {
289
+ key: text("key").primaryKey(),
290
+ value: text("value").notNull(),
291
+ updatedAt: integer("updated_at").notNull(),
238
292
  });
239
293
 
240
294
  // ── Reminders ────────────────────────────────────────────────────────
241
295
 
242
- export const reminders = sqliteTable('reminders', {
243
- id: text('id').primaryKey(),
244
- label: text('label').notNull(),
245
- message: text('message').notNull(),
246
- fireAt: integer('fire_at').notNull(), // epoch ms, absolute timestamp
247
- mode: text('mode').notNull(), // 'notify' | 'execute'
248
- status: text('status').notNull(), // 'pending' | 'firing' | 'fired' | 'cancelled'
249
- firedAt: integer('fired_at'),
250
- conversationId: text('conversation_id'),
251
- routingIntent: text('routing_intent').notNull().default('all_channels'), // 'single_channel' | 'multi_channel' | 'all_channels'
252
- routingHintsJson: text('routing_hints_json').notNull().default('{}'),
253
- createdAt: integer('created_at').notNull(),
254
- updatedAt: integer('updated_at').notNull(),
296
+ export const reminders = sqliteTable("reminders", {
297
+ id: text("id").primaryKey(),
298
+ label: text("label").notNull(),
299
+ message: text("message").notNull(),
300
+ fireAt: integer("fire_at").notNull(), // epoch ms, absolute timestamp
301
+ mode: text("mode").notNull(), // 'notify' | 'execute'
302
+ status: text("status").notNull(), // 'pending' | 'firing' | 'fired' | 'cancelled'
303
+ firedAt: integer("fired_at"),
304
+ conversationId: text("conversation_id"),
305
+ routingIntent: text("routing_intent").notNull().default("all_channels"), // 'single_channel' | 'multi_channel' | 'all_channels'
306
+ routingHintsJson: text("routing_hints_json").notNull().default("{}"),
307
+ createdAt: integer("created_at").notNull(),
308
+ updatedAt: integer("updated_at").notNull(),
255
309
  });
256
310
 
257
311
  // ── Recurrence Schedules ─────────────────────────────────────────────
258
312
 
259
- export const cronJobs = sqliteTable('cron_jobs', {
260
- id: text('id').primaryKey(),
261
- name: text('name').notNull(),
262
- enabled: integer('enabled', { mode: 'boolean' }).notNull().default(true),
263
- cronExpression: text('cron_expression').notNull(), // e.g. '0 9 * * 1-5'
264
- scheduleSyntax: text('schedule_syntax').notNull().default('cron'), // 'cron' | 'rrule'
265
- timezone: text('timezone'), // e.g. 'America/Los_Angeles'
266
- message: text('message').notNull(),
267
- nextRunAt: integer('next_run_at').notNull(),
268
- lastRunAt: integer('last_run_at'),
269
- lastStatus: text('last_status'), // 'ok' | 'error'
270
- retryCount: integer('retry_count').notNull().default(0),
271
- createdBy: text('created_by').notNull(), // 'agent' | 'user'
272
- createdAt: integer('created_at').notNull(),
273
- updatedAt: integer('updated_at').notNull(),
313
+ export const cronJobs = sqliteTable("cron_jobs", {
314
+ id: text("id").primaryKey(),
315
+ name: text("name").notNull(),
316
+ enabled: integer("enabled", { mode: "boolean" }).notNull().default(true),
317
+ cronExpression: text("cron_expression").notNull(), // e.g. '0 9 * * 1-5'
318
+ scheduleSyntax: text("schedule_syntax").notNull().default("cron"), // 'cron' | 'rrule'
319
+ timezone: text("timezone"), // e.g. 'America/Los_Angeles'
320
+ message: text("message").notNull(),
321
+ nextRunAt: integer("next_run_at").notNull(),
322
+ lastRunAt: integer("last_run_at"),
323
+ lastStatus: text("last_status"), // 'ok' | 'error'
324
+ retryCount: integer("retry_count").notNull().default(0),
325
+ createdBy: text("created_by").notNull(), // 'agent' | 'user'
326
+ createdAt: integer("created_at").notNull(),
327
+ updatedAt: integer("updated_at").notNull(),
274
328
  });
275
329
 
276
- export const accounts = sqliteTable('accounts', {
277
- id: text('id').primaryKey(),
278
- service: text('service').notNull(),
279
- username: text('username'),
280
- email: text('email'),
281
- displayName: text('display_name'),
282
- status: text('status').notNull().default('active'),
283
- credentialRef: text('credential_ref'),
284
- metadataJson: text('metadata_json'),
285
- createdAt: integer('created_at').notNull(),
286
- updatedAt: integer('updated_at').notNull(),
330
+ export const accounts = sqliteTable("accounts", {
331
+ id: text("id").primaryKey(),
332
+ service: text("service").notNull(),
333
+ username: text("username"),
334
+ email: text("email"),
335
+ displayName: text("display_name"),
336
+ status: text("status").notNull().default("active"),
337
+ credentialRef: text("credential_ref"),
338
+ metadataJson: text("metadata_json"),
339
+ createdAt: integer("created_at").notNull(),
340
+ updatedAt: integer("updated_at").notNull(),
287
341
  });
288
342
 
289
- export const cronRuns = sqliteTable('cron_runs', {
290
- id: text('id').primaryKey(),
291
- jobId: text('job_id')
343
+ export const cronRuns = sqliteTable("cron_runs", {
344
+ id: text("id").primaryKey(),
345
+ jobId: text("job_id")
292
346
  .notNull()
293
- .references(() => cronJobs.id, { onDelete: 'cascade' }),
294
- status: text('status').notNull(), // 'ok' | 'error'
295
- startedAt: integer('started_at').notNull(),
296
- finishedAt: integer('finished_at'),
297
- durationMs: integer('duration_ms'),
298
- output: text('output'),
299
- error: text('error'),
300
- conversationId: text('conversation_id'),
301
- createdAt: integer('created_at').notNull(),
347
+ .references(() => cronJobs.id, { onDelete: "cascade" }),
348
+ status: text("status").notNull(), // 'ok' | 'error'
349
+ startedAt: integer("started_at").notNull(),
350
+ finishedAt: integer("finished_at"),
351
+ durationMs: integer("duration_ms"),
352
+ output: text("output"),
353
+ error: text("error"),
354
+ conversationId: text("conversation_id"),
355
+ createdAt: integer("created_at").notNull(),
302
356
  });
303
357
 
304
358
  // Recurrence-centric aliases — prefer these in new code.
@@ -310,901 +364,1039 @@ export const scheduleRuns = cronRuns;
310
364
 
311
365
  // ── Entity Graph ─────────────────────────────────────────────────────
312
366
 
313
- export const memoryEntities = sqliteTable('memory_entities', {
314
- id: text('id').primaryKey(),
315
- name: text('name').notNull(),
316
- type: text('type').notNull(),
317
- aliases: text('aliases'),
318
- description: text('description'),
319
- firstSeenAt: integer('first_seen_at').notNull(),
320
- lastSeenAt: integer('last_seen_at').notNull(),
321
- mentionCount: integer('mention_count').notNull().default(1),
367
+ export const memoryEntities = sqliteTable("memory_entities", {
368
+ id: text("id").primaryKey(),
369
+ name: text("name").notNull(),
370
+ type: text("type").notNull(),
371
+ aliases: text("aliases"),
372
+ description: text("description"),
373
+ firstSeenAt: integer("first_seen_at").notNull(),
374
+ lastSeenAt: integer("last_seen_at").notNull(),
375
+ mentionCount: integer("mention_count").notNull().default(1),
322
376
  });
323
377
 
324
- export const memoryEntityRelations = sqliteTable('memory_entity_relations', {
325
- id: text('id').primaryKey(),
326
- sourceEntityId: text('source_entity_id').notNull(),
327
- targetEntityId: text('target_entity_id').notNull(),
328
- relation: text('relation').notNull(),
329
- evidence: text('evidence'),
330
- firstSeenAt: integer('first_seen_at').notNull(),
331
- lastSeenAt: integer('last_seen_at').notNull(),
378
+ export const memoryEntityRelations = sqliteTable("memory_entity_relations", {
379
+ id: text("id").primaryKey(),
380
+ sourceEntityId: text("source_entity_id").notNull(),
381
+ targetEntityId: text("target_entity_id").notNull(),
382
+ relation: text("relation").notNull(),
383
+ evidence: text("evidence"),
384
+ firstSeenAt: integer("first_seen_at").notNull(),
385
+ lastSeenAt: integer("last_seen_at").notNull(),
332
386
  });
333
387
 
334
- export const memoryItemEntities = sqliteTable('memory_item_entities', {
335
- memoryItemId: text('memory_item_id').notNull(),
336
- entityId: text('entity_id').notNull(),
388
+ export const memoryItemEntities = sqliteTable("memory_item_entities", {
389
+ memoryItemId: text("memory_item_id").notNull(),
390
+ entityId: text("entity_id").notNull(),
337
391
  });
338
392
 
339
- export const sharedAppLinks = sqliteTable('shared_app_links', {
340
- id: text('id').primaryKey(),
341
- shareToken: text('share_token').notNull().unique(),
342
- bundleData: blob('bundle_data', { mode: 'buffer' }).notNull(),
343
- bundleSizeBytes: integer('bundle_size_bytes').notNull(),
344
- manifestJson: text('manifest_json').notNull(),
345
- downloadCount: integer('download_count').notNull().default(0),
346
- createdAt: integer('created_at').notNull(),
347
- expiresAt: integer('expires_at'),
393
+ export const sharedAppLinks = sqliteTable("shared_app_links", {
394
+ id: text("id").primaryKey(),
395
+ shareToken: text("share_token").notNull().unique(),
396
+ bundleData: blob("bundle_data", { mode: "buffer" }).notNull(),
397
+ bundleSizeBytes: integer("bundle_size_bytes").notNull(),
398
+ manifestJson: text("manifest_json").notNull(),
399
+ downloadCount: integer("download_count").notNull().default(0),
400
+ createdAt: integer("created_at").notNull(),
401
+ expiresAt: integer("expires_at"),
348
402
  });
349
403
 
350
404
  // ── Contacts ─────────────────────────────────────────────────────────
351
405
 
352
- export const contacts = sqliteTable('contacts', {
353
- id: text('id').primaryKey(),
354
- displayName: text('display_name').notNull(),
355
- relationship: text('relationship'), // e.g. 'colleague', 'friend', 'manager', 'client'
356
- importance: real('importance').notNull().default(0.5), // 0-1 scale, learned from interaction patterns
357
- responseExpectation: text('response_expectation'), // e.g. 'immediate', 'within_hours', 'casual'
358
- preferredTone: text('preferred_tone'), // e.g. 'formal', 'casual', 'friendly'
359
- lastInteraction: integer('last_interaction'), // epoch ms
360
- interactionCount: integer('interaction_count').notNull().default(0),
361
- createdAt: integer('created_at').notNull(),
362
- updatedAt: integer('updated_at').notNull(),
406
+ export const contacts = sqliteTable("contacts", {
407
+ id: text("id").primaryKey(),
408
+ displayName: text("display_name").notNull(),
409
+ relationship: text("relationship"), // e.g. 'colleague', 'friend', 'manager', 'client'
410
+ importance: real("importance").notNull().default(0.5), // 0-1 scale, learned from interaction patterns
411
+ responseExpectation: text("response_expectation"), // e.g. 'immediate', 'within_hours', 'casual'
412
+ preferredTone: text("preferred_tone"), // e.g. 'formal', 'casual', 'friendly'
413
+ lastInteraction: integer("last_interaction"), // epoch ms
414
+ interactionCount: integer("interaction_count").notNull().default(0),
415
+ createdAt: integer("created_at").notNull(),
416
+ updatedAt: integer("updated_at").notNull(),
363
417
  });
364
418
 
365
- export const contactChannels = sqliteTable('contact_channels', {
366
- id: text('id').primaryKey(),
367
- contactId: text('contact_id')
419
+ export const contactChannels = sqliteTable("contact_channels", {
420
+ id: text("id").primaryKey(),
421
+ contactId: text("contact_id")
422
+ .notNull()
423
+ .references(() => contacts.id, { onDelete: "cascade" }),
424
+ type: text("type").notNull(), // 'email', 'slack', 'whatsapp', 'phone', etc.
425
+ address: text("address").notNull(), // the actual identifier on that channel
426
+ isPrimary: integer("is_primary", { mode: "boolean" })
368
427
  .notNull()
369
- .references(() => contacts.id, { onDelete: 'cascade' }),
370
- type: text('type').notNull(), // 'email', 'slack', 'whatsapp', 'phone', etc.
371
- address: text('address').notNull(), // the actual identifier on that channel
372
- isPrimary: integer('is_primary', { mode: 'boolean' }).notNull().default(false),
373
- createdAt: integer('created_at').notNull(),
428
+ .default(false),
429
+ createdAt: integer("created_at").notNull(),
374
430
  });
375
431
 
376
432
  // ── Triage Results ───────────────────────────────────────────────────
377
433
 
378
- export const triageResults = sqliteTable('triage_results', {
379
- id: text('id').primaryKey(),
380
- channel: text('channel').notNull(),
381
- sender: text('sender').notNull(),
382
- category: text('category').notNull(),
383
- confidence: real('confidence').notNull(),
384
- suggestedAction: text('suggested_action').notNull(),
385
- matchedPlaybookIds: text('matched_playbook_ids'), // JSON array of playbook memory item IDs
386
- messageId: text('message_id'), // optional external message identifier
387
- createdAt: integer('created_at').notNull(),
434
+ export const triageResults = sqliteTable("triage_results", {
435
+ id: text("id").primaryKey(),
436
+ channel: text("channel").notNull(),
437
+ sender: text("sender").notNull(),
438
+ category: text("category").notNull(),
439
+ confidence: real("confidence").notNull(),
440
+ suggestedAction: text("suggested_action").notNull(),
441
+ matchedPlaybookIds: text("matched_playbook_ids"), // JSON array of playbook memory item IDs
442
+ messageId: text("message_id"), // optional external message identifier
443
+ createdAt: integer("created_at").notNull(),
388
444
  });
389
445
 
390
446
  // ── Follow-ups ──────────────────────────────────────────────────────
391
447
 
392
- export const followups = sqliteTable('followups', {
393
- id: text('id').primaryKey(),
394
- channel: text('channel').notNull(), // 'email', 'slack', 'whatsapp', etc.
395
- threadId: text('thread_id').notNull(), // external thread/conversation identifier
396
- contactId: text('contact_id')
397
- .references(() => contacts.id, { onDelete: 'set null' }),
398
- sentAt: integer('sent_at').notNull(), // epoch ms — when the outbound message was sent
399
- expectedResponseBy: integer('expected_response_by'), // epoch ms — deadline for expected reply
400
- status: text('status').notNull().default('pending'), // 'pending' | 'resolved' | 'overdue' | 'nudged'
401
- reminderCronId: text('reminder_cron_id'), // optional cron job ID for reminder
402
- createdAt: integer('created_at').notNull(),
403
- updatedAt: integer('updated_at').notNull(),
448
+ export const followups = sqliteTable("followups", {
449
+ id: text("id").primaryKey(),
450
+ channel: text("channel").notNull(), // 'email', 'slack', 'whatsapp', etc.
451
+ threadId: text("thread_id").notNull(), // external thread/conversation identifier
452
+ contactId: text("contact_id").references(() => contacts.id, {
453
+ onDelete: "set null",
454
+ }),
455
+ sentAt: integer("sent_at").notNull(), // epoch ms — when the outbound message was sent
456
+ expectedResponseBy: integer("expected_response_by"), // epoch ms deadline for expected reply
457
+ status: text("status").notNull().default("pending"), // 'pending' | 'resolved' | 'overdue' | 'nudged'
458
+ reminderCronId: text("reminder_cron_id"), // optional cron job ID for reminder
459
+ createdAt: integer("created_at").notNull(),
460
+ updatedAt: integer("updated_at").notNull(),
404
461
  });
405
462
 
406
463
  // ── Tasks ────────────────────────────────────────────────────────────
407
464
 
408
- export const tasks = sqliteTable('tasks', {
409
- id: text('id').primaryKey(),
410
- title: text('title').notNull(),
411
- template: text('template').notNull(),
412
- inputSchema: text('input_schema'),
413
- contextFlags: text('context_flags'),
414
- requiredTools: text('required_tools'),
415
- createdFromConversationId: text('created_from_conversation_id'),
416
- status: text('status').notNull().default('active'),
417
- createdAt: integer('created_at').notNull(),
418
- updatedAt: integer('updated_at').notNull(),
465
+ export const tasks = sqliteTable("tasks", {
466
+ id: text("id").primaryKey(),
467
+ title: text("title").notNull(),
468
+ template: text("template").notNull(),
469
+ inputSchema: text("input_schema"),
470
+ contextFlags: text("context_flags"),
471
+ requiredTools: text("required_tools"),
472
+ createdFromConversationId: text("created_from_conversation_id"),
473
+ status: text("status").notNull().default("active"),
474
+ createdAt: integer("created_at").notNull(),
475
+ updatedAt: integer("updated_at").notNull(),
419
476
  });
420
477
 
421
- export const taskRuns = sqliteTable('task_runs', {
422
- id: text('id').primaryKey(),
423
- taskId: text('task_id')
478
+ export const taskRuns = sqliteTable("task_runs", {
479
+ id: text("id").primaryKey(),
480
+ taskId: text("task_id")
424
481
  .notNull()
425
- .references(() => tasks.id, { onDelete: 'cascade' }),
426
- conversationId: text('conversation_id'),
427
- status: text('status').notNull().default('pending'),
428
- startedAt: integer('started_at'),
429
- finishedAt: integer('finished_at'),
430
- error: text('error'),
431
- principalId: text('principal_id'),
432
- memoryScopeId: text('memory_scope_id'),
433
- createdAt: integer('created_at').notNull(),
482
+ .references(() => tasks.id, { onDelete: "cascade" }),
483
+ conversationId: text("conversation_id"),
484
+ status: text("status").notNull().default("pending"),
485
+ startedAt: integer("started_at"),
486
+ finishedAt: integer("finished_at"),
487
+ error: text("error"),
488
+ principalId: text("principal_id"),
489
+ memoryScopeId: text("memory_scope_id"),
490
+ createdAt: integer("created_at").notNull(),
434
491
  });
435
492
 
436
- export const taskCandidates = sqliteTable('task_candidates', {
437
- id: text('id').primaryKey(),
438
- sourceConversationId: text('source_conversation_id').notNull(),
439
- compiledTemplate: text('compiled_template').notNull(),
440
- confidence: real('confidence'),
441
- requiredTools: text('required_tools'), // JSON array string
442
- createdAt: integer('created_at').notNull(),
443
- promotedTaskId: text('promoted_task_id'), // set when candidate is promoted to a real task
493
+ export const taskCandidates = sqliteTable("task_candidates", {
494
+ id: text("id").primaryKey(),
495
+ sourceConversationId: text("source_conversation_id").notNull(),
496
+ compiledTemplate: text("compiled_template").notNull(),
497
+ confidence: real("confidence"),
498
+ requiredTools: text("required_tools"), // JSON array string
499
+ createdAt: integer("created_at").notNull(),
500
+ promotedTaskId: text("promoted_task_id"), // set when candidate is promoted to a real task
444
501
  });
445
502
 
446
503
  // ── Work Items (Tasks) ───────────────────────────────────────────────
447
504
 
448
- export const workItems = sqliteTable('work_items', {
449
- id: text('id').primaryKey(),
450
- taskId: text('task_id').notNull().references(() => tasks.id),
451
- title: text('title').notNull(),
452
- notes: text('notes'),
453
- status: text('status').notNull().default('queued'), // queued | running | awaiting_review | failed | cancelled | done | archived
454
- priorityTier: integer('priority_tier').notNull().default(1), // 0=high, 1=medium, 2=low
455
- sortIndex: integer('sort_index'), // manual ordering within same priority tier; null = fall back to updated_at
456
- lastRunId: text('last_run_id'),
457
- lastRunConversationId: text('last_run_conversation_id'),
458
- lastRunStatus: text('last_run_status'), // 'completed' | 'failed' | null
459
- sourceType: text('source_type'), // reserved for future bridge (e.g. 'followup', 'triage')
460
- sourceId: text('source_id'), // reserved for future bridge
461
- requiredTools: text('required_tools'), // JSON array snapshot of tools needed for this run (null=unknown, []=none, ["bash",...]=specific)
462
- approvedTools: text('approved_tools'), // JSON array of pre-approved tool names
463
- approvalStatus: text('approval_status').default('none'), // 'none' | 'approved' | 'denied'
464
- createdAt: integer('created_at').notNull(),
465
- updatedAt: integer('updated_at').notNull(),
505
+ export const workItems = sqliteTable("work_items", {
506
+ id: text("id").primaryKey(),
507
+ taskId: text("task_id")
508
+ .notNull()
509
+ .references(() => tasks.id),
510
+ title: text("title").notNull(),
511
+ notes: text("notes"),
512
+ status: text("status").notNull().default("queued"), // queued | running | awaiting_review | failed | cancelled | done | archived
513
+ priorityTier: integer("priority_tier").notNull().default(1), // 0=high, 1=medium, 2=low
514
+ sortIndex: integer("sort_index"), // manual ordering within same priority tier; null = fall back to updated_at
515
+ lastRunId: text("last_run_id"),
516
+ lastRunConversationId: text("last_run_conversation_id"),
517
+ lastRunStatus: text("last_run_status"), // 'completed' | 'failed' | null
518
+ sourceType: text("source_type"), // reserved for future bridge (e.g. 'followup', 'triage')
519
+ sourceId: text("source_id"), // reserved for future bridge
520
+ requiredTools: text("required_tools"), // JSON array snapshot of tools needed for this run (null=unknown, []=none, ["bash",...]=specific)
521
+ approvedTools: text("approved_tools"), // JSON array of pre-approved tool names
522
+ approvalStatus: text("approval_status").default("none"), // 'none' | 'approved' | 'denied'
523
+ createdAt: integer("created_at").notNull(),
524
+ updatedAt: integer("updated_at").notNull(),
466
525
  });
467
526
 
468
- export const homeBaseAppLinks = sqliteTable('home_base_app_links', {
469
- id: text('id').primaryKey(),
470
- appId: text('app_id').notNull(),
471
- source: text('source').notNull(),
472
- createdAt: integer('created_at').notNull(),
473
- updatedAt: integer('updated_at').notNull(),
527
+ export const homeBaseAppLinks = sqliteTable("home_base_app_links", {
528
+ id: text("id").primaryKey(),
529
+ appId: text("app_id").notNull(),
530
+ source: text("source").notNull(),
531
+ createdAt: integer("created_at").notNull(),
532
+ updatedAt: integer("updated_at").notNull(),
474
533
  });
475
534
 
476
- export const publishedPages = sqliteTable('published_pages', {
477
- id: text('id').primaryKey(),
478
- deploymentId: text('deployment_id').notNull().unique(),
479
- publicUrl: text('public_url').notNull(),
480
- pageTitle: text('page_title'),
481
- htmlHash: text('html_hash').notNull(),
482
- publishedAt: integer('published_at').notNull(),
483
- status: text('status').notNull().default('active'),
484
- appId: text('app_id'),
485
- projectSlug: text('project_slug'),
535
+ export const publishedPages = sqliteTable("published_pages", {
536
+ id: text("id").primaryKey(),
537
+ deploymentId: text("deployment_id").notNull().unique(),
538
+ publicUrl: text("public_url").notNull(),
539
+ pageTitle: text("page_title"),
540
+ htmlHash: text("html_hash").notNull(),
541
+ publishedAt: integer("published_at").notNull(),
542
+ status: text("status").notNull().default("active"),
543
+ appId: text("app_id"),
544
+ projectSlug: text("project_slug"),
486
545
  });
487
546
 
488
547
  // ── Watchers (event-driven polling) ──────────────────────────────────
489
548
 
490
- export const watchers = sqliteTable('watchers', {
491
- id: text('id').primaryKey(),
492
- name: text('name').notNull(),
493
- providerId: text('provider_id').notNull(),
494
- enabled: integer('enabled', { mode: 'boolean' }).notNull().default(true),
495
- pollIntervalMs: integer('poll_interval_ms').notNull().default(60000),
496
- actionPrompt: text('action_prompt').notNull(),
497
- watermark: text('watermark'),
498
- conversationId: text('conversation_id'),
499
- status: text('status').notNull().default('idle'), // idle | polling | error | disabled
500
- consecutiveErrors: integer('consecutive_errors').notNull().default(0),
501
- lastError: text('last_error'),
502
- lastPollAt: integer('last_poll_at'),
503
- nextPollAt: integer('next_poll_at').notNull(),
504
- configJson: text('config_json'),
505
- credentialService: text('credential_service').notNull(),
506
- createdAt: integer('created_at').notNull(),
507
- updatedAt: integer('updated_at').notNull(),
549
+ export const watchers = sqliteTable("watchers", {
550
+ id: text("id").primaryKey(),
551
+ name: text("name").notNull(),
552
+ providerId: text("provider_id").notNull(),
553
+ enabled: integer("enabled", { mode: "boolean" }).notNull().default(true),
554
+ pollIntervalMs: integer("poll_interval_ms").notNull().default(60000),
555
+ actionPrompt: text("action_prompt").notNull(),
556
+ watermark: text("watermark"),
557
+ conversationId: text("conversation_id"),
558
+ status: text("status").notNull().default("idle"), // idle | polling | error | disabled
559
+ consecutiveErrors: integer("consecutive_errors").notNull().default(0),
560
+ lastError: text("last_error"),
561
+ lastPollAt: integer("last_poll_at"),
562
+ nextPollAt: integer("next_poll_at").notNull(),
563
+ configJson: text("config_json"),
564
+ credentialService: text("credential_service").notNull(),
565
+ createdAt: integer("created_at").notNull(),
566
+ updatedAt: integer("updated_at").notNull(),
508
567
  });
509
568
 
510
- export const watcherEvents = sqliteTable('watcher_events', {
511
- id: text('id').primaryKey(),
512
- watcherId: text('watcher_id')
569
+ export const watcherEvents = sqliteTable("watcher_events", {
570
+ id: text("id").primaryKey(),
571
+ watcherId: text("watcher_id")
513
572
  .notNull()
514
- .references(() => watchers.id, { onDelete: 'cascade' }),
515
- externalId: text('external_id').notNull(),
516
- eventType: text('event_type').notNull(),
517
- summary: text('summary').notNull(),
518
- payloadJson: text('payload_json').notNull(),
519
- disposition: text('disposition').notNull().default('pending'), // pending | silent | notify | escalate | error
520
- llmAction: text('llm_action'),
521
- processedAt: integer('processed_at'),
522
- createdAt: integer('created_at').notNull(),
573
+ .references(() => watchers.id, { onDelete: "cascade" }),
574
+ externalId: text("external_id").notNull(),
575
+ eventType: text("event_type").notNull(),
576
+ summary: text("summary").notNull(),
577
+ payloadJson: text("payload_json").notNull(),
578
+ disposition: text("disposition").notNull().default("pending"), // pending | silent | notify | escalate | error
579
+ llmAction: text("llm_action"),
580
+ processedAt: integer("processed_at"),
581
+ createdAt: integer("created_at").notNull(),
523
582
  });
524
583
 
525
- export const llmRequestLogs = sqliteTable('llm_request_logs', {
526
- id: text('id').primaryKey(),
527
- conversationId: text('conversation_id').notNull(),
528
- requestPayload: text('request_payload').notNull(),
529
- responsePayload: text('response_payload').notNull(),
530
- createdAt: integer('created_at').notNull(),
584
+ export const llmRequestLogs = sqliteTable("llm_request_logs", {
585
+ id: text("id").primaryKey(),
586
+ conversationId: text("conversation_id").notNull(),
587
+ requestPayload: text("request_payload").notNull(),
588
+ responsePayload: text("response_payload").notNull(),
589
+ createdAt: integer("created_at").notNull(),
531
590
  });
532
591
 
533
- export const llmUsageEvents = sqliteTable('llm_usage_events', {
534
- id: text('id').primaryKey(),
535
- createdAt: integer('created_at').notNull(),
536
- conversationId: text('conversation_id'),
537
- runId: text('run_id'),
538
- requestId: text('request_id'),
539
- actor: text('actor').notNull(),
540
- provider: text('provider').notNull(),
541
- model: text('model').notNull(),
542
- inputTokens: integer('input_tokens').notNull(),
543
- outputTokens: integer('output_tokens').notNull(),
544
- cacheCreationInputTokens: integer('cache_creation_input_tokens'),
545
- cacheReadInputTokens: integer('cache_read_input_tokens'),
546
- estimatedCostUsd: real('estimated_cost_usd'),
547
- pricingStatus: text('pricing_status').notNull(),
548
- metadataJson: text('metadata_json'),
549
- }, (table) => [
550
- index('idx_llm_usage_events_conversation_id').on(table.conversationId),
551
- ]);
592
+ export const llmUsageEvents = sqliteTable(
593
+ "llm_usage_events",
594
+ {
595
+ id: text("id").primaryKey(),
596
+ createdAt: integer("created_at").notNull(),
597
+ conversationId: text("conversation_id"),
598
+ runId: text("run_id"),
599
+ requestId: text("request_id"),
600
+ actor: text("actor").notNull(),
601
+ provider: text("provider").notNull(),
602
+ model: text("model").notNull(),
603
+ inputTokens: integer("input_tokens").notNull(),
604
+ outputTokens: integer("output_tokens").notNull(),
605
+ cacheCreationInputTokens: integer("cache_creation_input_tokens"),
606
+ cacheReadInputTokens: integer("cache_read_input_tokens"),
607
+ estimatedCostUsd: real("estimated_cost_usd"),
608
+ pricingStatus: text("pricing_status").notNull(),
609
+ metadataJson: text("metadata_json"),
610
+ },
611
+ (table) => [
612
+ index("idx_llm_usage_events_conversation_id").on(table.conversationId),
613
+ ],
614
+ );
552
615
 
553
616
  // ── Call Sessions (outgoing AI phone calls) ──────────────────────────
554
617
 
555
- export const callSessions = sqliteTable('call_sessions', {
556
- id: text('id').primaryKey(),
557
- conversationId: text('conversation_id')
558
- .notNull()
559
- .references(() => conversations.id, { onDelete: 'cascade' }),
560
- provider: text('provider').notNull(),
561
- providerCallSid: text('provider_call_sid'),
562
- fromNumber: text('from_number').notNull(),
563
- toNumber: text('to_number').notNull(),
564
- task: text('task'),
565
- status: text('status').notNull().default('initiated'),
566
- callMode: text('call_mode'),
567
- guardianVerificationSessionId: text('guardian_verification_session_id'),
568
- callerIdentityMode: text('caller_identity_mode'),
569
- callerIdentitySource: text('caller_identity_source'),
570
- assistantId: text('assistant_id'),
571
- initiatedFromConversationId: text('initiated_from_conversation_id'),
572
- startedAt: integer('started_at'),
573
- endedAt: integer('ended_at'),
574
- lastError: text('last_error'),
575
- createdAt: integer('created_at').notNull(),
576
- updatedAt: integer('updated_at').notNull(),
577
- }, (table) => [
578
- index('idx_call_sessions_status').on(table.status),
579
- ]);
580
-
581
- export const callEvents = sqliteTable('call_events', {
582
- id: text('id').primaryKey(),
583
- callSessionId: text('call_session_id')
618
+ export const callSessions = sqliteTable(
619
+ "call_sessions",
620
+ {
621
+ id: text("id").primaryKey(),
622
+ conversationId: text("conversation_id")
623
+ .notNull()
624
+ .references(() => conversations.id, { onDelete: "cascade" }),
625
+ provider: text("provider").notNull(),
626
+ providerCallSid: text("provider_call_sid"),
627
+ fromNumber: text("from_number").notNull(),
628
+ toNumber: text("to_number").notNull(),
629
+ task: text("task"),
630
+ status: text("status").notNull().default("initiated"),
631
+ callMode: text("call_mode"),
632
+ guardianVerificationSessionId: text("guardian_verification_session_id"),
633
+ callerIdentityMode: text("caller_identity_mode"),
634
+ callerIdentitySource: text("caller_identity_source"),
635
+ assistantId: text("assistant_id"),
636
+ initiatedFromConversationId: text("initiated_from_conversation_id"),
637
+ startedAt: integer("started_at"),
638
+ endedAt: integer("ended_at"),
639
+ lastError: text("last_error"),
640
+ createdAt: integer("created_at").notNull(),
641
+ updatedAt: integer("updated_at").notNull(),
642
+ },
643
+ (table) => [index("idx_call_sessions_status").on(table.status)],
644
+ );
645
+
646
+ export const callEvents = sqliteTable("call_events", {
647
+ id: text("id").primaryKey(),
648
+ callSessionId: text("call_session_id")
584
649
  .notNull()
585
- .references(() => callSessions.id, { onDelete: 'cascade' }),
586
- eventType: text('event_type').notNull(),
587
- payloadJson: text('payload_json').notNull().default('{}'),
588
- createdAt: integer('created_at').notNull(),
650
+ .references(() => callSessions.id, { onDelete: "cascade" }),
651
+ eventType: text("event_type").notNull(),
652
+ payloadJson: text("payload_json").notNull().default("{}"),
653
+ createdAt: integer("created_at").notNull(),
589
654
  });
590
655
 
591
- export const callPendingQuestions = sqliteTable('call_pending_questions', {
592
- id: text('id').primaryKey(),
593
- callSessionId: text('call_session_id')
656
+ export const callPendingQuestions = sqliteTable("call_pending_questions", {
657
+ id: text("id").primaryKey(),
658
+ callSessionId: text("call_session_id")
594
659
  .notNull()
595
- .references(() => callSessions.id, { onDelete: 'cascade' }),
596
- questionText: text('question_text').notNull(),
597
- status: text('status').notNull().default('pending'),
598
- askedAt: integer('asked_at').notNull(),
599
- answeredAt: integer('answered_at'),
600
- answerText: text('answer_text'),
660
+ .references(() => callSessions.id, { onDelete: "cascade" }),
661
+ questionText: text("question_text").notNull(),
662
+ status: text("status").notNull().default("pending"),
663
+ askedAt: integer("asked_at").notNull(),
664
+ answeredAt: integer("answered_at"),
665
+ answerText: text("answer_text"),
601
666
  });
602
667
 
603
- export const processedCallbacks = sqliteTable('processed_callbacks', {
604
- id: text('id').primaryKey(),
605
- dedupeKey: text('dedupe_key').notNull().unique(),
606
- callSessionId: text('call_session_id')
668
+ export const processedCallbacks = sqliteTable("processed_callbacks", {
669
+ id: text("id").primaryKey(),
670
+ dedupeKey: text("dedupe_key").notNull().unique(),
671
+ callSessionId: text("call_session_id")
607
672
  .notNull()
608
- .references(() => callSessions.id, { onDelete: 'cascade' }),
609
- claimId: text('claim_id'),
610
- createdAt: integer('created_at').notNull(),
673
+ .references(() => callSessions.id, { onDelete: "cascade" }),
674
+ claimId: text("claim_id"),
675
+ createdAt: integer("created_at").notNull(),
611
676
  });
612
677
 
613
678
  // ── External Conversation Bindings ───────────────────────────────────
614
679
  // UNIQUE (source_channel, external_chat_id) enforced via idx_ext_conv_bindings_channel_chat_unique in db.ts
615
680
 
616
- export const externalConversationBindings = sqliteTable('external_conversation_bindings', {
617
- conversationId: text('conversation_id')
618
- .primaryKey()
619
- .references(() => conversations.id, { onDelete: 'cascade' }),
620
- sourceChannel: text('source_channel').notNull(),
621
- externalChatId: text('external_chat_id').notNull(),
622
- externalUserId: text('external_user_id'),
623
- displayName: text('display_name'),
624
- username: text('username'),
625
- createdAt: integer('created_at').notNull(),
626
- updatedAt: integer('updated_at').notNull(),
627
- lastInboundAt: integer('last_inbound_at'),
628
- lastOutboundAt: integer('last_outbound_at'),
629
- });
681
+ export const externalConversationBindings = sqliteTable(
682
+ "external_conversation_bindings",
683
+ {
684
+ conversationId: text("conversation_id")
685
+ .primaryKey()
686
+ .references(() => conversations.id, { onDelete: "cascade" }),
687
+ sourceChannel: text("source_channel").notNull(),
688
+ externalChatId: text("external_chat_id").notNull(),
689
+ externalUserId: text("external_user_id"),
690
+ displayName: text("display_name"),
691
+ username: text("username"),
692
+ createdAt: integer("created_at").notNull(),
693
+ updatedAt: integer("updated_at").notNull(),
694
+ lastInboundAt: integer("last_inbound_at"),
695
+ lastOutboundAt: integer("last_outbound_at"),
696
+ },
697
+ );
630
698
 
631
699
  // ── Channel Guardian Bindings ────────────────────────────────────────
632
700
  // UNIQUE (assistant_id, channel) WHERE status = 'active' enforced via index in db.ts
633
701
 
634
- export const channelGuardianBindings = sqliteTable('channel_guardian_bindings', {
635
- id: text('id').primaryKey(),
636
- assistantId: text('assistant_id').notNull(),
637
- channel: text('channel').notNull(),
638
- guardianExternalUserId: text('guardian_external_user_id').notNull(),
639
- guardianDeliveryChatId: text('guardian_delivery_chat_id').notNull(),
640
- guardianPrincipalId: text('guardian_principal_id').notNull(),
641
- status: text('status').notNull().default('active'),
642
- verifiedAt: integer('verified_at').notNull(),
643
- verifiedVia: text('verified_via').notNull().default('challenge'),
644
- metadataJson: text('metadata_json'),
645
- createdAt: integer('created_at').notNull(),
646
- updatedAt: integer('updated_at').notNull(),
647
- });
702
+ export const channelGuardianBindings = sqliteTable(
703
+ "channel_guardian_bindings",
704
+ {
705
+ id: text("id").primaryKey(),
706
+ assistantId: text("assistant_id").notNull(),
707
+ channel: text("channel").notNull(),
708
+ guardianExternalUserId: text("guardian_external_user_id").notNull(),
709
+ guardianDeliveryChatId: text("guardian_delivery_chat_id").notNull(),
710
+ guardianPrincipalId: text("guardian_principal_id").notNull(),
711
+ status: text("status").notNull().default("active"),
712
+ verifiedAt: integer("verified_at").notNull(),
713
+ verifiedVia: text("verified_via").notNull().default("challenge"),
714
+ metadataJson: text("metadata_json"),
715
+ createdAt: integer("created_at").notNull(),
716
+ updatedAt: integer("updated_at").notNull(),
717
+ },
718
+ );
648
719
 
649
720
  // ── Channel Guardian Verification Challenges ─────────────────────────
650
721
 
651
- export const channelGuardianVerificationChallenges = sqliteTable('channel_guardian_verification_challenges', {
652
- id: text('id').primaryKey(),
653
- assistantId: text('assistant_id').notNull(),
654
- channel: text('channel').notNull(),
655
- challengeHash: text('challenge_hash').notNull(),
656
- expiresAt: integer('expires_at').notNull(),
657
- status: text('status').notNull().default('pending'),
658
- createdBySessionId: text('created_by_session_id'),
659
- consumedByExternalUserId: text('consumed_by_external_user_id'),
660
- consumedByChatId: text('consumed_by_chat_id'),
661
- // Outbound session: expected-identity binding
662
- expectedExternalUserId: text('expected_external_user_id'),
663
- expectedChatId: text('expected_chat_id'),
664
- expectedPhoneE164: text('expected_phone_e164'),
665
- identityBindingStatus: text('identity_binding_status').default('bound'),
666
- // Outbound session: delivery tracking
667
- destinationAddress: text('destination_address'),
668
- lastSentAt: integer('last_sent_at'),
669
- sendCount: integer('send_count').default(0),
670
- nextResendAt: integer('next_resend_at'),
671
- // Session configuration
672
- codeDigits: integer('code_digits').default(6),
673
- maxAttempts: integer('max_attempts').default(3),
674
- // Distinguishes guardian verification from trusted contact verification
675
- verificationPurpose: text('verification_purpose').default('guardian'),
676
- // Telegram bootstrap deep-link token hash
677
- bootstrapTokenHash: text('bootstrap_token_hash'),
678
- createdAt: integer('created_at').notNull(),
679
- updatedAt: integer('updated_at').notNull(),
680
- });
722
+ export const channelGuardianVerificationChallenges = sqliteTable(
723
+ "channel_guardian_verification_challenges",
724
+ {
725
+ id: text("id").primaryKey(),
726
+ assistantId: text("assistant_id").notNull(),
727
+ channel: text("channel").notNull(),
728
+ challengeHash: text("challenge_hash").notNull(),
729
+ expiresAt: integer("expires_at").notNull(),
730
+ status: text("status").notNull().default("pending"),
731
+ createdBySessionId: text("created_by_session_id"),
732
+ consumedByExternalUserId: text("consumed_by_external_user_id"),
733
+ consumedByChatId: text("consumed_by_chat_id"),
734
+ // Outbound session: expected-identity binding
735
+ expectedExternalUserId: text("expected_external_user_id"),
736
+ expectedChatId: text("expected_chat_id"),
737
+ expectedPhoneE164: text("expected_phone_e164"),
738
+ identityBindingStatus: text("identity_binding_status").default("bound"),
739
+ // Outbound session: delivery tracking
740
+ destinationAddress: text("destination_address"),
741
+ lastSentAt: integer("last_sent_at"),
742
+ sendCount: integer("send_count").default(0),
743
+ nextResendAt: integer("next_resend_at"),
744
+ // Session configuration
745
+ codeDigits: integer("code_digits").default(6),
746
+ maxAttempts: integer("max_attempts").default(3),
747
+ // Distinguishes guardian verification from trusted contact verification
748
+ verificationPurpose: text("verification_purpose").default("guardian"),
749
+ // Telegram bootstrap deep-link token hash
750
+ bootstrapTokenHash: text("bootstrap_token_hash"),
751
+ createdAt: integer("created_at").notNull(),
752
+ updatedAt: integer("updated_at").notNull(),
753
+ },
754
+ );
681
755
 
682
756
  // ── Channel Guardian Approval Requests ───────────────────────────────
683
757
 
684
- export const channelGuardianApprovalRequests = sqliteTable('channel_guardian_approval_requests', {
685
- id: text('id').primaryKey(),
686
- runId: text('run_id').notNull(),
687
- requestId: text('request_id'),
688
- conversationId: text('conversation_id').notNull(),
689
- assistantId: text('assistant_id').notNull().default(DAEMON_INTERNAL_ASSISTANT_ID),
690
- channel: text('channel').notNull(),
691
- requesterExternalUserId: text('requester_external_user_id').notNull(),
692
- requesterChatId: text('requester_chat_id').notNull(),
693
- guardianExternalUserId: text('guardian_external_user_id').notNull(),
694
- guardianChatId: text('guardian_chat_id').notNull(),
695
- toolName: text('tool_name').notNull(),
696
- riskLevel: text('risk_level'),
697
- reason: text('reason'),
698
- status: text('status').notNull().default('pending'),
699
- decidedByExternalUserId: text('decided_by_external_user_id'),
700
- expiresAt: integer('expires_at').notNull(),
701
- createdAt: integer('created_at').notNull(),
702
- updatedAt: integer('updated_at').notNull(),
703
- });
758
+ export const channelGuardianApprovalRequests = sqliteTable(
759
+ "channel_guardian_approval_requests",
760
+ {
761
+ id: text("id").primaryKey(),
762
+ runId: text("run_id").notNull(),
763
+ requestId: text("request_id"),
764
+ conversationId: text("conversation_id").notNull(),
765
+ assistantId: text("assistant_id")
766
+ .notNull()
767
+ .default(DAEMON_INTERNAL_ASSISTANT_ID),
768
+ channel: text("channel").notNull(),
769
+ requesterExternalUserId: text("requester_external_user_id").notNull(),
770
+ requesterChatId: text("requester_chat_id").notNull(),
771
+ guardianExternalUserId: text("guardian_external_user_id").notNull(),
772
+ guardianChatId: text("guardian_chat_id").notNull(),
773
+ toolName: text("tool_name").notNull(),
774
+ riskLevel: text("risk_level"),
775
+ reason: text("reason"),
776
+ status: text("status").notNull().default("pending"),
777
+ decidedByExternalUserId: text("decided_by_external_user_id"),
778
+ expiresAt: integer("expires_at").notNull(),
779
+ createdAt: integer("created_at").notNull(),
780
+ updatedAt: integer("updated_at").notNull(),
781
+ },
782
+ );
704
783
 
705
784
  // ── Channel Guardian Verification Rate Limits ────────────────────────
706
785
 
707
- export const channelGuardianRateLimits = sqliteTable('channel_guardian_rate_limits', {
708
- id: text('id').primaryKey(),
709
- assistantId: text('assistant_id').notNull(),
710
- channel: text('channel').notNull(),
711
- actorExternalUserId: text('actor_external_user_id').notNull(),
712
- actorChatId: text('actor_chat_id').notNull(),
713
- // Legacy columns kept with defaults for backward compatibility with upgraded databases
714
- // that still have the old NOT NULL columns without DEFAULT. Not read by app logic.
715
- invalidAttempts: integer('invalid_attempts').notNull().default(0),
716
- windowStartedAt: integer('window_started_at').notNull().default(0),
717
- attemptTimestampsJson: text('attempt_timestamps_json').notNull().default('[]'),
718
- lockedUntil: integer('locked_until'),
719
- createdAt: integer('created_at').notNull(),
720
- updatedAt: integer('updated_at').notNull(),
721
- });
786
+ export const channelGuardianRateLimits = sqliteTable(
787
+ "channel_guardian_rate_limits",
788
+ {
789
+ id: text("id").primaryKey(),
790
+ assistantId: text("assistant_id").notNull(),
791
+ channel: text("channel").notNull(),
792
+ actorExternalUserId: text("actor_external_user_id").notNull(),
793
+ actorChatId: text("actor_chat_id").notNull(),
794
+ // Legacy columns kept with defaults for backward compatibility with upgraded databases
795
+ // that still have the old NOT NULL columns without DEFAULT. Not read by app logic.
796
+ invalidAttempts: integer("invalid_attempts").notNull().default(0),
797
+ windowStartedAt: integer("window_started_at").notNull().default(0),
798
+ attemptTimestampsJson: text("attempt_timestamps_json")
799
+ .notNull()
800
+ .default("[]"),
801
+ lockedUntil: integer("locked_until"),
802
+ createdAt: integer("created_at").notNull(),
803
+ updatedAt: integer("updated_at").notNull(),
804
+ },
805
+ );
722
806
 
723
807
  // ── Media Assets ─────────────────────────────────────────────────────
724
808
 
725
- export const mediaAssets = sqliteTable('media_assets', {
726
- id: text('id').primaryKey(),
727
- title: text('title').notNull(),
728
- filePath: text('file_path').notNull(),
729
- mimeType: text('mime_type').notNull(),
730
- durationSeconds: real('duration_seconds'),
731
- fileHash: text('file_hash').notNull(),
732
- status: text('status').notNull().default('registered'), // registered | processing | indexed | failed
733
- mediaType: text('media_type').notNull(), // video | audio | image
734
- metadata: text('metadata'), // JSON
735
- createdAt: integer('created_at').notNull(),
736
- updatedAt: integer('updated_at').notNull(),
809
+ export const mediaAssets = sqliteTable("media_assets", {
810
+ id: text("id").primaryKey(),
811
+ title: text("title").notNull(),
812
+ filePath: text("file_path").notNull(),
813
+ mimeType: text("mime_type").notNull(),
814
+ durationSeconds: real("duration_seconds"),
815
+ fileHash: text("file_hash").notNull(),
816
+ status: text("status").notNull().default("registered"), // registered | processing | indexed | failed
817
+ mediaType: text("media_type").notNull(), // video | audio | image
818
+ metadata: text("metadata"), // JSON
819
+ createdAt: integer("created_at").notNull(),
820
+ updatedAt: integer("updated_at").notNull(),
737
821
  });
738
822
 
739
- export const processingStages = sqliteTable('processing_stages', {
740
- id: text('id').primaryKey(),
741
- assetId: text('asset_id').notNull()
742
- .references(() => mediaAssets.id, { onDelete: 'cascade' }),
743
- stage: text('stage').notNull(),
744
- status: text('status').notNull().default('pending'), // pending | running | completed | failed
745
- progress: integer('progress').notNull().default(0), // 0-100
746
- lastError: text('last_error'),
747
- startedAt: integer('started_at'),
748
- completedAt: integer('completed_at'),
823
+ export const processingStages = sqliteTable("processing_stages", {
824
+ id: text("id").primaryKey(),
825
+ assetId: text("asset_id")
826
+ .notNull()
827
+ .references(() => mediaAssets.id, { onDelete: "cascade" }),
828
+ stage: text("stage").notNull(),
829
+ status: text("status").notNull().default("pending"), // pending | running | completed | failed
830
+ progress: integer("progress").notNull().default(0), // 0-100
831
+ lastError: text("last_error"),
832
+ startedAt: integer("started_at"),
833
+ completedAt: integer("completed_at"),
749
834
  });
750
835
 
751
- export const mediaKeyframes = sqliteTable('media_keyframes', {
752
- id: text('id').primaryKey(),
753
- assetId: text('asset_id').notNull()
754
- .references(() => mediaAssets.id, { onDelete: 'cascade' }),
755
- timestamp: real('timestamp').notNull(),
756
- filePath: text('file_path').notNull(),
757
- metadata: text('metadata'), // JSON
758
- createdAt: integer('created_at').notNull(),
836
+ export const mediaKeyframes = sqliteTable("media_keyframes", {
837
+ id: text("id").primaryKey(),
838
+ assetId: text("asset_id")
839
+ .notNull()
840
+ .references(() => mediaAssets.id, { onDelete: "cascade" }),
841
+ timestamp: real("timestamp").notNull(),
842
+ filePath: text("file_path").notNull(),
843
+ metadata: text("metadata"), // JSON
844
+ createdAt: integer("created_at").notNull(),
759
845
  });
760
846
 
761
- export const mediaVisionOutputs = sqliteTable('media_vision_outputs', {
762
- id: text('id').primaryKey(),
763
- assetId: text('asset_id').notNull()
764
- .references(() => mediaAssets.id, { onDelete: 'cascade' }),
765
- keyframeId: text('keyframe_id').notNull()
766
- .references(() => mediaKeyframes.id, { onDelete: 'cascade' }),
767
- analysisType: text('analysis_type').notNull(),
768
- output: text('output').notNull(), // JSON
769
- confidence: real('confidence'),
770
- createdAt: integer('created_at').notNull(),
847
+ export const mediaVisionOutputs = sqliteTable("media_vision_outputs", {
848
+ id: text("id").primaryKey(),
849
+ assetId: text("asset_id")
850
+ .notNull()
851
+ .references(() => mediaAssets.id, { onDelete: "cascade" }),
852
+ keyframeId: text("keyframe_id")
853
+ .notNull()
854
+ .references(() => mediaKeyframes.id, { onDelete: "cascade" }),
855
+ analysisType: text("analysis_type").notNull(),
856
+ output: text("output").notNull(), // JSON
857
+ confidence: real("confidence"),
858
+ createdAt: integer("created_at").notNull(),
771
859
  });
772
860
 
773
- export const mediaTimelines = sqliteTable('media_timelines', {
774
- id: text('id').primaryKey(),
775
- assetId: text('asset_id').notNull()
776
- .references(() => mediaAssets.id, { onDelete: 'cascade' }),
777
- startTime: real('start_time').notNull(),
778
- endTime: real('end_time').notNull(),
779
- segmentType: text('segment_type').notNull(),
780
- attributes: text('attributes'), // JSON
781
- confidence: real('confidence'),
782
- createdAt: integer('created_at').notNull(),
861
+ export const mediaTimelines = sqliteTable("media_timelines", {
862
+ id: text("id").primaryKey(),
863
+ assetId: text("asset_id")
864
+ .notNull()
865
+ .references(() => mediaAssets.id, { onDelete: "cascade" }),
866
+ startTime: real("start_time").notNull(),
867
+ endTime: real("end_time").notNull(),
868
+ segmentType: text("segment_type").notNull(),
869
+ attributes: text("attributes"), // JSON
870
+ confidence: real("confidence"),
871
+ createdAt: integer("created_at").notNull(),
783
872
  });
784
873
 
785
- export const mediaEvents = sqliteTable('media_events', {
786
- id: text('id').primaryKey(),
787
- assetId: text('asset_id').notNull()
788
- .references(() => mediaAssets.id, { onDelete: 'cascade' }),
789
- eventType: text('event_type').notNull(),
790
- startTime: real('start_time').notNull(),
791
- endTime: real('end_time').notNull(),
792
- confidence: real('confidence').notNull(),
793
- reasons: text('reasons').notNull(), // JSON array
794
- metadata: text('metadata'), // JSON
795
- createdAt: integer('created_at').notNull(),
874
+ export const mediaEvents = sqliteTable("media_events", {
875
+ id: text("id").primaryKey(),
876
+ assetId: text("asset_id")
877
+ .notNull()
878
+ .references(() => mediaAssets.id, { onDelete: "cascade" }),
879
+ eventType: text("event_type").notNull(),
880
+ startTime: real("start_time").notNull(),
881
+ endTime: real("end_time").notNull(),
882
+ confidence: real("confidence").notNull(),
883
+ reasons: text("reasons").notNull(), // JSON array
884
+ metadata: text("metadata"), // JSON
885
+ createdAt: integer("created_at").notNull(),
796
886
  });
797
887
 
798
- export const mediaTrackingProfiles = sqliteTable('media_tracking_profiles', {
799
- id: text('id').primaryKey(),
800
- assetId: text('asset_id').notNull()
801
- .references(() => mediaAssets.id, { onDelete: 'cascade' }),
802
- capabilities: text('capabilities').notNull(), // JSON: { [capName]: { enabled, tier } }
803
- createdAt: integer('created_at').notNull(),
888
+ export const mediaTrackingProfiles = sqliteTable("media_tracking_profiles", {
889
+ id: text("id").primaryKey(),
890
+ assetId: text("asset_id")
891
+ .notNull()
892
+ .references(() => mediaAssets.id, { onDelete: "cascade" }),
893
+ capabilities: text("capabilities").notNull(), // JSON: { [capName]: { enabled, tier } }
894
+ createdAt: integer("created_at").notNull(),
804
895
  });
805
896
 
806
- export const mediaEventFeedback = sqliteTable('media_event_feedback', {
807
- id: text('id').primaryKey(),
808
- assetId: text('asset_id').notNull()
809
- .references(() => mediaAssets.id, { onDelete: 'cascade' }),
810
- eventId: text('event_id').notNull()
811
- .references(() => mediaEvents.id, { onDelete: 'cascade' }),
812
- feedbackType: text('feedback_type').notNull(), // correct | incorrect | boundary_edit | missed
813
- originalStartTime: real('original_start_time'),
814
- originalEndTime: real('original_end_time'),
815
- correctedStartTime: real('corrected_start_time'),
816
- correctedEndTime: real('corrected_end_time'),
817
- notes: text('notes'),
818
- createdAt: integer('created_at').notNull(),
897
+ export const mediaEventFeedback = sqliteTable("media_event_feedback", {
898
+ id: text("id").primaryKey(),
899
+ assetId: text("asset_id")
900
+ .notNull()
901
+ .references(() => mediaAssets.id, { onDelete: "cascade" }),
902
+ eventId: text("event_id")
903
+ .notNull()
904
+ .references(() => mediaEvents.id, { onDelete: "cascade" }),
905
+ feedbackType: text("feedback_type").notNull(), // correct | incorrect | boundary_edit | missed
906
+ originalStartTime: real("original_start_time"),
907
+ originalEndTime: real("original_end_time"),
908
+ correctedStartTime: real("corrected_start_time"),
909
+ correctedEndTime: real("corrected_end_time"),
910
+ notes: text("notes"),
911
+ createdAt: integer("created_at").notNull(),
819
912
  });
820
913
 
821
914
  // ── Guardian Action Requests (cross-channel voice guardian) ──────────
822
915
 
823
- export const guardianActionRequests = sqliteTable('guardian_action_requests', {
824
- id: text('id').primaryKey(),
825
- assistantId: text('assistant_id').notNull().default(DAEMON_INTERNAL_ASSISTANT_ID),
826
- kind: text('kind').notNull(), // 'ask_guardian'
827
- sourceChannel: text('source_channel').notNull(), // 'voice'
828
- sourceConversationId: text('source_conversation_id').notNull(),
829
- callSessionId: text('call_session_id')
830
- .notNull()
831
- .references(() => callSessions.id, { onDelete: 'cascade' }),
832
- pendingQuestionId: text('pending_question_id')
833
- .notNull()
834
- .references(() => callPendingQuestions.id, { onDelete: 'cascade' }),
835
- questionText: text('question_text').notNull(),
836
- requestCode: text('request_code').notNull(), // short human-readable code for routing replies
837
- status: text('status').notNull().default('pending'), // pending | answered | expired | cancelled
838
- answerText: text('answer_text'),
839
- answeredByChannel: text('answered_by_channel'),
840
- answeredByExternalUserId: text('answered_by_external_user_id'),
841
- answeredAt: integer('answered_at'),
842
- expiresAt: integer('expires_at').notNull(),
843
- expiredReason: text('expired_reason'), // call_timeout | sweep_timeout | cancelled
844
- followupState: text('followup_state').notNull().default('none'), // none | awaiting_guardian_choice | dispatching | completed | declined | failed
845
- lateAnswerText: text('late_answer_text'),
846
- lateAnsweredAt: integer('late_answered_at'),
847
- followupAction: text('followup_action'), // call_back | message_back | decline
848
- followupCompletedAt: integer('followup_completed_at'),
849
- toolName: text('tool_name'), // tool identity for tool-approval requests
850
- inputDigest: text('input_digest'), // canonical SHA-256 digest of tool input
851
- supersededByRequestId: text('superseded_by_request_id'), // links to the request that replaced this one
852
- supersededAt: integer('superseded_at'), // epoch ms when supersession occurred
853
- createdAt: integer('created_at').notNull(),
854
- updatedAt: integer('updated_at').notNull(),
855
- }, (table) => [
856
- index('idx_guardian_action_requests_session_status_created').on(table.callSessionId, table.status, table.createdAt),
857
- ]);
916
+ export const guardianActionRequests = sqliteTable(
917
+ "guardian_action_requests",
918
+ {
919
+ id: text("id").primaryKey(),
920
+ assistantId: text("assistant_id")
921
+ .notNull()
922
+ .default(DAEMON_INTERNAL_ASSISTANT_ID),
923
+ kind: text("kind").notNull(), // 'ask_guardian'
924
+ sourceChannel: text("source_channel").notNull(), // 'voice'
925
+ sourceConversationId: text("source_conversation_id").notNull(),
926
+ callSessionId: text("call_session_id")
927
+ .notNull()
928
+ .references(() => callSessions.id, { onDelete: "cascade" }),
929
+ pendingQuestionId: text("pending_question_id")
930
+ .notNull()
931
+ .references(() => callPendingQuestions.id, { onDelete: "cascade" }),
932
+ questionText: text("question_text").notNull(),
933
+ requestCode: text("request_code").notNull(), // short human-readable code for routing replies
934
+ status: text("status").notNull().default("pending"), // pending | answered | expired | cancelled
935
+ answerText: text("answer_text"),
936
+ answeredByChannel: text("answered_by_channel"),
937
+ answeredByExternalUserId: text("answered_by_external_user_id"),
938
+ answeredAt: integer("answered_at"),
939
+ expiresAt: integer("expires_at").notNull(),
940
+ expiredReason: text("expired_reason"), // call_timeout | sweep_timeout | cancelled
941
+ followupState: text("followup_state").notNull().default("none"), // none | awaiting_guardian_choice | dispatching | completed | declined | failed
942
+ lateAnswerText: text("late_answer_text"),
943
+ lateAnsweredAt: integer("late_answered_at"),
944
+ followupAction: text("followup_action"), // call_back | message_back | decline
945
+ followupCompletedAt: integer("followup_completed_at"),
946
+ toolName: text("tool_name"), // tool identity for tool-approval requests
947
+ inputDigest: text("input_digest"), // canonical SHA-256 digest of tool input
948
+ supersededByRequestId: text("superseded_by_request_id"), // links to the request that replaced this one
949
+ supersededAt: integer("superseded_at"), // epoch ms when supersession occurred
950
+ createdAt: integer("created_at").notNull(),
951
+ updatedAt: integer("updated_at").notNull(),
952
+ },
953
+ (table) => [
954
+ index("idx_guardian_action_requests_session_status_created").on(
955
+ table.callSessionId,
956
+ table.status,
957
+ table.createdAt,
958
+ ),
959
+ ],
960
+ );
858
961
 
859
962
  // ── Guardian Action Deliveries (per-channel delivery tracking) ───────
860
963
 
861
- export const guardianActionDeliveries = sqliteTable('guardian_action_deliveries', {
862
- id: text('id').primaryKey(),
863
- requestId: text('request_id')
864
- .notNull()
865
- .references(() => guardianActionRequests.id, { onDelete: 'cascade' }),
866
- destinationChannel: text('destination_channel').notNull(), // 'telegram' | 'sms' | 'vellum'
867
- destinationConversationId: text('destination_conversation_id'),
868
- destinationChatId: text('destination_chat_id'),
869
- destinationExternalUserId: text('destination_external_user_id'),
870
- status: text('status').notNull().default('pending'), // pending | sent | failed | answered | expired | cancelled
871
- sentAt: integer('sent_at'),
872
- respondedAt: integer('responded_at'),
873
- lastError: text('last_error'),
874
- createdAt: integer('created_at').notNull(),
875
- updatedAt: integer('updated_at').notNull(),
876
- }, (table) => [
877
- index('idx_guardian_action_deliveries_dest_conversation').on(table.destinationConversationId),
878
- ]);
964
+ export const guardianActionDeliveries = sqliteTable(
965
+ "guardian_action_deliveries",
966
+ {
967
+ id: text("id").primaryKey(),
968
+ requestId: text("request_id")
969
+ .notNull()
970
+ .references(() => guardianActionRequests.id, { onDelete: "cascade" }),
971
+ destinationChannel: text("destination_channel").notNull(), // 'telegram' | 'sms' | 'vellum'
972
+ destinationConversationId: text("destination_conversation_id"),
973
+ destinationChatId: text("destination_chat_id"),
974
+ destinationExternalUserId: text("destination_external_user_id"),
975
+ status: text("status").notNull().default("pending"), // pending | sent | failed | answered | expired | cancelled
976
+ sentAt: integer("sent_at"),
977
+ respondedAt: integer("responded_at"),
978
+ lastError: text("last_error"),
979
+ createdAt: integer("created_at").notNull(),
980
+ updatedAt: integer("updated_at").notNull(),
981
+ },
982
+ (table) => [
983
+ index("idx_guardian_action_deliveries_dest_conversation").on(
984
+ table.destinationConversationId,
985
+ ),
986
+ ],
987
+ );
879
988
 
880
989
  // ── Canonical Guardian Requests (unified cross-source guardian domain) ─
881
990
 
882
- export const canonicalGuardianRequests = sqliteTable('canonical_guardian_requests', {
883
- id: text('id').primaryKey(),
884
- kind: text('kind').notNull(),
885
- sourceType: text('source_type').notNull(),
886
- sourceChannel: text('source_channel'),
887
- conversationId: text('conversation_id'),
888
- requesterExternalUserId: text('requester_external_user_id'),
889
- requesterChatId: text('requester_chat_id'),
890
- guardianExternalUserId: text('guardian_external_user_id'),
891
- guardianPrincipalId: text('guardian_principal_id'),
892
- callSessionId: text('call_session_id'),
893
- pendingQuestionId: text('pending_question_id'),
894
- questionText: text('question_text'),
895
- requestCode: text('request_code'),
896
- toolName: text('tool_name'),
897
- inputDigest: text('input_digest'),
898
- status: text('status').notNull().default('pending'),
899
- answerText: text('answer_text'),
900
- decidedByExternalUserId: text('decided_by_external_user_id'),
901
- decidedByPrincipalId: text('decided_by_principal_id'),
902
- followupState: text('followup_state'),
903
- expiresAt: text('expires_at'),
904
- createdAt: text('created_at').notNull(),
905
- updatedAt: text('updated_at').notNull(),
906
- }, (table) => [
907
- index('idx_canonical_guardian_requests_status').on(table.status),
908
- index('idx_canonical_guardian_requests_guardian').on(table.guardianExternalUserId, table.status),
909
- index('idx_canonical_guardian_requests_conversation').on(table.conversationId, table.status),
910
- index('idx_canonical_guardian_requests_source').on(table.sourceType, table.status),
911
- index('idx_canonical_guardian_requests_kind').on(table.kind, table.status),
912
- index('idx_canonical_guardian_requests_request_code').on(table.requestCode),
913
- ]);
991
+ export const canonicalGuardianRequests = sqliteTable(
992
+ "canonical_guardian_requests",
993
+ {
994
+ id: text("id").primaryKey(),
995
+ kind: text("kind").notNull(),
996
+ sourceType: text("source_type").notNull(),
997
+ sourceChannel: text("source_channel"),
998
+ conversationId: text("conversation_id"),
999
+ requesterExternalUserId: text("requester_external_user_id"),
1000
+ requesterChatId: text("requester_chat_id"),
1001
+ guardianExternalUserId: text("guardian_external_user_id"),
1002
+ guardianPrincipalId: text("guardian_principal_id"),
1003
+ callSessionId: text("call_session_id"),
1004
+ pendingQuestionId: text("pending_question_id"),
1005
+ questionText: text("question_text"),
1006
+ requestCode: text("request_code"),
1007
+ toolName: text("tool_name"),
1008
+ inputDigest: text("input_digest"),
1009
+ status: text("status").notNull().default("pending"),
1010
+ answerText: text("answer_text"),
1011
+ decidedByExternalUserId: text("decided_by_external_user_id"),
1012
+ decidedByPrincipalId: text("decided_by_principal_id"),
1013
+ followupState: text("followup_state"),
1014
+ expiresAt: text("expires_at"),
1015
+ createdAt: text("created_at").notNull(),
1016
+ updatedAt: text("updated_at").notNull(),
1017
+ },
1018
+ (table) => [
1019
+ index("idx_canonical_guardian_requests_status").on(table.status),
1020
+ index("idx_canonical_guardian_requests_guardian").on(
1021
+ table.guardianExternalUserId,
1022
+ table.status,
1023
+ ),
1024
+ index("idx_canonical_guardian_requests_conversation").on(
1025
+ table.conversationId,
1026
+ table.status,
1027
+ ),
1028
+ index("idx_canonical_guardian_requests_source").on(
1029
+ table.sourceType,
1030
+ table.status,
1031
+ ),
1032
+ index("idx_canonical_guardian_requests_kind").on(table.kind, table.status),
1033
+ index("idx_canonical_guardian_requests_request_code").on(table.requestCode),
1034
+ ],
1035
+ );
914
1036
 
915
1037
  // ── Canonical Guardian Deliveries (per-channel delivery tracking) ─────
916
1038
 
917
- export const canonicalGuardianDeliveries = sqliteTable('canonical_guardian_deliveries', {
918
- id: text('id').primaryKey(),
919
- requestId: text('request_id')
920
- .notNull()
921
- .references(() => canonicalGuardianRequests.id, { onDelete: 'cascade' }),
922
- destinationChannel: text('destination_channel').notNull(),
923
- destinationConversationId: text('destination_conversation_id'),
924
- destinationChatId: text('destination_chat_id'),
925
- destinationMessageId: text('destination_message_id'),
926
- status: text('status').notNull().default('pending'),
927
- createdAt: text('created_at').notNull(),
928
- updatedAt: text('updated_at').notNull(),
929
- }, (table) => [
930
- index('idx_canonical_guardian_deliveries_request_id').on(table.requestId),
931
- index('idx_canonical_guardian_deliveries_status').on(table.status),
932
- ]);
1039
+ export const canonicalGuardianDeliveries = sqliteTable(
1040
+ "canonical_guardian_deliveries",
1041
+ {
1042
+ id: text("id").primaryKey(),
1043
+ requestId: text("request_id")
1044
+ .notNull()
1045
+ .references(() => canonicalGuardianRequests.id, { onDelete: "cascade" }),
1046
+ destinationChannel: text("destination_channel").notNull(),
1047
+ destinationConversationId: text("destination_conversation_id"),
1048
+ destinationChatId: text("destination_chat_id"),
1049
+ destinationMessageId: text("destination_message_id"),
1050
+ status: text("status").notNull().default("pending"),
1051
+ createdAt: text("created_at").notNull(),
1052
+ updatedAt: text("updated_at").notNull(),
1053
+ },
1054
+ (table) => [
1055
+ index("idx_canonical_guardian_deliveries_request_id").on(table.requestId),
1056
+ index("idx_canonical_guardian_deliveries_status").on(table.status),
1057
+ ],
1058
+ );
933
1059
 
934
1060
  // ── Assistant Inbox ──────────────────────────────────────────────────
935
1061
 
936
- export const assistantIngressInvites = sqliteTable('assistant_ingress_invites', {
937
- id: text('id').primaryKey(),
938
- assistantId: text('assistant_id').notNull().default(DAEMON_INTERNAL_ASSISTANT_ID),
939
- sourceChannel: text('source_channel').notNull(),
940
- tokenHash: text('token_hash').notNull(),
941
- createdBySessionId: text('created_by_session_id'),
942
- note: text('note'),
943
- maxUses: integer('max_uses').notNull().default(1),
944
- useCount: integer('use_count').notNull().default(0),
945
- expiresAt: integer('expires_at').notNull(),
946
- status: text('status').notNull().default('active'),
947
- redeemedByExternalUserId: text('redeemed_by_external_user_id'),
948
- redeemedByExternalChatId: text('redeemed_by_external_chat_id'),
949
- redeemedAt: integer('redeemed_at'),
950
- // Voice invite fields (nullable — non-voice invites leave these NULL)
951
- expectedExternalUserId: text('expected_external_user_id'),
952
- voiceCodeHash: text('voice_code_hash'),
953
- voiceCodeDigits: integer('voice_code_digits'),
954
- // Display metadata for personalized voice prompts (nullable — non-voice invites leave these NULL)
955
- friendName: text('friend_name'),
956
- guardianName: text('guardian_name'),
957
- createdAt: integer('created_at').notNull(),
958
- updatedAt: integer('updated_at').notNull(),
959
- });
960
-
961
- export const assistantIngressMembers = sqliteTable('assistant_ingress_members', {
962
- id: text('id').primaryKey(),
963
- assistantId: text('assistant_id').notNull().default(DAEMON_INTERNAL_ASSISTANT_ID),
964
- sourceChannel: text('source_channel').notNull(),
965
- externalUserId: text('external_user_id'),
966
- externalChatId: text('external_chat_id'),
967
- displayName: text('display_name'),
968
- username: text('username'),
969
- status: text('status').notNull().default('pending'),
970
- policy: text('policy').notNull().default('allow'),
971
- inviteId: text('invite_id')
972
- .references(() => assistantIngressInvites.id, { onDelete: 'cascade' }),
973
- createdBySessionId: text('created_by_session_id'),
974
- revokedReason: text('revoked_reason'),
975
- blockedReason: text('blocked_reason'),
976
- lastSeenAt: integer('last_seen_at'),
977
- createdAt: integer('created_at').notNull(),
978
- updatedAt: integer('updated_at').notNull(),
979
- });
980
-
981
- export const assistantInboxThreadState = sqliteTable('assistant_inbox_thread_state', {
982
- conversationId: text('conversation_id')
983
- .primaryKey()
984
- .references(() => conversations.id, { onDelete: 'cascade' }),
985
- assistantId: text('assistant_id').notNull().default(DAEMON_INTERNAL_ASSISTANT_ID),
986
- sourceChannel: text('source_channel').notNull(),
987
- externalChatId: text('external_chat_id').notNull(),
988
- externalUserId: text('external_user_id'),
989
- displayName: text('display_name'),
990
- username: text('username'),
991
- lastInboundAt: integer('last_inbound_at'),
992
- lastOutboundAt: integer('last_outbound_at'),
993
- lastMessageAt: integer('last_message_at'),
994
- unreadCount: integer('unread_count').notNull().default(0),
995
- pendingEscalationCount: integer('pending_escalation_count').notNull().default(0),
996
- hasPendingEscalation: integer('has_pending_escalation').notNull().default(0),
997
- createdAt: integer('created_at').notNull(),
998
- updatedAt: integer('updated_at').notNull(),
999
- });
1062
+ export const assistantIngressInvites = sqliteTable(
1063
+ "assistant_ingress_invites",
1064
+ {
1065
+ id: text("id").primaryKey(),
1066
+ assistantId: text("assistant_id")
1067
+ .notNull()
1068
+ .default(DAEMON_INTERNAL_ASSISTANT_ID),
1069
+ sourceChannel: text("source_channel").notNull(),
1070
+ tokenHash: text("token_hash").notNull(),
1071
+ createdBySessionId: text("created_by_session_id"),
1072
+ note: text("note"),
1073
+ maxUses: integer("max_uses").notNull().default(1),
1074
+ useCount: integer("use_count").notNull().default(0),
1075
+ expiresAt: integer("expires_at").notNull(),
1076
+ status: text("status").notNull().default("active"),
1077
+ redeemedByExternalUserId: text("redeemed_by_external_user_id"),
1078
+ redeemedByExternalChatId: text("redeemed_by_external_chat_id"),
1079
+ redeemedAt: integer("redeemed_at"),
1080
+ // Voice invite fields (nullable — non-voice invites leave these NULL)
1081
+ expectedExternalUserId: text("expected_external_user_id"),
1082
+ voiceCodeHash: text("voice_code_hash"),
1083
+ voiceCodeDigits: integer("voice_code_digits"),
1084
+ // Display metadata for personalized voice prompts (nullable — non-voice invites leave these NULL)
1085
+ friendName: text("friend_name"),
1086
+ guardianName: text("guardian_name"),
1087
+ createdAt: integer("created_at").notNull(),
1088
+ updatedAt: integer("updated_at").notNull(),
1089
+ },
1090
+ );
1091
+
1092
+ export const assistantIngressMembers = sqliteTable(
1093
+ "assistant_ingress_members",
1094
+ {
1095
+ id: text("id").primaryKey(),
1096
+ assistantId: text("assistant_id")
1097
+ .notNull()
1098
+ .default(DAEMON_INTERNAL_ASSISTANT_ID),
1099
+ sourceChannel: text("source_channel").notNull(),
1100
+ externalUserId: text("external_user_id"),
1101
+ externalChatId: text("external_chat_id"),
1102
+ displayName: text("display_name"),
1103
+ username: text("username"),
1104
+ status: text("status").notNull().default("pending"),
1105
+ policy: text("policy").notNull().default("allow"),
1106
+ inviteId: text("invite_id").references(() => assistantIngressInvites.id, {
1107
+ onDelete: "cascade",
1108
+ }),
1109
+ createdBySessionId: text("created_by_session_id"),
1110
+ revokedReason: text("revoked_reason"),
1111
+ blockedReason: text("blocked_reason"),
1112
+ lastSeenAt: integer("last_seen_at"),
1113
+ createdAt: integer("created_at").notNull(),
1114
+ updatedAt: integer("updated_at").notNull(),
1115
+ },
1116
+ );
1117
+
1118
+ export const assistantInboxThreadState = sqliteTable(
1119
+ "assistant_inbox_thread_state",
1120
+ {
1121
+ conversationId: text("conversation_id")
1122
+ .primaryKey()
1123
+ .references(() => conversations.id, { onDelete: "cascade" }),
1124
+ assistantId: text("assistant_id")
1125
+ .notNull()
1126
+ .default(DAEMON_INTERNAL_ASSISTANT_ID),
1127
+ sourceChannel: text("source_channel").notNull(),
1128
+ externalChatId: text("external_chat_id").notNull(),
1129
+ externalUserId: text("external_user_id"),
1130
+ displayName: text("display_name"),
1131
+ username: text("username"),
1132
+ lastInboundAt: integer("last_inbound_at"),
1133
+ lastOutboundAt: integer("last_outbound_at"),
1134
+ lastMessageAt: integer("last_message_at"),
1135
+ unreadCount: integer("unread_count").notNull().default(0),
1136
+ pendingEscalationCount: integer("pending_escalation_count")
1137
+ .notNull()
1138
+ .default(0),
1139
+ hasPendingEscalation: integer("has_pending_escalation")
1140
+ .notNull()
1141
+ .default(0),
1142
+ createdAt: integer("created_at").notNull(),
1143
+ updatedAt: integer("updated_at").notNull(),
1144
+ },
1145
+ );
1000
1146
 
1001
1147
  // ── Notification System ──────────────────────────────────────────────
1002
1148
 
1003
- export const notificationEvents = sqliteTable('notification_events', {
1004
- id: text('id').primaryKey(),
1005
- assistantId: text('assistant_id').notNull(),
1006
- sourceEventName: text('source_event_name').notNull(),
1007
- sourceChannel: text('source_channel').notNull(),
1008
- sourceSessionId: text('source_session_id').notNull(),
1009
- attentionHintsJson: text('attention_hints_json').notNull().default('{}'),
1010
- payloadJson: text('payload_json').notNull().default('{}'),
1011
- dedupeKey: text('dedupe_key'),
1012
- createdAt: integer('created_at').notNull(),
1013
- updatedAt: integer('updated_at').notNull(),
1149
+ export const notificationEvents = sqliteTable("notification_events", {
1150
+ id: text("id").primaryKey(),
1151
+ assistantId: text("assistant_id").notNull(),
1152
+ sourceEventName: text("source_event_name").notNull(),
1153
+ sourceChannel: text("source_channel").notNull(),
1154
+ sourceSessionId: text("source_session_id").notNull(),
1155
+ attentionHintsJson: text("attention_hints_json").notNull().default("{}"),
1156
+ payloadJson: text("payload_json").notNull().default("{}"),
1157
+ dedupeKey: text("dedupe_key"),
1158
+ createdAt: integer("created_at").notNull(),
1159
+ updatedAt: integer("updated_at").notNull(),
1014
1160
  });
1015
1161
 
1016
- export const notificationDecisions = sqliteTable('notification_decisions', {
1017
- id: text('id').primaryKey(),
1018
- notificationEventId: text('notification_event_id')
1162
+ export const notificationDecisions = sqliteTable("notification_decisions", {
1163
+ id: text("id").primaryKey(),
1164
+ notificationEventId: text("notification_event_id")
1019
1165
  .notNull()
1020
- .references(() => notificationEvents.id, { onDelete: 'cascade' }),
1021
- shouldNotify: integer('should_notify').notNull(),
1022
- selectedChannels: text('selected_channels').notNull().default('[]'),
1023
- reasoningSummary: text('reasoning_summary').notNull(),
1024
- confidence: real('confidence').notNull(),
1025
- fallbackUsed: integer('fallback_used').notNull().default(0),
1026
- promptVersion: text('prompt_version'),
1027
- validationResults: text('validation_results'),
1028
- createdAt: integer('created_at').notNull(),
1166
+ .references(() => notificationEvents.id, { onDelete: "cascade" }),
1167
+ shouldNotify: integer("should_notify").notNull(),
1168
+ selectedChannels: text("selected_channels").notNull().default("[]"),
1169
+ reasoningSummary: text("reasoning_summary").notNull(),
1170
+ confidence: real("confidence").notNull(),
1171
+ fallbackUsed: integer("fallback_used").notNull().default(0),
1172
+ promptVersion: text("prompt_version"),
1173
+ validationResults: text("validation_results"),
1174
+ createdAt: integer("created_at").notNull(),
1029
1175
  });
1030
1176
 
1031
- export const notificationPreferences = sqliteTable('notification_preferences', {
1032
- id: text('id').primaryKey(),
1033
- assistantId: text('assistant_id').notNull(),
1034
- preferenceText: text('preference_text').notNull(),
1035
- appliesWhenJson: text('applies_when_json').notNull().default('{}'),
1036
- priority: integer('priority').notNull().default(0),
1037
- createdAt: integer('created_at').notNull(),
1038
- updatedAt: integer('updated_at').notNull(),
1177
+ export const notificationPreferences = sqliteTable("notification_preferences", {
1178
+ id: text("id").primaryKey(),
1179
+ assistantId: text("assistant_id").notNull(),
1180
+ preferenceText: text("preference_text").notNull(),
1181
+ appliesWhenJson: text("applies_when_json").notNull().default("{}"),
1182
+ priority: integer("priority").notNull().default(0),
1183
+ createdAt: integer("created_at").notNull(),
1184
+ updatedAt: integer("updated_at").notNull(),
1039
1185
  });
1040
1186
 
1041
1187
  // ── Sequences (multi-step outreach) ──────────────────────────────────
1042
1188
 
1043
- export const sequences = sqliteTable('sequences', {
1044
- id: text('id').primaryKey(),
1045
- name: text('name').notNull(),
1046
- description: text('description'),
1047
- channel: text('channel').notNull(),
1048
- steps: text('steps').notNull(), // JSON array of SequenceStep
1049
- exitOnReply: integer('exit_on_reply', { mode: 'boolean' }).notNull().default(true),
1050
- status: text('status').notNull().default('active'), // active | paused | archived
1051
- createdAt: integer('created_at').notNull(),
1052
- updatedAt: integer('updated_at').notNull(),
1189
+ export const sequences = sqliteTable("sequences", {
1190
+ id: text("id").primaryKey(),
1191
+ name: text("name").notNull(),
1192
+ description: text("description"),
1193
+ channel: text("channel").notNull(),
1194
+ steps: text("steps").notNull(), // JSON array of SequenceStep
1195
+ exitOnReply: integer("exit_on_reply", { mode: "boolean" })
1196
+ .notNull()
1197
+ .default(true),
1198
+ status: text("status").notNull().default("active"), // active | paused | archived
1199
+ createdAt: integer("created_at").notNull(),
1200
+ updatedAt: integer("updated_at").notNull(),
1053
1201
  });
1054
1202
 
1055
- export const sequenceEnrollments = sqliteTable('sequence_enrollments', {
1056
- id: text('id').primaryKey(),
1057
- sequenceId: text('sequence_id')
1058
- .notNull()
1059
- .references(() => sequences.id, { onDelete: 'cascade' }),
1060
- contactEmail: text('contact_email').notNull(),
1061
- contactName: text('contact_name'),
1062
- currentStep: integer('current_step').notNull().default(0),
1063
- status: text('status').notNull().default('active'), // active | paused | completed | replied | cancelled | failed
1064
- threadId: text('thread_id'),
1065
- nextStepAt: integer('next_step_at'), // epoch ms
1066
- context: text('context'), // JSON
1067
- createdAt: integer('created_at').notNull(),
1068
- updatedAt: integer('updated_at').notNull(),
1069
- }, (table) => [
1070
- index('idx_seq_enrollments_status_next_step').on(table.status, table.nextStepAt),
1071
- index('idx_seq_enrollments_sequence_id').on(table.sequenceId),
1072
- index('idx_seq_enrollments_contact_email').on(table.contactEmail),
1073
- ]);
1074
-
1075
- export const notificationDeliveries = sqliteTable('notification_deliveries', {
1076
- id: text('id').primaryKey(),
1077
- notificationDecisionId: text('notification_decision_id')
1078
- .notNull()
1079
- .references(() => notificationDecisions.id, { onDelete: 'cascade' }),
1080
- assistantId: text('assistant_id').notNull(),
1081
- channel: text('channel').notNull(),
1082
- destination: text('destination').notNull(),
1083
- status: text('status').notNull().default('pending'),
1084
- attempt: integer('attempt').notNull().default(1),
1085
- renderedTitle: text('rendered_title'),
1086
- renderedBody: text('rendered_body'),
1087
- errorCode: text('error_code'),
1088
- errorMessage: text('error_message'),
1089
- sentAt: integer('sent_at'),
1090
- conversationId: text('conversation_id'),
1091
- messageId: text('message_id'),
1092
- conversationStrategy: text('conversation_strategy'),
1093
- threadAction: text('thread_action'),
1094
- threadTargetConversationId: text('thread_target_conversation_id'),
1095
- threadDecisionFallbackUsed: integer('thread_decision_fallback_used'),
1096
- clientDeliveryStatus: text('client_delivery_status'),
1097
- clientDeliveryError: text('client_delivery_error'),
1098
- clientDeliveryAt: integer('client_delivery_at'),
1099
- createdAt: integer('created_at').notNull(),
1100
- updatedAt: integer('updated_at').notNull(),
1101
- }, (table) => [
1102
- uniqueIndex('idx_notification_deliveries_decision_channel').on(table.notificationDecisionId, table.channel),
1103
- ]);
1203
+ export const sequenceEnrollments = sqliteTable(
1204
+ "sequence_enrollments",
1205
+ {
1206
+ id: text("id").primaryKey(),
1207
+ sequenceId: text("sequence_id")
1208
+ .notNull()
1209
+ .references(() => sequences.id, { onDelete: "cascade" }),
1210
+ contactEmail: text("contact_email").notNull(),
1211
+ contactName: text("contact_name"),
1212
+ currentStep: integer("current_step").notNull().default(0),
1213
+ status: text("status").notNull().default("active"), // active | paused | completed | replied | cancelled | failed
1214
+ threadId: text("thread_id"),
1215
+ nextStepAt: integer("next_step_at"), // epoch ms
1216
+ context: text("context"), // JSON
1217
+ createdAt: integer("created_at").notNull(),
1218
+ updatedAt: integer("updated_at").notNull(),
1219
+ },
1220
+ (table) => [
1221
+ index("idx_seq_enrollments_status_next_step").on(
1222
+ table.status,
1223
+ table.nextStepAt,
1224
+ ),
1225
+ index("idx_seq_enrollments_sequence_id").on(table.sequenceId),
1226
+ index("idx_seq_enrollments_contact_email").on(table.contactEmail),
1227
+ ],
1228
+ );
1229
+
1230
+ export const notificationDeliveries = sqliteTable(
1231
+ "notification_deliveries",
1232
+ {
1233
+ id: text("id").primaryKey(),
1234
+ notificationDecisionId: text("notification_decision_id")
1235
+ .notNull()
1236
+ .references(() => notificationDecisions.id, { onDelete: "cascade" }),
1237
+ assistantId: text("assistant_id").notNull(),
1238
+ channel: text("channel").notNull(),
1239
+ destination: text("destination").notNull(),
1240
+ status: text("status").notNull().default("pending"),
1241
+ attempt: integer("attempt").notNull().default(1),
1242
+ renderedTitle: text("rendered_title"),
1243
+ renderedBody: text("rendered_body"),
1244
+ errorCode: text("error_code"),
1245
+ errorMessage: text("error_message"),
1246
+ sentAt: integer("sent_at"),
1247
+ conversationId: text("conversation_id"),
1248
+ messageId: text("message_id"),
1249
+ conversationStrategy: text("conversation_strategy"),
1250
+ threadAction: text("thread_action"),
1251
+ threadTargetConversationId: text("thread_target_conversation_id"),
1252
+ threadDecisionFallbackUsed: integer("thread_decision_fallback_used"),
1253
+ clientDeliveryStatus: text("client_delivery_status"),
1254
+ clientDeliveryError: text("client_delivery_error"),
1255
+ clientDeliveryAt: integer("client_delivery_at"),
1256
+ createdAt: integer("created_at").notNull(),
1257
+ updatedAt: integer("updated_at").notNull(),
1258
+ },
1259
+ (table) => [
1260
+ uniqueIndex("idx_notification_deliveries_decision_channel").on(
1261
+ table.notificationDecisionId,
1262
+ table.channel,
1263
+ ),
1264
+ ],
1265
+ );
1104
1266
 
1105
1267
  // ── Conversation Attention ───────────────────────────────────────────
1106
1268
 
1107
- export const conversationAttentionEvents = sqliteTable('conversation_attention_events', {
1108
- id: text('id').primaryKey(),
1109
- conversationId: text('conversation_id')
1110
- .notNull()
1111
- .references(() => conversations.id, { onDelete: 'cascade' }),
1112
- assistantId: text('assistant_id').notNull(),
1113
- sourceChannel: text('source_channel').notNull(),
1114
- signalType: text('signal_type').notNull(),
1115
- confidence: text('confidence').notNull(),
1116
- source: text('source').notNull(),
1117
- evidenceText: text('evidence_text'),
1118
- metadataJson: text('metadata_json').notNull().default('{}'),
1119
- observedAt: integer('observed_at').notNull(),
1120
- createdAt: integer('created_at').notNull(),
1121
- }, (table) => [
1122
- index('idx_conv_attn_events_conv_observed').on(table.conversationId, table.observedAt),
1123
- index('idx_conv_attn_events_assistant_observed').on(table.assistantId, table.observedAt),
1124
- index('idx_conv_attn_events_channel_observed').on(table.sourceChannel, table.observedAt),
1125
- ]);
1126
-
1127
- export const conversationAssistantAttentionState = sqliteTable('conversation_assistant_attention_state', {
1128
- conversationId: text('conversation_id')
1129
- .primaryKey()
1130
- .references(() => conversations.id, { onDelete: 'cascade' }),
1131
- assistantId: text('assistant_id').notNull(),
1132
- latestAssistantMessageId: text('latest_assistant_message_id'),
1133
- latestAssistantMessageAt: integer('latest_assistant_message_at'),
1134
- lastSeenAssistantMessageId: text('last_seen_assistant_message_id'),
1135
- lastSeenAssistantMessageAt: integer('last_seen_assistant_message_at'),
1136
- lastSeenEventAt: integer('last_seen_event_at'),
1137
- lastSeenConfidence: text('last_seen_confidence'),
1138
- lastSeenSignalType: text('last_seen_signal_type'),
1139
- lastSeenSourceChannel: text('last_seen_source_channel'),
1140
- lastSeenSource: text('last_seen_source'),
1141
- lastSeenEvidenceText: text('last_seen_evidence_text'),
1142
- createdAt: integer('created_at').notNull(),
1143
- updatedAt: integer('updated_at').notNull(),
1144
- }, (table) => [
1145
- index('idx_conv_attn_state_assistant_latest_msg').on(table.assistantId, table.latestAssistantMessageAt),
1146
- index('idx_conv_attn_state_assistant_last_seen').on(table.assistantId, table.lastSeenAssistantMessageAt),
1147
- ]);
1269
+ export const conversationAttentionEvents = sqliteTable(
1270
+ "conversation_attention_events",
1271
+ {
1272
+ id: text("id").primaryKey(),
1273
+ conversationId: text("conversation_id")
1274
+ .notNull()
1275
+ .references(() => conversations.id, { onDelete: "cascade" }),
1276
+ assistantId: text("assistant_id").notNull(),
1277
+ sourceChannel: text("source_channel").notNull(),
1278
+ signalType: text("signal_type").notNull(),
1279
+ confidence: text("confidence").notNull(),
1280
+ source: text("source").notNull(),
1281
+ evidenceText: text("evidence_text"),
1282
+ metadataJson: text("metadata_json").notNull().default("{}"),
1283
+ observedAt: integer("observed_at").notNull(),
1284
+ createdAt: integer("created_at").notNull(),
1285
+ },
1286
+ (table) => [
1287
+ index("idx_conv_attn_events_conv_observed").on(
1288
+ table.conversationId,
1289
+ table.observedAt,
1290
+ ),
1291
+ index("idx_conv_attn_events_assistant_observed").on(
1292
+ table.assistantId,
1293
+ table.observedAt,
1294
+ ),
1295
+ index("idx_conv_attn_events_channel_observed").on(
1296
+ table.sourceChannel,
1297
+ table.observedAt,
1298
+ ),
1299
+ ],
1300
+ );
1301
+
1302
+ export const conversationAssistantAttentionState = sqliteTable(
1303
+ "conversation_assistant_attention_state",
1304
+ {
1305
+ conversationId: text("conversation_id")
1306
+ .primaryKey()
1307
+ .references(() => conversations.id, { onDelete: "cascade" }),
1308
+ assistantId: text("assistant_id").notNull(),
1309
+ latestAssistantMessageId: text("latest_assistant_message_id"),
1310
+ latestAssistantMessageAt: integer("latest_assistant_message_at"),
1311
+ lastSeenAssistantMessageId: text("last_seen_assistant_message_id"),
1312
+ lastSeenAssistantMessageAt: integer("last_seen_assistant_message_at"),
1313
+ lastSeenEventAt: integer("last_seen_event_at"),
1314
+ lastSeenConfidence: text("last_seen_confidence"),
1315
+ lastSeenSignalType: text("last_seen_signal_type"),
1316
+ lastSeenSourceChannel: text("last_seen_source_channel"),
1317
+ lastSeenSource: text("last_seen_source"),
1318
+ lastSeenEvidenceText: text("last_seen_evidence_text"),
1319
+ createdAt: integer("created_at").notNull(),
1320
+ updatedAt: integer("updated_at").notNull(),
1321
+ },
1322
+ (table) => [
1323
+ index("idx_conv_attn_state_assistant_latest_msg").on(
1324
+ table.assistantId,
1325
+ table.latestAssistantMessageAt,
1326
+ ),
1327
+ index("idx_conv_attn_state_assistant_last_seen").on(
1328
+ table.assistantId,
1329
+ table.lastSeenAssistantMessageAt,
1330
+ ),
1331
+ ],
1332
+ );
1148
1333
 
1149
1334
  // ── Actor Token Records ──────────────────────────────────────────────
1150
1335
 
1151
- export const actorTokenRecords = sqliteTable('actor_token_records', {
1152
- id: text('id').primaryKey(),
1153
- tokenHash: text('token_hash').notNull(),
1154
- assistantId: text('assistant_id').notNull(),
1155
- guardianPrincipalId: text('guardian_principal_id').notNull(),
1156
- hashedDeviceId: text('hashed_device_id').notNull(),
1157
- platform: text('platform').notNull(),
1158
- status: text('status').notNull().default('active'),
1159
- issuedAt: integer('issued_at').notNull(),
1160
- expiresAt: integer('expires_at'),
1161
- createdAt: integer('created_at').notNull(),
1162
- updatedAt: integer('updated_at').notNull(),
1336
+ export const actorTokenRecords = sqliteTable("actor_token_records", {
1337
+ id: text("id").primaryKey(),
1338
+ tokenHash: text("token_hash").notNull(),
1339
+ assistantId: text("assistant_id").notNull(),
1340
+ guardianPrincipalId: text("guardian_principal_id").notNull(),
1341
+ hashedDeviceId: text("hashed_device_id").notNull(),
1342
+ platform: text("platform").notNull(),
1343
+ status: text("status").notNull().default("active"),
1344
+ issuedAt: integer("issued_at").notNull(),
1345
+ expiresAt: integer("expires_at"),
1346
+ createdAt: integer("created_at").notNull(),
1347
+ updatedAt: integer("updated_at").notNull(),
1163
1348
  });
1164
1349
 
1165
1350
  // ── Actor Refresh Token Records ──────────────────────────────────────
1166
1351
 
1167
- export const actorRefreshTokenRecords = sqliteTable('actor_refresh_token_records', {
1168
- id: text('id').primaryKey(),
1169
- tokenHash: text('token_hash').notNull(),
1170
- familyId: text('family_id').notNull(),
1171
- assistantId: text('assistant_id').notNull(),
1172
- guardianPrincipalId: text('guardian_principal_id').notNull(),
1173
- hashedDeviceId: text('hashed_device_id').notNull(),
1174
- platform: text('platform').notNull(),
1175
- status: text('status').notNull().default('active'),
1176
- issuedAt: integer('issued_at').notNull(),
1177
- absoluteExpiresAt: integer('absolute_expires_at').notNull(),
1178
- inactivityExpiresAt: integer('inactivity_expires_at').notNull(),
1179
- lastUsedAt: integer('last_used_at'),
1180
- createdAt: integer('created_at').notNull(),
1181
- updatedAt: integer('updated_at').notNull(),
1182
- });
1352
+ export const actorRefreshTokenRecords = sqliteTable(
1353
+ "actor_refresh_token_records",
1354
+ {
1355
+ id: text("id").primaryKey(),
1356
+ tokenHash: text("token_hash").notNull(),
1357
+ familyId: text("family_id").notNull(),
1358
+ assistantId: text("assistant_id").notNull(),
1359
+ guardianPrincipalId: text("guardian_principal_id").notNull(),
1360
+ hashedDeviceId: text("hashed_device_id").notNull(),
1361
+ platform: text("platform").notNull(),
1362
+ status: text("status").notNull().default("active"),
1363
+ issuedAt: integer("issued_at").notNull(),
1364
+ absoluteExpiresAt: integer("absolute_expires_at").notNull(),
1365
+ inactivityExpiresAt: integer("inactivity_expires_at").notNull(),
1366
+ lastUsedAt: integer("last_used_at"),
1367
+ createdAt: integer("created_at").notNull(),
1368
+ updatedAt: integer("updated_at").notNull(),
1369
+ },
1370
+ );
1183
1371
 
1184
1372
  // ── Scoped Approval Grants ──────────────────────────────────────────
1185
1373
 
1186
- export const scopedApprovalGrants = sqliteTable('scoped_approval_grants', {
1187
- id: text('id').primaryKey(),
1188
- assistantId: text('assistant_id').notNull(),
1189
- scopeMode: text('scope_mode').notNull(), // 'request_id' | 'tool_signature'
1190
- requestId: text('request_id'),
1191
- toolName: text('tool_name'),
1192
- inputDigest: text('input_digest'),
1193
- requestChannel: text('request_channel').notNull(),
1194
- decisionChannel: text('decision_channel').notNull(),
1195
- executionChannel: text('execution_channel'), // null = any channel
1196
- conversationId: text('conversation_id'),
1197
- callSessionId: text('call_session_id'),
1198
- requesterExternalUserId: text('requester_external_user_id'),
1199
- guardianExternalUserId: text('guardian_external_user_id'),
1200
- status: text('status').notNull(), // 'active' | 'consumed' | 'expired' | 'revoked'
1201
- expiresAt: text('expires_at').notNull(),
1202
- consumedAt: text('consumed_at'),
1203
- consumedByRequestId: text('consumed_by_request_id'),
1204
- createdAt: text('created_at').notNull(),
1205
- updatedAt: text('updated_at').notNull(),
1206
- }, (table) => [
1207
- index('idx_scoped_grants_request_id').on(table.requestId),
1208
- index('idx_scoped_grants_tool_sig').on(table.toolName, table.inputDigest),
1209
- index('idx_scoped_grants_status_expires').on(table.status, table.expiresAt),
1210
- ]);
1374
+ export const scopedApprovalGrants = sqliteTable(
1375
+ "scoped_approval_grants",
1376
+ {
1377
+ id: text("id").primaryKey(),
1378
+ assistantId: text("assistant_id").notNull(),
1379
+ scopeMode: text("scope_mode").notNull(), // 'request_id' | 'tool_signature'
1380
+ requestId: text("request_id"),
1381
+ toolName: text("tool_name"),
1382
+ inputDigest: text("input_digest"),
1383
+ requestChannel: text("request_channel").notNull(),
1384
+ decisionChannel: text("decision_channel").notNull(),
1385
+ executionChannel: text("execution_channel"), // null = any channel
1386
+ conversationId: text("conversation_id"),
1387
+ callSessionId: text("call_session_id"),
1388
+ requesterExternalUserId: text("requester_external_user_id"),
1389
+ guardianExternalUserId: text("guardian_external_user_id"),
1390
+ status: text("status").notNull(), // 'active' | 'consumed' | 'expired' | 'revoked'
1391
+ expiresAt: text("expires_at").notNull(),
1392
+ consumedAt: text("consumed_at"),
1393
+ consumedByRequestId: text("consumed_by_request_id"),
1394
+ createdAt: text("created_at").notNull(),
1395
+ updatedAt: text("updated_at").notNull(),
1396
+ },
1397
+ (table) => [
1398
+ index("idx_scoped_grants_request_id").on(table.requestId),
1399
+ index("idx_scoped_grants_tool_sig").on(table.toolName, table.inputDigest),
1400
+ index("idx_scoped_grants_status_expires").on(table.status, table.expiresAt),
1401
+ ],
1402
+ );