@vellumai/assistant 0.3.16 → 0.3.19
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 +74 -13
- package/README.md +6 -0
- package/docs/architecture/http-token-refresh.md +23 -1
- package/docs/architecture/security.md +80 -0
- package/package.json +1 -1
- package/src/__tests__/__snapshots__/ipc-snapshot.test.ts.snap +4 -0
- package/src/__tests__/access-request-decision.test.ts +4 -7
- package/src/__tests__/call-controller.test.ts +170 -0
- package/src/__tests__/channel-guardian.test.ts +3 -1
- package/src/__tests__/checker.test.ts +139 -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-grant-mint-consume.test.ts +511 -0
- 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 +180 -0
- package/src/__tests__/guardian-grant-minting.test.ts +543 -0
- package/src/__tests__/ipc-snapshot.test.ts +22 -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__/remote-skill-policy.test.ts +215 -0
- package/src/__tests__/scoped-approval-grants.test.ts +521 -0
- package/src/__tests__/scoped-grant-security-matrix.test.ts +443 -0
- package/src/__tests__/slack-channel-config.test.ts +3 -3
- package/src/__tests__/trust-store.test.ts +23 -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-scoped-grant-consumer.test.ts +571 -0
- package/src/__tests__/voice-session-bridge.test.ts +109 -9
- package/src/calls/call-controller.ts +150 -8
- package/src/calls/call-domain.ts +12 -0
- package/src/calls/guardian-action-sweep.ts +1 -1
- package/src/calls/guardian-dispatch.ts +16 -0
- package/src/calls/relay-server.ts +13 -0
- package/src/calls/voice-session-bridge.ts +46 -5
- package/src/cli/core-commands.ts +41 -1
- package/src/config/bundled-skills/notifications/SKILL.md +18 -0
- package/src/config/schema.ts +6 -0
- package/src/config/skills-schema.ts +27 -0
- 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-channels.ts +18 -0
- 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/handlers/skills.ts +45 -2
- package/src/daemon/ipc-contract/sessions.ts +1 -1
- package/src/daemon/ipc-contract/skills.ts +1 -0
- 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 +450 -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 +17 -1
- package/src/memory/embedding-local.ts +16 -7
- package/src/memory/fts-reconciler.ts +41 -26
- package/src/memory/guardian-action-store.ts +65 -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/033-scoped-approval-grants.ts +51 -0
- package/src/memory/migrations/034-guardian-action-tool-metadata.ts +12 -0
- package/src/memory/migrations/index.ts +6 -2
- package/src/memory/schema-migration.ts +1 -0
- package/src/memory/schema.ts +36 -1
- package/src/memory/scoped-approval-grants.ts +509 -0
- 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 +28 -16
- package/src/permissions/defaults.ts +14 -4
- package/src/runtime/guardian-action-followup-executor.ts +1 -1
- package/src/runtime/guardian-action-grant-minter.ts +97 -0
- 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 +120 -4
- package/src/runtime/routes/inbound-message-handler.ts +100 -33
- package/src/runtime/routes/integration-routes.ts +2 -2
- package/src/security/tool-approval-digest.ts +67 -0
- package/src/skills/remote-skill-policy.ts +131 -0
- 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
|
@@ -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
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import type { DrizzleDb } from '../db-connection.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Create the scoped_approval_grants table for channel-agnostic scoped
|
|
5
|
+
* approval grants. Supports two scope modes:
|
|
6
|
+
* - request_id: grant is scoped to a specific request
|
|
7
|
+
* - tool_signature: grant is scoped to a tool name + input digest
|
|
8
|
+
*
|
|
9
|
+
* Grants are one-time-use (active -> consumed via CAS) and carry a
|
|
10
|
+
* mandatory TTL (expires_at).
|
|
11
|
+
*/
|
|
12
|
+
export function createScopedApprovalGrantsTable(database: DrizzleDb): void {
|
|
13
|
+
database.run(/*sql*/ `
|
|
14
|
+
CREATE TABLE IF NOT EXISTS scoped_approval_grants (
|
|
15
|
+
id TEXT PRIMARY KEY,
|
|
16
|
+
assistant_id TEXT NOT NULL,
|
|
17
|
+
scope_mode TEXT NOT NULL,
|
|
18
|
+
request_id TEXT,
|
|
19
|
+
tool_name TEXT,
|
|
20
|
+
input_digest TEXT,
|
|
21
|
+
request_channel TEXT NOT NULL,
|
|
22
|
+
decision_channel TEXT NOT NULL,
|
|
23
|
+
execution_channel TEXT,
|
|
24
|
+
conversation_id TEXT,
|
|
25
|
+
call_session_id TEXT,
|
|
26
|
+
requester_external_user_id TEXT,
|
|
27
|
+
guardian_external_user_id TEXT,
|
|
28
|
+
status TEXT NOT NULL,
|
|
29
|
+
expires_at TEXT NOT NULL,
|
|
30
|
+
consumed_at TEXT,
|
|
31
|
+
consumed_by_request_id TEXT,
|
|
32
|
+
created_at TEXT NOT NULL,
|
|
33
|
+
updated_at TEXT NOT NULL
|
|
34
|
+
)
|
|
35
|
+
`);
|
|
36
|
+
|
|
37
|
+
// Index for request_id-based lookups (scope_mode = 'request_id')
|
|
38
|
+
database.run(
|
|
39
|
+
/*sql*/ `CREATE INDEX IF NOT EXISTS idx_scoped_grants_request_id ON scoped_approval_grants(request_id) WHERE request_id IS NOT NULL`,
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
// Index for tool_signature-based lookups (scope_mode = 'tool_signature')
|
|
43
|
+
database.run(
|
|
44
|
+
/*sql*/ `CREATE INDEX IF NOT EXISTS idx_scoped_grants_tool_sig ON scoped_approval_grants(tool_name, input_digest) WHERE tool_name IS NOT NULL`,
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
// Index for expiry sweeps
|
|
48
|
+
database.run(
|
|
49
|
+
/*sql*/ `CREATE INDEX IF NOT EXISTS idx_scoped_grants_status_expires ON scoped_approval_grants(status, expires_at)`,
|
|
50
|
+
);
|
|
51
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { DrizzleDb } from '../db-connection.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Add tool_name and input_digest columns to guardian_action_requests for
|
|
5
|
+
* structured tool-approval tracking. These are nullable — informational
|
|
6
|
+
* ASK_GUARDIAN requests leave them NULL while tool-approval requests
|
|
7
|
+
* carry the tool identity and canonical input digest.
|
|
8
|
+
*/
|
|
9
|
+
export function migrateGuardianActionToolMetadata(database: DrizzleDb): void {
|
|
10
|
+
try { database.run(/*sql*/ `ALTER TABLE guardian_action_requests ADD COLUMN tool_name TEXT`); } catch { /* already exists */ }
|
|
11
|
+
try { database.run(/*sql*/ `ALTER TABLE guardian_action_requests ADD COLUMN input_digest TEXT`); } catch { /* already exists */ }
|
|
12
|
+
}
|
|
@@ -23,15 +23,19 @@ 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 { createScopedApprovalGrantsTable } from './033-scoped-approval-grants.js';
|
|
37
|
+
export { migrateGuardianActionToolMetadata } from './034-guardian-action-tool-metadata.js';
|
|
38
|
+
export { migrateNotificationDeliveryThreadDecision } from './032-notification-delivery-thread-decision.js';
|
|
35
39
|
export { createCoreTables } from './100-core-tables.js';
|
|
36
40
|
export { createWatchersAndLogsTables } from './101-watchers-and-logs.js';
|
|
37
41
|
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,
|
package/src/memory/schema.ts
CHANGED
|
@@ -843,6 +843,8 @@ export const guardianActionRequests = sqliteTable('guardian_action_requests', {
|
|
|
843
843
|
lateAnsweredAt: integer('late_answered_at'),
|
|
844
844
|
followupAction: text('followup_action'), // call_back | message_back | decline
|
|
845
845
|
followupCompletedAt: integer('followup_completed_at'),
|
|
846
|
+
toolName: text('tool_name'), // tool identity for tool-approval requests
|
|
847
|
+
inputDigest: text('input_digest'), // canonical SHA-256 digest of tool input
|
|
846
848
|
createdAt: integer('created_at').notNull(),
|
|
847
849
|
updatedAt: integer('updated_at').notNull(),
|
|
848
850
|
});
|
|
@@ -864,7 +866,9 @@ export const guardianActionDeliveries = sqliteTable('guardian_action_deliveries'
|
|
|
864
866
|
lastError: text('last_error'),
|
|
865
867
|
createdAt: integer('created_at').notNull(),
|
|
866
868
|
updatedAt: integer('updated_at').notNull(),
|
|
867
|
-
})
|
|
869
|
+
}, (table) => [
|
|
870
|
+
index('idx_guardian_action_deliveries_dest_conversation').on(table.destinationConversationId),
|
|
871
|
+
]);
|
|
868
872
|
|
|
869
873
|
// ── Assistant Inbox ──────────────────────────────────────────────────
|
|
870
874
|
|
|
@@ -1018,6 +1022,9 @@ export const notificationDeliveries = sqliteTable('notification_deliveries', {
|
|
|
1018
1022
|
conversationId: text('conversation_id'),
|
|
1019
1023
|
messageId: text('message_id'),
|
|
1020
1024
|
conversationStrategy: text('conversation_strategy'),
|
|
1025
|
+
threadAction: text('thread_action'),
|
|
1026
|
+
threadTargetConversationId: text('thread_target_conversation_id'),
|
|
1027
|
+
threadDecisionFallbackUsed: integer('thread_decision_fallback_used'),
|
|
1021
1028
|
clientDeliveryStatus: text('client_delivery_status'),
|
|
1022
1029
|
clientDeliveryError: text('client_delivery_error'),
|
|
1023
1030
|
clientDeliveryAt: integer('client_delivery_at'),
|
|
@@ -1070,3 +1077,31 @@ export const conversationAssistantAttentionState = sqliteTable('conversation_ass
|
|
|
1070
1077
|
index('idx_conv_attn_state_assistant_latest_msg').on(table.assistantId, table.latestAssistantMessageAt),
|
|
1071
1078
|
index('idx_conv_attn_state_assistant_last_seen').on(table.assistantId, table.lastSeenAssistantMessageAt),
|
|
1072
1079
|
]);
|
|
1080
|
+
|
|
1081
|
+
// ── Scoped Approval Grants ──────────────────────────────────────────
|
|
1082
|
+
|
|
1083
|
+
export const scopedApprovalGrants = sqliteTable('scoped_approval_grants', {
|
|
1084
|
+
id: text('id').primaryKey(),
|
|
1085
|
+
assistantId: text('assistant_id').notNull(),
|
|
1086
|
+
scopeMode: text('scope_mode').notNull(), // 'request_id' | 'tool_signature'
|
|
1087
|
+
requestId: text('request_id'),
|
|
1088
|
+
toolName: text('tool_name'),
|
|
1089
|
+
inputDigest: text('input_digest'),
|
|
1090
|
+
requestChannel: text('request_channel').notNull(),
|
|
1091
|
+
decisionChannel: text('decision_channel').notNull(),
|
|
1092
|
+
executionChannel: text('execution_channel'), // null = any channel
|
|
1093
|
+
conversationId: text('conversation_id'),
|
|
1094
|
+
callSessionId: text('call_session_id'),
|
|
1095
|
+
requesterExternalUserId: text('requester_external_user_id'),
|
|
1096
|
+
guardianExternalUserId: text('guardian_external_user_id'),
|
|
1097
|
+
status: text('status').notNull(), // 'active' | 'consumed' | 'expired' | 'revoked'
|
|
1098
|
+
expiresAt: text('expires_at').notNull(),
|
|
1099
|
+
consumedAt: text('consumed_at'),
|
|
1100
|
+
consumedByRequestId: text('consumed_by_request_id'),
|
|
1101
|
+
createdAt: text('created_at').notNull(),
|
|
1102
|
+
updatedAt: text('updated_at').notNull(),
|
|
1103
|
+
}, (table) => [
|
|
1104
|
+
index('idx_scoped_grants_request_id').on(table.requestId),
|
|
1105
|
+
index('idx_scoped_grants_tool_sig').on(table.toolName, table.inputDigest),
|
|
1106
|
+
index('idx_scoped_grants_status_expires').on(table.status, table.expiresAt),
|
|
1107
|
+
]);
|