@vellumai/assistant 0.3.16 → 0.3.18
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 +70 -13
- package/README.md +6 -0
- package/docs/architecture/http-token-refresh.md +23 -1
- package/package.json +1 -1
- package/src/__tests__/access-request-decision.test.ts +4 -7
- package/src/__tests__/channel-guardian.test.ts +3 -1
- package/src/__tests__/checker.test.ts +79 -48
- package/src/__tests__/config-watcher.test.ts +11 -13
- package/src/__tests__/conversation-pairing.test.ts +103 -3
- package/src/__tests__/guardian-action-conversation-turn.test.ts +1 -1
- package/src/__tests__/guardian-action-followup-executor.test.ts +1 -1
- package/src/__tests__/guardian-action-late-reply.test.ts +131 -0
- package/src/__tests__/guardian-action-store.test.ts +182 -0
- package/src/__tests__/guardian-dispatch.test.ts +120 -0
- package/src/__tests__/ipc-snapshot.test.ts +21 -0
- package/src/__tests__/non-member-access-request.test.ts +1 -2
- package/src/__tests__/notification-broadcaster.test.ts +115 -4
- package/src/__tests__/notification-decision-strategy.test.ts +2 -1
- package/src/__tests__/notification-deep-link.test.ts +44 -1
- package/src/__tests__/notification-guardian-path.test.ts +157 -0
- package/src/__tests__/notification-thread-candidate-validation.test.ts +215 -0
- package/src/__tests__/slack-channel-config.test.ts +3 -3
- package/src/__tests__/trust-store.test.ts +21 -21
- package/src/__tests__/trusted-contact-lifecycle-notifications.test.ts +5 -7
- package/src/__tests__/trusted-contact-multichannel.test.ts +2 -6
- package/src/__tests__/trusted-contact-verification.test.ts +9 -9
- package/src/__tests__/update-bulletin-state.test.ts +1 -1
- package/src/__tests__/update-bulletin.test.ts +66 -3
- package/src/__tests__/update-template-contract.test.ts +6 -11
- package/src/__tests__/voice-session-bridge.test.ts +109 -9
- package/src/calls/call-controller.ts +129 -8
- package/src/calls/guardian-action-sweep.ts +1 -1
- package/src/calls/guardian-dispatch.ts +8 -0
- package/src/calls/voice-session-bridge.ts +4 -2
- package/src/cli/core-commands.ts +41 -1
- package/src/config/templates/UPDATES.md +5 -6
- package/src/config/update-bulletin-format.ts +2 -0
- package/src/config/update-bulletin-state.ts +1 -1
- package/src/config/update-bulletin-template-path.ts +6 -0
- package/src/config/update-bulletin.ts +21 -6
- package/src/daemon/config-watcher.ts +3 -2
- package/src/daemon/daemon-control.ts +64 -10
- package/src/daemon/handlers/config-slack-channel.ts +1 -1
- package/src/daemon/handlers/identity.ts +45 -25
- package/src/daemon/handlers/sessions.ts +1 -1
- package/src/daemon/ipc-contract/sessions.ts +1 -1
- package/src/daemon/ipc-contract/workspace.ts +12 -1
- package/src/daemon/ipc-contract-inventory.json +1 -0
- package/src/daemon/lifecycle.ts +8 -0
- package/src/daemon/server.ts +25 -3
- package/src/daemon/session-process.ts +438 -184
- package/src/daemon/tls-certs.ts +17 -12
- package/src/daemon/tool-side-effects.ts +1 -1
- package/src/memory/channel-delivery-store.ts +18 -20
- package/src/memory/channel-guardian-store.ts +39 -42
- package/src/memory/conversation-crud.ts +2 -2
- package/src/memory/conversation-queries.ts +2 -2
- package/src/memory/conversation-store.ts +24 -25
- package/src/memory/db-init.ts +9 -1
- package/src/memory/fts-reconciler.ts +41 -26
- package/src/memory/guardian-action-store.ts +57 -7
- package/src/memory/guardian-verification.ts +1 -0
- package/src/memory/jobs-worker.ts +2 -2
- package/src/memory/migrations/032-guardian-delivery-conversation-index.ts +15 -0
- package/src/memory/migrations/032-notification-delivery-thread-decision.ts +20 -0
- package/src/memory/migrations/index.ts +4 -2
- package/src/memory/schema-migration.ts +1 -0
- package/src/memory/schema.ts +6 -1
- package/src/memory/search/semantic.ts +3 -3
- package/src/notifications/README.md +158 -17
- package/src/notifications/broadcaster.ts +68 -50
- package/src/notifications/conversation-pairing.ts +96 -18
- package/src/notifications/decision-engine.ts +6 -3
- package/src/notifications/deliveries-store.ts +12 -0
- package/src/notifications/emit-signal.ts +1 -0
- package/src/notifications/thread-candidates.ts +60 -25
- package/src/notifications/types.ts +2 -1
- package/src/permissions/checker.ts +1 -16
- package/src/permissions/defaults.ts +14 -4
- package/src/runtime/guardian-action-followup-executor.ts +1 -1
- package/src/runtime/http-server.ts +11 -11
- package/src/runtime/routes/access-request-decision.ts +1 -1
- package/src/runtime/routes/debug-routes.ts +4 -4
- package/src/runtime/routes/guardian-approval-interception.ts +4 -4
- package/src/runtime/routes/inbound-message-handler.ts +6 -6
- package/src/runtime/routes/integration-routes.ts +2 -2
- package/src/tools/permission-checker.ts +1 -2
- package/src/tools/secret-detection-handler.ts +1 -1
- package/src/tools/system/voice-config.ts +1 -1
- package/src/version.ts +29 -2
package/src/daemon/tls-certs.ts
CHANGED
|
@@ -152,20 +152,25 @@ export async function ensureTlsCert(): Promise<{ cert: string; key: string; fing
|
|
|
152
152
|
}
|
|
153
153
|
|
|
154
154
|
if (status === 'approaching_expiry') {
|
|
155
|
-
// Buffer existing cert/key/fingerprint before attempting renewal.
|
|
156
|
-
// generateNewCert() overwrites key.pem in-place, so if it fails mid-flight
|
|
157
|
-
// (e.g., key written but cert generation fails), reading from disk in the
|
|
158
|
-
// catch block would return a mismatched key/cert pair.
|
|
159
|
-
const [existingCert, existingKey, existingFp] = await Promise.all([
|
|
160
|
-
readFile(certPath, 'utf-8'),
|
|
161
|
-
readFile(keyPath, 'utf-8'),
|
|
162
|
-
readFile(fpPath, 'utf-8'),
|
|
163
|
-
]);
|
|
164
155
|
try {
|
|
165
|
-
|
|
156
|
+
// Buffer existing cert/key/fingerprint before attempting renewal.
|
|
157
|
+
// generateNewCert() overwrites key.pem in-place, so if it fails mid-flight
|
|
158
|
+
// (e.g., key written but cert generation fails), reading from disk in the
|
|
159
|
+
// catch block would return a mismatched key/cert pair.
|
|
160
|
+
const [existingCert, existingKey, existingFp] = await Promise.all([
|
|
161
|
+
readFile(certPath, 'utf-8'),
|
|
162
|
+
readFile(keyPath, 'utf-8'),
|
|
163
|
+
readFile(fpPath, 'utf-8'),
|
|
164
|
+
]);
|
|
165
|
+
try {
|
|
166
|
+
return await generateNewCert(tlsDir, certPath, keyPath, fpPath);
|
|
167
|
+
} catch (err) {
|
|
168
|
+
log.warn({ err }, 'Proactive TLS renewal failed, continuing with existing certificate');
|
|
169
|
+
return { cert: existingCert, key: existingKey, fingerprint: existingFp.trim() };
|
|
170
|
+
}
|
|
166
171
|
} catch (err) {
|
|
167
|
-
log.warn({ err }, '
|
|
168
|
-
return
|
|
172
|
+
log.warn({ err }, 'Failed to read existing TLS cert for buffering, attempting regeneration');
|
|
173
|
+
return await generateNewCert(tlsDir, certPath, keyPath, fpPath);
|
|
169
174
|
}
|
|
170
175
|
}
|
|
171
176
|
|
|
@@ -7,11 +7,11 @@
|
|
|
7
7
|
* registry entry instead of another if/else branch.
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
import { normalizeActivationKey } from './handlers/config-voice.js';
|
|
11
10
|
import { updatePublishedAppDeployment } from '../services/published-app-updater.js';
|
|
12
11
|
import { openAppViaSurface } from '../tools/apps/open-proxy.js';
|
|
13
12
|
import type { ToolExecutionResult } from '../tools/types.js';
|
|
14
13
|
import { isDoordashCommand, updateDoordashProgress } from './doordash-steps.js';
|
|
14
|
+
import { normalizeActivationKey } from './handlers/config-voice.js';
|
|
15
15
|
import type { ServerMessage } from './ipc-protocol.js';
|
|
16
16
|
import {
|
|
17
17
|
refreshSurfacesForApp,
|
|
@@ -8,33 +8,31 @@
|
|
|
8
8
|
* - delivery-channels.ts — verification replies, segment progress, delivery guards
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
+
export type { PendingVerificationReply } from './delivery-channels.js';
|
|
12
|
+
export {
|
|
13
|
+
claimRunDelivery,
|
|
14
|
+
clearPendingVerificationReply,
|
|
15
|
+
getDeliveredSegmentCount,
|
|
16
|
+
getPendingVerificationReply,
|
|
17
|
+
resetAllRunDeliveryClaims,
|
|
18
|
+
resetRunDeliveryClaim,
|
|
19
|
+
storePendingVerificationReply,
|
|
20
|
+
updateDeliveredSegmentCount,
|
|
21
|
+
} from './delivery-channels.js';
|
|
22
|
+
export type { InboundResult, RecordInboundOptions } from './delivery-crud.js';
|
|
11
23
|
export {
|
|
12
|
-
recordInbound,
|
|
13
|
-
linkMessage,
|
|
14
|
-
findMessageBySourceId,
|
|
15
|
-
storePayload,
|
|
16
24
|
clearPayload,
|
|
25
|
+
findMessageBySourceId,
|
|
17
26
|
getLatestStoredPayload,
|
|
27
|
+
linkMessage,
|
|
28
|
+
recordInbound,
|
|
29
|
+
storePayload,
|
|
18
30
|
} from './delivery-crud.js';
|
|
19
|
-
export type { InboundResult, RecordInboundOptions } from './delivery-crud.js';
|
|
20
|
-
|
|
21
31
|
export {
|
|
22
32
|
acknowledgeDelivery,
|
|
33
|
+
getDeadLetterEvents,
|
|
34
|
+
getRetryableEvents,
|
|
23
35
|
markProcessed,
|
|
24
36
|
recordProcessingFailure,
|
|
25
|
-
getRetryableEvents,
|
|
26
|
-
getDeadLetterEvents,
|
|
27
37
|
replayDeadLetters,
|
|
28
38
|
} from './delivery-status.js';
|
|
29
|
-
|
|
30
|
-
export {
|
|
31
|
-
storePendingVerificationReply,
|
|
32
|
-
getPendingVerificationReply,
|
|
33
|
-
clearPendingVerificationReply,
|
|
34
|
-
getDeliveredSegmentCount,
|
|
35
|
-
updateDeliveredSegmentCount,
|
|
36
|
-
claimRunDelivery,
|
|
37
|
-
resetRunDeliveryClaim,
|
|
38
|
-
resetAllRunDeliveryClaims,
|
|
39
|
-
} from './delivery-channels.js';
|
|
40
|
-
export type { PendingVerificationReply } from './delivery-channels.js';
|
|
@@ -10,60 +10,57 @@
|
|
|
10
10
|
* This file re-exports everything for backward compatibility.
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
|
+
export {
|
|
14
|
+
type ApprovalRequestStatus,
|
|
15
|
+
countPendingByConversation,
|
|
16
|
+
createApprovalRequest,
|
|
17
|
+
findPendingAccessRequestForRequester,
|
|
18
|
+
getAllPendingApprovalsByGuardianChat,
|
|
19
|
+
getApprovalRequestById,
|
|
20
|
+
getApprovalRequestByRunId,
|
|
21
|
+
getExpiredPendingApprovals,
|
|
22
|
+
getPendingApprovalByGuardianChat,
|
|
23
|
+
getPendingApprovalByRequestAndGuardianChat,
|
|
24
|
+
getPendingApprovalByRunAndGuardianChat,
|
|
25
|
+
getPendingApprovalForRequest,
|
|
26
|
+
getPendingApprovalForRun,
|
|
27
|
+
getUnresolvedApprovalForRequest,
|
|
28
|
+
getUnresolvedApprovalForRun,
|
|
29
|
+
type GuardianApprovalRequest,
|
|
30
|
+
listPendingApprovalRequests,
|
|
31
|
+
resolveApprovalRequest,
|
|
32
|
+
updateApprovalDecision,
|
|
33
|
+
} from './guardian-approvals.js';
|
|
13
34
|
export {
|
|
14
35
|
type BindingStatus,
|
|
15
|
-
type GuardianBinding,
|
|
16
36
|
createBinding,
|
|
17
37
|
getActiveBinding,
|
|
38
|
+
type GuardianBinding,
|
|
18
39
|
revokeBinding,
|
|
19
40
|
} from './guardian-bindings.js';
|
|
20
|
-
|
|
21
41
|
export {
|
|
42
|
+
getRateLimit,
|
|
43
|
+
recordInvalidAttempt,
|
|
44
|
+
resetRateLimit,
|
|
45
|
+
type VerificationRateLimit,
|
|
46
|
+
} from './guardian-rate-limits.js';
|
|
47
|
+
export {
|
|
48
|
+
bindSessionIdentity,
|
|
22
49
|
type ChallengeStatus,
|
|
23
|
-
type SessionStatus,
|
|
24
|
-
type IdentityBindingStatus,
|
|
25
|
-
type VerificationPurpose,
|
|
26
|
-
type VerificationChallenge,
|
|
27
|
-
createChallenge,
|
|
28
|
-
revokePendingChallenges,
|
|
29
|
-
findPendingChallengeByHash,
|
|
30
|
-
findPendingChallengeForChannel,
|
|
31
50
|
consumeChallenge,
|
|
51
|
+
countRecentSendsToDestination,
|
|
52
|
+
createChallenge,
|
|
32
53
|
createVerificationSession,
|
|
33
54
|
findActiveSession,
|
|
55
|
+
findPendingChallengeByHash,
|
|
56
|
+
findPendingChallengeForChannel,
|
|
34
57
|
findSessionByBootstrapTokenHash,
|
|
35
58
|
findSessionByIdentity,
|
|
36
|
-
|
|
59
|
+
type IdentityBindingStatus,
|
|
60
|
+
revokePendingChallenges,
|
|
61
|
+
type SessionStatus,
|
|
37
62
|
updateSessionDelivery,
|
|
38
|
-
|
|
39
|
-
|
|
63
|
+
updateSessionStatus,
|
|
64
|
+
type VerificationChallenge,
|
|
65
|
+
type VerificationPurpose,
|
|
40
66
|
} from './guardian-verification.js';
|
|
41
|
-
|
|
42
|
-
export {
|
|
43
|
-
type ApprovalRequestStatus,
|
|
44
|
-
type GuardianApprovalRequest,
|
|
45
|
-
createApprovalRequest,
|
|
46
|
-
getPendingApprovalForRun,
|
|
47
|
-
getPendingApprovalForRequest,
|
|
48
|
-
getUnresolvedApprovalForRun,
|
|
49
|
-
getUnresolvedApprovalForRequest,
|
|
50
|
-
getPendingApprovalByGuardianChat,
|
|
51
|
-
getPendingApprovalByRunAndGuardianChat,
|
|
52
|
-
getPendingApprovalByRequestAndGuardianChat,
|
|
53
|
-
getAllPendingApprovalsByGuardianChat,
|
|
54
|
-
getExpiredPendingApprovals,
|
|
55
|
-
updateApprovalDecision,
|
|
56
|
-
listPendingApprovalRequests,
|
|
57
|
-
getApprovalRequestById,
|
|
58
|
-
getApprovalRequestByRunId,
|
|
59
|
-
resolveApprovalRequest,
|
|
60
|
-
countPendingByConversation,
|
|
61
|
-
findPendingAccessRequestForRequester,
|
|
62
|
-
} from './guardian-approvals.js';
|
|
63
|
-
|
|
64
|
-
export {
|
|
65
|
-
type VerificationRateLimit,
|
|
66
|
-
getRateLimit,
|
|
67
|
-
recordInvalidAttempt,
|
|
68
|
-
resetRateLimit,
|
|
69
|
-
} from './guardian-rate-limits.js';
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { and, count, eq, inArray, isNull, sql
|
|
1
|
+
import { and, asc,count, eq, inArray, isNull, sql } from 'drizzle-orm';
|
|
2
2
|
import { v4 as uuid } from 'uuid';
|
|
3
3
|
import { z } from 'zod';
|
|
4
4
|
|
|
@@ -11,7 +11,7 @@ import { getLogger } from '../util/logger.js';
|
|
|
11
11
|
import { createRowMapper } from '../util/row-mapper.js';
|
|
12
12
|
import { deleteOrphanAttachments } from './attachments-store.js';
|
|
13
13
|
import { projectAssistantMessage } from './conversation-attention-store.js';
|
|
14
|
-
import { getDb,
|
|
14
|
+
import { getDb, rawExec, rawGet } from './db.js';
|
|
15
15
|
import { indexMessageNow } from './indexer.js';
|
|
16
16
|
import { channelInboundEvents, conversations, llmRequestLogs, memoryEmbeddings, memoryItemEntities, memoryItems, memoryItemSources, memorySegments, messageAttachments, messages, toolInvocations } from './schema.js';
|
|
17
17
|
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { and, asc, count, desc, eq, gte, lt, ne, or, sql } from 'drizzle-orm';
|
|
2
2
|
|
|
3
3
|
import { getLogger } from '../util/logger.js';
|
|
4
|
-
import { getDb, rawAll } from './db.js';
|
|
5
|
-
import { parseConversation, parseMessage } from './conversation-crud.js';
|
|
6
4
|
import type { ConversationRow, MessageRow } from './conversation-crud.js';
|
|
5
|
+
import { parseConversation, parseMessage } from './conversation-crud.js';
|
|
6
|
+
import { getDb, rawAll } from './db.js';
|
|
7
7
|
import { conversations, messages } from './schema.js';
|
|
8
8
|
import { buildFtsMatchQuery } from './search/lexical.js';
|
|
9
9
|
|
|
@@ -2,44 +2,43 @@
|
|
|
2
2
|
// Existing imports from this file continue to work without changes.
|
|
3
3
|
|
|
4
4
|
export {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
provenanceFromGuardianContext,
|
|
5
|
+
addMessage,
|
|
6
|
+
clearAll,
|
|
8
7
|
type ConversationRow,
|
|
9
|
-
parseConversation,
|
|
10
|
-
type MessageRow,
|
|
11
|
-
parseMessage,
|
|
12
8
|
createConversation,
|
|
9
|
+
deleteConversation,
|
|
10
|
+
type DeletedMemoryIds,
|
|
11
|
+
deleteLastExchange,
|
|
12
|
+
deleteMessageById,
|
|
13
13
|
getConversation,
|
|
14
|
-
getConversationThreadType,
|
|
15
14
|
getConversationMemoryScopeId,
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
15
|
+
getConversationOriginChannel,
|
|
16
|
+
getConversationOriginInterface,
|
|
17
|
+
getConversationThreadType,
|
|
19
18
|
getMessageById,
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
19
|
+
getMessages,
|
|
20
|
+
type MessageMetadata,
|
|
21
|
+
messageMetadataSchema,
|
|
22
|
+
type MessageRow,
|
|
23
|
+
parseConversation,
|
|
24
|
+
parseMessage,
|
|
25
|
+
provenanceFromGuardianContext,
|
|
27
26
|
relinkAttachments,
|
|
28
|
-
deleteMessageById,
|
|
29
27
|
setConversationOriginChannelIfUnset,
|
|
30
|
-
getConversationOriginChannel,
|
|
31
28
|
setConversationOriginInterfaceIfUnset,
|
|
32
|
-
|
|
29
|
+
updateConversationContextWindow,
|
|
30
|
+
updateConversationTitle,
|
|
31
|
+
updateConversationUsage,
|
|
32
|
+
updateMessageContent,
|
|
33
33
|
} from './conversation-crud.js';
|
|
34
|
-
|
|
35
34
|
export {
|
|
36
|
-
|
|
35
|
+
type ConversationSearchResult,
|
|
37
36
|
countConversations,
|
|
38
37
|
getLatestConversation,
|
|
39
|
-
getNextMessage,
|
|
40
|
-
type PaginatedMessagesResult,
|
|
41
38
|
getMessagesPaginated,
|
|
39
|
+
getNextMessage,
|
|
42
40
|
isLastUserMessageToolResult,
|
|
43
|
-
|
|
41
|
+
listConversations,
|
|
42
|
+
type PaginatedMessagesResult,
|
|
44
43
|
searchConversations,
|
|
45
44
|
} from './conversation-queries.js';
|
package/src/memory/db-init.ts
CHANGED
|
@@ -17,14 +17,16 @@ import {
|
|
|
17
17
|
createTasksAndWorkItemsTables,
|
|
18
18
|
createWatchersAndLogsTables,
|
|
19
19
|
migrateCallSessionMode,
|
|
20
|
-
migrateFkCascadeRebuilds,
|
|
21
20
|
migrateChannelInboundDeliveredSegments,
|
|
22
21
|
migrateConversationsThreadTypeIndex,
|
|
22
|
+
migrateFkCascadeRebuilds,
|
|
23
23
|
migrateGuardianActionFollowup,
|
|
24
24
|
migrateGuardianBootstrapToken,
|
|
25
|
+
migrateGuardianDeliveryConversationIndex,
|
|
25
26
|
migrateGuardianVerificationPurpose,
|
|
26
27
|
migrateGuardianVerificationSessions,
|
|
27
28
|
migrateMessagesFtsBackfill,
|
|
29
|
+
migrateNotificationDeliveryThreadDecision,
|
|
28
30
|
migrateReminderRoutingIntent,
|
|
29
31
|
migrateSchemaIndexesAndColumns,
|
|
30
32
|
recoverCrashedMigrations,
|
|
@@ -103,6 +105,9 @@ export function initializeDb(): void {
|
|
|
103
105
|
// 14d. Index on conversations.thread_type for frequent WHERE filters
|
|
104
106
|
migrateConversationsThreadTypeIndex(database);
|
|
105
107
|
|
|
108
|
+
// 14e. Index on guardian_action_deliveries.destination_conversation_id for conversation-based lookups
|
|
109
|
+
migrateGuardianDeliveryConversationIndex(database);
|
|
110
|
+
|
|
106
111
|
// 15. Notification system
|
|
107
112
|
createNotificationTables(database);
|
|
108
113
|
|
|
@@ -125,5 +130,8 @@ export function initializeDb(): void {
|
|
|
125
130
|
// 21. Rebuild tables to add ON DELETE CASCADE to FK constraints
|
|
126
131
|
migrateFkCascadeRebuilds(database);
|
|
127
132
|
|
|
133
|
+
// 22. Thread decision audit columns on notification_deliveries
|
|
134
|
+
migrateNotificationDeliveryThreadDecision(database);
|
|
135
|
+
|
|
128
136
|
validateMigrationState(database);
|
|
129
137
|
}
|
|
@@ -84,37 +84,52 @@ function reconcileTable(opts: {
|
|
|
84
84
|
*/
|
|
85
85
|
export function reconcileFtsIndexes(): FtsReconciliationResult[] {
|
|
86
86
|
const results: FtsReconciliationResult[] = [];
|
|
87
|
+
const errors: unknown[] = [];
|
|
87
88
|
|
|
88
89
|
// memory_segment_fts tracks memory_segments
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
90
|
+
try {
|
|
91
|
+
const memResult = reconcileTable({
|
|
92
|
+
ftsTable: 'memory_segment_fts',
|
|
93
|
+
ftsIdColumn: 'segment_id',
|
|
94
|
+
ftsContentColumn: 'text',
|
|
95
|
+
baseTable: 'memory_segments',
|
|
96
|
+
baseIdColumn: 'id',
|
|
97
|
+
baseContentColumn: 'text',
|
|
98
|
+
});
|
|
99
|
+
results.push(memResult);
|
|
100
|
+
if (memResult.missingInserted > 0 || memResult.orphansRemoved > 0 || memResult.staleRefreshed > 0) {
|
|
101
|
+
log.info(memResult, 'Reconciled memory_segment_fts');
|
|
102
|
+
} else {
|
|
103
|
+
log.debug(memResult, 'memory_segment_fts is in sync');
|
|
104
|
+
}
|
|
105
|
+
} catch (err) {
|
|
106
|
+
log.error({ err }, 'Failed to reconcile memory_segment_fts');
|
|
107
|
+
errors.push(err);
|
|
102
108
|
}
|
|
103
109
|
|
|
104
110
|
// messages_fts tracks messages
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
111
|
+
try {
|
|
112
|
+
const msgResult = reconcileTable({
|
|
113
|
+
ftsTable: 'messages_fts',
|
|
114
|
+
ftsIdColumn: 'message_id',
|
|
115
|
+
ftsContentColumn: 'content',
|
|
116
|
+
baseTable: 'messages',
|
|
117
|
+
baseIdColumn: 'id',
|
|
118
|
+
baseContentColumn: 'content',
|
|
119
|
+
});
|
|
120
|
+
results.push(msgResult);
|
|
121
|
+
if (msgResult.missingInserted > 0 || msgResult.orphansRemoved > 0 || msgResult.staleRefreshed > 0) {
|
|
122
|
+
log.info(msgResult, 'Reconciled messages_fts');
|
|
123
|
+
} else {
|
|
124
|
+
log.debug(msgResult, 'messages_fts is in sync');
|
|
125
|
+
}
|
|
126
|
+
} catch (err) {
|
|
127
|
+
log.error({ err }, 'Failed to reconcile messages_fts');
|
|
128
|
+
errors.push(err);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
if (errors.length > 0) {
|
|
132
|
+
throw new AggregateError(errors, `FTS reconciliation failed for ${errors.length} table(s)`);
|
|
118
133
|
}
|
|
119
134
|
|
|
120
135
|
return results;
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* answer resolves the request and all other deliveries are marked answered.
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
import { and, desc, eq, inArray, lt } from 'drizzle-orm';
|
|
10
|
+
import { and, count, desc, eq, inArray, lt } from 'drizzle-orm';
|
|
11
11
|
import { v4 as uuid } from 'uuid';
|
|
12
12
|
|
|
13
13
|
import { getLogger } from '../util/logger.js';
|
|
@@ -212,6 +212,26 @@ export function getPendingRequestByCallSessionId(callSessionId: string): Guardia
|
|
|
212
212
|
return row ? rowToRequest(row) : null;
|
|
213
213
|
}
|
|
214
214
|
|
|
215
|
+
/**
|
|
216
|
+
* Count pending guardian action requests for a given call session.
|
|
217
|
+
* Used as a candidate-affinity hint so the decision engine knows how many
|
|
218
|
+
* active guardian requests already exist for the current call.
|
|
219
|
+
*/
|
|
220
|
+
export function countPendingRequestsByCallSessionId(callSessionId: string): number {
|
|
221
|
+
const db = getDb();
|
|
222
|
+
const row = db
|
|
223
|
+
.select({ count: count() })
|
|
224
|
+
.from(guardianActionRequests)
|
|
225
|
+
.where(
|
|
226
|
+
and(
|
|
227
|
+
eq(guardianActionRequests.callSessionId, callSessionId),
|
|
228
|
+
eq(guardianActionRequests.status, 'pending'),
|
|
229
|
+
),
|
|
230
|
+
)
|
|
231
|
+
.get();
|
|
232
|
+
return row?.count ?? 0;
|
|
233
|
+
}
|
|
234
|
+
|
|
215
235
|
/**
|
|
216
236
|
* First-response-wins resolution. Checks that the request is still
|
|
217
237
|
* 'pending' before updating; returns the updated request on success
|
|
@@ -595,6 +615,16 @@ export function getPendingDeliveriesByDestination(
|
|
|
595
615
|
* Look up a pending delivery by destination conversation ID (for mac channel routing).
|
|
596
616
|
*/
|
|
597
617
|
export function getPendingDeliveryByConversation(conversationId: string): GuardianActionDelivery | null {
|
|
618
|
+
const all = getPendingDeliveriesByConversation(conversationId);
|
|
619
|
+
return all.length > 0 ? all[0] : null;
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
/**
|
|
623
|
+
* Look up all pending deliveries by destination conversation ID.
|
|
624
|
+
* Used for disambiguation when a reused vellum thread has multiple active
|
|
625
|
+
* guardian requests.
|
|
626
|
+
*/
|
|
627
|
+
export function getPendingDeliveriesByConversation(conversationId: string): GuardianActionDelivery[] {
|
|
598
628
|
try {
|
|
599
629
|
const db = getDb();
|
|
600
630
|
const rows = db
|
|
@@ -612,11 +642,11 @@ export function getPendingDeliveryByConversation(conversationId: string): Guardi
|
|
|
612
642
|
),
|
|
613
643
|
)
|
|
614
644
|
.all();
|
|
615
|
-
return rows.
|
|
645
|
+
return rows.map((r) => rowToDelivery(r.delivery));
|
|
616
646
|
} catch (err) {
|
|
617
647
|
if (err instanceof Error && err.message.includes('no such table')) {
|
|
618
648
|
log.warn({ err }, 'guardian tables not yet created');
|
|
619
|
-
return
|
|
649
|
+
return [];
|
|
620
650
|
}
|
|
621
651
|
throw err;
|
|
622
652
|
}
|
|
@@ -669,6 +699,16 @@ export function getExpiredDeliveriesByDestination(
|
|
|
669
699
|
* Look up an expired delivery by destination conversation ID (for mac channel routing).
|
|
670
700
|
*/
|
|
671
701
|
export function getExpiredDeliveryByConversation(conversationId: string): GuardianActionDelivery | null {
|
|
702
|
+
const all = getExpiredDeliveriesByConversation(conversationId);
|
|
703
|
+
return all.length > 0 ? all[0] : null;
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
/**
|
|
707
|
+
* Look up all expired deliveries by destination conversation ID.
|
|
708
|
+
* Used for disambiguation when a reused vellum thread has multiple expired
|
|
709
|
+
* guardian requests eligible for follow-up.
|
|
710
|
+
*/
|
|
711
|
+
export function getExpiredDeliveriesByConversation(conversationId: string): GuardianActionDelivery[] {
|
|
672
712
|
try {
|
|
673
713
|
const db = getDb();
|
|
674
714
|
const rows = db
|
|
@@ -687,11 +727,11 @@ export function getExpiredDeliveryByConversation(conversationId: string): Guardi
|
|
|
687
727
|
),
|
|
688
728
|
)
|
|
689
729
|
.all();
|
|
690
|
-
return rows.
|
|
730
|
+
return rows.map((r) => rowToDelivery(r.delivery));
|
|
691
731
|
} catch (err) {
|
|
692
732
|
if (err instanceof Error && err.message.includes('no such table')) {
|
|
693
733
|
log.warn({ err }, 'guardian tables not yet created');
|
|
694
|
-
return
|
|
734
|
+
return [];
|
|
695
735
|
}
|
|
696
736
|
throw err;
|
|
697
737
|
}
|
|
@@ -746,6 +786,16 @@ export function getFollowupDeliveriesByDestination(
|
|
|
746
786
|
* state by destination conversation ID (for mac channel routing).
|
|
747
787
|
*/
|
|
748
788
|
export function getFollowupDeliveryByConversation(conversationId: string): GuardianActionDelivery | null {
|
|
789
|
+
const all = getFollowupDeliveriesByConversation(conversationId);
|
|
790
|
+
return all.length > 0 ? all[0] : null;
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
/**
|
|
794
|
+
* Look up all deliveries for requests in `awaiting_guardian_choice` follow-up
|
|
795
|
+
* state by destination conversation ID. Used for disambiguation when a reused
|
|
796
|
+
* vellum thread has multiple follow-up guardian requests.
|
|
797
|
+
*/
|
|
798
|
+
export function getFollowupDeliveriesByConversation(conversationId: string): GuardianActionDelivery[] {
|
|
749
799
|
try {
|
|
750
800
|
const db = getDb();
|
|
751
801
|
const rows = db
|
|
@@ -764,11 +814,11 @@ export function getFollowupDeliveryByConversation(conversationId: string): Guard
|
|
|
764
814
|
),
|
|
765
815
|
)
|
|
766
816
|
.all();
|
|
767
|
-
return rows.
|
|
817
|
+
return rows.map((r) => rowToDelivery(r.delivery));
|
|
768
818
|
} catch (err) {
|
|
769
819
|
if (err instanceof Error && err.message.includes('no such table')) {
|
|
770
820
|
log.warn({ err }, 'guardian tables not yet created');
|
|
771
|
-
return
|
|
821
|
+
return [];
|
|
772
822
|
}
|
|
773
823
|
throw err;
|
|
774
824
|
}
|
|
@@ -2,6 +2,7 @@ import { getConfig } from '../config/loader.js';
|
|
|
2
2
|
import type { AssistantConfig } from '../config/types.js';
|
|
3
3
|
import { getLogger } from '../util/logger.js';
|
|
4
4
|
import { rawRun } from './db.js';
|
|
5
|
+
import { reconcileFtsIndexes } from './fts-reconciler.js';
|
|
5
6
|
import { backfillEntityRelationsJob,backfillJob } from './job-handlers/backfill.js';
|
|
6
7
|
import { checkContradictionsJob, cleanupStaleSupersededItemsJob, pruneOldConversationsJob } from './job-handlers/cleanup.js';
|
|
7
8
|
import { cleanupResolvedConflictsJob,resolvePendingConflictsForMessageJob } from './job-handlers/conflict.js';
|
|
@@ -9,7 +10,6 @@ import { cleanupResolvedConflictsJob,resolvePendingConflictsForMessageJob } from
|
|
|
9
10
|
import { embedItemJob, embedSegmentJob, embedSummaryJob } from './job-handlers/embedding.js';
|
|
10
11
|
import { extractEntitiesJob,extractItemsJob } from './job-handlers/extraction.js';
|
|
11
12
|
import { deleteQdrantVectorsJob,rebuildIndexJob } from './job-handlers/index-maintenance.js';
|
|
12
|
-
import { reconcileFtsIndexes } from './fts-reconciler.js';
|
|
13
13
|
import { mediaProcessingJob } from './job-handlers/media-processing.js';
|
|
14
14
|
import { buildConversationSummaryJob, buildGlobalSummaryJob } from './job-handlers/summarization.js';
|
|
15
15
|
import {
|
|
@@ -18,7 +18,6 @@ import {
|
|
|
18
18
|
RETRY_MAX_ATTEMPTS,
|
|
19
19
|
retryDelayForAttempt,
|
|
20
20
|
} from './job-utils.js';
|
|
21
|
-
import { QdrantCircuitOpenError } from './qdrant-circuit-breaker.js';
|
|
22
21
|
import {
|
|
23
22
|
claimMemoryJobs,
|
|
24
23
|
completeMemoryJob,
|
|
@@ -32,6 +31,7 @@ import {
|
|
|
32
31
|
type MemoryJob,
|
|
33
32
|
resetRunningJobsToPending,
|
|
34
33
|
} from './jobs-store.js';
|
|
34
|
+
import { QdrantCircuitOpenError } from './qdrant-circuit-breaker.js';
|
|
35
35
|
import { bumpMemoryVersion } from './recall-cache.js';
|
|
36
36
|
|
|
37
37
|
// Re-export public utilities consumed by tests and other modules
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { DrizzleDb } from '../db-connection.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Add index on guardian_action_deliveries.destination_conversation_id.
|
|
5
|
+
*
|
|
6
|
+
* Several lookup paths (getPendingDeliveryByConversation,
|
|
7
|
+
* getExpiredDeliveryByConversation, getFollowupDeliveryByConversation)
|
|
8
|
+
* filter deliveries by destination_conversation_id. Without an index
|
|
9
|
+
* these degrade to full table scans as delivery history grows.
|
|
10
|
+
*/
|
|
11
|
+
export function migrateGuardianDeliveryConversationIndex(database: DrizzleDb): void {
|
|
12
|
+
database.run(
|
|
13
|
+
/*sql*/ `CREATE INDEX IF NOT EXISTS idx_guardian_action_deliveries_dest_conversation ON guardian_action_deliveries(destination_conversation_id)`,
|
|
14
|
+
);
|
|
15
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { DrizzleDb } from '../db-connection.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Add thread decision audit columns to notification_deliveries.
|
|
5
|
+
*
|
|
6
|
+
* These columns record the model's per-channel thread action (start_new
|
|
7
|
+
* or reuse_existing), the target conversation ID for reuse, and whether
|
|
8
|
+
* a fallback to start_new was needed due to an invalid/stale target.
|
|
9
|
+
*/
|
|
10
|
+
export function migrateNotificationDeliveryThreadDecision(database: DrizzleDb): void {
|
|
11
|
+
try {
|
|
12
|
+
database.run(/*sql*/ `ALTER TABLE notification_deliveries ADD COLUMN thread_action TEXT`);
|
|
13
|
+
} catch { /* Column already exists */ }
|
|
14
|
+
try {
|
|
15
|
+
database.run(/*sql*/ `ALTER TABLE notification_deliveries ADD COLUMN thread_target_conversation_id TEXT`);
|
|
16
|
+
} catch { /* Column already exists */ }
|
|
17
|
+
try {
|
|
18
|
+
database.run(/*sql*/ `ALTER TABLE notification_deliveries ADD COLUMN thread_decision_fallback_used INTEGER`);
|
|
19
|
+
} catch { /* Column already exists */ }
|
|
20
|
+
}
|
|
@@ -23,15 +23,17 @@ export { migrateAddOriginInterface } from './022-add-origin-interface.js';
|
|
|
23
23
|
export { migrateMemoryItemSourcesIndexes } from './023-memory-item-sources-indexes.js';
|
|
24
24
|
export { migrateEmbeddingVectorBlob } from './024-embedding-vector-blob.js';
|
|
25
25
|
export { migrateMessagesFtsBackfill } from './025-messages-fts-backfill.js';
|
|
26
|
-
export { migrateEmbeddingsNullableVectorJson } from './026a-embeddings-nullable-vector-json.js';
|
|
27
26
|
export { migrateGuardianVerificationSessions } from './026-guardian-verification-sessions.js';
|
|
28
|
-
export {
|
|
27
|
+
export { migrateEmbeddingsNullableVectorJson } from './026a-embeddings-nullable-vector-json.js';
|
|
29
28
|
export { migrateNotificationDeliveryPairingColumns } from './027-notification-delivery-pairing-columns.js';
|
|
29
|
+
export { migrateGuardianBootstrapToken } from './027a-guardian-bootstrap-token.js';
|
|
30
30
|
export { migrateCallSessionMode } from './028-call-session-mode.js';
|
|
31
31
|
export { migrateChannelInboundDeliveredSegments } from './029-channel-inbound-delivered-segments.js';
|
|
32
32
|
export { migrateGuardianActionFollowup } from './030-guardian-action-followup.js';
|
|
33
33
|
export { migrateGuardianVerificationPurpose } from './030-guardian-verification-purpose.js';
|
|
34
34
|
export { migrateConversationsThreadTypeIndex } from './031-conversations-thread-type-index.js';
|
|
35
|
+
export { migrateGuardianDeliveryConversationIndex } from './032-guardian-delivery-conversation-index.js';
|
|
36
|
+
export { migrateNotificationDeliveryThreadDecision } from './032-notification-delivery-thread-decision.js';
|
|
35
37
|
export { createCoreTables } from './100-core-tables.js';
|
|
36
38
|
export { createWatchersAndLogsTables } from './101-watchers-and-logs.js';
|
|
37
39
|
export { addCoreColumns } from './102-alter-table-columns.js';
|
|
@@ -20,6 +20,7 @@ export {
|
|
|
20
20
|
migrateMemorySegmentsIndexes,
|
|
21
21
|
migrateMessagesFtsBackfill,
|
|
22
22
|
migrateNotificationDeliveryPairingColumns,
|
|
23
|
+
migrateNotificationDeliveryThreadDecision,
|
|
23
24
|
migrateNotificationTablesSchema,
|
|
24
25
|
migrateRemainingTableIndexes,
|
|
25
26
|
migrateReminderRoutingIntent,
|