@vellumai/assistant 0.4.33 → 0.4.35
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/package.json +1 -1
- package/src/__tests__/access-request-decision.test.ts +2 -3
- package/src/__tests__/actor-token-service.test.ts +4 -11
- package/src/__tests__/approval-primitive.test.ts +0 -45
- package/src/__tests__/assistant-id-boundary-guard.test.ts +169 -0
- package/src/__tests__/callback-handoff-copy.test.ts +0 -1
- package/src/__tests__/channel-approval-routes.test.ts +5 -45
- package/src/__tests__/channel-guardian.test.ts +122 -345
- package/src/__tests__/confirmation-request-guardian-bridge.test.ts +4 -3
- package/src/__tests__/contacts-tools.test.ts +4 -5
- package/src/__tests__/conversation-attention-store.test.ts +2 -65
- package/src/__tests__/conversation-attention-telegram.test.ts +0 -2
- package/src/__tests__/conversation-pairing.test.ts +0 -1
- package/src/__tests__/deterministic-verification-control-plane.test.ts +0 -2
- package/src/__tests__/guardian-action-conversation-turn.test.ts +1 -7
- package/src/__tests__/guardian-action-grant-mint-consume.test.ts +0 -74
- package/src/__tests__/guardian-action-late-reply.test.ts +1 -8
- package/src/__tests__/guardian-grant-minting.test.ts +0 -1
- package/src/__tests__/guardian-routing-state.test.ts +0 -3
- package/src/__tests__/inbound-invite-redemption.test.ts +0 -3
- package/src/__tests__/non-member-access-request.test.ts +0 -7
- package/src/__tests__/notification-broadcaster.test.ts +1 -2
- package/src/__tests__/notification-decision-fallback.test.ts +0 -2
- package/src/__tests__/notification-decision-strategy.test.ts +0 -1
- package/src/__tests__/relay-server.test.ts +11 -83
- package/src/__tests__/scoped-approval-grants.test.ts +9 -40
- package/src/__tests__/scoped-grant-security-matrix.test.ts +0 -36
- package/src/__tests__/send-endpoint-busy.test.ts +0 -1
- package/src/__tests__/send-notification-tool.test.ts +0 -1
- package/src/__tests__/slack-inbound-verification.test.ts +2 -4
- package/src/__tests__/thread-seed-composer.test.ts +0 -1
- package/src/__tests__/tool-approval-handler.test.ts +0 -1
- package/src/__tests__/tool-grant-request-escalation.test.ts +0 -4
- package/src/__tests__/trusted-contact-approval-notifier.test.ts +1 -5
- package/src/__tests__/trusted-contact-lifecycle-notifications.test.ts +1 -17
- package/src/__tests__/trusted-contact-multichannel.test.ts +0 -13
- package/src/__tests__/trusted-contact-verification.test.ts +3 -15
- package/src/__tests__/twilio-routes.test.ts +2 -2
- package/src/__tests__/voice-invite-redemption.test.ts +0 -1
- package/src/__tests__/voice-scoped-grant-consumer.test.ts +0 -37
- package/src/approvals/approval-primitive.ts +0 -15
- package/src/approvals/guardian-decision-primitive.ts +0 -3
- package/src/approvals/guardian-request-resolvers.ts +0 -5
- package/src/calls/call-domain.ts +0 -3
- package/src/calls/call-store.ts +0 -3
- package/src/calls/guardian-action-sweep.ts +2 -1
- package/src/calls/guardian-dispatch.ts +1 -2
- package/src/calls/relay-access-wait.ts +0 -4
- package/src/calls/relay-server.ts +3 -11
- package/src/calls/relay-setup-router.ts +1 -2
- package/src/calls/relay-verification.ts +0 -1
- package/src/calls/twilio-routes.ts +0 -3
- package/src/calls/types.ts +0 -1
- package/src/calls/voice-session-bridge.ts +0 -1
- package/src/config/bundled-skills/google-oauth-setup/SKILL.md +100 -171
- package/src/config/bundled-skills/notifications/tools/send-notification.ts +0 -1
- package/src/contacts/contact-store.ts +13 -88
- package/src/contacts/contacts-write.ts +3 -11
- package/src/contacts/types.ts +0 -1
- package/src/daemon/handlers/config-channels.ts +16 -42
- package/src/daemon/handlers/config-inbox.ts +6 -6
- package/src/daemon/handlers/contacts.ts +3 -11
- package/src/daemon/handlers/index.ts +0 -2
- package/src/daemon/session-process.ts +0 -4
- package/src/memory/conversation-attention-store.ts +4 -19
- package/src/memory/conversation-crud.ts +0 -2
- package/src/memory/db-init.ts +4 -0
- package/src/memory/guardian-action-store.ts +0 -12
- package/src/memory/guardian-approvals.ts +35 -80
- package/src/memory/guardian-rate-limits.ts +1 -14
- package/src/memory/guardian-verification.ts +6 -34
- package/src/memory/invite-store.ts +5 -14
- package/src/memory/migrations/026-guardian-verification-sessions.ts +28 -9
- package/src/memory/migrations/027a-guardian-bootstrap-token.ts +16 -3
- package/src/memory/migrations/038-actor-token-records.ts +8 -1
- package/src/memory/migrations/039-actor-refresh-token-records.ts +11 -2
- package/src/memory/migrations/110-channel-guardian.ts +27 -6
- package/src/memory/migrations/112-assistant-inbox.ts +39 -15
- package/src/memory/migrations/114-notifications.ts +37 -15
- package/src/memory/migrations/117-conversation-attention.ts +33 -9
- package/src/memory/migrations/134-contacts-notes-column.ts +64 -45
- package/src/memory/migrations/136-drop-assistant-id-columns.ts +263 -0
- package/src/memory/migrations/index.ts +1 -0
- package/src/memory/migrations/registry.ts +14 -1
- package/src/memory/migrations/schema-introspection.ts +18 -0
- package/src/memory/schema/calls.ts +0 -7
- package/src/memory/schema/contacts.ts +0 -8
- package/src/memory/schema/guardian.ts +0 -5
- package/src/memory/schema/infrastructure.ts +0 -2
- package/src/memory/schema/notifications.ts +3 -17
- package/src/memory/scoped-approval-grants.ts +2 -24
- package/src/notifications/adapters/sms.ts +2 -1
- package/src/notifications/broadcaster.ts +1 -6
- package/src/notifications/decision-engine.ts +3 -4
- package/src/notifications/deliveries-store.ts +0 -4
- package/src/notifications/destination-resolver.ts +4 -6
- package/src/notifications/deterministic-checks.ts +1 -6
- package/src/notifications/emit-signal.ts +4 -11
- package/src/notifications/events-store.ts +7 -17
- package/src/notifications/preference-summary.ts +2 -2
- package/src/notifications/preferences-store.ts +2 -9
- package/src/notifications/signal.ts +0 -1
- package/src/notifications/thread-candidates.ts +1 -11
- package/src/notifications/types.ts +0 -3
- package/src/runtime/access-request-helper.ts +3 -10
- package/src/runtime/actor-refresh-token-store.ts +0 -6
- package/src/runtime/actor-token-store.ts +3 -16
- package/src/runtime/actor-trust-resolver.ts +1 -4
- package/src/runtime/auth/__tests__/credential-service.test.ts +0 -9
- package/src/runtime/auth/credential-service.ts +1 -15
- package/src/runtime/auth/require-bound-guardian.ts +1 -4
- package/src/runtime/channel-guardian-service.ts +15 -46
- package/src/runtime/channel-invite-transport.ts +8 -0
- package/src/runtime/channel-invite-transports/email.ts +4 -0
- package/src/runtime/channel-invite-transports/slack.ts +6 -0
- package/src/runtime/channel-invite-transports/sms.ts +4 -0
- package/src/runtime/channel-invite-transports/telegram.ts +6 -0
- package/src/runtime/confirmation-request-guardian-bridge.ts +0 -1
- package/src/runtime/guardian-action-followup-executor.ts +3 -2
- package/src/runtime/guardian-action-grant-minter.ts +0 -1
- package/src/runtime/guardian-outbound-actions.ts +2 -12
- package/src/runtime/guardian-vellum-migration.ts +2 -3
- package/src/runtime/http-server.ts +3 -10
- package/src/runtime/http-types.ts +13 -1
- package/src/runtime/invite-redemption-service.ts +1 -14
- package/src/runtime/local-actor-identity.ts +2 -5
- package/src/runtime/routes/access-request-decision.ts +0 -1
- package/src/runtime/routes/approval-strategies/guardian-callback-strategy.ts +0 -9
- package/src/runtime/routes/channel-readiness-routes.ts +29 -18
- package/src/runtime/routes/contact-routes.ts +15 -40
- package/src/runtime/routes/conversation-attention-routes.ts +0 -2
- package/src/runtime/routes/global-search-routes.ts +0 -2
- package/src/runtime/routes/guardian-bootstrap-routes.ts +6 -7
- package/src/runtime/routes/guardian-expiry-sweep.ts +3 -2
- package/src/runtime/routes/inbound-message-handler.ts +0 -3
- package/src/runtime/routes/inbound-stages/acl-enforcement.ts +7 -43
- package/src/runtime/routes/inbound-stages/background-dispatch.ts +1 -4
- package/src/runtime/routes/inbound-stages/bootstrap-intercept.ts +1 -6
- package/src/runtime/routes/inbound-stages/escalation-intercept.ts +0 -1
- package/src/runtime/routes/inbound-stages/secret-ingress-check.ts +0 -1
- package/src/runtime/routes/inbound-stages/verification-intercept.ts +3 -7
- package/src/runtime/routes/pairing-routes.ts +4 -4
- package/src/runtime/routes/surface-content-routes.ts +104 -0
- package/src/runtime/tool-grant-request-helper.ts +0 -1
- package/src/tools/browser/browser-manager.ts +22 -21
- package/src/tools/browser/runtime-check.ts +111 -6
- package/src/tools/calls/call-start.ts +1 -3
- package/src/tools/followups/followup_create.ts +1 -2
- package/src/tools/tool-approval-handler.ts +0 -2
|
@@ -41,7 +41,7 @@ export function resolveLocalIpcTrustContext(
|
|
|
41
41
|
const assistantId = DAEMON_INTERNAL_ASSISTANT_ID;
|
|
42
42
|
|
|
43
43
|
// Try contacts-first for the vellum guardian channel
|
|
44
|
-
const guardianResult = findGuardianForChannel("vellum"
|
|
44
|
+
const guardianResult = findGuardianForChannel("vellum");
|
|
45
45
|
if (guardianResult && guardianResult.contact.principalId) {
|
|
46
46
|
const guardianPrincipalId = guardianResult.contact.principalId;
|
|
47
47
|
const trustCtx = resolveTrustContext({
|
|
@@ -93,10 +93,7 @@ export function resolveLocalIpcAuthContext(sessionId: string): AuthContext {
|
|
|
93
93
|
const authContext = buildIpcAuthContext(sessionId);
|
|
94
94
|
|
|
95
95
|
// Enrich with the guardian principal ID from contacts-first path
|
|
96
|
-
const guardianResult = findGuardianForChannel(
|
|
97
|
-
"vellum",
|
|
98
|
-
authContext.assistantId,
|
|
99
|
-
);
|
|
96
|
+
const guardianResult = findGuardianForChannel("vellum");
|
|
100
97
|
if (guardianResult && guardianResult.contact.principalId) {
|
|
101
98
|
return {
|
|
102
99
|
...authContext,
|
|
@@ -86,7 +86,6 @@ export function handleAccessRequestDecision(
|
|
|
86
86
|
// so only the original requester can consume the code. Mark as
|
|
87
87
|
// trusted_contact so the consume path skips guardian binding creation.
|
|
88
88
|
const session = createOutboundSession({
|
|
89
|
-
assistantId: approval.assistantId,
|
|
90
89
|
channel: approval.channel,
|
|
91
90
|
expectedExternalUserId: approval.requesterExternalUserId,
|
|
92
91
|
expectedChatId: approval.requesterChatId,
|
|
@@ -99,7 +99,6 @@ export async function handleGuardianCallbackDecision(
|
|
|
99
99
|
callbackDecision.requestId,
|
|
100
100
|
sourceChannel,
|
|
101
101
|
conversationExternalId,
|
|
102
|
-
assistantId,
|
|
103
102
|
)
|
|
104
103
|
: null;
|
|
105
104
|
|
|
@@ -110,7 +109,6 @@ export async function handleGuardianCallbackDecision(
|
|
|
110
109
|
const allPending = getAllPendingApprovalsByGuardianChat(
|
|
111
110
|
sourceChannel,
|
|
112
111
|
conversationExternalId,
|
|
113
|
-
assistantId,
|
|
114
112
|
);
|
|
115
113
|
if (allPending.length === 1) {
|
|
116
114
|
guardianApproval = allPending[0];
|
|
@@ -141,7 +139,6 @@ export async function handleGuardianCallbackDecision(
|
|
|
141
139
|
const allPending = getAllPendingApprovalsByGuardianChat(
|
|
142
140
|
sourceChannel,
|
|
143
141
|
conversationExternalId,
|
|
144
|
-
assistantId,
|
|
145
142
|
);
|
|
146
143
|
if (allPending.length === 1) {
|
|
147
144
|
guardianApproval = allPending[0];
|
|
@@ -204,7 +201,6 @@ export async function handleGuardianCallbackDecision(
|
|
|
204
201
|
const allGuardianPending = getAllPendingApprovalsByGuardianChat(
|
|
205
202
|
sourceChannel,
|
|
206
203
|
conversationExternalId,
|
|
207
|
-
assistantId,
|
|
208
204
|
);
|
|
209
205
|
// Only present approvals that belong to this sender so the engine
|
|
210
206
|
// does not offer disambiguation for requests assigned to a rotated
|
|
@@ -609,7 +605,6 @@ async function handleLegacyDecision(params: {
|
|
|
609
605
|
legacyGuardianDecision.requestId,
|
|
610
606
|
sourceChannel,
|
|
611
607
|
conversationExternalId,
|
|
612
|
-
assistantId,
|
|
613
608
|
);
|
|
614
609
|
if (!resolvedByRequest) {
|
|
615
610
|
// The referenced request doesn't match any pending guardian
|
|
@@ -807,7 +802,6 @@ async function handleAccessRequestApproval(
|
|
|
807
802
|
sourceEventName: "ingress.trusted_contact.guardian_decision",
|
|
808
803
|
sourceChannel: approval.channel,
|
|
809
804
|
sourceSessionId: approval.conversationId,
|
|
810
|
-
assistantId,
|
|
811
805
|
attentionHints: {
|
|
812
806
|
requiresAction: false,
|
|
813
807
|
urgency: "medium",
|
|
@@ -822,7 +816,6 @@ async function handleAccessRequestApproval(
|
|
|
822
816
|
sourceEventName: "ingress.trusted_contact.denied",
|
|
823
817
|
sourceChannel: approval.channel,
|
|
824
818
|
sourceSessionId: approval.conversationId,
|
|
825
|
-
assistantId,
|
|
826
819
|
attentionHints: {
|
|
827
820
|
requiresAction: false,
|
|
828
821
|
urgency: "low",
|
|
@@ -891,7 +884,6 @@ async function handleAccessRequestApproval(
|
|
|
891
884
|
sourceEventName: "ingress.trusted_contact.guardian_decision",
|
|
892
885
|
sourceChannel: approval.channel,
|
|
893
886
|
sourceSessionId: approval.conversationId,
|
|
894
|
-
assistantId,
|
|
895
887
|
attentionHints: {
|
|
896
888
|
requiresAction: false,
|
|
897
889
|
urgency: "medium",
|
|
@@ -918,7 +910,6 @@ async function handleAccessRequestApproval(
|
|
|
918
910
|
sourceEventName: "ingress.trusted_contact.verification_sent",
|
|
919
911
|
sourceChannel: approval.channel,
|
|
920
912
|
sourceSessionId: approval.conversationId,
|
|
921
|
-
assistantId,
|
|
922
913
|
attentionHints: {
|
|
923
914
|
requiresAction: false,
|
|
924
915
|
urgency: "low",
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
|
|
8
8
|
import type { ChannelId } from "../../channels/types.js";
|
|
9
9
|
import { getReadinessService } from "../../daemon/handlers/config-channels.js";
|
|
10
|
+
import { getInviteAdapterRegistry } from "../channel-invite-transport.js";
|
|
10
11
|
import type { RouteDefinition } from "../http-router.js";
|
|
11
12
|
|
|
12
13
|
/**
|
|
@@ -21,18 +22,23 @@ export async function handleGetChannelReadiness(url: URL): Promise<Response> {
|
|
|
21
22
|
|
|
22
23
|
const service = getReadinessService();
|
|
23
24
|
const snapshots = await service.getReadiness(channel, includeRemote);
|
|
25
|
+
const adapterRegistry = getInviteAdapterRegistry();
|
|
24
26
|
|
|
25
27
|
return Response.json({
|
|
26
28
|
success: true,
|
|
27
|
-
snapshots: snapshots.map((s) =>
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
29
|
+
snapshots: snapshots.map((s) => {
|
|
30
|
+
const adapter = adapterRegistry.get(s.channel);
|
|
31
|
+
return {
|
|
32
|
+
channel: s.channel,
|
|
33
|
+
ready: s.ready,
|
|
34
|
+
checkedAt: s.checkedAt,
|
|
35
|
+
stale: s.stale,
|
|
36
|
+
reasons: s.reasons,
|
|
37
|
+
localChecks: s.localChecks,
|
|
38
|
+
remoteChecks: s.remoteChecks,
|
|
39
|
+
channelHandle: adapter?.resolveChannelHandle?.() ?? undefined,
|
|
40
|
+
};
|
|
41
|
+
}),
|
|
36
42
|
});
|
|
37
43
|
}
|
|
38
44
|
|
|
@@ -62,18 +68,23 @@ export async function handleRefreshChannelReadiness(
|
|
|
62
68
|
body.channel,
|
|
63
69
|
body.includeRemote,
|
|
64
70
|
);
|
|
71
|
+
const adapterRegistry = getInviteAdapterRegistry();
|
|
65
72
|
|
|
66
73
|
return Response.json({
|
|
67
74
|
success: true,
|
|
68
|
-
snapshots: snapshots.map((s) =>
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
75
|
+
snapshots: snapshots.map((s) => {
|
|
76
|
+
const adapter = adapterRegistry.get(s.channel);
|
|
77
|
+
return {
|
|
78
|
+
channel: s.channel,
|
|
79
|
+
ready: s.ready,
|
|
80
|
+
checkedAt: s.checkedAt,
|
|
81
|
+
stale: s.stale,
|
|
82
|
+
reasons: s.reasons,
|
|
83
|
+
localChecks: s.localChecks,
|
|
84
|
+
remoteChecks: s.remoteChecks,
|
|
85
|
+
channelHandle: adapter?.resolveChannelHandle?.() ?? undefined,
|
|
86
|
+
};
|
|
87
|
+
}),
|
|
77
88
|
});
|
|
78
89
|
}
|
|
79
90
|
|
|
@@ -81,7 +81,7 @@ const VALID_ASSISTANT_SPECIES: readonly AssistantSpecies[] = [
|
|
|
81
81
|
* Also supports search query params: query, channelAddress, channelType.
|
|
82
82
|
* When any search param is provided, delegates to searchContacts() instead of listContacts().
|
|
83
83
|
*/
|
|
84
|
-
export function handleListContacts(url: URL
|
|
84
|
+
export function handleListContacts(url: URL): Response {
|
|
85
85
|
const limit = Number(url.searchParams.get("limit") ?? 50);
|
|
86
86
|
const role = url.searchParams.get("role") as ContactRole | null;
|
|
87
87
|
const contactTypeParam = url.searchParams.get("contactType");
|
|
@@ -104,7 +104,6 @@ export function handleListContacts(url: URL, assistantId: string): Response {
|
|
|
104
104
|
|
|
105
105
|
if (hasSearchParams) {
|
|
106
106
|
const contacts = searchContacts({
|
|
107
|
-
assistantId,
|
|
108
107
|
query: query ?? undefined,
|
|
109
108
|
channelAddress: channelAddress ?? undefined,
|
|
110
109
|
channelType: channelType ?? undefined,
|
|
@@ -118,12 +117,7 @@ export function handleListContacts(url: URL, assistantId: string): Response {
|
|
|
118
117
|
});
|
|
119
118
|
}
|
|
120
119
|
|
|
121
|
-
const contacts = listContacts(
|
|
122
|
-
assistantId,
|
|
123
|
-
limit,
|
|
124
|
-
role ?? undefined,
|
|
125
|
-
contactType,
|
|
126
|
-
);
|
|
120
|
+
const contacts = listContacts(limit, role ?? undefined, contactType);
|
|
127
121
|
return Response.json({
|
|
128
122
|
ok: true,
|
|
129
123
|
contacts: contacts.map(withGuardianNameOverride),
|
|
@@ -133,11 +127,8 @@ export function handleListContacts(url: URL, assistantId: string): Response {
|
|
|
133
127
|
/**
|
|
134
128
|
* GET /v1/contacts/:id
|
|
135
129
|
*/
|
|
136
|
-
export function handleGetContact(
|
|
137
|
-
contactId
|
|
138
|
-
assistantId: string,
|
|
139
|
-
): Response {
|
|
140
|
-
const contact = getContact(contactId, assistantId);
|
|
130
|
+
export function handleGetContact(contactId: string): Response {
|
|
131
|
+
const contact = getContact(contactId);
|
|
141
132
|
if (!contact) {
|
|
142
133
|
return httpError("NOT_FOUND", `Contact "${contactId}" not found`, 404);
|
|
143
134
|
}
|
|
@@ -155,10 +146,7 @@ export function handleGetContact(
|
|
|
155
146
|
/**
|
|
156
147
|
* POST /v1/contacts/merge { keepId, mergeId }
|
|
157
148
|
*/
|
|
158
|
-
export async function handleMergeContacts(
|
|
159
|
-
req: Request,
|
|
160
|
-
assistantId: string,
|
|
161
|
-
): Promise<Response> {
|
|
149
|
+
export async function handleMergeContacts(req: Request): Promise<Response> {
|
|
162
150
|
const body = (await req.json()) as { keepId?: string; mergeId?: string };
|
|
163
151
|
|
|
164
152
|
if (!body.keepId || !body.mergeId) {
|
|
@@ -166,7 +154,7 @@ export async function handleMergeContacts(
|
|
|
166
154
|
}
|
|
167
155
|
|
|
168
156
|
try {
|
|
169
|
-
const contact = mergeContacts(body.keepId, body.mergeId
|
|
157
|
+
const contact = mergeContacts(body.keepId, body.mergeId);
|
|
170
158
|
return Response.json({
|
|
171
159
|
ok: true,
|
|
172
160
|
contact: withGuardianNameOverride(contact),
|
|
@@ -209,10 +197,7 @@ function isChannelPolicy(value: string): value is ChannelPolicy {
|
|
|
209
197
|
/**
|
|
210
198
|
* POST /v1/contacts { displayName, id?, notes?, contactType?, assistantMetadata?, ... }
|
|
211
199
|
*/
|
|
212
|
-
export async function handleUpsertContact(
|
|
213
|
-
req: Request,
|
|
214
|
-
assistantId: string,
|
|
215
|
-
): Promise<Response> {
|
|
200
|
+
export async function handleUpsertContact(req: Request): Promise<Response> {
|
|
216
201
|
const body = (await req.json()) as {
|
|
217
202
|
id?: string;
|
|
218
203
|
displayName?: string;
|
|
@@ -328,7 +313,6 @@ export async function handleUpsertContact(
|
|
|
328
313
|
notes: body.notes,
|
|
329
314
|
role: body.role as ContactRole | undefined,
|
|
330
315
|
contactType: body.contactType as ContactType | undefined,
|
|
331
|
-
assistantId,
|
|
332
316
|
channels: body.channels?.map((ch) => ({
|
|
333
317
|
...ch,
|
|
334
318
|
status: ch.status as ChannelStatus | undefined,
|
|
@@ -360,7 +344,6 @@ export async function handleUpsertContact(
|
|
|
360
344
|
export async function handleUpdateContactChannel(
|
|
361
345
|
req: Request,
|
|
362
346
|
channelId: string,
|
|
363
|
-
assistantId: string,
|
|
364
347
|
): Promise<Response> {
|
|
365
348
|
const body = (await req.json()) as {
|
|
366
349
|
status?: string;
|
|
@@ -426,7 +409,7 @@ export async function handleUpdateContactChannel(
|
|
|
426
409
|
return httpError("NOT_FOUND", `Channel "${channelId}" not found`, 404);
|
|
427
410
|
}
|
|
428
411
|
|
|
429
|
-
const parentContact = getContact(updated.contactId
|
|
412
|
+
const parentContact = getContact(updated.contactId);
|
|
430
413
|
return Response.json({
|
|
431
414
|
ok: true,
|
|
432
415
|
contact: parentContact
|
|
@@ -487,7 +470,7 @@ export async function handleVerifyContactChannel(
|
|
|
487
470
|
channelId: string,
|
|
488
471
|
assistantId: string,
|
|
489
472
|
): Promise<Response> {
|
|
490
|
-
const contact = getContact(contactId
|
|
473
|
+
const contact = getContact(contactId);
|
|
491
474
|
if (!contact) {
|
|
492
475
|
return httpError("NOT_FOUND", `Contact "${contactId}" not found`, 404);
|
|
493
476
|
}
|
|
@@ -555,7 +538,6 @@ export async function handleVerifyContactChannel(
|
|
|
555
538
|
}
|
|
556
539
|
|
|
557
540
|
const sessionResult = createOutboundSession({
|
|
558
|
-
assistantId,
|
|
559
541
|
channel: verificationChannel,
|
|
560
542
|
expectedPhoneE164: phoneE164,
|
|
561
543
|
expectedExternalUserId: channel.externalUserId ?? undefined,
|
|
@@ -589,7 +571,6 @@ export async function handleVerifyContactChannel(
|
|
|
589
571
|
// Telegram with known chat ID: identity is already bound
|
|
590
572
|
if (channel.externalChatId) {
|
|
591
573
|
const sessionResult = createOutboundSession({
|
|
592
|
-
assistantId,
|
|
593
574
|
channel: verificationChannel,
|
|
594
575
|
expectedChatId: channel.externalChatId,
|
|
595
576
|
expectedExternalUserId: channel.externalUserId ?? undefined,
|
|
@@ -639,7 +620,6 @@ export async function handleVerifyContactChannel(
|
|
|
639
620
|
.digest("hex");
|
|
640
621
|
|
|
641
622
|
const sessionResult = createOutboundSession({
|
|
642
|
-
assistantId,
|
|
643
623
|
channel: verificationChannel,
|
|
644
624
|
identityBindingStatus: "pending_bootstrap",
|
|
645
625
|
destinationAddress: effectiveDestination,
|
|
@@ -675,7 +655,6 @@ export async function handleVerifyContactChannel(
|
|
|
675
655
|
}
|
|
676
656
|
|
|
677
657
|
const sessionResult = createOutboundSession({
|
|
678
|
-
assistantId,
|
|
679
658
|
channel: verificationChannel,
|
|
680
659
|
expectedExternalUserId: channel.externalUserId ?? undefined,
|
|
681
660
|
expectedChatId: channel.externalChatId ?? undefined,
|
|
@@ -721,27 +700,24 @@ export function contactRouteDefinitions(): RouteDefinition[] {
|
|
|
721
700
|
{
|
|
722
701
|
endpoint: "contacts",
|
|
723
702
|
method: "GET",
|
|
724
|
-
handler: ({ url
|
|
725
|
-
handleListContacts(url, authContext.assistantId),
|
|
703
|
+
handler: ({ url }) => handleListContacts(url),
|
|
726
704
|
},
|
|
727
705
|
{
|
|
728
706
|
endpoint: "contacts",
|
|
729
707
|
method: "POST",
|
|
730
|
-
handler: async ({ req
|
|
731
|
-
handleUpsertContact(req, authContext.assistantId),
|
|
708
|
+
handler: async ({ req }) => handleUpsertContact(req),
|
|
732
709
|
},
|
|
733
710
|
{
|
|
734
711
|
endpoint: "contacts/merge",
|
|
735
712
|
method: "POST",
|
|
736
|
-
handler: async ({ req
|
|
737
|
-
handleMergeContacts(req, authContext.assistantId),
|
|
713
|
+
handler: async ({ req }) => handleMergeContacts(req),
|
|
738
714
|
},
|
|
739
715
|
{
|
|
740
716
|
endpoint: "contacts/channels/:id",
|
|
741
717
|
method: "PATCH",
|
|
742
718
|
policyKey: "contacts/channels",
|
|
743
|
-
handler: async ({ req, params
|
|
744
|
-
handleUpdateContactChannel(req, params.id
|
|
719
|
+
handler: async ({ req, params }) =>
|
|
720
|
+
handleUpdateContactChannel(req, params.id),
|
|
745
721
|
},
|
|
746
722
|
{
|
|
747
723
|
endpoint: "contacts/:contactId/channels/:channelId/verify",
|
|
@@ -768,8 +744,7 @@ export function contactCatchAllRouteDefinitions(): RouteDefinition[] {
|
|
|
768
744
|
endpoint: "contacts/:id",
|
|
769
745
|
method: "GET",
|
|
770
746
|
policyKey: "contacts",
|
|
771
|
-
handler: ({ params
|
|
772
|
-
handleGetContact(params.id, authContext.assistantId),
|
|
747
|
+
handler: ({ params }) => handleGetContact(params.id),
|
|
773
748
|
},
|
|
774
749
|
];
|
|
775
750
|
}
|
|
@@ -10,7 +10,6 @@ import {
|
|
|
10
10
|
} from "../../memory/conversation-attention-store.js";
|
|
11
11
|
import * as conversationStore from "../../memory/conversation-store.js";
|
|
12
12
|
import { truncate } from "../../util/truncate.js";
|
|
13
|
-
import { DAEMON_INTERNAL_ASSISTANT_ID } from "../assistant-scope.js";
|
|
14
13
|
import { httpError } from "../http-errors.js";
|
|
15
14
|
import type { RouteDefinition } from "../http-router.js";
|
|
16
15
|
|
|
@@ -38,7 +37,6 @@ export function handleListConversationAttention(url: URL): Response {
|
|
|
38
37
|
}
|
|
39
38
|
|
|
40
39
|
const attentionStates = listConversationAttention({
|
|
41
|
-
assistantId: DAEMON_INTERNAL_ASSISTANT_ID,
|
|
42
40
|
state: stateParam as AttentionFilterState,
|
|
43
41
|
sourceChannel: channel,
|
|
44
42
|
source: sourceParam !== "all" ? sourceParam : undefined,
|
|
@@ -19,7 +19,6 @@ import { rawAll } from "../../memory/raw-query.js";
|
|
|
19
19
|
import { semanticSearch } from "../../memory/search/semantic.js";
|
|
20
20
|
import { listSchedules } from "../../schedule/schedule-store.js";
|
|
21
21
|
import { getLogger } from "../../util/logger.js";
|
|
22
|
-
import { DAEMON_INTERNAL_ASSISTANT_ID } from "../assistant-scope.js";
|
|
23
22
|
import { httpError } from "../http-errors.js";
|
|
24
23
|
import type { RouteDefinition } from "../http-router.js";
|
|
25
24
|
|
|
@@ -250,7 +249,6 @@ export async function handleGlobalSearch(url: URL): Promise<Response> {
|
|
|
250
249
|
|
|
251
250
|
if (categories.has("contacts")) {
|
|
252
251
|
const contactResults = searchContacts({
|
|
253
|
-
assistantId: DAEMON_INTERNAL_ASSISTANT_ID,
|
|
254
252
|
query,
|
|
255
253
|
limit,
|
|
256
254
|
});
|
|
@@ -45,7 +45,7 @@ function ensureGuardianPrincipal(assistantId: string): {
|
|
|
45
45
|
guardianPrincipalId: string;
|
|
46
46
|
isNew: boolean;
|
|
47
47
|
} {
|
|
48
|
-
const guardianResult = findGuardianForChannel("vellum"
|
|
48
|
+
const guardianResult = findGuardianForChannel("vellum");
|
|
49
49
|
if (guardianResult && guardianResult.contact.principalId) {
|
|
50
50
|
return {
|
|
51
51
|
guardianPrincipalId: guardianResult.contact.principalId,
|
|
@@ -57,7 +57,6 @@ function ensureGuardianPrincipal(assistantId: string): {
|
|
|
57
57
|
const guardianPrincipalId = `vellum-principal-${uuid()}`;
|
|
58
58
|
|
|
59
59
|
createGuardianBinding({
|
|
60
|
-
assistantId,
|
|
61
60
|
channel: "vellum",
|
|
62
61
|
guardianExternalUserId: guardianPrincipalId,
|
|
63
62
|
guardianDeliveryChatId: "local",
|
|
@@ -115,7 +114,7 @@ export async function handleGuardianBootstrap(
|
|
|
115
114
|
);
|
|
116
115
|
}
|
|
117
116
|
|
|
118
|
-
if (platform !== "macos" && platform !== "cli") {
|
|
117
|
+
if (platform !== "macos" && platform !== "cli" && platform !== "web") {
|
|
119
118
|
return httpError(
|
|
120
119
|
"BAD_REQUEST",
|
|
121
120
|
"Invalid platform. Bootstrap is macOS/CLI-only; iOS uses QR pairing.",
|
|
@@ -123,13 +122,13 @@ export async function handleGuardianBootstrap(
|
|
|
123
122
|
);
|
|
124
123
|
}
|
|
125
124
|
|
|
126
|
-
const
|
|
127
|
-
|
|
125
|
+
const { guardianPrincipalId, isNew } = ensureGuardianPrincipal(
|
|
126
|
+
DAEMON_INTERNAL_ASSISTANT_ID,
|
|
127
|
+
);
|
|
128
128
|
const hashedDeviceId = hashDeviceId(deviceId);
|
|
129
129
|
|
|
130
130
|
// Mint credential pair (access token + refresh token)
|
|
131
131
|
const credentials = mintCredentialPair({
|
|
132
|
-
assistantId,
|
|
133
132
|
platform,
|
|
134
133
|
deviceId,
|
|
135
134
|
guardianPrincipalId,
|
|
@@ -137,7 +136,7 @@ export async function handleGuardianBootstrap(
|
|
|
137
136
|
});
|
|
138
137
|
|
|
139
138
|
log.info(
|
|
140
|
-
{
|
|
139
|
+
{ platform, guardianPrincipalId, isNew },
|
|
141
140
|
"Guardian bootstrap completed",
|
|
142
141
|
);
|
|
143
142
|
|
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
} from "../../memory/channel-guardian-store.js";
|
|
10
10
|
import { getLogger } from "../../util/logger.js";
|
|
11
11
|
import { composeApprovalMessageGenerative } from "../approval-message-composer.js";
|
|
12
|
+
import { DAEMON_INTERNAL_ASSISTANT_ID } from "../assistant-scope.js";
|
|
12
13
|
import type { ApprovalDecisionResult } from "../channel-approval-types.js";
|
|
13
14
|
import { handleChannelDecision } from "../channel-approvals.js";
|
|
14
15
|
import { deliverChannelReply } from "../gateway-client.js";
|
|
@@ -68,7 +69,7 @@ export function sweepExpiredGuardianApprovals(
|
|
|
68
69
|
{
|
|
69
70
|
chatId: approval.requesterChatId,
|
|
70
71
|
text: requesterText,
|
|
71
|
-
assistantId:
|
|
72
|
+
assistantId: DAEMON_INTERNAL_ASSISTANT_ID,
|
|
72
73
|
},
|
|
73
74
|
mintBearerToken?.(),
|
|
74
75
|
);
|
|
@@ -96,7 +97,7 @@ export function sweepExpiredGuardianApprovals(
|
|
|
96
97
|
{
|
|
97
98
|
chatId: approval.guardianChatId,
|
|
98
99
|
text: guardianText,
|
|
99
|
-
assistantId:
|
|
100
|
+
assistantId: DAEMON_INTERNAL_ASSISTANT_ID,
|
|
100
101
|
},
|
|
101
102
|
mintBearerToken?.(),
|
|
102
103
|
);
|
|
@@ -501,7 +501,6 @@ export async function handleChannelInbound(
|
|
|
501
501
|
: body.callbackData!;
|
|
502
502
|
recordConversationSeenSignal({
|
|
503
503
|
conversationId: result.conversationId,
|
|
504
|
-
assistantId: canonicalAssistantId,
|
|
505
504
|
signalType: `${sourceChannel}_callback` as SignalType,
|
|
506
505
|
confidence: "inferred",
|
|
507
506
|
sourceChannel,
|
|
@@ -515,7 +514,6 @@ export async function handleChannelInbound(
|
|
|
515
514
|
: trimmedContent;
|
|
516
515
|
recordConversationSeenSignal({
|
|
517
516
|
conversationId: result.conversationId,
|
|
518
|
-
assistantId: canonicalAssistantId,
|
|
519
517
|
signalType: `${sourceChannel}_inbound_message` as SignalType,
|
|
520
518
|
confidence: "inferred",
|
|
521
519
|
sourceChannel,
|
|
@@ -554,7 +552,6 @@ export async function handleChannelInbound(
|
|
|
554
552
|
: body.callbackData!;
|
|
555
553
|
recordConversationSeenSignal({
|
|
556
554
|
conversationId: result.conversationId,
|
|
557
|
-
assistantId: canonicalAssistantId,
|
|
558
555
|
signalType: `${sourceChannel}_callback` as SignalType,
|
|
559
556
|
confidence: "inferred",
|
|
560
557
|
sourceChannel,
|
|
@@ -191,14 +191,8 @@ export async function enforceIngressAcl(
|
|
|
191
191
|
// omitted: rebind sessions create a consumable challenge while a
|
|
192
192
|
// binding already exists, and the identity check inside
|
|
193
193
|
// validateAndConsumeChallenge prevents unauthorized takeovers.
|
|
194
|
-
const hasPendingChallenge = !!getPendingChallenge(
|
|
195
|
-
|
|
196
|
-
sourceChannel,
|
|
197
|
-
);
|
|
198
|
-
const hasActiveOutboundSession = !!findActiveSession(
|
|
199
|
-
canonicalAssistantId,
|
|
200
|
-
sourceChannel,
|
|
201
|
-
);
|
|
194
|
+
const hasPendingChallenge = !!getPendingChallenge(sourceChannel);
|
|
195
|
+
const hasActiveOutboundSession = !!findActiveSession(sourceChannel);
|
|
202
196
|
if (hasPendingChallenge || hasActiveOutboundSession) {
|
|
203
197
|
denyNonMember = false;
|
|
204
198
|
} else {
|
|
@@ -219,7 +213,6 @@ export async function enforceIngressAcl(
|
|
|
219
213
|
).payload as string;
|
|
220
214
|
const bootstrapTokenForAcl = bootstrapPayload.slice(3); // strip 'gv_' prefix
|
|
221
215
|
const bootstrapSessionForAcl = resolveBootstrapToken(
|
|
222
|
-
canonicalAssistantId,
|
|
223
216
|
sourceChannel,
|
|
224
217
|
bootstrapTokenForAcl,
|
|
225
218
|
);
|
|
@@ -299,13 +292,8 @@ export async function enforceIngressAcl(
|
|
|
299
292
|
// user can reply with the code in the DM to self-verify.
|
|
300
293
|
if (sourceChannel === "slack" && (canonicalSenderId ?? rawSenderId)) {
|
|
301
294
|
const slackVerifyResult = initiateSlackVerificationChallenge({
|
|
302
|
-
canonicalAssistantId,
|
|
303
295
|
sourceChannel,
|
|
304
|
-
conversationExternalId,
|
|
305
296
|
senderUserId: (canonicalSenderId ?? rawSenderId)!,
|
|
306
|
-
replyCallbackUrl,
|
|
307
|
-
mintBearerToken,
|
|
308
|
-
assistantId,
|
|
309
297
|
});
|
|
310
298
|
|
|
311
299
|
if (slackVerifyResult.initiated) {
|
|
@@ -420,14 +408,8 @@ export async function enforceIngressAcl(
|
|
|
420
408
|
// revoked/blocked — otherwise the user can never re-verify.
|
|
421
409
|
let denyInactiveMember = true;
|
|
422
410
|
if (isGuardianVerifyCode) {
|
|
423
|
-
const hasPendingChallenge = !!getPendingChallenge(
|
|
424
|
-
|
|
425
|
-
sourceChannel,
|
|
426
|
-
);
|
|
427
|
-
const hasActiveOutboundSession = !!findActiveSession(
|
|
428
|
-
canonicalAssistantId,
|
|
429
|
-
sourceChannel,
|
|
430
|
-
);
|
|
411
|
+
const hasPendingChallenge = !!getPendingChallenge(sourceChannel);
|
|
412
|
+
const hasActiveOutboundSession = !!findActiveSession(sourceChannel);
|
|
431
413
|
if (hasPendingChallenge || hasActiveOutboundSession) {
|
|
432
414
|
denyInactiveMember = false;
|
|
433
415
|
} else {
|
|
@@ -448,7 +430,6 @@ export async function enforceIngressAcl(
|
|
|
448
430
|
).payload as string;
|
|
449
431
|
const bootstrapTokenForAcl = bootstrapPayload.slice(3);
|
|
450
432
|
const bootstrapSessionForAcl = resolveBootstrapToken(
|
|
451
|
-
canonicalAssistantId,
|
|
452
433
|
sourceChannel,
|
|
453
434
|
bootstrapTokenForAcl,
|
|
454
435
|
);
|
|
@@ -538,13 +519,8 @@ export async function enforceIngressAcl(
|
|
|
538
519
|
(canonicalSenderId ?? rawSenderId)
|
|
539
520
|
) {
|
|
540
521
|
const slackVerifyResult = initiateSlackVerificationChallenge({
|
|
541
|
-
canonicalAssistantId,
|
|
542
522
|
sourceChannel,
|
|
543
|
-
conversationExternalId,
|
|
544
523
|
senderUserId: (canonicalSenderId ?? rawSenderId)!,
|
|
545
|
-
replyCallbackUrl,
|
|
546
|
-
mintBearerToken,
|
|
547
|
-
assistantId,
|
|
548
524
|
});
|
|
549
525
|
|
|
550
526
|
if (slackVerifyResult.initiated) {
|
|
@@ -1098,28 +1074,17 @@ interface SlackVerificationResult {
|
|
|
1098
1074
|
* creates a trusted contact record (not a guardian binding).
|
|
1099
1075
|
*/
|
|
1100
1076
|
function initiateSlackVerificationChallenge(params: {
|
|
1101
|
-
canonicalAssistantId: string;
|
|
1102
1077
|
sourceChannel: ChannelId;
|
|
1103
|
-
conversationExternalId: string;
|
|
1104
1078
|
senderUserId: string;
|
|
1105
|
-
replyCallbackUrl: string | undefined;
|
|
1106
|
-
mintBearerToken: () => string;
|
|
1107
|
-
assistantId: string;
|
|
1108
1079
|
}): SlackVerificationResult {
|
|
1109
|
-
const {
|
|
1080
|
+
const { sourceChannel, senderUserId } = params;
|
|
1110
1081
|
|
|
1111
1082
|
// Skip if there is already a pending challenge or active session for
|
|
1112
1083
|
// this sender to avoid flooding them with duplicate codes. We scope by
|
|
1113
1084
|
// sender identity (expectedExternalUserId) so that a pending session for
|
|
1114
1085
|
// user A does not suppress challenges for user B.
|
|
1115
|
-
const existingChallenge = getPendingChallenge(
|
|
1116
|
-
|
|
1117
|
-
sourceChannel,
|
|
1118
|
-
);
|
|
1119
|
-
const existingSession = findActiveSession(
|
|
1120
|
-
canonicalAssistantId,
|
|
1121
|
-
sourceChannel,
|
|
1122
|
-
);
|
|
1086
|
+
const existingChallenge = getPendingChallenge(sourceChannel);
|
|
1087
|
+
const existingSession = findActiveSession(sourceChannel);
|
|
1123
1088
|
const senderHasPending =
|
|
1124
1089
|
(existingChallenge &&
|
|
1125
1090
|
existingChallenge.expectedExternalUserId === senderUserId) ||
|
|
@@ -1140,7 +1105,6 @@ function initiateSlackVerificationChallenge(params: {
|
|
|
1140
1105
|
|
|
1141
1106
|
try {
|
|
1142
1107
|
const session = createOutboundSession({
|
|
1143
|
-
assistantId: canonicalAssistantId,
|
|
1144
1108
|
channel: sourceChannel,
|
|
1145
1109
|
expectedExternalUserId: senderUserId,
|
|
1146
1110
|
expectedChatId: senderUserId,
|
|
@@ -507,10 +507,7 @@ export function startTrustedContactApprovalNotifier(params: {
|
|
|
507
507
|
|
|
508
508
|
if (info && !globalNotifiedApprovalRequestIds.has(info.requestId)) {
|
|
509
509
|
globalNotifiedApprovalRequestIds.set(info.requestId, conversationId);
|
|
510
|
-
const guardian = findGuardianForChannel(
|
|
511
|
-
sourceChannel,
|
|
512
|
-
assistantId ?? DAEMON_INTERNAL_ASSISTANT_ID,
|
|
513
|
-
);
|
|
510
|
+
const guardian = findGuardianForChannel(sourceChannel);
|
|
514
511
|
const guardianName = resolveGuardianName(
|
|
515
512
|
guardian?.contact.displayName,
|
|
516
513
|
);
|
|
@@ -74,11 +74,7 @@ export async function handleBootstrapIntercept(
|
|
|
74
74
|
}
|
|
75
75
|
|
|
76
76
|
const bootstrapToken = (commandIntent.payload as string).slice(3);
|
|
77
|
-
const bootstrapSession = resolveBootstrapToken(
|
|
78
|
-
canonicalAssistantId,
|
|
79
|
-
sourceChannel,
|
|
80
|
-
bootstrapToken,
|
|
81
|
-
);
|
|
77
|
+
const bootstrapSession = resolveBootstrapToken(sourceChannel, bootstrapToken);
|
|
82
78
|
|
|
83
79
|
if (!bootstrapSession || bootstrapSession.status !== "pending_bootstrap") {
|
|
84
80
|
// Not found or expired — fall through to normal /start handling
|
|
@@ -94,7 +90,6 @@ export async function handleBootstrapIntercept(
|
|
|
94
90
|
// Create a new identity-bound outbound session with a fresh secret.
|
|
95
91
|
// The old bootstrap session is auto-revoked by createOutboundSession.
|
|
96
92
|
const newSession = createOutboundSession({
|
|
97
|
-
assistantId: canonicalAssistantId,
|
|
98
93
|
channel: sourceChannel,
|
|
99
94
|
expectedExternalUserId: rawSenderId,
|
|
100
95
|
expectedChatId: conversationExternalId,
|
|
@@ -115,7 +115,6 @@ export function runSecretIngressCheck(params: SecretIngressCheckParams): void {
|
|
|
115
115
|
: "User sent media attachment";
|
|
116
116
|
recordConversationSeenSignal({
|
|
117
117
|
conversationId,
|
|
118
|
-
assistantId: canonicalAssistantId,
|
|
119
118
|
signalType: "telegram_inbound_message",
|
|
120
119
|
confidence: "inferred",
|
|
121
120
|
sourceChannel: "telegram",
|