@vellumai/assistant 0.3.28 → 0.4.1
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 +33 -3
- package/bun.lock +4 -1
- package/docs/trusted-contact-access.md +9 -2
- package/package.json +6 -3
- package/scripts/ipc/generate-swift.ts +3 -3
- package/src/__tests__/__snapshots__/ipc-snapshot.test.ts.snap +80 -0
- package/src/__tests__/agent-loop-thinking.test.ts +1 -1
- package/src/__tests__/approval-routes-http.test.ts +13 -5
- package/src/__tests__/asset-materialize-tool.test.ts +2 -0
- package/src/__tests__/asset-search-tool.test.ts +2 -0
- package/src/__tests__/assistant-events-sse-hardening.test.ts +4 -2
- package/src/__tests__/attachments-store.test.ts +2 -0
- package/src/__tests__/browser-skill-endstate.test.ts +3 -3
- package/src/__tests__/call-controller.test.ts +30 -29
- package/src/__tests__/call-routes-http.test.ts +34 -32
- package/src/__tests__/call-start-guardian-guard.test.ts +2 -0
- package/src/__tests__/channel-invite-transport.test.ts +6 -6
- package/src/__tests__/channel-reply-delivery.test.ts +19 -0
- package/src/__tests__/channel-retry-sweep.test.ts +130 -0
- package/src/__tests__/clarification-resolver.test.ts +2 -0
- package/src/__tests__/claude-code-skill-regression.test.ts +2 -0
- package/src/__tests__/claude-code-tool-profiles.test.ts +2 -0
- package/src/__tests__/commit-message-enrichment-service.test.ts +9 -1
- package/src/__tests__/computer-use-session-lifecycle.test.ts +2 -0
- package/src/__tests__/computer-use-session-working-dir.test.ts +1 -0
- package/src/__tests__/computer-use-skill-lifecycle-cleanup.test.ts +2 -0
- package/src/__tests__/config-schema.test.ts +5 -5
- package/src/__tests__/config-watcher.test.ts +3 -1
- package/src/__tests__/connection-policy.test.ts +14 -5
- package/src/__tests__/contacts-tools.test.ts +3 -1
- package/src/__tests__/contradiction-checker.test.ts +2 -0
- package/src/__tests__/conversation-pairing.test.ts +10 -0
- package/src/__tests__/conversation-routes.test.ts +1 -1
- package/src/__tests__/credential-security-invariants.test.ts +16 -6
- package/src/__tests__/credential-vault-unit.test.ts +2 -2
- package/src/__tests__/credential-vault.test.ts +5 -4
- package/src/__tests__/daemon-lifecycle.test.ts +9 -0
- package/src/__tests__/daemon-server-session-init.test.ts +27 -0
- package/src/__tests__/elevenlabs-config.test.ts +2 -0
- package/src/__tests__/encrypted-store.test.ts +10 -5
- package/src/__tests__/followup-tools.test.ts +3 -1
- package/src/__tests__/gateway-only-enforcement.test.ts +21 -21
- package/src/__tests__/gmail-integration.test.ts +0 -1
- package/src/__tests__/guardian-control-plane-policy.test.ts +25 -21
- package/src/__tests__/guardian-dispatch.test.ts +2 -0
- package/src/__tests__/guardian-grant-minting.test.ts +68 -1
- package/src/__tests__/guardian-outbound-http.test.ts +12 -9
- package/src/__tests__/guardian-routing-invariants.test.ts +138 -0
- package/src/__tests__/handle-user-message-secret-resume.test.ts +1 -0
- package/src/__tests__/handlers-slack-config.test.ts +3 -1
- package/src/__tests__/handlers-telegram-config.test.ts +3 -1
- package/src/__tests__/handlers-twilio-config.test.ts +3 -1
- package/src/__tests__/handlers-twitter-config.test.ts +3 -1
- package/src/__tests__/handlers-user-message-approval-consumption.test.ts +318 -0
- package/src/__tests__/heartbeat-service.test.ts +20 -0
- package/src/__tests__/inbound-invite-redemption.test.ts +33 -0
- package/src/__tests__/ingress-reconcile.test.ts +3 -1
- package/src/__tests__/ingress-routes-http.test.ts +231 -4
- package/src/__tests__/intent-routing.test.ts +2 -0
- package/src/__tests__/ipc-snapshot.test.ts +13 -0
- package/src/__tests__/media-generate-image.test.ts +21 -0
- package/src/__tests__/media-reuse-story.e2e.test.ts +2 -0
- package/src/__tests__/memory-regressions.test.ts +20 -20
- package/src/__tests__/non-member-access-request.test.ts +183 -9
- package/src/__tests__/notification-decision-fallback.test.ts +2 -0
- package/src/__tests__/notification-decision-strategy.test.ts +61 -0
- package/src/__tests__/notification-guardian-path.test.ts +2 -0
- package/src/__tests__/oauth-connect-handler.test.ts +3 -1
- package/src/__tests__/oauth2-gateway-transport.test.ts +2 -0
- package/src/__tests__/onboarding-starter-tasks.test.ts +4 -4
- package/src/__tests__/pairing-routes.test.ts +171 -0
- package/src/__tests__/playbook-execution.test.ts +3 -1
- package/src/__tests__/playbook-tools.test.ts +3 -1
- package/src/__tests__/provider-error-scenarios.test.ts +59 -8
- package/src/__tests__/proxy-approval-callback.test.ts +2 -0
- package/src/__tests__/recording-handler.test.ts +11 -0
- package/src/__tests__/recording-intent-handler.test.ts +15 -0
- package/src/__tests__/recording-state-machine.test.ts +13 -2
- package/src/__tests__/registry.test.ts +7 -3
- package/src/__tests__/relay-server.test.ts +148 -28
- package/src/__tests__/runtime-attachment-metadata.test.ts +4 -2
- package/src/__tests__/runtime-events-sse-parity.test.ts +21 -0
- package/src/__tests__/runtime-events-sse.test.ts +4 -2
- package/src/__tests__/sandbox-diagnostics.test.ts +2 -0
- package/src/__tests__/schedule-tools.test.ts +3 -1
- package/src/__tests__/send-endpoint-busy.test.ts +288 -0
- package/src/__tests__/session-abort-tool-results.test.ts +23 -0
- package/src/__tests__/session-agent-loop.test.ts +16 -0
- package/src/__tests__/session-conflict-gate.test.ts +21 -0
- package/src/__tests__/session-load-history-repair.test.ts +27 -17
- package/src/__tests__/session-pre-run-repair.test.ts +23 -0
- package/src/__tests__/session-profile-injection.test.ts +21 -0
- package/src/__tests__/session-provider-retry-repair.test.ts +20 -0
- package/src/__tests__/session-queue.test.ts +23 -0
- package/src/__tests__/session-runtime-assembly.test.ts +50 -12
- package/src/__tests__/session-skill-tools.test.ts +27 -5
- package/src/__tests__/session-slash-known.test.ts +23 -0
- package/src/__tests__/session-slash-queue.test.ts +23 -0
- package/src/__tests__/session-slash-unknown.test.ts +23 -0
- package/src/__tests__/session-workspace-cache-state.test.ts +7 -0
- package/src/__tests__/session-workspace-injection.test.ts +21 -0
- package/src/__tests__/session-workspace-tool-tracking.test.ts +21 -0
- package/src/__tests__/shell-credential-ref.test.ts +2 -0
- package/src/__tests__/skill-feature-flags-integration.test.ts +6 -6
- package/src/__tests__/skill-load-feature-flag.test.ts +5 -4
- package/src/__tests__/skill-projection-feature-flag.test.ts +22 -0
- package/src/__tests__/skills.test.ts +8 -4
- package/src/__tests__/slack-channel-config.test.ts +3 -1
- package/src/__tests__/subagent-tools.test.ts +19 -0
- package/src/__tests__/swarm-recursion.test.ts +2 -0
- package/src/__tests__/swarm-session-integration.test.ts +2 -0
- package/src/__tests__/swarm-tool.test.ts +2 -0
- package/src/__tests__/system-prompt.test.ts +3 -1
- package/src/__tests__/task-compiler.test.ts +3 -1
- package/src/__tests__/task-management-tools.test.ts +3 -1
- package/src/__tests__/task-tools.test.ts +3 -1
- package/src/__tests__/terminal-sandbox.test.ts +13 -12
- package/src/__tests__/terminal-tools.test.ts +2 -0
- package/src/__tests__/tool-approval-handler.test.ts +15 -15
- package/src/__tests__/tool-execution-abort-cleanup.test.ts +2 -0
- package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +2 -0
- package/src/__tests__/tool-grant-request-escalation.test.ts +7 -7
- package/src/__tests__/trusted-contact-lifecycle-notifications.test.ts +48 -0
- package/src/__tests__/trusted-contact-multichannel.test.ts +22 -19
- package/src/__tests__/trusted-contact-verification.test.ts +91 -0
- package/src/__tests__/twilio-routes-elevenlabs.test.ts +2 -0
- package/src/__tests__/twitter-auth-handler.test.ts +3 -1
- package/src/__tests__/twitter-cli-routing.test.ts +3 -1
- package/src/__tests__/view-image-tool.test.ts +3 -1
- package/src/__tests__/voice-invite-redemption.test.ts +329 -0
- package/src/__tests__/voice-scoped-grant-consumer.test.ts +7 -5
- package/src/__tests__/voice-session-bridge.test.ts +10 -10
- package/src/__tests__/work-item-output.test.ts +3 -1
- package/src/__tests__/workspace-lifecycle.test.ts +13 -2
- package/src/calls/call-controller.ts +26 -23
- package/src/calls/guardian-action-sweep.ts +10 -2
- package/src/calls/relay-server.ts +216 -27
- package/src/calls/types.ts +1 -1
- package/src/calls/voice-session-bridge.ts +3 -3
- package/src/cli.ts +12 -0
- package/src/config/agent-schema.ts +14 -3
- package/src/config/calls-schema.ts +6 -6
- package/src/config/core-schema.ts +3 -3
- package/src/config/feature-flag-registry.json +8 -0
- package/src/config/mcp-schema.ts +1 -1
- package/src/config/memory-schema.ts +27 -19
- package/src/config/schema.ts +21 -21
- package/src/config/skills-schema.ts +7 -7
- package/src/config/vellum-skills/trusted-contacts/SKILL.md +139 -16
- package/src/daemon/handlers/config-inbox.ts +4 -4
- package/src/daemon/handlers/sessions.ts +148 -4
- package/src/daemon/ipc-contract/messages.ts +16 -0
- package/src/daemon/ipc-contract-inventory.json +1 -0
- package/src/daemon/lifecycle.ts +19 -0
- package/src/daemon/pairing-store.ts +86 -3
- package/src/daemon/response-tier.ts +6 -5
- package/src/daemon/session-agent-loop.ts +5 -5
- package/src/daemon/session-lifecycle.ts +25 -17
- package/src/daemon/session-memory.ts +2 -2
- package/src/daemon/session-process.ts +1 -20
- package/src/daemon/session-runtime-assembly.ts +28 -22
- package/src/daemon/session-tool-setup.ts +2 -2
- package/src/daemon/session.ts +3 -3
- package/src/memory/canonical-guardian-store.ts +63 -1
- package/src/memory/channel-guardian-store.ts +1 -0
- package/src/memory/conversation-crud.ts +7 -7
- package/src/memory/db-init.ts +4 -0
- package/src/memory/embedding-local.ts +257 -39
- package/src/memory/embedding-runtime-manager.ts +471 -0
- package/src/memory/guardian-bindings.ts +25 -1
- package/src/memory/indexer.ts +3 -3
- package/src/memory/ingress-invite-store.ts +45 -0
- package/src/memory/job-handlers/backfill.ts +16 -9
- package/src/memory/migrations/037-voice-invite-columns.ts +16 -0
- package/src/memory/migrations/index.ts +1 -0
- package/src/memory/qdrant-client.ts +31 -22
- package/src/memory/schema.ts +4 -0
- package/src/notifications/copy-composer.ts +15 -0
- package/src/runtime/access-request-helper.ts +43 -7
- package/src/runtime/actor-trust-resolver.ts +46 -50
- package/src/runtime/channel-invite-transports/voice.ts +58 -0
- package/src/runtime/channel-retry-sweep.ts +18 -6
- package/src/runtime/guardian-context-resolver.ts +38 -96
- package/src/runtime/guardian-reply-router.ts +31 -1
- package/src/runtime/ingress-service.ts +80 -3
- package/src/runtime/invite-redemption-service.ts +141 -2
- package/src/runtime/routes/channel-route-shared.ts +1 -1
- package/src/runtime/routes/channel-routes.ts +1 -1
- package/src/runtime/routes/conversation-routes.ts +166 -2
- package/src/runtime/routes/guardian-approval-interception.ts +17 -6
- package/src/runtime/routes/inbound-message-handler.ts +41 -10
- package/src/runtime/routes/ingress-routes.ts +52 -4
- package/src/runtime/routes/pairing-routes.ts +3 -0
- package/src/tools/guardian-control-plane-policy.ts +2 -2
- package/src/tools/reminder/reminder-store.ts +10 -14
- package/src/tools/tool-approval-handler.ts +11 -11
- package/src/tools/types.ts +2 -2
- package/src/util/logger.ts +20 -8
- package/src/util/platform.ts +10 -0
- package/src/util/voice-code.ts +29 -0
- package/src/daemon/guardian-invite-intent.ts +0 -124
|
@@ -38,6 +38,7 @@ export { createScopedApprovalGrantsTable } from './033-scoped-approval-grants.js
|
|
|
38
38
|
export { migrateGuardianActionToolMetadata } from './034-guardian-action-tool-metadata.js';
|
|
39
39
|
export { migrateGuardianActionSupersession } from './035-guardian-action-supersession.js';
|
|
40
40
|
export { migrateNormalizePhoneIdentities } from './036-normalize-phone-identities.js';
|
|
41
|
+
export { migrateVoiceInviteColumns } from './037-voice-invite-columns.js';
|
|
41
42
|
export { createCoreTables } from './100-core-tables.js';
|
|
42
43
|
export { createWatchersAndLogsTables } from './101-watchers-and-logs.js';
|
|
43
44
|
export { addCoreColumns } from './102-alter-table-columns.js';
|
|
@@ -80,28 +80,37 @@ export class VellumQdrantClient {
|
|
|
80
80
|
|
|
81
81
|
log.info({ collection: this.collection, vectorSize: this.vectorSize }, 'Creating Qdrant collection');
|
|
82
82
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
83
|
+
try {
|
|
84
|
+
await this.client.createCollection(this.collection, {
|
|
85
|
+
vectors: {
|
|
86
|
+
size: this.vectorSize,
|
|
87
|
+
distance: 'Cosine',
|
|
88
|
+
on_disk: this.onDisk,
|
|
89
|
+
},
|
|
90
|
+
hnsw_config: {
|
|
91
|
+
on_disk: this.onDisk,
|
|
92
|
+
m: 16,
|
|
93
|
+
ef_construct: 100,
|
|
94
|
+
},
|
|
95
|
+
quantization_config: this.quantization === 'scalar'
|
|
96
|
+
? {
|
|
97
|
+
scalar: {
|
|
98
|
+
type: 'int8',
|
|
99
|
+
quantile: 0.99,
|
|
100
|
+
always_ram: true,
|
|
101
|
+
},
|
|
102
|
+
}
|
|
103
|
+
: undefined,
|
|
104
|
+
on_disk_payload: this.onDisk,
|
|
105
|
+
});
|
|
106
|
+
} catch (err) {
|
|
107
|
+
// 409 = collection was created by a concurrent caller — that's fine
|
|
108
|
+
if (err instanceof Error && 'status' in err && (err as { status: number }).status === 409) {
|
|
109
|
+
this.collectionReady = true;
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
throw err;
|
|
113
|
+
}
|
|
105
114
|
|
|
106
115
|
// Create payload indexes for efficient filtering
|
|
107
116
|
await Promise.all([
|
package/src/memory/schema.ts
CHANGED
|
@@ -942,6 +942,10 @@ export const assistantIngressInvites = sqliteTable('assistant_ingress_invites',
|
|
|
942
942
|
redeemedByExternalUserId: text('redeemed_by_external_user_id'),
|
|
943
943
|
redeemedByExternalChatId: text('redeemed_by_external_chat_id'),
|
|
944
944
|
redeemedAt: integer('redeemed_at'),
|
|
945
|
+
// Voice invite fields (nullable — non-voice invites leave these NULL)
|
|
946
|
+
expectedExternalUserId: text('expected_external_user_id'),
|
|
947
|
+
voiceCodeHash: text('voice_code_hash'),
|
|
948
|
+
voiceCodeDigits: integer('voice_code_digits'),
|
|
945
949
|
createdAt: integer('created_at').notNull(),
|
|
946
950
|
updatedAt: integer('updated_at').notNull(),
|
|
947
951
|
});
|
|
@@ -54,6 +54,21 @@ const TEMPLATES: Record<string, CopyTemplate> = {
|
|
|
54
54
|
};
|
|
55
55
|
},
|
|
56
56
|
|
|
57
|
+
'ingress.access_request': (payload) => {
|
|
58
|
+
const requester = str(payload.senderIdentifier, 'Someone');
|
|
59
|
+
const requestCode = nonEmpty(typeof payload.requestCode === 'string' ? payload.requestCode : undefined);
|
|
60
|
+
const lines: string[] = [`${requester} is requesting access to the assistant.`];
|
|
61
|
+
if (requestCode) {
|
|
62
|
+
const code = requestCode.toUpperCase();
|
|
63
|
+
lines.push(`Reply "${code} approve" to grant access or "${code} reject" to deny.`);
|
|
64
|
+
}
|
|
65
|
+
lines.push('Reply "open invite flow" to start Trusted Contacts invite flow.');
|
|
66
|
+
return {
|
|
67
|
+
title: 'Access Request',
|
|
68
|
+
body: lines.join('\n'),
|
|
69
|
+
};
|
|
70
|
+
},
|
|
71
|
+
|
|
57
72
|
'ingress.escalation': (payload) => ({
|
|
58
73
|
title: 'Escalation',
|
|
59
74
|
body: str(payload.senderIdentifier, 'An incoming message') + ' needs attention',
|
|
@@ -4,6 +4,13 @@
|
|
|
4
4
|
* Encapsulates the "create/dedupe canonical access request + emit notification"
|
|
5
5
|
* logic so both text-channel and voice-channel ingress paths use identical
|
|
6
6
|
* guardian notification flows.
|
|
7
|
+
*
|
|
8
|
+
* Access requests are a special case: they always create a canonical request
|
|
9
|
+
* and emit a notification signal, even when no same-channel guardian binding
|
|
10
|
+
* exists. Guardian binding resolution uses a fallback strategy:
|
|
11
|
+
* 1. Source-channel active binding first.
|
|
12
|
+
* 2. Any active binding for the assistant (deterministic, most-recently-verified).
|
|
13
|
+
* 3. No guardian identity (trusted/vellum-only resolution path).
|
|
7
14
|
*/
|
|
8
15
|
|
|
9
16
|
import type { ChannelId } from '../channels/types.js';
|
|
@@ -11,6 +18,7 @@ import {
|
|
|
11
18
|
createCanonicalGuardianRequest,
|
|
12
19
|
listCanonicalGuardianRequests,
|
|
13
20
|
} from '../memory/canonical-guardian-store.js';
|
|
21
|
+
import { listActiveBindingsByAssistant } from '../memory/channel-guardian-store.js';
|
|
14
22
|
import { emitNotificationSignal } from '../notifications/emit-signal.js';
|
|
15
23
|
import { getLogger } from '../util/logger.js';
|
|
16
24
|
import { getGuardianBinding } from './channel-guardian-service.js';
|
|
@@ -33,7 +41,7 @@ export interface AccessRequestParams {
|
|
|
33
41
|
|
|
34
42
|
export type AccessRequestResult =
|
|
35
43
|
| { notified: true; created: boolean; requestId: string }
|
|
36
|
-
| { notified: false; reason: 'no_sender_id'
|
|
44
|
+
| { notified: false; reason: 'no_sender_id' };
|
|
37
45
|
|
|
38
46
|
// ---------------------------------------------------------------------------
|
|
39
47
|
// Helper
|
|
@@ -46,6 +54,10 @@ export type AccessRequestResult =
|
|
|
46
54
|
* Returns a result indicating whether the guardian was notified and whether
|
|
47
55
|
* a new request was created or an existing one was deduped.
|
|
48
56
|
*
|
|
57
|
+
* Guardian binding resolution: source-channel first, then any active binding
|
|
58
|
+
* for the assistant, then null (notification pipeline handles delivery via
|
|
59
|
+
* trusted/vellum channels when no binding exists).
|
|
60
|
+
*
|
|
49
61
|
* This is intentionally synchronous with respect to the canonical store writes
|
|
50
62
|
* and fire-and-forget for the notification signal emission.
|
|
51
63
|
*/
|
|
@@ -65,10 +77,32 @@ export function notifyGuardianOfAccessRequest(
|
|
|
65
77
|
return { notified: false, reason: 'no_sender_id' };
|
|
66
78
|
}
|
|
67
79
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
80
|
+
// Resolve guardian binding with fallback strategy:
|
|
81
|
+
// 1. Source-channel active binding
|
|
82
|
+
// 2. Any active binding for the assistant (deterministic order)
|
|
83
|
+
// 3. null (no guardian identity — notification pipeline uses trusted channels)
|
|
84
|
+
const sourceBinding = getGuardianBinding(canonicalAssistantId, sourceChannel);
|
|
85
|
+
let guardianExternalUserId: string | null = null;
|
|
86
|
+
let guardianBindingChannel: string | null = null;
|
|
87
|
+
|
|
88
|
+
if (sourceBinding) {
|
|
89
|
+
guardianExternalUserId = sourceBinding.guardianExternalUserId;
|
|
90
|
+
guardianBindingChannel = sourceBinding.channel;
|
|
91
|
+
} else {
|
|
92
|
+
const allBindings = listActiveBindingsByAssistant(canonicalAssistantId);
|
|
93
|
+
if (allBindings.length > 0) {
|
|
94
|
+
guardianExternalUserId = allBindings[0].guardianExternalUserId;
|
|
95
|
+
guardianBindingChannel = allBindings[0].channel;
|
|
96
|
+
log.debug(
|
|
97
|
+
{ sourceChannel, fallbackChannel: guardianBindingChannel, canonicalAssistantId },
|
|
98
|
+
'Using cross-channel guardian binding fallback for access request',
|
|
99
|
+
);
|
|
100
|
+
} else {
|
|
101
|
+
log.debug(
|
|
102
|
+
{ sourceChannel, canonicalAssistantId },
|
|
103
|
+
'No guardian binding for access request — proceeding without guardian identity',
|
|
104
|
+
);
|
|
105
|
+
}
|
|
72
106
|
}
|
|
73
107
|
|
|
74
108
|
// Deduplicate: skip creation if there is already a pending canonical request
|
|
@@ -99,7 +133,7 @@ export function notifyGuardianOfAccessRequest(
|
|
|
99
133
|
conversationId: `access-req-${sourceChannel}-${senderExternalUserId}`,
|
|
100
134
|
requesterExternalUserId: senderExternalUserId,
|
|
101
135
|
requesterChatId: externalChatId,
|
|
102
|
-
guardianExternalUserId:
|
|
136
|
+
guardianExternalUserId: guardianExternalUserId ?? undefined,
|
|
103
137
|
toolName: 'ingress_access_request',
|
|
104
138
|
questionText: `${senderIdentifier} is requesting access to the assistant`,
|
|
105
139
|
expiresAt: new Date(Date.now() + GUARDIAN_APPROVAL_TTL_MS).toISOString(),
|
|
@@ -118,18 +152,20 @@ export function notifyGuardianOfAccessRequest(
|
|
|
118
152
|
},
|
|
119
153
|
contextPayload: {
|
|
120
154
|
requestId,
|
|
155
|
+
requestCode: canonicalRequest.requestCode,
|
|
121
156
|
sourceChannel,
|
|
122
157
|
externalChatId,
|
|
123
158
|
senderExternalUserId,
|
|
124
159
|
senderName: senderName ?? null,
|
|
125
160
|
senderUsername: senderUsername ?? null,
|
|
126
161
|
senderIdentifier,
|
|
162
|
+
guardianBindingChannel,
|
|
127
163
|
},
|
|
128
164
|
dedupeKey: `access-request:${canonicalRequest.id}`,
|
|
129
165
|
});
|
|
130
166
|
|
|
131
167
|
log.info(
|
|
132
|
-
{ sourceChannel, senderExternalUserId, senderIdentifier },
|
|
168
|
+
{ sourceChannel, senderExternalUserId, senderIdentifier, guardianBindingChannel },
|
|
133
169
|
'Guardian notified of access request',
|
|
134
170
|
);
|
|
135
171
|
|
|
@@ -10,25 +10,22 @@
|
|
|
10
10
|
* - `guardian`: sender matches the active guardian binding for this channel.
|
|
11
11
|
* - `trusted_contact`: sender is an active ingress member (not the guardian).
|
|
12
12
|
* - `unknown`: sender has no member record or no identity could be established.
|
|
13
|
-
*
|
|
14
|
-
* The legacy `ActorRole` enum (`guardian` / `non-guardian` / `unverified_channel`)
|
|
15
|
-
* is still required by existing policy gates. The `toLegacyActorRole()` mapper
|
|
16
|
-
* converts the new trust classification to the legacy enum.
|
|
17
13
|
*/
|
|
18
14
|
|
|
19
15
|
import type { ChannelId } from '../channels/types.js';
|
|
16
|
+
import type { GuardianRuntimeContext } from '../daemon/session-runtime-assembly.js';
|
|
20
17
|
import type { IngressMember } from '../memory/ingress-member-store.js';
|
|
21
18
|
import { findMember } from '../memory/ingress-member-store.js';
|
|
22
19
|
import { canonicalizeInboundIdentity } from '../util/canonicalize-identity.js';
|
|
23
20
|
import { normalizeAssistantId } from '../util/platform.js';
|
|
24
21
|
import { getGuardianBinding } from './channel-guardian-service.js';
|
|
25
|
-
import type { ActorRole, DenialReason, GuardianContext } from './guardian-context-resolver.js';
|
|
26
22
|
|
|
27
23
|
// ---------------------------------------------------------------------------
|
|
28
24
|
// Types
|
|
29
25
|
// ---------------------------------------------------------------------------
|
|
30
26
|
|
|
31
27
|
export type TrustClass = 'guardian' | 'trusted_contact' | 'unknown';
|
|
28
|
+
export type DenialReason = 'no_binding' | 'no_identity';
|
|
32
29
|
|
|
33
30
|
export interface ActorTrustContext {
|
|
34
31
|
/** Canonical (normalized) sender identity. Null when identity could not be established. */
|
|
@@ -46,6 +43,8 @@ export interface ActorTrustContext {
|
|
|
46
43
|
actorMetadata: {
|
|
47
44
|
identifier: string | undefined;
|
|
48
45
|
displayName: string | undefined;
|
|
46
|
+
senderDisplayName: string | undefined;
|
|
47
|
+
memberDisplayName: string | undefined;
|
|
49
48
|
username: string | undefined;
|
|
50
49
|
channel: ChannelId;
|
|
51
50
|
trustStatus: TrustClass;
|
|
@@ -108,6 +107,8 @@ export function resolveActorTrust(input: ResolveActorTrustInput): ActorTrustCont
|
|
|
108
107
|
actorMetadata: {
|
|
109
108
|
identifier,
|
|
110
109
|
displayName: senderDisplayName,
|
|
110
|
+
senderDisplayName,
|
|
111
|
+
memberDisplayName: undefined,
|
|
111
112
|
username: senderUsername,
|
|
112
113
|
channel: input.sourceChannel,
|
|
113
114
|
trustStatus: 'unknown',
|
|
@@ -139,11 +140,33 @@ export function resolveActorTrust(input: ResolveActorTrustInput): ActorTrustCont
|
|
|
139
140
|
externalChatId: input.externalChatId,
|
|
140
141
|
});
|
|
141
142
|
|
|
143
|
+
// In group chats, findMember may match on externalChatId and return a
|
|
144
|
+
// record for a different user. Only use member metadata when the record's
|
|
145
|
+
// externalUserId matches the current sender to avoid misidentification.
|
|
146
|
+
// Canonicalize the stored member ID to handle formatting variance (e.g.
|
|
147
|
+
// phone numbers stored without E.164 normalization).
|
|
148
|
+
const memberMatchesSender = memberRecord?.externalUserId
|
|
149
|
+
? canonicalizeInboundIdentity(input.sourceChannel, memberRecord.externalUserId) === canonicalSenderId
|
|
150
|
+
: false;
|
|
151
|
+
|
|
152
|
+
const memberUsername = memberMatchesSender && typeof memberRecord?.username === 'string' && memberRecord.username.trim().length > 0
|
|
153
|
+
? memberRecord.username.trim()
|
|
154
|
+
: undefined;
|
|
155
|
+
const memberDisplayName = memberMatchesSender && typeof memberRecord?.displayName === 'string' && memberRecord.displayName.trim().length > 0
|
|
156
|
+
? memberRecord.displayName.trim()
|
|
157
|
+
: undefined;
|
|
158
|
+
// Prefer member profile metadata over transient sender metadata so guardian-
|
|
159
|
+
// curated contact details are canonical for assistant-facing identity —
|
|
160
|
+
// but only when the member record actually belongs to the current sender.
|
|
161
|
+
const resolvedUsername = memberUsername ?? senderUsername;
|
|
162
|
+
const resolvedDisplayName = memberDisplayName ?? senderDisplayName;
|
|
163
|
+
const resolvedIdentifier = resolvedUsername ? `@${resolvedUsername}` : canonicalSenderId ?? undefined;
|
|
164
|
+
|
|
142
165
|
// Trust classification
|
|
143
166
|
let trustClass: TrustClass;
|
|
144
167
|
if (isGuardian) {
|
|
145
168
|
trustClass = 'guardian';
|
|
146
|
-
} else if (memberRecord && memberRecord.status === 'active') {
|
|
169
|
+
} else if (memberMatchesSender && memberRecord && memberRecord.status === 'active') {
|
|
147
170
|
trustClass = 'trusted_contact';
|
|
148
171
|
} else {
|
|
149
172
|
trustClass = 'unknown';
|
|
@@ -161,9 +184,11 @@ export function resolveActorTrust(input: ResolveActorTrustInput): ActorTrustCont
|
|
|
161
184
|
memberRecord,
|
|
162
185
|
trustClass,
|
|
163
186
|
actorMetadata: {
|
|
164
|
-
identifier,
|
|
165
|
-
displayName:
|
|
166
|
-
|
|
187
|
+
identifier: resolvedIdentifier,
|
|
188
|
+
displayName: resolvedDisplayName,
|
|
189
|
+
senderDisplayName,
|
|
190
|
+
memberDisplayName,
|
|
191
|
+
username: resolvedUsername,
|
|
167
192
|
channel: input.sourceChannel,
|
|
168
193
|
trustStatus: trustClass,
|
|
169
194
|
},
|
|
@@ -171,53 +196,24 @@ export function resolveActorTrust(input: ResolveActorTrustInput): ActorTrustCont
|
|
|
171
196
|
};
|
|
172
197
|
}
|
|
173
198
|
|
|
174
|
-
// ---------------------------------------------------------------------------
|
|
175
|
-
// Legacy compatibility mapper
|
|
176
|
-
// ---------------------------------------------------------------------------
|
|
177
|
-
|
|
178
|
-
/**
|
|
179
|
-
* Map the new trust classification to the legacy ActorRole enum used by
|
|
180
|
-
* existing policy gates. This preserves backward compatibility while the
|
|
181
|
-
* codebase migrates to the unified trust model.
|
|
182
|
-
*
|
|
183
|
-
* Mapping:
|
|
184
|
-
* - guardian => 'guardian'
|
|
185
|
-
* - trusted_contact => 'non-guardian' (existing gates treat active members as non-guardian)
|
|
186
|
-
* - unknown (no_identity) => 'unverified_channel'
|
|
187
|
-
* - unknown (no_binding) => 'unverified_channel'
|
|
188
|
-
* - unknown (with binding, not guardian) => 'non-guardian'
|
|
189
|
-
*/
|
|
190
|
-
export function toLegacyActorRole(ctx: ActorTrustContext): ActorRole {
|
|
191
|
-
if (ctx.trustClass === 'guardian') return 'guardian';
|
|
192
|
-
if (ctx.trustClass === 'trusted_contact') return 'non-guardian';
|
|
193
|
-
|
|
194
|
-
// unknown: distinguish between unverified_channel and non-guardian
|
|
195
|
-
if (ctx.denialReason === 'no_identity' || ctx.denialReason === 'no_binding') {
|
|
196
|
-
return 'unverified_channel';
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
// Has a binding, has identity, but not guardian and not a member => non-guardian
|
|
200
|
-
if (ctx.guardianBindingMatch && ctx.canonicalSenderId) {
|
|
201
|
-
return 'non-guardian';
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
return 'unverified_channel';
|
|
205
|
-
}
|
|
206
|
-
|
|
207
199
|
/**
|
|
208
|
-
* Convert an ActorTrustContext into the
|
|
209
|
-
*
|
|
210
|
-
* migration — new code should consume ActorTrustContext directly.
|
|
200
|
+
* Convert an ActorTrustContext into the runtime trust context shape used by
|
|
201
|
+
* sessions/tooling.
|
|
211
202
|
*/
|
|
212
|
-
export function
|
|
213
|
-
|
|
214
|
-
|
|
203
|
+
export function toGuardianRuntimeContextFromTrust(
|
|
204
|
+
ctx: ActorTrustContext,
|
|
205
|
+
externalChatId: string,
|
|
206
|
+
): GuardianRuntimeContext {
|
|
215
207
|
return {
|
|
216
|
-
|
|
208
|
+
sourceChannel: ctx.actorMetadata.channel,
|
|
209
|
+
trustClass: ctx.trustClass,
|
|
217
210
|
guardianChatId: ctx.guardianBindingMatch?.guardianDeliveryChatId ??
|
|
218
|
-
(
|
|
211
|
+
(ctx.trustClass === 'guardian' ? externalChatId : undefined),
|
|
219
212
|
guardianExternalUserId: ctx.guardianBindingMatch?.guardianExternalUserId,
|
|
220
213
|
requesterIdentifier: ctx.actorMetadata.identifier,
|
|
214
|
+
requesterDisplayName: ctx.actorMetadata.displayName,
|
|
215
|
+
requesterSenderDisplayName: ctx.actorMetadata.senderDisplayName,
|
|
216
|
+
requesterMemberDisplayName: ctx.actorMetadata.memberDisplayName,
|
|
221
217
|
requesterExternalUserId: ctx.canonicalSenderId ?? undefined,
|
|
222
218
|
requesterChatId: externalChatId,
|
|
223
219
|
denialReason: ctx.denialReason,
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Voice channel invite transport adapter.
|
|
3
|
+
*
|
|
4
|
+
* Voice invites are identity-bound: the invitee must call from a specific
|
|
5
|
+
* phone number and enter a numeric code. Unlike Telegram invites, there is
|
|
6
|
+
* no shareable deep link — the guardian relays the code and calling
|
|
7
|
+
* instructions verbally or via another channel.
|
|
8
|
+
*
|
|
9
|
+
* The transport builds human-readable instruction text and provides a
|
|
10
|
+
* no-op token extractor since voice invite redemption uses the dedicated
|
|
11
|
+
* voice-code path rather than generic token extraction.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import type { ChannelId } from '../../channels/types.js';
|
|
15
|
+
import {
|
|
16
|
+
type ChannelInviteTransport,
|
|
17
|
+
type InviteSharePayload,
|
|
18
|
+
registerTransport,
|
|
19
|
+
} from '../channel-invite-transport.js';
|
|
20
|
+
|
|
21
|
+
// ---------------------------------------------------------------------------
|
|
22
|
+
// Transport implementation
|
|
23
|
+
// ---------------------------------------------------------------------------
|
|
24
|
+
|
|
25
|
+
export const voiceInviteTransport: ChannelInviteTransport = {
|
|
26
|
+
channel: 'voice' as ChannelId,
|
|
27
|
+
|
|
28
|
+
buildShareableInvite(_params: {
|
|
29
|
+
rawToken: string;
|
|
30
|
+
sourceChannel: ChannelId;
|
|
31
|
+
}): InviteSharePayload {
|
|
32
|
+
// Voice invites do not produce a clickable URL. The "url" field contains
|
|
33
|
+
// a placeholder — callers should use displayText for presentation.
|
|
34
|
+
return {
|
|
35
|
+
url: '',
|
|
36
|
+
displayText: [
|
|
37
|
+
'Voice invite created.',
|
|
38
|
+
'The invitee must call the assistant\'s phone number from the authorized number and enter their invite code when prompted.',
|
|
39
|
+
].join(' '),
|
|
40
|
+
};
|
|
41
|
+
},
|
|
42
|
+
|
|
43
|
+
extractInboundToken(_params: {
|
|
44
|
+
commandIntent?: Record<string, unknown>;
|
|
45
|
+
content: string;
|
|
46
|
+
sourceMetadata?: Record<string, unknown>;
|
|
47
|
+
}): string | undefined {
|
|
48
|
+
// Voice invite redemption bypasses generic token extraction — it uses
|
|
49
|
+
// the identity-bound voice-code flow in invite-redemption-service.ts.
|
|
50
|
+
return undefined;
|
|
51
|
+
},
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
// ---------------------------------------------------------------------------
|
|
55
|
+
// Auto-register on import
|
|
56
|
+
// ---------------------------------------------------------------------------
|
|
57
|
+
|
|
58
|
+
registerTransport(voiceInviteTransport);
|
|
@@ -14,11 +14,20 @@ const log = getLogger('runtime-http');
|
|
|
14
14
|
function parseGuardianRuntimeContext(value: unknown): GuardianRuntimeContext | undefined {
|
|
15
15
|
if (!value || typeof value !== 'object') return undefined;
|
|
16
16
|
const raw = value as Record<string, unknown>;
|
|
17
|
-
const
|
|
17
|
+
const trustClass = raw.trustClass
|
|
18
|
+
?? (
|
|
19
|
+
raw.actorRole === 'guardian'
|
|
20
|
+
? 'guardian'
|
|
21
|
+
: raw.actorRole === 'non-guardian'
|
|
22
|
+
? 'trusted_contact'
|
|
23
|
+
: raw.actorRole === 'unverified_channel'
|
|
24
|
+
? 'unknown'
|
|
25
|
+
: undefined
|
|
26
|
+
);
|
|
18
27
|
if (
|
|
19
|
-
|
|
20
|
-
&&
|
|
21
|
-
&&
|
|
28
|
+
trustClass !== 'guardian'
|
|
29
|
+
&& trustClass !== 'trusted_contact'
|
|
30
|
+
&& trustClass !== 'unknown'
|
|
22
31
|
) {
|
|
23
32
|
return undefined;
|
|
24
33
|
}
|
|
@@ -33,10 +42,13 @@ function parseGuardianRuntimeContext(value: unknown): GuardianRuntimeContext | u
|
|
|
33
42
|
: undefined;
|
|
34
43
|
return {
|
|
35
44
|
sourceChannel,
|
|
36
|
-
|
|
45
|
+
trustClass,
|
|
37
46
|
guardianChatId: typeof raw.guardianChatId === 'string' ? raw.guardianChatId : undefined,
|
|
38
47
|
guardianExternalUserId: typeof raw.guardianExternalUserId === 'string' ? raw.guardianExternalUserId : undefined,
|
|
39
48
|
requesterIdentifier: typeof raw.requesterIdentifier === 'string' ? raw.requesterIdentifier : undefined,
|
|
49
|
+
requesterDisplayName: typeof raw.requesterDisplayName === 'string' ? raw.requesterDisplayName : undefined,
|
|
50
|
+
requesterSenderDisplayName: typeof raw.requesterSenderDisplayName === 'string' ? raw.requesterSenderDisplayName : undefined,
|
|
51
|
+
requesterMemberDisplayName: typeof raw.requesterMemberDisplayName === 'string' ? raw.requesterMemberDisplayName : undefined,
|
|
40
52
|
requesterExternalUserId: typeof raw.requesterExternalUserId === 'string' ? raw.requesterExternalUserId : undefined,
|
|
41
53
|
requesterChatId: typeof raw.requesterChatId === 'string' ? raw.requesterChatId : undefined,
|
|
42
54
|
denialReason,
|
|
@@ -117,7 +129,7 @@ export async function sweepFailedEvents(
|
|
|
117
129
|
},
|
|
118
130
|
assistantId,
|
|
119
131
|
guardianContext,
|
|
120
|
-
isInteractive: guardianContext?.
|
|
132
|
+
isInteractive: guardianContext?.trustClass === 'guardian',
|
|
121
133
|
},
|
|
122
134
|
sourceChannel,
|
|
123
135
|
sourceInterface,
|
|
@@ -1,131 +1,73 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Shared
|
|
2
|
+
* Shared inbound trust resolution for channel actors.
|
|
3
3
|
*
|
|
4
|
-
* This module
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* Guardian binding comparisons now use canonicalized identities (E.164 for
|
|
9
|
-
* phone-like channels) to eliminate formatting-variance mismatches.
|
|
4
|
+
* This module provides a compact route-level shape used by channel routes
|
|
5
|
+
* while delegating canonical classification to the unified actor trust
|
|
6
|
+
* resolver.
|
|
10
7
|
*/
|
|
11
8
|
import type { ChannelId } from '../channels/types.js';
|
|
12
9
|
import type { GuardianRuntimeContext } from '../daemon/session-runtime-assembly.js';
|
|
13
|
-
import {
|
|
14
|
-
|
|
15
|
-
|
|
10
|
+
import {
|
|
11
|
+
type DenialReason,
|
|
12
|
+
resolveActorTrust,
|
|
13
|
+
type ResolveActorTrustInput,
|
|
14
|
+
type TrustClass,
|
|
15
|
+
} from './actor-trust-resolver.js';
|
|
16
|
+
export type { DenialReason } from './actor-trust-resolver.js';
|
|
16
17
|
|
|
17
|
-
/**
|
|
18
|
-
export type
|
|
19
|
-
export type ActorRole = 'guardian' | 'non-guardian' | 'unverified_channel';
|
|
18
|
+
/** Trust classification used by route-level channel logic. */
|
|
19
|
+
export type ActorTrustClass = TrustClass;
|
|
20
20
|
|
|
21
21
|
/** Guardian actor context used by route-level approval logic. */
|
|
22
22
|
export interface GuardianContext {
|
|
23
|
-
|
|
23
|
+
trustClass: ActorTrustClass;
|
|
24
24
|
guardianChatId?: string;
|
|
25
25
|
guardianExternalUserId?: string;
|
|
26
26
|
requesterIdentifier?: string;
|
|
27
|
+
requesterDisplayName?: string;
|
|
28
|
+
requesterSenderDisplayName?: string;
|
|
29
|
+
requesterMemberDisplayName?: string;
|
|
27
30
|
requesterExternalUserId?: string;
|
|
28
31
|
requesterChatId?: string;
|
|
32
|
+
memberStatus?: string;
|
|
33
|
+
memberPolicy?: string;
|
|
29
34
|
denialReason?: DenialReason;
|
|
30
35
|
}
|
|
31
36
|
|
|
32
|
-
export
|
|
33
|
-
assistantId: string;
|
|
34
|
-
sourceChannel: ChannelId;
|
|
35
|
-
externalChatId: string;
|
|
36
|
-
senderExternalUserId?: string;
|
|
37
|
-
senderUsername?: string;
|
|
38
|
-
}
|
|
37
|
+
export type ResolveGuardianContextInput = ResolveActorTrustInput;
|
|
39
38
|
|
|
40
39
|
/**
|
|
41
|
-
* Resolve
|
|
42
|
-
*
|
|
43
|
-
* Behavior:
|
|
44
|
-
* - sender matches active binding -> guardian
|
|
45
|
-
* - active binding exists but sender differs -> non-guardian
|
|
46
|
-
* - no sender identity -> unverified_channel (no_identity)
|
|
47
|
-
* - no binding -> unverified_channel (no_binding)
|
|
48
|
-
*
|
|
49
|
-
* Identity comparison is normalization-safe: both the sender ID and the
|
|
50
|
-
* guardian binding ID are canonicalized for the source channel before
|
|
51
|
-
* comparison, so formatting differences (e.g. `+1 (555) 123-4567` vs
|
|
52
|
-
* `+15551234567`) do not cause false non-guardian classifications.
|
|
40
|
+
* Resolve route-level trust context from canonical identity state.
|
|
53
41
|
*/
|
|
54
42
|
export function resolveGuardianContext(input: ResolveGuardianContextInput): GuardianContext {
|
|
55
|
-
const
|
|
56
|
-
const rawUserId = typeof input.senderExternalUserId === 'string' && input.senderExternalUserId.trim().length > 0
|
|
57
|
-
? input.senderExternalUserId.trim()
|
|
58
|
-
: undefined;
|
|
59
|
-
const senderUsername = typeof input.senderUsername === 'string' && input.senderUsername.trim().length > 0
|
|
60
|
-
? input.senderUsername.trim()
|
|
61
|
-
: undefined;
|
|
62
|
-
|
|
63
|
-
// Canonicalize sender identity for normalization-safe comparisons.
|
|
64
|
-
// canonicalizeInboundIdentity returns string | null; coerce to
|
|
65
|
-
// string | undefined so assignments to optional (string | undefined)
|
|
66
|
-
// fields in GuardianContext don't produce a type mismatch.
|
|
67
|
-
const canonicalSenderId = rawUserId
|
|
68
|
-
? (canonicalizeInboundIdentity(input.sourceChannel, rawUserId) ?? undefined)
|
|
69
|
-
: undefined;
|
|
70
|
-
|
|
71
|
-
const requesterIdentifier = senderUsername ? `@${senderUsername}` : canonicalSenderId;
|
|
72
|
-
|
|
73
|
-
if (!canonicalSenderId) {
|
|
74
|
-
return {
|
|
75
|
-
actorRole: 'unverified_channel',
|
|
76
|
-
denialReason: 'no_identity',
|
|
77
|
-
requesterIdentifier,
|
|
78
|
-
requesterExternalUserId: undefined,
|
|
79
|
-
requesterChatId: input.externalChatId,
|
|
80
|
-
};
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
const binding = getGuardianBinding(assistantId, input.sourceChannel);
|
|
84
|
-
if (!binding) {
|
|
85
|
-
return {
|
|
86
|
-
actorRole: 'unverified_channel',
|
|
87
|
-
denialReason: 'no_binding',
|
|
88
|
-
requesterIdentifier,
|
|
89
|
-
requesterExternalUserId: canonicalSenderId,
|
|
90
|
-
requesterChatId: input.externalChatId,
|
|
91
|
-
};
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
// Canonicalize the stored guardian identity for the same channel so
|
|
95
|
-
// phone-format variance in the binding record doesn't cause mismatches.
|
|
96
|
-
const canonicalGuardianId = canonicalizeInboundIdentity(
|
|
97
|
-
input.sourceChannel,
|
|
98
|
-
binding.guardianExternalUserId,
|
|
99
|
-
) ?? undefined;
|
|
100
|
-
|
|
101
|
-
if (canonicalGuardianId === canonicalSenderId) {
|
|
102
|
-
return {
|
|
103
|
-
actorRole: 'guardian',
|
|
104
|
-
guardianChatId: binding.guardianDeliveryChatId || input.externalChatId,
|
|
105
|
-
guardianExternalUserId: binding.guardianExternalUserId,
|
|
106
|
-
requesterIdentifier,
|
|
107
|
-
requesterExternalUserId: canonicalSenderId,
|
|
108
|
-
requesterChatId: input.externalChatId,
|
|
109
|
-
};
|
|
110
|
-
}
|
|
111
|
-
|
|
43
|
+
const trust = resolveActorTrust(input);
|
|
112
44
|
return {
|
|
113
|
-
|
|
114
|
-
guardianChatId:
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
45
|
+
trustClass: trust.trustClass,
|
|
46
|
+
guardianChatId: trust.guardianBindingMatch?.guardianDeliveryChatId ??
|
|
47
|
+
(trust.trustClass === 'guardian' ? input.externalChatId : undefined),
|
|
48
|
+
guardianExternalUserId: trust.guardianBindingMatch?.guardianExternalUserId,
|
|
49
|
+
requesterIdentifier: trust.actorMetadata.identifier,
|
|
50
|
+
requesterDisplayName: trust.actorMetadata.displayName,
|
|
51
|
+
requesterSenderDisplayName: trust.actorMetadata.senderDisplayName,
|
|
52
|
+
requesterMemberDisplayName: trust.actorMetadata.memberDisplayName,
|
|
53
|
+
requesterExternalUserId: trust.canonicalSenderId ?? undefined,
|
|
118
54
|
requesterChatId: input.externalChatId,
|
|
55
|
+
memberStatus: trust.memberRecord?.status ?? undefined,
|
|
56
|
+
memberPolicy: trust.memberRecord?.policy ?? undefined,
|
|
57
|
+
denialReason: trust.denialReason,
|
|
119
58
|
};
|
|
120
59
|
}
|
|
121
60
|
|
|
122
61
|
export function toGuardianRuntimeContext(sourceChannel: ChannelId, ctx: GuardianContext): GuardianRuntimeContext {
|
|
123
62
|
return {
|
|
124
63
|
sourceChannel,
|
|
125
|
-
|
|
64
|
+
trustClass: ctx.trustClass,
|
|
126
65
|
guardianChatId: ctx.guardianChatId,
|
|
127
66
|
guardianExternalUserId: ctx.guardianExternalUserId,
|
|
128
67
|
requesterIdentifier: ctx.requesterIdentifier,
|
|
68
|
+
requesterDisplayName: ctx.requesterDisplayName,
|
|
69
|
+
requesterSenderDisplayName: ctx.requesterSenderDisplayName,
|
|
70
|
+
requesterMemberDisplayName: ctx.requesterMemberDisplayName,
|
|
129
71
|
requesterExternalUserId: ctx.requesterExternalUserId,
|
|
130
72
|
requesterChatId: ctx.requesterChatId,
|
|
131
73
|
denialReason: ctx.denialReason,
|