@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.
- package/ARCHITECTURE.md +1 -1
- package/package.json +1 -1
- package/src/__tests__/__snapshots__/ipc-snapshot.test.ts.snap +0 -7
- package/src/__tests__/anthropic-provider.test.ts +86 -1
- package/src/__tests__/assistant-feature-flags-integration.test.ts +2 -2
- package/src/__tests__/checker.test.ts +37 -98
- package/src/__tests__/commit-message-enrichment-service.test.ts +15 -0
- package/src/__tests__/config-schema.test.ts +6 -5
- package/src/__tests__/credential-security-invariants.test.ts +1 -0
- package/src/__tests__/daemon-server-session-init.test.ts +1 -19
- package/src/__tests__/followup-tools.test.ts +0 -30
- package/src/__tests__/gemini-provider.test.ts +79 -1
- package/src/__tests__/ipc-snapshot.test.ts +0 -4
- package/src/__tests__/managed-proxy-context.test.ts +163 -0
- package/src/__tests__/memory-lifecycle-e2e.test.ts +2 -2
- package/src/__tests__/memory-regressions.test.ts +6 -6
- package/src/__tests__/openai-provider.test.ts +82 -0
- package/src/__tests__/provider-fail-open-selection.test.ts +134 -1
- package/src/__tests__/provider-managed-proxy-integration.test.ts +269 -0
- package/src/__tests__/recurrence-types.test.ts +0 -15
- package/src/__tests__/schedule-tools.test.ts +28 -44
- package/src/__tests__/skill-feature-flags.test.ts +2 -2
- package/src/__tests__/task-management-tools.test.ts +111 -0
- package/src/__tests__/twilio-config.test.ts +0 -3
- package/src/amazon/session.ts +30 -91
- package/src/calls/call-controller.ts +423 -571
- package/src/calls/finalize-call.ts +20 -0
- package/src/calls/relay-access-wait.ts +340 -0
- package/src/calls/relay-server.ts +267 -902
- package/src/calls/relay-setup-router.ts +307 -0
- package/src/calls/relay-verification.ts +280 -0
- package/src/calls/twilio-config.ts +1 -8
- package/src/calls/voice-control-protocol.ts +184 -0
- package/src/calls/voice-session-bridge.ts +1 -8
- package/src/config/agent-schema.ts +1 -1
- package/src/config/bundled-skills/followups/TOOLS.json +0 -4
- package/src/config/bundled-skills/schedule/SKILL.md +1 -1
- package/src/config/bundled-skills/schedule/TOOLS.json +2 -10
- package/src/config/core-schema.ts +1 -1
- package/src/config/env.ts +0 -10
- package/src/config/feature-flag-registry.json +1 -1
- package/src/config/loader.ts +19 -0
- package/src/config/schema.ts +2 -2
- package/src/daemon/handlers/session-history.ts +398 -0
- package/src/daemon/handlers/session-user-message.ts +982 -0
- package/src/daemon/handlers/sessions.ts +9 -1338
- package/src/daemon/ipc-contract/sessions.ts +0 -6
- package/src/daemon/ipc-contract-inventory.json +0 -1
- package/src/daemon/lifecycle.ts +0 -29
- package/src/home-base/app-link-store.ts +0 -7
- package/src/memory/conversation-attention-store.ts +1 -1
- package/src/memory/conversation-store.ts +0 -51
- package/src/memory/db-init.ts +5 -1
- package/src/memory/job-handlers/conflict.ts +24 -0
- package/src/memory/migrations/105-contacts-and-triage.ts +4 -7
- package/src/memory/migrations/134-contacts-notes-column.ts +50 -33
- package/src/memory/migrations/registry.ts +6 -0
- package/src/memory/recall-cache.ts +0 -5
- package/src/memory/schema/calls.ts +274 -0
- package/src/memory/schema/contacts.ts +125 -0
- package/src/memory/schema/conversations.ts +129 -0
- package/src/memory/schema/guardian.ts +172 -0
- package/src/memory/schema/index.ts +8 -0
- package/src/memory/schema/infrastructure.ts +205 -0
- package/src/memory/schema/memory-core.ts +196 -0
- package/src/memory/schema/notifications.ts +191 -0
- package/src/memory/schema/tasks.ts +78 -0
- package/src/memory/schema.ts +1 -1385
- package/src/memory/slack-thread-store.ts +0 -69
- package/src/notifications/decisions-store.ts +2 -105
- package/src/notifications/deliveries-store.ts +0 -11
- package/src/notifications/preferences-store.ts +1 -58
- package/src/permissions/checker.ts +6 -17
- package/src/providers/anthropic/client.ts +6 -2
- package/src/providers/gemini/client.ts +13 -2
- package/src/providers/managed-proxy/constants.ts +55 -0
- package/src/providers/managed-proxy/context.ts +77 -0
- package/src/providers/registry.ts +112 -0
- package/src/runtime/auth/__tests__/guard-tests.test.ts +51 -23
- package/src/runtime/http-server.ts +83 -722
- package/src/runtime/http-types.ts +0 -16
- package/src/runtime/middleware/auth.ts +0 -12
- package/src/runtime/routes/app-routes.ts +33 -0
- package/src/runtime/routes/approval-routes.ts +32 -0
- package/src/runtime/routes/attachment-routes.ts +32 -0
- package/src/runtime/routes/brain-graph-routes.ts +27 -0
- package/src/runtime/routes/call-routes.ts +41 -0
- package/src/runtime/routes/channel-readiness-routes.ts +20 -0
- package/src/runtime/routes/channel-routes.ts +70 -0
- package/src/runtime/routes/contact-routes.ts +63 -0
- package/src/runtime/routes/conversation-attention-routes.ts +15 -0
- package/src/runtime/routes/conversation-routes.ts +190 -193
- package/src/runtime/routes/debug-routes.ts +15 -0
- package/src/runtime/routes/events-routes.ts +16 -0
- package/src/runtime/routes/global-search-routes.ts +15 -0
- package/src/runtime/routes/guardian-action-routes.ts +22 -0
- package/src/runtime/routes/guardian-bootstrap-routes.ts +20 -0
- package/src/runtime/routes/guardian-refresh-routes.ts +20 -0
- package/src/runtime/routes/identity-routes.ts +20 -0
- package/src/runtime/routes/inbound-message-handler.ts +8 -0
- package/src/runtime/routes/inbound-stages/acl-enforcement.ts +6 -6
- package/src/runtime/routes/integration-routes.ts +83 -0
- package/src/runtime/routes/invite-routes.ts +31 -0
- package/src/runtime/routes/migration-routes.ts +30 -0
- package/src/runtime/routes/pairing-routes.ts +18 -0
- package/src/runtime/routes/secret-routes.ts +20 -0
- package/src/runtime/routes/surface-action-routes.ts +26 -0
- package/src/runtime/routes/trust-rules-routes.ts +31 -0
- package/src/runtime/routes/twilio-routes.ts +79 -0
- package/src/schedule/recurrence-types.ts +1 -11
- package/src/tools/followups/followup_create.ts +9 -3
- package/src/tools/mcp/mcp-tool-factory.ts +0 -17
- package/src/tools/memory/definitions.ts +0 -6
- package/src/tools/network/script-proxy/session-manager.ts +38 -3
- package/src/tools/schedule/create.ts +1 -3
- package/src/tools/schedule/update.ts +9 -6
- package/src/twitter/session.ts +29 -77
- package/src/util/cookie-session.ts +114 -0
- package/src/__tests__/conversation-routes.test.ts +0 -99
- package/src/__tests__/task-tools.test.ts +0 -685
- package/src/contacts/startup-migration.ts +0 -21
|
@@ -141,11 +141,6 @@ export interface UsageRequest {
|
|
|
141
141
|
sessionId: string;
|
|
142
142
|
}
|
|
143
143
|
|
|
144
|
-
export interface SandboxSetRequest {
|
|
145
|
-
type: "sandbox_set";
|
|
146
|
-
enabled: boolean;
|
|
147
|
-
}
|
|
148
|
-
|
|
149
144
|
export interface SessionsClearRequest {
|
|
150
145
|
type: "sessions_clear";
|
|
151
146
|
}
|
|
@@ -421,7 +416,6 @@ export type _SessionsClientMessages =
|
|
|
421
416
|
| UndoRequest
|
|
422
417
|
| RegenerateRequest
|
|
423
418
|
| UsageRequest
|
|
424
|
-
| SandboxSetRequest
|
|
425
419
|
| SessionListRequest
|
|
426
420
|
| SessionCreateRequest
|
|
427
421
|
| SessionSwitchRequest
|
package/src/daemon/lifecycle.ts
CHANGED
|
@@ -20,7 +20,6 @@ import {
|
|
|
20
20
|
import { loadConfig } from "../config/loader.js";
|
|
21
21
|
import { ensurePromptFiles } from "../config/system-prompt.js";
|
|
22
22
|
import { syncUpdateBulletinOnStartup } from "../config/update-bulletin.js";
|
|
23
|
-
import { migrateContactsFromLegacyTables } from "../contacts/startup-migration.js";
|
|
24
23
|
import { HeartbeatService } from "../heartbeat/heartbeat-service.js";
|
|
25
24
|
import { getHookManager } from "../hooks/manager.js";
|
|
26
25
|
import { installTemplates } from "../hooks/templates.js";
|
|
@@ -204,18 +203,6 @@ export async function runDaemon(): Promise<void> {
|
|
|
204
203
|
);
|
|
205
204
|
}
|
|
206
205
|
|
|
207
|
-
// Catch-up migration: populate contacts table from legacy guardian
|
|
208
|
-
// bindings and contact rows. Ensures upgrades from pre-contacts
|
|
209
|
-
// versions have a populated contacts table on first boot.
|
|
210
|
-
try {
|
|
211
|
-
migrateContactsFromLegacyTables("self");
|
|
212
|
-
} catch (err) {
|
|
213
|
-
log.warn(
|
|
214
|
-
{ err },
|
|
215
|
-
"Contacts startup migration failed — continuing startup",
|
|
216
|
-
);
|
|
217
|
-
}
|
|
218
|
-
|
|
219
206
|
try {
|
|
220
207
|
syncUpdateBulletinOnStartup();
|
|
221
208
|
} catch (err) {
|
|
@@ -437,22 +424,6 @@ export async function runDaemon(): Promise<void> {
|
|
|
437
424
|
sourceChannel,
|
|
438
425
|
sourceInterface,
|
|
439
426
|
),
|
|
440
|
-
persistAndProcessMessage: (
|
|
441
|
-
conversationId,
|
|
442
|
-
content,
|
|
443
|
-
attachmentIds,
|
|
444
|
-
options,
|
|
445
|
-
sourceChannel,
|
|
446
|
-
sourceInterface,
|
|
447
|
-
) =>
|
|
448
|
-
server.persistAndProcessMessage(
|
|
449
|
-
conversationId,
|
|
450
|
-
content,
|
|
451
|
-
attachmentIds,
|
|
452
|
-
options,
|
|
453
|
-
sourceChannel,
|
|
454
|
-
sourceInterface,
|
|
455
|
-
),
|
|
456
427
|
interfacesDir: getInterfacesDir(),
|
|
457
428
|
approvalCopyGenerator: createApprovalCopyGenerator(),
|
|
458
429
|
approvalConversationGenerator: createApprovalConversationGenerator(),
|
|
@@ -439,7 +439,7 @@ export function listConversationAttention(
|
|
|
439
439
|
})
|
|
440
440
|
.from(conversationAssistantAttentionState);
|
|
441
441
|
|
|
442
|
-
//
|
|
442
|
+
// Only join conversations table when filtering by source or sourceChannel
|
|
443
443
|
if (source || sourceChannel) {
|
|
444
444
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
445
445
|
query = (query as any).innerJoin(
|
|
@@ -54,27 +54,6 @@ export { ensureDisplayOrderMigration as ensureColumns } from "./conversation-dis
|
|
|
54
54
|
// CRUD functions for display_order and is_pinned
|
|
55
55
|
// ---------------------------------------------------------------------------
|
|
56
56
|
|
|
57
|
-
export function getDisplayOrder(conversationId: string): number | null {
|
|
58
|
-
ensureDisplayOrderMigration();
|
|
59
|
-
const row = rawGet<{ display_order: number | null }>(
|
|
60
|
-
"SELECT display_order FROM conversations WHERE id = ?",
|
|
61
|
-
conversationId,
|
|
62
|
-
);
|
|
63
|
-
return row?.display_order ?? null;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
export function setDisplayOrder(
|
|
67
|
-
conversationId: string,
|
|
68
|
-
order: number | null,
|
|
69
|
-
): void {
|
|
70
|
-
ensureDisplayOrderMigration();
|
|
71
|
-
rawRun(
|
|
72
|
-
"UPDATE conversations SET display_order = ? WHERE id = ?",
|
|
73
|
-
order,
|
|
74
|
-
conversationId,
|
|
75
|
-
);
|
|
76
|
-
}
|
|
77
|
-
|
|
78
57
|
export function batchSetDisplayOrders(
|
|
79
58
|
updates: Array<{
|
|
80
59
|
id: string;
|
|
@@ -100,36 +79,6 @@ export function batchSetDisplayOrders(
|
|
|
100
79
|
}
|
|
101
80
|
}
|
|
102
81
|
|
|
103
|
-
export function setConversationPinned(
|
|
104
|
-
conversationId: string,
|
|
105
|
-
isPinned: boolean,
|
|
106
|
-
): void {
|
|
107
|
-
ensureDisplayOrderMigration();
|
|
108
|
-
rawRun(
|
|
109
|
-
"UPDATE conversations SET is_pinned = ? WHERE id = ?",
|
|
110
|
-
isPinned ? 1 : 0,
|
|
111
|
-
conversationId,
|
|
112
|
-
);
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
export function getConversationDisplayMeta(conversationId: string): {
|
|
116
|
-
displayOrder: number | null;
|
|
117
|
-
isPinned: boolean;
|
|
118
|
-
} {
|
|
119
|
-
ensureDisplayOrderMigration();
|
|
120
|
-
const row = rawGet<{
|
|
121
|
-
display_order: number | null;
|
|
122
|
-
is_pinned: number | null;
|
|
123
|
-
}>(
|
|
124
|
-
"SELECT display_order, is_pinned FROM conversations WHERE id = ?",
|
|
125
|
-
conversationId,
|
|
126
|
-
);
|
|
127
|
-
return {
|
|
128
|
-
displayOrder: row?.display_order ?? null,
|
|
129
|
-
isPinned: (row?.is_pinned ?? 0) === 1,
|
|
130
|
-
};
|
|
131
|
-
}
|
|
132
|
-
|
|
133
82
|
export function getDisplayMetaForConversations(
|
|
134
83
|
conversationIds: string[],
|
|
135
84
|
): Map<string, { displayOrder: number | null; isPinned: boolean }> {
|
package/src/memory/db-init.ts
CHANGED
|
@@ -42,6 +42,7 @@ import {
|
|
|
42
42
|
migrateContactChannelsAccessFields,
|
|
43
43
|
migrateContactChannelsTypeChatIdIndex,
|
|
44
44
|
migrateContactsAssistantId,
|
|
45
|
+
migrateContactsNotesColumn,
|
|
45
46
|
migrateContactsRolePrincipal,
|
|
46
47
|
migrateConversationsThreadTypeIndex,
|
|
47
48
|
migrateDropLegacyMemberGuardianTables,
|
|
@@ -278,7 +279,10 @@ export function initializeDb(): void {
|
|
|
278
279
|
// 37. Add contact_type to contacts and assistant_contact_metadata table
|
|
279
280
|
migrateAssistantContactMetadata(database);
|
|
280
281
|
|
|
281
|
-
// 38.
|
|
282
|
+
// 38. Consolidate contact metadata columns into single notes field
|
|
283
|
+
migrateContactsNotesColumn(database);
|
|
284
|
+
|
|
285
|
+
// 39. Backfill contact interaction stats from channel lastSeenAt
|
|
282
286
|
migrateBackfillContactInteractionStats(database);
|
|
283
287
|
|
|
284
288
|
validateMigrationState(database);
|
|
@@ -4,12 +4,14 @@ import type { AssistantConfig } from "../../config/types.js";
|
|
|
4
4
|
import { getLogger } from "../../util/logger.js";
|
|
5
5
|
import { resolveConflictClarification } from "../clarification-resolver.js";
|
|
6
6
|
import {
|
|
7
|
+
areStatementsCoherent,
|
|
7
8
|
computeConflictRelevance,
|
|
8
9
|
looksLikeClarificationReply,
|
|
9
10
|
shouldAttemptConflictResolution,
|
|
10
11
|
} from "../conflict-intent.js";
|
|
11
12
|
import {
|
|
12
13
|
isConflictKindPairEligible,
|
|
14
|
+
isConflictUserEvidenced,
|
|
13
15
|
isStatementConflictEligible,
|
|
14
16
|
} from "../conflict-policy.js";
|
|
15
17
|
import {
|
|
@@ -82,6 +84,28 @@ export async function resolvePendingConflictsForMessageJob(
|
|
|
82
84
|
status: "dismissed",
|
|
83
85
|
resolutionNote: "Dismissed by conflict policy (transient/non-durable).",
|
|
84
86
|
});
|
|
87
|
+
} else if (
|
|
88
|
+
!isConflictUserEvidenced(
|
|
89
|
+
conflict.existingVerificationState,
|
|
90
|
+
conflict.candidateVerificationState,
|
|
91
|
+
)
|
|
92
|
+
) {
|
|
93
|
+
resolveConflict(conflict.id, {
|
|
94
|
+
status: "dismissed",
|
|
95
|
+
resolutionNote:
|
|
96
|
+
"Dismissed by conflict policy (no user-evidenced provenance).",
|
|
97
|
+
});
|
|
98
|
+
} else if (
|
|
99
|
+
!areStatementsCoherent(
|
|
100
|
+
conflict.existingStatement,
|
|
101
|
+
conflict.candidateStatement,
|
|
102
|
+
)
|
|
103
|
+
) {
|
|
104
|
+
resolveConflict(conflict.id, {
|
|
105
|
+
status: "dismissed",
|
|
106
|
+
resolutionNote:
|
|
107
|
+
"Dismissed by conflict policy (incoherent — zero statement overlap).",
|
|
108
|
+
});
|
|
85
109
|
}
|
|
86
110
|
}
|
|
87
111
|
|
|
@@ -4,14 +4,14 @@ import type { DrizzleDb } from "../db-connection.js";
|
|
|
4
4
|
* Contacts, contact channels, and triage results tables with indexes.
|
|
5
5
|
*/
|
|
6
6
|
export function createContactsAndTriageTables(database: DrizzleDb): void {
|
|
7
|
+
// Columns removed: relationship, importance, response_expectation, preferred_tone
|
|
8
|
+
// — dropped by migration 134 (contacts-notes-column). Omitting them here keeps
|
|
9
|
+
// the CREATE TABLE idempotent when initializeDb() runs a second time (e.g. the
|
|
10
|
+
// "daemon restart" tests) after migration 134 has already dropped them.
|
|
7
11
|
database.run(/*sql*/ `
|
|
8
12
|
CREATE TABLE IF NOT EXISTS contacts (
|
|
9
13
|
id TEXT PRIMARY KEY,
|
|
10
14
|
display_name TEXT NOT NULL,
|
|
11
|
-
relationship TEXT,
|
|
12
|
-
importance REAL NOT NULL DEFAULT 0.5,
|
|
13
|
-
response_expectation TEXT,
|
|
14
|
-
preferred_tone TEXT,
|
|
15
15
|
last_interaction INTEGER,
|
|
16
16
|
interaction_count INTEGER NOT NULL DEFAULT 0,
|
|
17
17
|
created_at INTEGER NOT NULL,
|
|
@@ -33,9 +33,6 @@ export function createContactsAndTriageTables(database: DrizzleDb): void {
|
|
|
33
33
|
database.run(
|
|
34
34
|
/*sql*/ `CREATE INDEX IF NOT EXISTS idx_contacts_display_name ON contacts(display_name)`,
|
|
35
35
|
);
|
|
36
|
-
database.run(
|
|
37
|
-
/*sql*/ `CREATE INDEX IF NOT EXISTS idx_contacts_importance ON contacts(importance DESC)`,
|
|
38
|
-
);
|
|
39
36
|
database.run(
|
|
40
37
|
/*sql*/ `CREATE INDEX IF NOT EXISTS idx_contacts_last_interaction ON contacts(last_interaction DESC)`,
|
|
41
38
|
);
|
|
@@ -6,46 +6,63 @@ const log = getLogger("migration-134");
|
|
|
6
6
|
export function migrateContactsNotesColumn(database: DrizzleDb): void {
|
|
7
7
|
const raw = getSqliteFrom(database);
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
try {
|
|
10
|
+
raw.exec(/*sql*/ `ALTER TABLE contacts ADD COLUMN notes TEXT`);
|
|
11
|
+
} catch {
|
|
12
|
+
/* already exists */
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// Check if legacy columns still exist before attempting backfill + drop
|
|
16
|
+
const cols = new Set(
|
|
17
|
+
(
|
|
18
|
+
raw.query(`PRAGMA table_info(contacts)`).all() as Array<{ name: string }>
|
|
19
|
+
).map((c) => c.name),
|
|
20
|
+
);
|
|
10
21
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
22
|
+
if (cols.has("relationship")) {
|
|
23
|
+
const rows = raw
|
|
24
|
+
.query(
|
|
25
|
+
`SELECT id, relationship, importance, response_expectation, preferred_tone
|
|
14
26
|
FROM contacts
|
|
15
27
|
WHERE relationship IS NOT NULL
|
|
16
28
|
OR importance != 0.5
|
|
17
29
|
OR response_expectation IS NOT NULL
|
|
18
30
|
OR preferred_tone IS NOT NULL`,
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
31
|
+
)
|
|
32
|
+
.all() as Array<{
|
|
33
|
+
id: string;
|
|
34
|
+
relationship: string | null;
|
|
35
|
+
importance: number;
|
|
36
|
+
response_expectation: string | null;
|
|
37
|
+
preferred_tone: string | null;
|
|
38
|
+
}>;
|
|
39
|
+
|
|
40
|
+
const update = raw.prepare(`UPDATE contacts SET notes = ? WHERE id = ?`);
|
|
41
|
+
|
|
42
|
+
for (const row of rows) {
|
|
43
|
+
const parts: string[] = [];
|
|
44
|
+
if (row.relationship) parts.push(`Relationship: ${row.relationship}`);
|
|
45
|
+
if (row.importance !== 0.5) parts.push(`Importance: ${row.importance}`);
|
|
46
|
+
if (row.response_expectation)
|
|
47
|
+
parts.push(`Response expectation: ${row.response_expectation}`);
|
|
48
|
+
if (row.preferred_tone)
|
|
49
|
+
parts.push(`Preferred tone: ${row.preferred_tone}`);
|
|
50
|
+
if (parts.length > 0) {
|
|
51
|
+
update.run(parts.join("\n"), row.id);
|
|
52
|
+
}
|
|
39
53
|
}
|
|
40
|
-
}
|
|
41
54
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
55
|
+
const migrated = rows.length;
|
|
56
|
+
if (migrated > 0) {
|
|
57
|
+
log.info({ migrated }, "Migrated contact metadata to notes field");
|
|
58
|
+
}
|
|
46
59
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
60
|
+
// Drop indexes that reference columns we're about to remove
|
|
61
|
+
raw.exec(/*sql*/ `DROP INDEX IF EXISTS idx_contacts_importance`);
|
|
62
|
+
|
|
63
|
+
raw.exec(/*sql*/ `ALTER TABLE contacts DROP COLUMN relationship`);
|
|
64
|
+
raw.exec(/*sql*/ `ALTER TABLE contacts DROP COLUMN importance`);
|
|
65
|
+
raw.exec(/*sql*/ `ALTER TABLE contacts DROP COLUMN response_expectation`);
|
|
66
|
+
raw.exec(/*sql*/ `ALTER TABLE contacts DROP COLUMN preferred_tone`);
|
|
67
|
+
}
|
|
51
68
|
}
|
|
@@ -122,6 +122,12 @@ export const MIGRATION_REGISTRY: MigrationRegistryEntry[] = [
|
|
|
122
122
|
description:
|
|
123
123
|
"Enforce NOT NULL on channel_guardian_bindings.guardian_principal_id after backfill",
|
|
124
124
|
},
|
|
125
|
+
{
|
|
126
|
+
key: "backfill_contact_interaction_stats",
|
|
127
|
+
version: 17,
|
|
128
|
+
description:
|
|
129
|
+
"Backfill contacts.last_interaction from the max lastSeenAt across each contact's channels",
|
|
130
|
+
},
|
|
125
131
|
];
|
|
126
132
|
|
|
127
133
|
export interface MigrationValidationResult {
|
|
@@ -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
|
+
});
|