@vellumai/assistant 0.4.33 → 0.4.34
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 +150 -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/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/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/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 +0 -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 +5 -6
- 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/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
|
@@ -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",
|
|
@@ -95,8 +95,8 @@ export async function handleVerificationIntercept(
|
|
|
95
95
|
// Only intercept when there is a pending challenge or active outbound session
|
|
96
96
|
const shouldIntercept =
|
|
97
97
|
guardianVerifyCode !== undefined &&
|
|
98
|
-
(!!getPendingChallenge(
|
|
99
|
-
!!findActiveSession(
|
|
98
|
+
(!!getPendingChallenge(sourceChannel) ||
|
|
99
|
+
!!findActiveSession(sourceChannel));
|
|
100
100
|
|
|
101
101
|
if (
|
|
102
102
|
isDuplicate ||
|
|
@@ -108,7 +108,6 @@ export async function handleVerificationIntercept(
|
|
|
108
108
|
}
|
|
109
109
|
|
|
110
110
|
const verifyResult = validateAndConsumeChallenge(
|
|
111
|
-
canonicalAssistantId,
|
|
112
111
|
sourceChannel,
|
|
113
112
|
guardianVerifyCode,
|
|
114
113
|
canonicalSenderId ?? rawSenderId,
|
|
@@ -144,7 +143,6 @@ export async function handleVerificationIntercept(
|
|
|
144
143
|
: actorDisplayName;
|
|
145
144
|
|
|
146
145
|
upsertMember({
|
|
147
|
-
assistantId: canonicalAssistantId,
|
|
148
146
|
sourceChannel,
|
|
149
147
|
externalUserId: canonicalSenderId ?? rawSenderId,
|
|
150
148
|
externalChatId: conversationExternalId,
|
|
@@ -181,7 +179,7 @@ export async function handleVerificationIntercept(
|
|
|
181
179
|
);
|
|
182
180
|
} else {
|
|
183
181
|
// Revoke any existing active binding before creating a new one (same-user re-verification)
|
|
184
|
-
revokeGuardianBinding(
|
|
182
|
+
revokeGuardianBinding(sourceChannel);
|
|
185
183
|
|
|
186
184
|
const metadata: Record<string, string> = {};
|
|
187
185
|
if (actorUsername && actorUsername.trim().length > 0) {
|
|
@@ -202,7 +200,6 @@ export async function handleVerificationIntercept(
|
|
|
202
200
|
rawSenderId;
|
|
203
201
|
|
|
204
202
|
createGuardianBinding({
|
|
205
|
-
assistantId: canonicalAssistantId,
|
|
206
203
|
channel: sourceChannel,
|
|
207
204
|
guardianExternalUserId: canonicalSenderId ?? rawSenderId,
|
|
208
205
|
guardianDeliveryChatId: conversationExternalId,
|
|
@@ -235,7 +232,6 @@ export async function handleVerificationIntercept(
|
|
|
235
232
|
sourceEventName: "ingress.trusted_contact.activated",
|
|
236
233
|
sourceChannel,
|
|
237
234
|
sourceSessionId: conversationId,
|
|
238
|
-
assistantId: canonicalAssistantId,
|
|
239
235
|
attentionHints: {
|
|
240
236
|
requiresAction: false,
|
|
241
237
|
urgency: "low",
|
|
@@ -37,22 +37,22 @@ function mintPairingCredentials(
|
|
|
37
37
|
platform: string,
|
|
38
38
|
): PairingCredentials | null {
|
|
39
39
|
try {
|
|
40
|
-
const assistantId = DAEMON_INTERNAL_ASSISTANT_ID;
|
|
41
40
|
// Pairing can run before a local client has touched the actor-token
|
|
42
41
|
// bootstrap path. Ensure the vellum guardian principal exists so iOS
|
|
43
42
|
// pairings always have a mint target.
|
|
44
|
-
const guardianPrincipalId = ensureVellumGuardianBinding(
|
|
43
|
+
const guardianPrincipalId = ensureVellumGuardianBinding(
|
|
44
|
+
DAEMON_INTERNAL_ASSISTANT_ID,
|
|
45
|
+
);
|
|
45
46
|
const hashedDeviceId = hashDeviceId(deviceId);
|
|
46
47
|
|
|
47
48
|
const credentials = mintCredentialPair({
|
|
48
|
-
assistantId,
|
|
49
49
|
platform,
|
|
50
50
|
deviceId,
|
|
51
51
|
guardianPrincipalId,
|
|
52
52
|
hashedDeviceId,
|
|
53
53
|
});
|
|
54
54
|
|
|
55
|
-
log.info({
|
|
55
|
+
log.info({ platform }, "Minted credentials during pairing");
|
|
56
56
|
return {
|
|
57
57
|
accessToken: credentials.accessToken,
|
|
58
58
|
accessTokenExpiresAt: credentials.accessTokenExpiresAt,
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import { mkdirSync } from "node:fs";
|
|
1
|
+
import { existsSync, mkdirSync } from "node:fs";
|
|
2
2
|
import { join } from "node:path";
|
|
3
3
|
|
|
4
4
|
import { getLogger } from "../../util/logger.js";
|
|
5
5
|
import { getDataDir } from "../../util/platform.js";
|
|
6
6
|
import { authSessionCache } from "./auth-cache.js";
|
|
7
7
|
import type { ExtractedCredential } from "./network-recording-types.js";
|
|
8
|
-
import {
|
|
8
|
+
import { importPlaywright } from "./runtime-check.js";
|
|
9
9
|
|
|
10
10
|
const log = getLogger("browser-manager");
|
|
11
11
|
|
|
@@ -129,20 +129,6 @@ export function setLaunchFn(fn: LaunchFn | null): void {
|
|
|
129
129
|
launchPersistentContext = fn;
|
|
130
130
|
}
|
|
131
131
|
|
|
132
|
-
async function getDefaultLaunchFn(): Promise<LaunchFn> {
|
|
133
|
-
const pw = await import("playwright");
|
|
134
|
-
// In compiled Bun binaries, CJS→ESM interop may place named exports
|
|
135
|
-
// under .default instead of at the top level of the module namespace.
|
|
136
|
-
const chromium =
|
|
137
|
-
pw.chromium ?? (pw.default as typeof pw | undefined)?.chromium;
|
|
138
|
-
if (!chromium) {
|
|
139
|
-
throw new Error(
|
|
140
|
-
"Failed to resolve Playwright chromium — the module loaded but 'chromium' is missing",
|
|
141
|
-
);
|
|
142
|
-
}
|
|
143
|
-
return chromium.launchPersistentContext.bind(chromium);
|
|
144
|
-
}
|
|
145
|
-
|
|
146
132
|
function getProfileDir(): string {
|
|
147
133
|
return join(getDataDir(), "browser-profile");
|
|
148
134
|
}
|
|
@@ -177,10 +163,24 @@ class BrowserManager {
|
|
|
177
163
|
this.contextCreating = (async () => {
|
|
178
164
|
await authSessionCache.load();
|
|
179
165
|
|
|
180
|
-
//
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
166
|
+
// Resolve launch function: use injected test launcher or resolve
|
|
167
|
+
// playwright (may install at runtime in compiled binaries).
|
|
168
|
+
let launch: LaunchFn;
|
|
169
|
+
if (launchPersistentContext) {
|
|
170
|
+
launch = launchPersistentContext;
|
|
171
|
+
} else {
|
|
172
|
+
const pw = await importPlaywright();
|
|
173
|
+
|
|
174
|
+
// Auto-install Chromium if the browser binary is missing
|
|
175
|
+
let chromiumInstalled = false;
|
|
176
|
+
try {
|
|
177
|
+
const execPath = pw.chromium.executablePath();
|
|
178
|
+
chromiumInstalled = existsSync(execPath);
|
|
179
|
+
} catch {
|
|
180
|
+
// executablePath() may throw if registry is missing
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
if (!chromiumInstalled) {
|
|
184
184
|
log.info("Chromium not installed, installing via playwright...");
|
|
185
185
|
const proc = Bun.spawn(
|
|
186
186
|
["bunx", "playwright", "install", "chromium"],
|
|
@@ -213,11 +213,12 @@ class BrowserManager {
|
|
|
213
213
|
throw new Error(`Failed to install Chromium: ${msg}`);
|
|
214
214
|
}
|
|
215
215
|
}
|
|
216
|
+
|
|
217
|
+
launch = pw.chromium.launchPersistentContext.bind(pw.chromium);
|
|
216
218
|
}
|
|
217
219
|
|
|
218
220
|
const profileDir = getProfileDir();
|
|
219
221
|
mkdirSync(profileDir, { recursive: true });
|
|
220
|
-
const launch = launchPersistentContext ?? (await getDefaultLaunchFn());
|
|
221
222
|
const headless = !canDisplayGui();
|
|
222
223
|
const ctx = await launch(profileDir, { headless });
|
|
223
224
|
log.info(
|
|
@@ -1,4 +1,7 @@
|
|
|
1
|
-
import { existsSync } from "node:fs";
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
|
|
4
|
+
import { getWorkspaceDir } from "../../util/platform.js";
|
|
2
5
|
|
|
3
6
|
export interface BrowserRuntimeStatus {
|
|
4
7
|
playwrightAvailable: boolean;
|
|
@@ -7,14 +10,116 @@ export interface BrowserRuntimeStatus {
|
|
|
7
10
|
error: string | null;
|
|
8
11
|
}
|
|
9
12
|
|
|
13
|
+
/**
|
|
14
|
+
* Resolve playwright's chromium export from a module namespace object,
|
|
15
|
+
* handling CJS→ESM interop where named exports may land under .default.
|
|
16
|
+
*/
|
|
17
|
+
function resolveChromium(
|
|
18
|
+
pw: Record<string, unknown>,
|
|
19
|
+
): Record<string, unknown> | undefined {
|
|
20
|
+
if (pw.chromium) return pw;
|
|
21
|
+
const def = pw.default as Record<string, unknown> | undefined;
|
|
22
|
+
if (def?.chromium) return def;
|
|
23
|
+
return undefined;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Try importing playwright from the bundled binary. Returns the module
|
|
28
|
+
* if chromium is resolvable, otherwise undefined. This never installs
|
|
29
|
+
* anything — safe for diagnostic/read-only use.
|
|
30
|
+
*/
|
|
31
|
+
async function tryBundledPlaywright(): Promise<
|
|
32
|
+
typeof import("playwright") | undefined
|
|
33
|
+
> {
|
|
34
|
+
try {
|
|
35
|
+
const pw = await import("playwright");
|
|
36
|
+
const mod = resolveChromium(pw as unknown as Record<string, unknown>);
|
|
37
|
+
if (mod?.chromium) return mod as unknown as typeof import("playwright");
|
|
38
|
+
} catch {
|
|
39
|
+
// Bundled import failed entirely
|
|
40
|
+
}
|
|
41
|
+
return undefined;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Resolve the package entry point from its package.json exports/main fields.
|
|
46
|
+
*/
|
|
47
|
+
function resolvePackageEntry(pkgDir: string): string {
|
|
48
|
+
try {
|
|
49
|
+
const pkg = JSON.parse(readFileSync(join(pkgDir, "package.json"), "utf-8"));
|
|
50
|
+
// Prefer ESM entry from exports map
|
|
51
|
+
const exportsRoot = pkg.exports?.["."];
|
|
52
|
+
if (typeof exportsRoot === "object" && exportsRoot?.import) {
|
|
53
|
+
return join(pkgDir, exportsRoot.import);
|
|
54
|
+
}
|
|
55
|
+
if (pkg.module) return join(pkgDir, pkg.module);
|
|
56
|
+
if (pkg.main) return join(pkgDir, pkg.main);
|
|
57
|
+
} catch {
|
|
58
|
+
// Fall through to default
|
|
59
|
+
}
|
|
60
|
+
return join(pkgDir, "index.mjs");
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Import playwright, falling back to a runtime-installed copy if the
|
|
65
|
+
* bundled import fails (compiled Bun binaries can't initialize
|
|
66
|
+
* playwright's in-process client/server bridge correctly).
|
|
67
|
+
*/
|
|
68
|
+
export async function importPlaywright(): Promise<typeof import("playwright")> {
|
|
69
|
+
// Try bundled import (works in dev/source mode)
|
|
70
|
+
const bundled = await tryBundledPlaywright();
|
|
71
|
+
if (bundled) return bundled;
|
|
72
|
+
|
|
73
|
+
// Compiled binary fallback: install playwright to disk and import
|
|
74
|
+
// from an absolute path so the JS runtime resolves it from the
|
|
75
|
+
// filesystem instead of the compiled module cache.
|
|
76
|
+
const externalDir = join(getWorkspaceDir(), "external");
|
|
77
|
+
const pwPkg = join(externalDir, "node_modules", "playwright");
|
|
78
|
+
|
|
79
|
+
if (!existsSync(join(pwPkg, "package.json"))) {
|
|
80
|
+
mkdirSync(externalDir, { recursive: true });
|
|
81
|
+
if (!existsSync(join(externalDir, "package.json"))) {
|
|
82
|
+
writeFileSync(join(externalDir, "package.json"), '{"private":true}\n');
|
|
83
|
+
}
|
|
84
|
+
const proc = Bun.spawn(["bun", "add", "playwright"], {
|
|
85
|
+
cwd: externalDir,
|
|
86
|
+
stdout: "pipe",
|
|
87
|
+
stderr: "pipe",
|
|
88
|
+
});
|
|
89
|
+
const exitCode = await proc.exited;
|
|
90
|
+
if (exitCode !== 0) {
|
|
91
|
+
const stderr = await new Response(proc.stderr).text();
|
|
92
|
+
throw new Error(`Failed to install playwright: ${stderr}`);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Dynamic import with a runtime-computed path — bun can't statically
|
|
97
|
+
// analyze this, so it resolves from the filesystem at runtime.
|
|
98
|
+
const entryPath = resolvePackageEntry(pwPkg);
|
|
99
|
+
const pw: Record<string, unknown> = await import(entryPath);
|
|
100
|
+
const mod = resolveChromium(pw);
|
|
101
|
+
if (!mod?.chromium) {
|
|
102
|
+
throw new Error(
|
|
103
|
+
"Failed to resolve Playwright chromium from runtime-installed copy",
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
return mod as unknown as typeof import("playwright");
|
|
107
|
+
}
|
|
108
|
+
|
|
10
109
|
export async function checkBrowserRuntime(): Promise<BrowserRuntimeStatus> {
|
|
11
|
-
//
|
|
110
|
+
// Diagnostic only — no side effects (no playwright installation)
|
|
12
111
|
let chromium: { executablePath: () => string };
|
|
13
112
|
try {
|
|
14
|
-
const pw = await
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
113
|
+
const pw = await tryBundledPlaywright();
|
|
114
|
+
if (!pw) {
|
|
115
|
+
return {
|
|
116
|
+
playwrightAvailable: false,
|
|
117
|
+
chromiumInstalled: false,
|
|
118
|
+
chromiumPath: null,
|
|
119
|
+
error: "playwright package not available",
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
chromium = pw.chromium;
|
|
18
123
|
} catch {
|
|
19
124
|
return {
|
|
20
125
|
playwrightAvailable: false,
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { startCall } from "../../calls/call-domain.js";
|
|
2
2
|
import { getConfig } from "../../config/loader.js";
|
|
3
|
-
import { DAEMON_INTERNAL_ASSISTANT_ID } from "../../runtime/assistant-scope.js";
|
|
4
3
|
import { findActiveSession } from "../../runtime/channel-guardian-service.js";
|
|
5
4
|
import { normalizePhoneNumber } from "../../util/phone.js";
|
|
6
5
|
import type { ToolContext, ToolExecutionResult } from "../types.js";
|
|
@@ -22,8 +21,7 @@ export async function executeCallStart(
|
|
|
22
21
|
? normalizePhoneNumber(input.phone_number)
|
|
23
22
|
: null;
|
|
24
23
|
if (requestedPhone) {
|
|
25
|
-
const
|
|
26
|
-
const activeVoiceVerification = findActiveSession(assistantId, "voice");
|
|
24
|
+
const activeVoiceVerification = findActiveSession("voice");
|
|
27
25
|
const verificationDestination =
|
|
28
26
|
activeVoiceVerification?.destinationAddress ??
|
|
29
27
|
activeVoiceVerification?.expectedPhoneE164;
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { getContact } from "../../contacts/contact-store.js";
|
|
2
2
|
import { createFollowUp } from "../../followups/followup-store.js";
|
|
3
3
|
import type { FollowUp } from "../../followups/types.js";
|
|
4
|
-
import { DAEMON_INTERNAL_ASSISTANT_ID } from "../../runtime/assistant-scope.js";
|
|
5
4
|
import type { ToolContext, ToolExecutionResult } from "../types.js";
|
|
6
5
|
|
|
7
6
|
function formatFollowUp(f: FollowUp): string {
|
|
@@ -63,7 +62,7 @@ export async function executeFollowupCreate(
|
|
|
63
62
|
|
|
64
63
|
// Validate contact exists if provided
|
|
65
64
|
if (contactId) {
|
|
66
|
-
const contact = getContact(contactId
|
|
65
|
+
const contact = getContact(contactId);
|
|
67
66
|
if (!contact) {
|
|
68
67
|
return {
|
|
69
68
|
content: `Error: Contact "${contactId}" not found`,
|
|
@@ -5,7 +5,6 @@ import {
|
|
|
5
5
|
updateCanonicalGuardianRequest,
|
|
6
6
|
} from "../memory/canonical-guardian-store.js";
|
|
7
7
|
import { isUntrustedTrustClass } from "../runtime/actor-trust-resolver.js";
|
|
8
|
-
import { DAEMON_INTERNAL_ASSISTANT_ID } from "../runtime/assistant-scope.js";
|
|
9
8
|
import { createOrReuseToolGrantRequest } from "../runtime/tool-grant-request-helper.js";
|
|
10
9
|
import { computeToolApprovalDigest } from "../security/tool-approval-digest.js";
|
|
11
10
|
import { getTaskRunRules } from "../tasks/ephemeral-permissions.js";
|
|
@@ -275,7 +274,6 @@ export class ToolApprovalHandler {
|
|
|
275
274
|
inputDigest,
|
|
276
275
|
consumingRequestId:
|
|
277
276
|
context.requestId ?? `preexec-${context.sessionId}-${Date.now()}`,
|
|
278
|
-
assistantId: context.assistantId ?? DAEMON_INTERNAL_ASSISTANT_ID,
|
|
279
277
|
executionChannel: context.executionChannel,
|
|
280
278
|
conversationId: context.conversationId,
|
|
281
279
|
callSessionId: context.callSessionId,
|