@vellumai/assistant 0.4.31 → 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 (121) hide show
  1. package/ARCHITECTURE.md +1 -1
  2. package/package.json +1 -1
  3. package/src/__tests__/__snapshots__/ipc-snapshot.test.ts.snap +0 -7
  4. package/src/__tests__/anthropic-provider.test.ts +86 -1
  5. package/src/__tests__/assistant-feature-flags-integration.test.ts +2 -2
  6. package/src/__tests__/checker.test.ts +37 -98
  7. package/src/__tests__/commit-message-enrichment-service.test.ts +15 -0
  8. package/src/__tests__/config-schema.test.ts +6 -5
  9. package/src/__tests__/credential-security-invariants.test.ts +1 -0
  10. package/src/__tests__/daemon-server-session-init.test.ts +1 -19
  11. package/src/__tests__/followup-tools.test.ts +0 -30
  12. package/src/__tests__/gemini-provider.test.ts +79 -1
  13. package/src/__tests__/ipc-snapshot.test.ts +0 -4
  14. package/src/__tests__/managed-proxy-context.test.ts +163 -0
  15. package/src/__tests__/memory-lifecycle-e2e.test.ts +2 -2
  16. package/src/__tests__/memory-regressions.test.ts +6 -6
  17. package/src/__tests__/openai-provider.test.ts +82 -0
  18. package/src/__tests__/provider-fail-open-selection.test.ts +134 -1
  19. package/src/__tests__/provider-managed-proxy-integration.test.ts +269 -0
  20. package/src/__tests__/recurrence-types.test.ts +0 -15
  21. package/src/__tests__/schedule-tools.test.ts +28 -44
  22. package/src/__tests__/skill-feature-flags.test.ts +2 -2
  23. package/src/__tests__/task-management-tools.test.ts +111 -0
  24. package/src/__tests__/twilio-config.test.ts +0 -3
  25. package/src/amazon/session.ts +30 -91
  26. package/src/calls/call-controller.ts +423 -571
  27. package/src/calls/finalize-call.ts +20 -0
  28. package/src/calls/relay-access-wait.ts +340 -0
  29. package/src/calls/relay-server.ts +267 -902
  30. package/src/calls/relay-setup-router.ts +307 -0
  31. package/src/calls/relay-verification.ts +280 -0
  32. package/src/calls/twilio-config.ts +1 -8
  33. package/src/calls/voice-control-protocol.ts +184 -0
  34. package/src/calls/voice-session-bridge.ts +1 -8
  35. package/src/config/agent-schema.ts +1 -1
  36. package/src/config/bundled-skills/followups/TOOLS.json +0 -4
  37. package/src/config/bundled-skills/schedule/SKILL.md +1 -1
  38. package/src/config/bundled-skills/schedule/TOOLS.json +2 -10
  39. package/src/config/core-schema.ts +1 -1
  40. package/src/config/env.ts +0 -10
  41. package/src/config/feature-flag-registry.json +1 -1
  42. package/src/config/loader.ts +19 -0
  43. package/src/config/schema.ts +2 -2
  44. package/src/daemon/handlers/session-history.ts +398 -0
  45. package/src/daemon/handlers/session-user-message.ts +982 -0
  46. package/src/daemon/handlers/sessions.ts +9 -1338
  47. package/src/daemon/ipc-contract/sessions.ts +0 -6
  48. package/src/daemon/ipc-contract-inventory.json +0 -1
  49. package/src/daemon/lifecycle.ts +0 -29
  50. package/src/home-base/app-link-store.ts +0 -7
  51. package/src/memory/conversation-attention-store.ts +1 -1
  52. package/src/memory/conversation-store.ts +0 -51
  53. package/src/memory/db-init.ts +5 -1
  54. package/src/memory/job-handlers/conflict.ts +24 -0
  55. package/src/memory/migrations/105-contacts-and-triage.ts +4 -7
  56. package/src/memory/migrations/134-contacts-notes-column.ts +50 -33
  57. package/src/memory/migrations/registry.ts +6 -0
  58. package/src/memory/recall-cache.ts +0 -5
  59. package/src/memory/schema/calls.ts +274 -0
  60. package/src/memory/schema/contacts.ts +125 -0
  61. package/src/memory/schema/conversations.ts +129 -0
  62. package/src/memory/schema/guardian.ts +172 -0
  63. package/src/memory/schema/index.ts +8 -0
  64. package/src/memory/schema/infrastructure.ts +205 -0
  65. package/src/memory/schema/memory-core.ts +196 -0
  66. package/src/memory/schema/notifications.ts +191 -0
  67. package/src/memory/schema/tasks.ts +78 -0
  68. package/src/memory/schema.ts +1 -1385
  69. package/src/memory/slack-thread-store.ts +0 -69
  70. package/src/notifications/decisions-store.ts +2 -105
  71. package/src/notifications/deliveries-store.ts +0 -11
  72. package/src/notifications/preferences-store.ts +1 -58
  73. package/src/permissions/checker.ts +6 -17
  74. package/src/providers/anthropic/client.ts +6 -2
  75. package/src/providers/gemini/client.ts +13 -2
  76. package/src/providers/managed-proxy/constants.ts +55 -0
  77. package/src/providers/managed-proxy/context.ts +77 -0
  78. package/src/providers/registry.ts +112 -0
  79. package/src/runtime/auth/__tests__/guard-tests.test.ts +51 -23
  80. package/src/runtime/http-server.ts +83 -722
  81. package/src/runtime/http-types.ts +0 -16
  82. package/src/runtime/middleware/auth.ts +0 -12
  83. package/src/runtime/routes/app-routes.ts +33 -0
  84. package/src/runtime/routes/approval-routes.ts +32 -0
  85. package/src/runtime/routes/attachment-routes.ts +32 -0
  86. package/src/runtime/routes/brain-graph-routes.ts +27 -0
  87. package/src/runtime/routes/call-routes.ts +41 -0
  88. package/src/runtime/routes/channel-readiness-routes.ts +20 -0
  89. package/src/runtime/routes/channel-routes.ts +70 -0
  90. package/src/runtime/routes/contact-routes.ts +63 -0
  91. package/src/runtime/routes/conversation-attention-routes.ts +15 -0
  92. package/src/runtime/routes/conversation-routes.ts +190 -193
  93. package/src/runtime/routes/debug-routes.ts +15 -0
  94. package/src/runtime/routes/events-routes.ts +16 -0
  95. package/src/runtime/routes/global-search-routes.ts +15 -0
  96. package/src/runtime/routes/guardian-action-routes.ts +22 -0
  97. package/src/runtime/routes/guardian-bootstrap-routes.ts +20 -0
  98. package/src/runtime/routes/guardian-refresh-routes.ts +20 -0
  99. package/src/runtime/routes/identity-routes.ts +20 -0
  100. package/src/runtime/routes/inbound-message-handler.ts +8 -0
  101. package/src/runtime/routes/inbound-stages/acl-enforcement.ts +6 -6
  102. package/src/runtime/routes/integration-routes.ts +83 -0
  103. package/src/runtime/routes/invite-routes.ts +31 -0
  104. package/src/runtime/routes/migration-routes.ts +30 -0
  105. package/src/runtime/routes/pairing-routes.ts +18 -0
  106. package/src/runtime/routes/secret-routes.ts +20 -0
  107. package/src/runtime/routes/surface-action-routes.ts +26 -0
  108. package/src/runtime/routes/trust-rules-routes.ts +31 -0
  109. package/src/runtime/routes/twilio-routes.ts +79 -0
  110. package/src/schedule/recurrence-types.ts +1 -11
  111. package/src/tools/followups/followup_create.ts +9 -3
  112. package/src/tools/mcp/mcp-tool-factory.ts +0 -17
  113. package/src/tools/memory/definitions.ts +0 -6
  114. package/src/tools/network/script-proxy/session-manager.ts +38 -3
  115. package/src/tools/schedule/create.ts +1 -3
  116. package/src/tools/schedule/update.ts +9 -6
  117. package/src/twitter/session.ts +29 -77
  118. package/src/util/cookie-session.ts +114 -0
  119. package/src/__tests__/conversation-routes.test.ts +0 -99
  120. package/src/__tests__/task-tools.test.ts +0 -685
  121. package/src/contacts/startup-migration.ts +0 -21
@@ -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
+ });
@@ -0,0 +1,172 @@
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 { callPendingQuestions, callSessions } from "./calls.js";
5
+
6
+ export const guardianActionRequests = sqliteTable(
7
+ "guardian_action_requests",
8
+ {
9
+ id: text("id").primaryKey(),
10
+ assistantId: text("assistant_id")
11
+ .notNull()
12
+ .default(DAEMON_INTERNAL_ASSISTANT_ID),
13
+ kind: text("kind").notNull(), // 'ask_guardian'
14
+ sourceChannel: text("source_channel").notNull(), // 'voice'
15
+ sourceConversationId: text("source_conversation_id").notNull(),
16
+ callSessionId: text("call_session_id")
17
+ .notNull()
18
+ .references(() => callSessions.id, { onDelete: "cascade" }),
19
+ pendingQuestionId: text("pending_question_id")
20
+ .notNull()
21
+ .references(() => callPendingQuestions.id, { onDelete: "cascade" }),
22
+ questionText: text("question_text").notNull(),
23
+ requestCode: text("request_code").notNull(), // short human-readable code for routing replies
24
+ status: text("status").notNull().default("pending"), // pending | answered | expired | cancelled
25
+ answerText: text("answer_text"),
26
+ answeredByChannel: text("answered_by_channel"),
27
+ answeredByExternalUserId: text("answered_by_external_user_id"),
28
+ answeredAt: integer("answered_at"),
29
+ expiresAt: integer("expires_at").notNull(),
30
+ expiredReason: text("expired_reason"), // call_timeout | sweep_timeout | cancelled
31
+ followupState: text("followup_state").notNull().default("none"), // none | awaiting_guardian_choice | dispatching | completed | declined | failed
32
+ lateAnswerText: text("late_answer_text"),
33
+ lateAnsweredAt: integer("late_answered_at"),
34
+ followupAction: text("followup_action"), // call_back | message_back | decline
35
+ followupCompletedAt: integer("followup_completed_at"),
36
+ toolName: text("tool_name"), // tool identity for tool-approval requests
37
+ inputDigest: text("input_digest"), // canonical SHA-256 digest of tool input
38
+ supersededByRequestId: text("superseded_by_request_id"), // links to the request that replaced this one
39
+ supersededAt: integer("superseded_at"), // epoch ms when supersession occurred
40
+ createdAt: integer("created_at").notNull(),
41
+ updatedAt: integer("updated_at").notNull(),
42
+ },
43
+ (table) => [
44
+ index("idx_guardian_action_requests_session_status_created").on(
45
+ table.callSessionId,
46
+ table.status,
47
+ table.createdAt,
48
+ ),
49
+ ],
50
+ );
51
+
52
+ export const guardianActionDeliveries = sqliteTable(
53
+ "guardian_action_deliveries",
54
+ {
55
+ id: text("id").primaryKey(),
56
+ requestId: text("request_id")
57
+ .notNull()
58
+ .references(() => guardianActionRequests.id, { onDelete: "cascade" }),
59
+ destinationChannel: text("destination_channel").notNull(), // 'telegram' | 'sms' | 'vellum'
60
+ destinationConversationId: text("destination_conversation_id"),
61
+ destinationChatId: text("destination_chat_id"),
62
+ destinationExternalUserId: text("destination_external_user_id"),
63
+ status: text("status").notNull().default("pending"), // pending | sent | failed | answered | expired | cancelled
64
+ sentAt: integer("sent_at"),
65
+ respondedAt: integer("responded_at"),
66
+ lastError: text("last_error"),
67
+ createdAt: integer("created_at").notNull(),
68
+ updatedAt: integer("updated_at").notNull(),
69
+ },
70
+ (table) => [
71
+ index("idx_guardian_action_deliveries_dest_conversation").on(
72
+ table.destinationConversationId,
73
+ ),
74
+ ],
75
+ );
76
+
77
+ export const canonicalGuardianRequests = sqliteTable(
78
+ "canonical_guardian_requests",
79
+ {
80
+ id: text("id").primaryKey(),
81
+ kind: text("kind").notNull(),
82
+ sourceType: text("source_type").notNull(),
83
+ sourceChannel: text("source_channel"),
84
+ conversationId: text("conversation_id"),
85
+ requesterExternalUserId: text("requester_external_user_id"),
86
+ requesterChatId: text("requester_chat_id"),
87
+ guardianExternalUserId: text("guardian_external_user_id"),
88
+ guardianPrincipalId: text("guardian_principal_id"),
89
+ callSessionId: text("call_session_id"),
90
+ pendingQuestionId: text("pending_question_id"),
91
+ questionText: text("question_text"),
92
+ requestCode: text("request_code"),
93
+ toolName: text("tool_name"),
94
+ inputDigest: text("input_digest"),
95
+ status: text("status").notNull().default("pending"),
96
+ answerText: text("answer_text"),
97
+ decidedByExternalUserId: text("decided_by_external_user_id"),
98
+ decidedByPrincipalId: text("decided_by_principal_id"),
99
+ followupState: text("followup_state"),
100
+ expiresAt: text("expires_at"),
101
+ createdAt: text("created_at").notNull(),
102
+ updatedAt: text("updated_at").notNull(),
103
+ },
104
+ (table) => [
105
+ index("idx_canonical_guardian_requests_status").on(table.status),
106
+ index("idx_canonical_guardian_requests_guardian").on(
107
+ table.guardianExternalUserId,
108
+ table.status,
109
+ ),
110
+ index("idx_canonical_guardian_requests_conversation").on(
111
+ table.conversationId,
112
+ table.status,
113
+ ),
114
+ index("idx_canonical_guardian_requests_source").on(
115
+ table.sourceType,
116
+ table.status,
117
+ ),
118
+ index("idx_canonical_guardian_requests_kind").on(table.kind, table.status),
119
+ index("idx_canonical_guardian_requests_request_code").on(table.requestCode),
120
+ ],
121
+ );
122
+
123
+ export const canonicalGuardianDeliveries = sqliteTable(
124
+ "canonical_guardian_deliveries",
125
+ {
126
+ id: text("id").primaryKey(),
127
+ requestId: text("request_id")
128
+ .notNull()
129
+ .references(() => canonicalGuardianRequests.id, { onDelete: "cascade" }),
130
+ destinationChannel: text("destination_channel").notNull(),
131
+ destinationConversationId: text("destination_conversation_id"),
132
+ destinationChatId: text("destination_chat_id"),
133
+ destinationMessageId: text("destination_message_id"),
134
+ status: text("status").notNull().default("pending"),
135
+ createdAt: text("created_at").notNull(),
136
+ updatedAt: text("updated_at").notNull(),
137
+ },
138
+ (table) => [
139
+ index("idx_canonical_guardian_deliveries_request_id").on(table.requestId),
140
+ index("idx_canonical_guardian_deliveries_status").on(table.status),
141
+ ],
142
+ );
143
+
144
+ export const scopedApprovalGrants = sqliteTable(
145
+ "scoped_approval_grants",
146
+ {
147
+ id: text("id").primaryKey(),
148
+ assistantId: text("assistant_id").notNull(),
149
+ scopeMode: text("scope_mode").notNull(), // 'request_id' | 'tool_signature'
150
+ requestId: text("request_id"),
151
+ toolName: text("tool_name"),
152
+ inputDigest: text("input_digest"),
153
+ requestChannel: text("request_channel").notNull(),
154
+ decisionChannel: text("decision_channel").notNull(),
155
+ executionChannel: text("execution_channel"), // null = any channel
156
+ conversationId: text("conversation_id"),
157
+ callSessionId: text("call_session_id"),
158
+ requesterExternalUserId: text("requester_external_user_id"),
159
+ guardianExternalUserId: text("guardian_external_user_id"),
160
+ status: text("status").notNull(), // 'active' | 'consumed' | 'expired' | 'revoked'
161
+ expiresAt: text("expires_at").notNull(),
162
+ consumedAt: text("consumed_at"),
163
+ consumedByRequestId: text("consumed_by_request_id"),
164
+ createdAt: text("created_at").notNull(),
165
+ updatedAt: text("updated_at").notNull(),
166
+ },
167
+ (table) => [
168
+ index("idx_scoped_grants_request_id").on(table.requestId),
169
+ index("idx_scoped_grants_tool_sig").on(table.toolName, table.inputDigest),
170
+ index("idx_scoped_grants_status_expires").on(table.status, table.expiresAt),
171
+ ],
172
+ );
@@ -0,0 +1,8 @@
1
+ export * from "./calls.js";
2
+ export * from "./contacts.js";
3
+ export * from "./conversations.js";
4
+ export * from "./guardian.js";
5
+ export * from "./infrastructure.js";
6
+ export * from "./memory-core.js";
7
+ export * from "./notifications.js";
8
+ export * from "./tasks.js";
@@ -0,0 +1,205 @@
1
+ import {
2
+ blob,
3
+ index,
4
+ integer,
5
+ real,
6
+ sqliteTable,
7
+ text,
8
+ } from "drizzle-orm/sqlite-core";
9
+
10
+ export const accounts = sqliteTable("accounts", {
11
+ id: text("id").primaryKey(),
12
+ service: text("service").notNull(),
13
+ username: text("username"),
14
+ email: text("email"),
15
+ displayName: text("display_name"),
16
+ status: text("status").notNull().default("active"),
17
+ credentialRef: text("credential_ref"),
18
+ metadataJson: text("metadata_json"),
19
+ createdAt: integer("created_at").notNull(),
20
+ updatedAt: integer("updated_at").notNull(),
21
+ });
22
+
23
+ export const reminders = sqliteTable("reminders", {
24
+ id: text("id").primaryKey(),
25
+ label: text("label").notNull(),
26
+ message: text("message").notNull(),
27
+ fireAt: integer("fire_at").notNull(), // epoch ms, absolute timestamp
28
+ mode: text("mode").notNull(), // 'notify' | 'execute'
29
+ status: text("status").notNull(), // 'pending' | 'firing' | 'fired' | 'cancelled'
30
+ firedAt: integer("fired_at"),
31
+ conversationId: text("conversation_id"),
32
+ routingIntent: text("routing_intent").notNull().default("all_channels"), // 'single_channel' | 'multi_channel' | 'all_channels'
33
+ routingHintsJson: text("routing_hints_json").notNull().default("{}"),
34
+ createdAt: integer("created_at").notNull(),
35
+ updatedAt: integer("updated_at").notNull(),
36
+ });
37
+
38
+ export const cronJobs = sqliteTable("cron_jobs", {
39
+ id: text("id").primaryKey(),
40
+ name: text("name").notNull(),
41
+ enabled: integer("enabled", { mode: "boolean" }).notNull().default(true),
42
+ cronExpression: text("cron_expression").notNull(), // e.g. '0 9 * * 1-5'
43
+ scheduleSyntax: text("schedule_syntax").notNull().default("cron"), // 'cron' | 'rrule'
44
+ timezone: text("timezone"), // e.g. 'America/Los_Angeles'
45
+ message: text("message").notNull(),
46
+ nextRunAt: integer("next_run_at").notNull(),
47
+ lastRunAt: integer("last_run_at"),
48
+ lastStatus: text("last_status"), // 'ok' | 'error'
49
+ retryCount: integer("retry_count").notNull().default(0),
50
+ createdBy: text("created_by").notNull(), // 'agent' | 'user'
51
+ createdAt: integer("created_at").notNull(),
52
+ updatedAt: integer("updated_at").notNull(),
53
+ });
54
+
55
+ export const cronRuns = sqliteTable("cron_runs", {
56
+ id: text("id").primaryKey(),
57
+ jobId: text("job_id")
58
+ .notNull()
59
+ .references(() => cronJobs.id, { onDelete: "cascade" }),
60
+ status: text("status").notNull(), // 'ok' | 'error'
61
+ startedAt: integer("started_at").notNull(),
62
+ finishedAt: integer("finished_at"),
63
+ durationMs: integer("duration_ms"),
64
+ output: text("output"),
65
+ error: text("error"),
66
+ conversationId: text("conversation_id"),
67
+ createdAt: integer("created_at").notNull(),
68
+ });
69
+
70
+ // Recurrence-centric aliases — prefer these in new code.
71
+ // Physical table names remain `cron_jobs` / `cron_runs` for migration compatibility.
72
+ export const scheduleJobs = cronJobs;
73
+ export const scheduleRuns = cronRuns;
74
+
75
+ export const sharedAppLinks = sqliteTable("shared_app_links", {
76
+ id: text("id").primaryKey(),
77
+ shareToken: text("share_token").notNull().unique(),
78
+ bundleData: blob("bundle_data", { mode: "buffer" }).notNull(),
79
+ bundleSizeBytes: integer("bundle_size_bytes").notNull(),
80
+ manifestJson: text("manifest_json").notNull(),
81
+ downloadCount: integer("download_count").notNull().default(0),
82
+ createdAt: integer("created_at").notNull(),
83
+ expiresAt: integer("expires_at"),
84
+ });
85
+
86
+ export const homeBaseAppLinks = sqliteTable("home_base_app_links", {
87
+ id: text("id").primaryKey(),
88
+ appId: text("app_id").notNull(),
89
+ source: text("source").notNull(),
90
+ createdAt: integer("created_at").notNull(),
91
+ updatedAt: integer("updated_at").notNull(),
92
+ });
93
+
94
+ export const publishedPages = sqliteTable("published_pages", {
95
+ id: text("id").primaryKey(),
96
+ deploymentId: text("deployment_id").notNull().unique(),
97
+ publicUrl: text("public_url").notNull(),
98
+ pageTitle: text("page_title"),
99
+ htmlHash: text("html_hash").notNull(),
100
+ publishedAt: integer("published_at").notNull(),
101
+ status: text("status").notNull().default("active"),
102
+ appId: text("app_id"),
103
+ projectSlug: text("project_slug"),
104
+ });
105
+
106
+ export const watchers = sqliteTable("watchers", {
107
+ id: text("id").primaryKey(),
108
+ name: text("name").notNull(),
109
+ providerId: text("provider_id").notNull(),
110
+ enabled: integer("enabled", { mode: "boolean" }).notNull().default(true),
111
+ pollIntervalMs: integer("poll_interval_ms").notNull().default(60000),
112
+ actionPrompt: text("action_prompt").notNull(),
113
+ watermark: text("watermark"),
114
+ conversationId: text("conversation_id"),
115
+ status: text("status").notNull().default("idle"), // idle | polling | error | disabled
116
+ consecutiveErrors: integer("consecutive_errors").notNull().default(0),
117
+ lastError: text("last_error"),
118
+ lastPollAt: integer("last_poll_at"),
119
+ nextPollAt: integer("next_poll_at").notNull(),
120
+ configJson: text("config_json"),
121
+ credentialService: text("credential_service").notNull(),
122
+ createdAt: integer("created_at").notNull(),
123
+ updatedAt: integer("updated_at").notNull(),
124
+ });
125
+
126
+ export const watcherEvents = sqliteTable("watcher_events", {
127
+ id: text("id").primaryKey(),
128
+ watcherId: text("watcher_id")
129
+ .notNull()
130
+ .references(() => watchers.id, { onDelete: "cascade" }),
131
+ externalId: text("external_id").notNull(),
132
+ eventType: text("event_type").notNull(),
133
+ summary: text("summary").notNull(),
134
+ payloadJson: text("payload_json").notNull(),
135
+ disposition: text("disposition").notNull().default("pending"), // pending | silent | notify | escalate | error
136
+ llmAction: text("llm_action"),
137
+ processedAt: integer("processed_at"),
138
+ createdAt: integer("created_at").notNull(),
139
+ });
140
+
141
+ export const llmRequestLogs = sqliteTable("llm_request_logs", {
142
+ id: text("id").primaryKey(),
143
+ conversationId: text("conversation_id").notNull(),
144
+ requestPayload: text("request_payload").notNull(),
145
+ responsePayload: text("response_payload").notNull(),
146
+ createdAt: integer("created_at").notNull(),
147
+ });
148
+
149
+ export const llmUsageEvents = sqliteTable(
150
+ "llm_usage_events",
151
+ {
152
+ id: text("id").primaryKey(),
153
+ createdAt: integer("created_at").notNull(),
154
+ conversationId: text("conversation_id"),
155
+ runId: text("run_id"),
156
+ requestId: text("request_id"),
157
+ actor: text("actor").notNull(),
158
+ provider: text("provider").notNull(),
159
+ model: text("model").notNull(),
160
+ inputTokens: integer("input_tokens").notNull(),
161
+ outputTokens: integer("output_tokens").notNull(),
162
+ cacheCreationInputTokens: integer("cache_creation_input_tokens"),
163
+ cacheReadInputTokens: integer("cache_read_input_tokens"),
164
+ estimatedCostUsd: real("estimated_cost_usd"),
165
+ pricingStatus: text("pricing_status").notNull(),
166
+ metadataJson: text("metadata_json"),
167
+ },
168
+ (table) => [
169
+ index("idx_llm_usage_events_conversation_id").on(table.conversationId),
170
+ ],
171
+ );
172
+
173
+ export const actorTokenRecords = sqliteTable("actor_token_records", {
174
+ id: text("id").primaryKey(),
175
+ tokenHash: text("token_hash").notNull(),
176
+ assistantId: text("assistant_id").notNull(),
177
+ guardianPrincipalId: text("guardian_principal_id").notNull(),
178
+ hashedDeviceId: text("hashed_device_id").notNull(),
179
+ platform: text("platform").notNull(),
180
+ status: text("status").notNull().default("active"),
181
+ issuedAt: integer("issued_at").notNull(),
182
+ expiresAt: integer("expires_at"),
183
+ createdAt: integer("created_at").notNull(),
184
+ updatedAt: integer("updated_at").notNull(),
185
+ });
186
+
187
+ export const actorRefreshTokenRecords = sqliteTable(
188
+ "actor_refresh_token_records",
189
+ {
190
+ id: text("id").primaryKey(),
191
+ tokenHash: text("token_hash").notNull(),
192
+ familyId: text("family_id").notNull(),
193
+ assistantId: text("assistant_id").notNull(),
194
+ guardianPrincipalId: text("guardian_principal_id").notNull(),
195
+ hashedDeviceId: text("hashed_device_id").notNull(),
196
+ platform: text("platform").notNull(),
197
+ status: text("status").notNull().default("active"),
198
+ issuedAt: integer("issued_at").notNull(),
199
+ absoluteExpiresAt: integer("absolute_expires_at").notNull(),
200
+ inactivityExpiresAt: integer("inactivity_expires_at").notNull(),
201
+ lastUsedAt: integer("last_used_at"),
202
+ createdAt: integer("created_at").notNull(),
203
+ updatedAt: integer("updated_at").notNull(),
204
+ },
205
+ );