@vellumai/assistant 0.4.30 → 0.4.32

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.
Files changed (194) hide show
  1. package/ARCHITECTURE.md +1 -1
  2. package/Dockerfile +14 -8
  3. package/README.md +2 -2
  4. package/docs/architecture/memory.md +28 -29
  5. package/docs/runbook-trusted-contacts.md +1 -4
  6. package/package.json +1 -1
  7. package/src/__tests__/__snapshots__/ipc-snapshot.test.ts.snap +0 -7
  8. package/src/__tests__/anthropic-provider.test.ts +86 -1
  9. package/src/__tests__/assistant-feature-flags-integration.test.ts +2 -2
  10. package/src/__tests__/checker.test.ts +37 -98
  11. package/src/__tests__/commit-message-enrichment-service.test.ts +15 -4
  12. package/src/__tests__/config-schema.test.ts +6 -14
  13. package/src/__tests__/conflict-policy.test.ts +76 -0
  14. package/src/__tests__/conflict-store.test.ts +14 -20
  15. package/src/__tests__/contacts-tools.test.ts +8 -61
  16. package/src/__tests__/contradiction-checker.test.ts +5 -1
  17. package/src/__tests__/credential-security-invariants.test.ts +1 -0
  18. package/src/__tests__/daemon-server-session-init.test.ts +1 -19
  19. package/src/__tests__/followup-tools.test.ts +0 -30
  20. package/src/__tests__/gemini-provider.test.ts +79 -1
  21. package/src/__tests__/guardian-decision-primitive-canonical.test.ts +5 -3
  22. package/src/__tests__/guardian-routing-invariants.test.ts +6 -4
  23. package/src/__tests__/ipc-snapshot.test.ts +0 -4
  24. package/src/__tests__/managed-proxy-context.test.ts +163 -0
  25. package/src/__tests__/memory-lifecycle-e2e.test.ts +13 -12
  26. package/src/__tests__/memory-regressions.test.ts +6 -6
  27. package/src/__tests__/openai-provider.test.ts +82 -0
  28. package/src/__tests__/provider-fail-open-selection.test.ts +134 -1
  29. package/src/__tests__/provider-managed-proxy-integration.test.ts +269 -0
  30. package/src/__tests__/recurrence-types.test.ts +0 -15
  31. package/src/__tests__/registry.test.ts +0 -10
  32. package/src/__tests__/schedule-tools.test.ts +28 -44
  33. package/src/__tests__/script-proxy-session-runtime.test.ts +6 -1
  34. package/src/__tests__/session-agent-loop.test.ts +0 -2
  35. package/src/__tests__/session-conflict-gate.test.ts +243 -388
  36. package/src/__tests__/session-profile-injection.test.ts +0 -2
  37. package/src/__tests__/session-runtime-assembly.test.ts +2 -3
  38. package/src/__tests__/session-skill-tools.test.ts +0 -49
  39. package/src/__tests__/session-workspace-cache-state.test.ts +0 -1
  40. package/src/__tests__/session-workspace-injection.test.ts +0 -1
  41. package/src/__tests__/session-workspace-tool-tracking.test.ts +0 -1
  42. package/src/__tests__/skill-feature-flags.test.ts +2 -2
  43. package/src/__tests__/task-management-tools.test.ts +111 -0
  44. package/src/__tests__/tool-grant-request-escalation.test.ts +2 -1
  45. package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +2 -1
  46. package/src/__tests__/twilio-config.test.ts +0 -3
  47. package/src/amazon/session.ts +30 -91
  48. package/src/approvals/guardian-decision-primitive.ts +11 -7
  49. package/src/approvals/guardian-request-resolvers.ts +5 -3
  50. package/src/calls/call-controller.ts +423 -571
  51. package/src/calls/finalize-call.ts +20 -0
  52. package/src/calls/relay-access-wait.ts +340 -0
  53. package/src/calls/relay-server.ts +269 -899
  54. package/src/calls/relay-setup-router.ts +307 -0
  55. package/src/calls/relay-verification.ts +280 -0
  56. package/src/calls/twilio-config.ts +1 -8
  57. package/src/calls/voice-control-protocol.ts +184 -0
  58. package/src/calls/voice-session-bridge.ts +1 -8
  59. package/src/config/agent-schema.ts +1 -1
  60. package/src/config/bundled-skills/contacts/SKILL.md +7 -18
  61. package/src/config/bundled-skills/contacts/TOOLS.json +4 -20
  62. package/src/config/bundled-skills/contacts/tools/contact-merge.ts +2 -4
  63. package/src/config/bundled-skills/contacts/tools/contact-search.ts +6 -12
  64. package/src/config/bundled-skills/contacts/tools/contact-upsert.ts +3 -24
  65. package/src/config/bundled-skills/followups/TOOLS.json +0 -4
  66. package/src/config/bundled-skills/schedule/SKILL.md +1 -1
  67. package/src/config/bundled-skills/schedule/TOOLS.json +2 -10
  68. package/src/config/bundled-tool-registry.ts +0 -5
  69. package/src/config/core-schema.ts +1 -1
  70. package/src/config/env.ts +0 -10
  71. package/src/config/feature-flag-registry.json +1 -1
  72. package/src/config/loader.ts +19 -0
  73. package/src/config/memory-schema.ts +0 -10
  74. package/src/config/schema.ts +2 -2
  75. package/src/config/system-prompt.ts +6 -0
  76. package/src/contacts/contact-store.ts +36 -62
  77. package/src/contacts/contacts-write.ts +14 -3
  78. package/src/contacts/types.ts +9 -4
  79. package/src/daemon/handlers/config-heartbeat.ts +1 -2
  80. package/src/daemon/handlers/contacts.ts +2 -2
  81. package/src/daemon/handlers/guardian-actions.ts +1 -1
  82. package/src/daemon/handlers/session-history.ts +398 -0
  83. package/src/daemon/handlers/session-user-message.ts +982 -0
  84. package/src/daemon/handlers/sessions.ts +9 -1337
  85. package/src/daemon/ipc-contract/contacts.ts +2 -2
  86. package/src/daemon/ipc-contract/sessions.ts +0 -6
  87. package/src/daemon/ipc-contract-inventory.json +0 -1
  88. package/src/daemon/lifecycle.ts +0 -29
  89. package/src/daemon/session-agent-loop.ts +1 -45
  90. package/src/daemon/session-conflict-gate.ts +21 -82
  91. package/src/daemon/session-memory.ts +7 -52
  92. package/src/daemon/session-process.ts +3 -1
  93. package/src/daemon/session-runtime-assembly.ts +18 -35
  94. package/src/heartbeat/heartbeat-service.ts +5 -1
  95. package/src/home-base/app-link-store.ts +0 -7
  96. package/src/memory/conflict-intent.ts +3 -6
  97. package/src/memory/conflict-policy.ts +34 -0
  98. package/src/memory/conflict-store.ts +10 -18
  99. package/src/memory/contradiction-checker.ts +2 -2
  100. package/src/memory/conversation-attention-store.ts +1 -1
  101. package/src/memory/conversation-store.ts +0 -51
  102. package/src/memory/db-init.ts +8 -0
  103. package/src/memory/job-handlers/conflict.ts +24 -7
  104. package/src/memory/migrations/105-contacts-and-triage.ts +4 -7
  105. package/src/memory/migrations/134-contacts-notes-column.ts +68 -0
  106. package/src/memory/migrations/135-backfill-contact-interaction-stats.ts +31 -0
  107. package/src/memory/migrations/index.ts +2 -0
  108. package/src/memory/migrations/registry.ts +6 -0
  109. package/src/memory/recall-cache.ts +0 -5
  110. package/src/memory/schema/calls.ts +274 -0
  111. package/src/memory/schema/contacts.ts +125 -0
  112. package/src/memory/schema/conversations.ts +129 -0
  113. package/src/memory/schema/guardian.ts +172 -0
  114. package/src/memory/schema/index.ts +8 -0
  115. package/src/memory/schema/infrastructure.ts +205 -0
  116. package/src/memory/schema/memory-core.ts +196 -0
  117. package/src/memory/schema/notifications.ts +191 -0
  118. package/src/memory/schema/tasks.ts +78 -0
  119. package/src/memory/schema.ts +1 -1402
  120. package/src/memory/slack-thread-store.ts +0 -69
  121. package/src/messaging/index.ts +0 -1
  122. package/src/messaging/types.ts +0 -38
  123. package/src/notifications/decisions-store.ts +2 -105
  124. package/src/notifications/deliveries-store.ts +0 -11
  125. package/src/notifications/preferences-store.ts +1 -58
  126. package/src/permissions/checker.ts +6 -17
  127. package/src/providers/anthropic/client.ts +6 -2
  128. package/src/providers/gemini/client.ts +13 -2
  129. package/src/providers/managed-proxy/constants.ts +55 -0
  130. package/src/providers/managed-proxy/context.ts +77 -0
  131. package/src/providers/registry.ts +112 -0
  132. package/src/runtime/auth/__tests__/guard-tests.test.ts +51 -23
  133. package/src/runtime/guardian-action-service.ts +3 -2
  134. package/src/runtime/guardian-outbound-actions.ts +3 -3
  135. package/src/runtime/guardian-reply-router.ts +4 -4
  136. package/src/runtime/http-server.ts +83 -710
  137. package/src/runtime/http-types.ts +0 -16
  138. package/src/runtime/middleware/auth.ts +0 -12
  139. package/src/runtime/routes/app-routes.ts +33 -0
  140. package/src/runtime/routes/approval-routes.ts +32 -0
  141. package/src/runtime/routes/approval-strategies/guardian-callback-strategy.ts +6 -3
  142. package/src/runtime/routes/attachment-routes.ts +32 -0
  143. package/src/runtime/routes/brain-graph-routes.ts +27 -0
  144. package/src/runtime/routes/call-routes.ts +41 -0
  145. package/src/runtime/routes/channel-readiness-routes.ts +20 -0
  146. package/src/runtime/routes/channel-routes.ts +70 -0
  147. package/src/runtime/routes/contact-routes.ts +371 -29
  148. package/src/runtime/routes/conversation-attention-routes.ts +15 -0
  149. package/src/runtime/routes/conversation-routes.ts +192 -194
  150. package/src/runtime/routes/debug-routes.ts +15 -0
  151. package/src/runtime/routes/events-routes.ts +16 -0
  152. package/src/runtime/routes/global-search-routes.ts +17 -2
  153. package/src/runtime/routes/guardian-action-routes.ts +23 -1
  154. package/src/runtime/routes/guardian-approval-interception.ts +2 -1
  155. package/src/runtime/routes/guardian-bootstrap-routes.ts +26 -1
  156. package/src/runtime/routes/guardian-refresh-routes.ts +20 -0
  157. package/src/runtime/routes/identity-routes.ts +20 -0
  158. package/src/runtime/routes/inbound-message-handler.ts +8 -0
  159. package/src/runtime/routes/inbound-stages/acl-enforcement.ts +5 -1
  160. package/src/runtime/routes/inbound-stages/guardian-reply-intercept.ts +2 -1
  161. package/src/runtime/routes/integration-routes.ts +83 -0
  162. package/src/runtime/routes/invite-routes.ts +31 -0
  163. package/src/runtime/routes/migration-routes.ts +47 -17
  164. package/src/runtime/routes/pairing-routes.ts +18 -0
  165. package/src/runtime/routes/secret-routes.ts +20 -0
  166. package/src/runtime/routes/surface-action-routes.ts +26 -0
  167. package/src/runtime/routes/trust-rules-routes.ts +31 -0
  168. package/src/runtime/routes/twilio-routes.ts +79 -0
  169. package/src/schedule/recurrence-types.ts +1 -11
  170. package/src/tools/followups/followup_create.ts +9 -3
  171. package/src/tools/mcp/mcp-tool-factory.ts +0 -17
  172. package/src/tools/memory/definitions.ts +0 -6
  173. package/src/tools/network/script-proxy/session-manager.ts +38 -3
  174. package/src/tools/schedule/create.ts +1 -3
  175. package/src/tools/schedule/update.ts +9 -6
  176. package/src/twitter/session.ts +29 -77
  177. package/src/util/cookie-session.ts +114 -0
  178. package/src/workspace/git-service.ts +6 -4
  179. package/src/__tests__/conversation-routes.test.ts +0 -99
  180. package/src/__tests__/get-weather.test.ts +0 -393
  181. package/src/__tests__/task-tools.test.ts +0 -685
  182. package/src/__tests__/weather-skill-regression.test.ts +0 -276
  183. package/src/autonomy/autonomy-resolver.ts +0 -62
  184. package/src/autonomy/autonomy-store.ts +0 -138
  185. package/src/autonomy/disposition-mapper.ts +0 -31
  186. package/src/autonomy/index.ts +0 -11
  187. package/src/autonomy/types.ts +0 -43
  188. package/src/config/bundled-skills/weather/SKILL.md +0 -38
  189. package/src/config/bundled-skills/weather/TOOLS.json +0 -36
  190. package/src/config/bundled-skills/weather/icon.svg +0 -24
  191. package/src/config/bundled-skills/weather/tools/get-weather.ts +0 -12
  192. package/src/contacts/startup-migration.ts +0 -21
  193. package/src/messaging/triage-engine.ts +0 -344
  194. package/src/tools/weather/service.ts +0 -712
@@ -0,0 +1,274 @@
1
+ import {
2
+ index,
3
+ integer,
4
+ real,
5
+ sqliteTable,
6
+ text,
7
+ } from "drizzle-orm/sqlite-core";
8
+
9
+ import { DAEMON_INTERNAL_ASSISTANT_ID } from "../../runtime/assistant-scope.js";
10
+ import { conversations } from "./conversations.js";
11
+
12
+ export const callSessions = sqliteTable(
13
+ "call_sessions",
14
+ {
15
+ id: text("id").primaryKey(),
16
+ conversationId: text("conversation_id")
17
+ .notNull()
18
+ .references(() => conversations.id, { onDelete: "cascade" }),
19
+ provider: text("provider").notNull(),
20
+ providerCallSid: text("provider_call_sid"),
21
+ fromNumber: text("from_number").notNull(),
22
+ toNumber: text("to_number").notNull(),
23
+ task: text("task"),
24
+ status: text("status").notNull().default("initiated"),
25
+ callMode: text("call_mode"),
26
+ guardianVerificationSessionId: text("guardian_verification_session_id"),
27
+ callerIdentityMode: text("caller_identity_mode"),
28
+ callerIdentitySource: text("caller_identity_source"),
29
+ assistantId: text("assistant_id"),
30
+ initiatedFromConversationId: text("initiated_from_conversation_id"),
31
+ startedAt: integer("started_at"),
32
+ endedAt: integer("ended_at"),
33
+ lastError: text("last_error"),
34
+ createdAt: integer("created_at").notNull(),
35
+ updatedAt: integer("updated_at").notNull(),
36
+ },
37
+ (table) => [index("idx_call_sessions_status").on(table.status)],
38
+ );
39
+
40
+ export const callEvents = sqliteTable("call_events", {
41
+ id: text("id").primaryKey(),
42
+ callSessionId: text("call_session_id")
43
+ .notNull()
44
+ .references(() => callSessions.id, { onDelete: "cascade" }),
45
+ eventType: text("event_type").notNull(),
46
+ payloadJson: text("payload_json").notNull().default("{}"),
47
+ createdAt: integer("created_at").notNull(),
48
+ });
49
+
50
+ export const callPendingQuestions = sqliteTable("call_pending_questions", {
51
+ id: text("id").primaryKey(),
52
+ callSessionId: text("call_session_id")
53
+ .notNull()
54
+ .references(() => callSessions.id, { onDelete: "cascade" }),
55
+ questionText: text("question_text").notNull(),
56
+ status: text("status").notNull().default("pending"),
57
+ askedAt: integer("asked_at").notNull(),
58
+ answeredAt: integer("answered_at"),
59
+ answerText: text("answer_text"),
60
+ });
61
+
62
+ export const processedCallbacks = sqliteTable("processed_callbacks", {
63
+ id: text("id").primaryKey(),
64
+ dedupeKey: text("dedupe_key").notNull().unique(),
65
+ callSessionId: text("call_session_id")
66
+ .notNull()
67
+ .references(() => callSessions.id, { onDelete: "cascade" }),
68
+ claimId: text("claim_id"),
69
+ createdAt: integer("created_at").notNull(),
70
+ });
71
+
72
+ export const externalConversationBindings = sqliteTable(
73
+ "external_conversation_bindings",
74
+ {
75
+ conversationId: text("conversation_id")
76
+ .primaryKey()
77
+ .references(() => conversations.id, { onDelete: "cascade" }),
78
+ sourceChannel: text("source_channel").notNull(),
79
+ externalChatId: text("external_chat_id").notNull(),
80
+ externalUserId: text("external_user_id"),
81
+ displayName: text("display_name"),
82
+ username: text("username"),
83
+ createdAt: integer("created_at").notNull(),
84
+ updatedAt: integer("updated_at").notNull(),
85
+ lastInboundAt: integer("last_inbound_at"),
86
+ lastOutboundAt: integer("last_outbound_at"),
87
+ },
88
+ );
89
+
90
+ export const channelGuardianVerificationChallenges = sqliteTable(
91
+ "channel_guardian_verification_challenges",
92
+ {
93
+ id: text("id").primaryKey(),
94
+ assistantId: text("assistant_id").notNull(),
95
+ channel: text("channel").notNull(),
96
+ challengeHash: text("challenge_hash").notNull(),
97
+ expiresAt: integer("expires_at").notNull(),
98
+ status: text("status").notNull().default("pending"),
99
+ createdBySessionId: text("created_by_session_id"),
100
+ consumedByExternalUserId: text("consumed_by_external_user_id"),
101
+ consumedByChatId: text("consumed_by_chat_id"),
102
+ // Outbound session: expected-identity binding
103
+ expectedExternalUserId: text("expected_external_user_id"),
104
+ expectedChatId: text("expected_chat_id"),
105
+ expectedPhoneE164: text("expected_phone_e164"),
106
+ identityBindingStatus: text("identity_binding_status").default("bound"),
107
+ // Outbound session: delivery tracking
108
+ destinationAddress: text("destination_address"),
109
+ lastSentAt: integer("last_sent_at"),
110
+ sendCount: integer("send_count").default(0),
111
+ nextResendAt: integer("next_resend_at"),
112
+ // Session configuration
113
+ codeDigits: integer("code_digits").default(6),
114
+ maxAttempts: integer("max_attempts").default(3),
115
+ // Distinguishes guardian verification from trusted contact verification
116
+ verificationPurpose: text("verification_purpose").default("guardian"),
117
+ // Telegram bootstrap deep-link token hash
118
+ bootstrapTokenHash: text("bootstrap_token_hash"),
119
+ createdAt: integer("created_at").notNull(),
120
+ updatedAt: integer("updated_at").notNull(),
121
+ },
122
+ );
123
+
124
+ export const channelGuardianApprovalRequests = sqliteTable(
125
+ "channel_guardian_approval_requests",
126
+ {
127
+ id: text("id").primaryKey(),
128
+ runId: text("run_id").notNull(),
129
+ requestId: text("request_id"),
130
+ conversationId: text("conversation_id").notNull(),
131
+ assistantId: text("assistant_id")
132
+ .notNull()
133
+ .default(DAEMON_INTERNAL_ASSISTANT_ID),
134
+ channel: text("channel").notNull(),
135
+ requesterExternalUserId: text("requester_external_user_id").notNull(),
136
+ requesterChatId: text("requester_chat_id").notNull(),
137
+ guardianExternalUserId: text("guardian_external_user_id").notNull(),
138
+ guardianChatId: text("guardian_chat_id").notNull(),
139
+ toolName: text("tool_name").notNull(),
140
+ riskLevel: text("risk_level"),
141
+ reason: text("reason"),
142
+ status: text("status").notNull().default("pending"),
143
+ decidedByExternalUserId: text("decided_by_external_user_id"),
144
+ expiresAt: integer("expires_at").notNull(),
145
+ createdAt: integer("created_at").notNull(),
146
+ updatedAt: integer("updated_at").notNull(),
147
+ },
148
+ );
149
+
150
+ export const channelGuardianRateLimits = sqliteTable(
151
+ "channel_guardian_rate_limits",
152
+ {
153
+ id: text("id").primaryKey(),
154
+ assistantId: text("assistant_id").notNull(),
155
+ channel: text("channel").notNull(),
156
+ actorExternalUserId: text("actor_external_user_id").notNull(),
157
+ actorChatId: text("actor_chat_id").notNull(),
158
+ // Legacy columns kept with defaults for backward compatibility with upgraded databases
159
+ // that still have the old NOT NULL columns without DEFAULT. Not read by app logic.
160
+ invalidAttempts: integer("invalid_attempts").notNull().default(0),
161
+ windowStartedAt: integer("window_started_at").notNull().default(0),
162
+ attemptTimestampsJson: text("attempt_timestamps_json")
163
+ .notNull()
164
+ .default("[]"),
165
+ lockedUntil: integer("locked_until"),
166
+ createdAt: integer("created_at").notNull(),
167
+ updatedAt: integer("updated_at").notNull(),
168
+ },
169
+ );
170
+
171
+ export const mediaAssets = sqliteTable("media_assets", {
172
+ id: text("id").primaryKey(),
173
+ title: text("title").notNull(),
174
+ filePath: text("file_path").notNull(),
175
+ mimeType: text("mime_type").notNull(),
176
+ durationSeconds: real("duration_seconds"),
177
+ fileHash: text("file_hash").notNull(),
178
+ status: text("status").notNull().default("registered"), // registered | processing | indexed | failed
179
+ mediaType: text("media_type").notNull(), // video | audio | image
180
+ metadata: text("metadata"), // JSON
181
+ createdAt: integer("created_at").notNull(),
182
+ updatedAt: integer("updated_at").notNull(),
183
+ });
184
+
185
+ export const processingStages = sqliteTable("processing_stages", {
186
+ id: text("id").primaryKey(),
187
+ assetId: text("asset_id")
188
+ .notNull()
189
+ .references(() => mediaAssets.id, { onDelete: "cascade" }),
190
+ stage: text("stage").notNull(),
191
+ status: text("status").notNull().default("pending"), // pending | running | completed | failed
192
+ progress: integer("progress").notNull().default(0), // 0-100
193
+ lastError: text("last_error"),
194
+ startedAt: integer("started_at"),
195
+ completedAt: integer("completed_at"),
196
+ });
197
+
198
+ export const mediaKeyframes = sqliteTable("media_keyframes", {
199
+ id: text("id").primaryKey(),
200
+ assetId: text("asset_id")
201
+ .notNull()
202
+ .references(() => mediaAssets.id, { onDelete: "cascade" }),
203
+ timestamp: real("timestamp").notNull(),
204
+ filePath: text("file_path").notNull(),
205
+ metadata: text("metadata"), // JSON
206
+ createdAt: integer("created_at").notNull(),
207
+ });
208
+
209
+ export const mediaVisionOutputs = sqliteTable("media_vision_outputs", {
210
+ id: text("id").primaryKey(),
211
+ assetId: text("asset_id")
212
+ .notNull()
213
+ .references(() => mediaAssets.id, { onDelete: "cascade" }),
214
+ keyframeId: text("keyframe_id")
215
+ .notNull()
216
+ .references(() => mediaKeyframes.id, { onDelete: "cascade" }),
217
+ analysisType: text("analysis_type").notNull(),
218
+ output: text("output").notNull(), // JSON
219
+ confidence: real("confidence"),
220
+ createdAt: integer("created_at").notNull(),
221
+ });
222
+
223
+ export const mediaTimelines = sqliteTable("media_timelines", {
224
+ id: text("id").primaryKey(),
225
+ assetId: text("asset_id")
226
+ .notNull()
227
+ .references(() => mediaAssets.id, { onDelete: "cascade" }),
228
+ startTime: real("start_time").notNull(),
229
+ endTime: real("end_time").notNull(),
230
+ segmentType: text("segment_type").notNull(),
231
+ attributes: text("attributes"), // JSON
232
+ confidence: real("confidence"),
233
+ createdAt: integer("created_at").notNull(),
234
+ });
235
+
236
+ export const mediaEvents = sqliteTable("media_events", {
237
+ id: text("id").primaryKey(),
238
+ assetId: text("asset_id")
239
+ .notNull()
240
+ .references(() => mediaAssets.id, { onDelete: "cascade" }),
241
+ eventType: text("event_type").notNull(),
242
+ startTime: real("start_time").notNull(),
243
+ endTime: real("end_time").notNull(),
244
+ confidence: real("confidence").notNull(),
245
+ reasons: text("reasons").notNull(), // JSON array
246
+ metadata: text("metadata"), // JSON
247
+ createdAt: integer("created_at").notNull(),
248
+ });
249
+
250
+ export const mediaTrackingProfiles = sqliteTable("media_tracking_profiles", {
251
+ id: text("id").primaryKey(),
252
+ assetId: text("asset_id")
253
+ .notNull()
254
+ .references(() => mediaAssets.id, { onDelete: "cascade" }),
255
+ capabilities: text("capabilities").notNull(), // JSON: { [capName]: { enabled, tier } }
256
+ createdAt: integer("created_at").notNull(),
257
+ });
258
+
259
+ export const mediaEventFeedback = sqliteTable("media_event_feedback", {
260
+ id: text("id").primaryKey(),
261
+ assetId: text("asset_id")
262
+ .notNull()
263
+ .references(() => mediaAssets.id, { onDelete: "cascade" }),
264
+ eventId: text("event_id")
265
+ .notNull()
266
+ .references(() => mediaEvents.id, { onDelete: "cascade" }),
267
+ feedbackType: text("feedback_type").notNull(), // correct | incorrect | boundary_edit | missed
268
+ originalStartTime: real("original_start_time"),
269
+ originalEndTime: real("original_end_time"),
270
+ correctedStartTime: real("corrected_start_time"),
271
+ correctedEndTime: real("corrected_end_time"),
272
+ notes: text("notes"),
273
+ createdAt: integer("created_at").notNull(),
274
+ });
@@ -0,0 +1,125 @@
1
+ import { index, integer, sqliteTable, text } from "drizzle-orm/sqlite-core";
2
+
3
+ import { DAEMON_INTERNAL_ASSISTANT_ID } from "../../runtime/assistant-scope.js";
4
+ import { conversations } from "./conversations.js";
5
+
6
+ export const contacts = sqliteTable("contacts", {
7
+ id: text("id").primaryKey(),
8
+ displayName: text("display_name").notNull(),
9
+ notes: text("notes"),
10
+ lastInteraction: integer("last_interaction"), // epoch ms
11
+ interactionCount: integer("interaction_count").notNull().default(0),
12
+ createdAt: integer("created_at").notNull(),
13
+ updatedAt: integer("updated_at").notNull(),
14
+ role: text("role").notNull().default("contact"), // 'guardian' | 'contact'
15
+ principalId: text("principal_id"), // internal auth principal (nullable)
16
+ assistantId: text("assistant_id"), // which assistant this guardian is for (nullable, daemon default is DAEMON_INTERNAL_ASSISTANT_ID)
17
+ contactType: text("contact_type").notNull().default("human"), // 'human' | 'assistant'
18
+ });
19
+
20
+ export const contactChannels = sqliteTable(
21
+ "contact_channels",
22
+ {
23
+ id: text("id").primaryKey(),
24
+ contactId: text("contact_id")
25
+ .notNull()
26
+ .references(() => contacts.id, { onDelete: "cascade" }),
27
+ type: text("type").notNull(), // 'email', 'slack', 'whatsapp', 'phone', etc.
28
+ address: text("address").notNull(), // the actual identifier on that channel
29
+ isPrimary: integer("is_primary", { mode: "boolean" })
30
+ .notNull()
31
+ .default(false),
32
+ externalUserId: text("external_user_id"), // channel-native user ID (e.g., Telegram numeric ID, E.164 phone)
33
+ externalChatId: text("external_chat_id"), // delivery/notification routing address (e.g., Telegram chat ID)
34
+ status: text("status").notNull().default("unverified"), // 'active' | 'pending' | 'revoked' | 'blocked' | 'unverified'
35
+ policy: text("policy").notNull().default("allow"), // 'allow' | 'deny' | 'escalate'
36
+ verifiedAt: integer("verified_at"), // epoch ms
37
+ verifiedVia: text("verified_via"), // 'challenge' | 'invite' | 'bootstrap' | etc.
38
+ inviteId: text("invite_id"), // reference to invite that onboarded
39
+ revokedReason: text("revoked_reason"),
40
+ blockedReason: text("blocked_reason"),
41
+ lastSeenAt: integer("last_seen_at"), // epoch ms
42
+ updatedAt: integer("updated_at"), // epoch ms
43
+ createdAt: integer("created_at").notNull(),
44
+ },
45
+ (table) => [
46
+ index("idx_contact_channels_type_ext_user").on(
47
+ table.type,
48
+ table.externalUserId,
49
+ ),
50
+ index("idx_contact_channels_type_ext_chat").on(
51
+ table.type,
52
+ table.externalChatId,
53
+ ),
54
+ ],
55
+ );
56
+
57
+ export const assistantContactMetadata = sqliteTable(
58
+ "assistant_contact_metadata",
59
+ {
60
+ contactId: text("contact_id")
61
+ .primaryKey()
62
+ .references(() => contacts.id, { onDelete: "cascade" }),
63
+ species: text("species").notNull(), // 'vellum' | 'openclaw'
64
+ metadata: text("metadata"), // JSON blob for species-specific fields
65
+ },
66
+ );
67
+
68
+ export const assistantIngressInvites = sqliteTable(
69
+ "assistant_ingress_invites",
70
+ {
71
+ id: text("id").primaryKey(),
72
+ assistantId: text("assistant_id")
73
+ .notNull()
74
+ .default(DAEMON_INTERNAL_ASSISTANT_ID),
75
+ sourceChannel: text("source_channel").notNull(),
76
+ tokenHash: text("token_hash").notNull(),
77
+ createdBySessionId: text("created_by_session_id"),
78
+ note: text("note"),
79
+ maxUses: integer("max_uses").notNull().default(1),
80
+ useCount: integer("use_count").notNull().default(0),
81
+ expiresAt: integer("expires_at").notNull(),
82
+ status: text("status").notNull().default("active"),
83
+ redeemedByExternalUserId: text("redeemed_by_external_user_id"),
84
+ redeemedByExternalChatId: text("redeemed_by_external_chat_id"),
85
+ redeemedAt: integer("redeemed_at"),
86
+ // Voice invite fields (nullable — non-voice invites leave these NULL)
87
+ expectedExternalUserId: text("expected_external_user_id"),
88
+ voiceCodeHash: text("voice_code_hash"),
89
+ voiceCodeDigits: integer("voice_code_digits"),
90
+ // Display metadata for personalized voice prompts (nullable — non-voice invites leave these NULL)
91
+ friendName: text("friend_name"),
92
+ guardianName: text("guardian_name"),
93
+ createdAt: integer("created_at").notNull(),
94
+ updatedAt: integer("updated_at").notNull(),
95
+ },
96
+ );
97
+
98
+ export const assistantInboxThreadState = sqliteTable(
99
+ "assistant_inbox_thread_state",
100
+ {
101
+ conversationId: text("conversation_id")
102
+ .primaryKey()
103
+ .references(() => conversations.id, { onDelete: "cascade" }),
104
+ assistantId: text("assistant_id")
105
+ .notNull()
106
+ .default(DAEMON_INTERNAL_ASSISTANT_ID),
107
+ sourceChannel: text("source_channel").notNull(),
108
+ externalChatId: text("external_chat_id").notNull(),
109
+ externalUserId: text("external_user_id"),
110
+ displayName: text("display_name"),
111
+ username: text("username"),
112
+ lastInboundAt: integer("last_inbound_at"),
113
+ lastOutboundAt: integer("last_outbound_at"),
114
+ lastMessageAt: integer("last_message_at"),
115
+ unreadCount: integer("unread_count").notNull().default(0),
116
+ pendingEscalationCount: integer("pending_escalation_count")
117
+ .notNull()
118
+ .default(0),
119
+ hasPendingEscalation: integer("has_pending_escalation")
120
+ .notNull()
121
+ .default(0),
122
+ createdAt: integer("created_at").notNull(),
123
+ updatedAt: integer("updated_at").notNull(),
124
+ },
125
+ );
@@ -0,0 +1,129 @@
1
+ import {
2
+ index,
3
+ integer,
4
+ real,
5
+ sqliteTable,
6
+ text,
7
+ } from "drizzle-orm/sqlite-core";
8
+
9
+ export const conversations = sqliteTable(
10
+ "conversations",
11
+ {
12
+ id: text("id").primaryKey(),
13
+ title: text("title"),
14
+ createdAt: integer("created_at").notNull(),
15
+ updatedAt: integer("updated_at").notNull(),
16
+ totalInputTokens: integer("total_input_tokens").notNull().default(0),
17
+ totalOutputTokens: integer("total_output_tokens").notNull().default(0),
18
+ totalEstimatedCost: real("total_estimated_cost").notNull().default(0),
19
+ contextSummary: text("context_summary"),
20
+ contextCompactedMessageCount: integer("context_compacted_message_count")
21
+ .notNull()
22
+ .default(0),
23
+ contextCompactedAt: integer("context_compacted_at"),
24
+ threadType: text("thread_type").notNull().default("standard"),
25
+ source: text("source").notNull().default("user"),
26
+ memoryScopeId: text("memory_scope_id").notNull().default("default"),
27
+ originChannel: text("origin_channel"),
28
+ originInterface: text("origin_interface"),
29
+ isAutoTitle: integer("is_auto_title").notNull().default(1),
30
+ scheduleJobId: text("schedule_job_id"),
31
+ },
32
+ (table) => [
33
+ index("idx_conversations_updated_at").on(table.updatedAt),
34
+ index("idx_conversations_thread_type").on(table.threadType),
35
+ ],
36
+ );
37
+
38
+ export const messages = sqliteTable(
39
+ "messages",
40
+ {
41
+ id: text("id").primaryKey(),
42
+ conversationId: text("conversation_id")
43
+ .notNull()
44
+ .references(() => conversations.id, { onDelete: "cascade" }),
45
+ role: text("role").notNull(),
46
+ content: text("content").notNull(),
47
+ createdAt: integer("created_at").notNull(),
48
+ metadata: text("metadata"),
49
+ },
50
+ (table) => [index("idx_messages_conversation_id").on(table.conversationId)],
51
+ );
52
+
53
+ export const toolInvocations = sqliteTable(
54
+ "tool_invocations",
55
+ {
56
+ id: text("id").primaryKey(),
57
+ conversationId: text("conversation_id")
58
+ .notNull()
59
+ .references(() => conversations.id),
60
+ toolName: text("tool_name").notNull(),
61
+ input: text("input").notNull(),
62
+ result: text("result").notNull(),
63
+ decision: text("decision").notNull(),
64
+ riskLevel: text("risk_level").notNull(),
65
+ durationMs: integer("duration_ms").notNull(),
66
+ createdAt: integer("created_at").notNull(),
67
+ },
68
+ (table) => [
69
+ index("idx_tool_invocations_conversation_id").on(table.conversationId),
70
+ ],
71
+ );
72
+
73
+ export const conversationKeys = sqliteTable("conversation_keys", {
74
+ id: text("id").primaryKey(),
75
+ conversationKey: text("conversation_key").notNull(),
76
+ conversationId: text("conversation_id")
77
+ .notNull()
78
+ .references(() => conversations.id, { onDelete: "cascade" }),
79
+ createdAt: integer("created_at").notNull(),
80
+ });
81
+
82
+ export const attachments = sqliteTable("attachments", {
83
+ id: text("id").primaryKey(),
84
+ originalFilename: text("original_filename").notNull(),
85
+ mimeType: text("mime_type").notNull(),
86
+ sizeBytes: integer("size_bytes").notNull(),
87
+ kind: text("kind").notNull(),
88
+ dataBase64: text("data_base64").notNull(),
89
+ contentHash: text("content_hash"),
90
+ thumbnailBase64: text("thumbnail_base64"),
91
+ createdAt: integer("created_at").notNull(),
92
+ });
93
+
94
+ export const messageAttachments = sqliteTable("message_attachments", {
95
+ id: text("id").primaryKey(),
96
+ messageId: text("message_id")
97
+ .notNull()
98
+ .references(() => messages.id, { onDelete: "cascade" }),
99
+ attachmentId: text("attachment_id")
100
+ .notNull()
101
+ .references(() => attachments.id, { onDelete: "cascade" }),
102
+ position: integer("position").notNull().default(0),
103
+ createdAt: integer("created_at").notNull(),
104
+ });
105
+
106
+ export const channelInboundEvents = sqliteTable("channel_inbound_events", {
107
+ id: text("id").primaryKey(),
108
+ sourceChannel: text("source_channel").notNull(),
109
+ externalChatId: text("external_chat_id").notNull(),
110
+ externalMessageId: text("external_message_id").notNull(),
111
+ sourceMessageId: text("source_message_id"),
112
+ conversationId: text("conversation_id")
113
+ .notNull()
114
+ .references(() => conversations.id, { onDelete: "cascade" }),
115
+ messageId: text("message_id").references(() => messages.id, {
116
+ onDelete: "cascade",
117
+ }),
118
+ deliveryStatus: text("delivery_status").notNull().default("pending"),
119
+ processingStatus: text("processing_status").notNull().default("pending"),
120
+ processingAttempts: integer("processing_attempts").notNull().default(0),
121
+ lastProcessingError: text("last_processing_error"),
122
+ retryAfter: integer("retry_after"),
123
+ rawPayload: text("raw_payload"),
124
+ deliveredSegmentCount: integer("delivered_segment_count")
125
+ .notNull()
126
+ .default(0),
127
+ createdAt: integer("created_at").notNull(),
128
+ updatedAt: integer("updated_at").notNull(),
129
+ });