@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.
Files changed (149) hide show
  1. package/package.json +1 -1
  2. package/src/__tests__/access-request-decision.test.ts +2 -3
  3. package/src/__tests__/actor-token-service.test.ts +4 -11
  4. package/src/__tests__/approval-primitive.test.ts +0 -45
  5. package/src/__tests__/assistant-id-boundary-guard.test.ts +169 -0
  6. package/src/__tests__/callback-handoff-copy.test.ts +0 -1
  7. package/src/__tests__/channel-approval-routes.test.ts +5 -45
  8. package/src/__tests__/channel-guardian.test.ts +122 -345
  9. package/src/__tests__/confirmation-request-guardian-bridge.test.ts +4 -3
  10. package/src/__tests__/contacts-tools.test.ts +4 -5
  11. package/src/__tests__/conversation-attention-store.test.ts +2 -65
  12. package/src/__tests__/conversation-attention-telegram.test.ts +0 -2
  13. package/src/__tests__/conversation-pairing.test.ts +0 -1
  14. package/src/__tests__/deterministic-verification-control-plane.test.ts +0 -2
  15. package/src/__tests__/guardian-action-conversation-turn.test.ts +1 -7
  16. package/src/__tests__/guardian-action-grant-mint-consume.test.ts +0 -74
  17. package/src/__tests__/guardian-action-late-reply.test.ts +1 -8
  18. package/src/__tests__/guardian-grant-minting.test.ts +0 -1
  19. package/src/__tests__/guardian-routing-state.test.ts +0 -3
  20. package/src/__tests__/inbound-invite-redemption.test.ts +0 -3
  21. package/src/__tests__/non-member-access-request.test.ts +0 -7
  22. package/src/__tests__/notification-broadcaster.test.ts +1 -2
  23. package/src/__tests__/notification-decision-fallback.test.ts +0 -2
  24. package/src/__tests__/notification-decision-strategy.test.ts +0 -1
  25. package/src/__tests__/relay-server.test.ts +11 -83
  26. package/src/__tests__/scoped-approval-grants.test.ts +9 -40
  27. package/src/__tests__/scoped-grant-security-matrix.test.ts +0 -36
  28. package/src/__tests__/send-endpoint-busy.test.ts +0 -1
  29. package/src/__tests__/send-notification-tool.test.ts +0 -1
  30. package/src/__tests__/slack-inbound-verification.test.ts +2 -4
  31. package/src/__tests__/thread-seed-composer.test.ts +0 -1
  32. package/src/__tests__/tool-approval-handler.test.ts +0 -1
  33. package/src/__tests__/tool-grant-request-escalation.test.ts +0 -4
  34. package/src/__tests__/trusted-contact-approval-notifier.test.ts +1 -5
  35. package/src/__tests__/trusted-contact-lifecycle-notifications.test.ts +1 -17
  36. package/src/__tests__/trusted-contact-multichannel.test.ts +0 -13
  37. package/src/__tests__/trusted-contact-verification.test.ts +3 -15
  38. package/src/__tests__/twilio-routes.test.ts +2 -2
  39. package/src/__tests__/voice-invite-redemption.test.ts +0 -1
  40. package/src/__tests__/voice-scoped-grant-consumer.test.ts +0 -37
  41. package/src/approvals/approval-primitive.ts +0 -15
  42. package/src/approvals/guardian-decision-primitive.ts +0 -3
  43. package/src/approvals/guardian-request-resolvers.ts +0 -5
  44. package/src/calls/call-domain.ts +0 -3
  45. package/src/calls/call-store.ts +0 -3
  46. package/src/calls/guardian-action-sweep.ts +2 -1
  47. package/src/calls/guardian-dispatch.ts +1 -2
  48. package/src/calls/relay-access-wait.ts +0 -4
  49. package/src/calls/relay-server.ts +3 -11
  50. package/src/calls/relay-setup-router.ts +1 -2
  51. package/src/calls/relay-verification.ts +0 -1
  52. package/src/calls/twilio-routes.ts +0 -3
  53. package/src/calls/types.ts +0 -1
  54. package/src/calls/voice-session-bridge.ts +0 -1
  55. package/src/config/bundled-skills/google-oauth-setup/SKILL.md +100 -171
  56. package/src/config/bundled-skills/notifications/tools/send-notification.ts +0 -1
  57. package/src/contacts/contact-store.ts +13 -88
  58. package/src/contacts/contacts-write.ts +3 -11
  59. package/src/contacts/types.ts +0 -1
  60. package/src/daemon/handlers/config-channels.ts +16 -42
  61. package/src/daemon/handlers/config-inbox.ts +6 -6
  62. package/src/daemon/handlers/contacts.ts +3 -11
  63. package/src/daemon/handlers/index.ts +0 -2
  64. package/src/daemon/session-process.ts +0 -4
  65. package/src/memory/conversation-attention-store.ts +4 -19
  66. package/src/memory/conversation-crud.ts +0 -2
  67. package/src/memory/db-init.ts +4 -0
  68. package/src/memory/guardian-action-store.ts +0 -12
  69. package/src/memory/guardian-approvals.ts +35 -80
  70. package/src/memory/guardian-rate-limits.ts +1 -14
  71. package/src/memory/guardian-verification.ts +6 -34
  72. package/src/memory/invite-store.ts +5 -14
  73. package/src/memory/migrations/026-guardian-verification-sessions.ts +28 -9
  74. package/src/memory/migrations/027a-guardian-bootstrap-token.ts +16 -3
  75. package/src/memory/migrations/038-actor-token-records.ts +8 -1
  76. package/src/memory/migrations/039-actor-refresh-token-records.ts +11 -2
  77. package/src/memory/migrations/110-channel-guardian.ts +27 -6
  78. package/src/memory/migrations/112-assistant-inbox.ts +39 -15
  79. package/src/memory/migrations/114-notifications.ts +37 -15
  80. package/src/memory/migrations/117-conversation-attention.ts +33 -9
  81. package/src/memory/migrations/134-contacts-notes-column.ts +64 -45
  82. package/src/memory/migrations/136-drop-assistant-id-columns.ts +263 -0
  83. package/src/memory/migrations/index.ts +1 -0
  84. package/src/memory/migrations/registry.ts +14 -1
  85. package/src/memory/migrations/schema-introspection.ts +18 -0
  86. package/src/memory/schema/calls.ts +0 -7
  87. package/src/memory/schema/contacts.ts +0 -8
  88. package/src/memory/schema/guardian.ts +0 -5
  89. package/src/memory/schema/infrastructure.ts +0 -2
  90. package/src/memory/schema/notifications.ts +3 -17
  91. package/src/memory/scoped-approval-grants.ts +2 -24
  92. package/src/notifications/adapters/sms.ts +2 -1
  93. package/src/notifications/broadcaster.ts +1 -6
  94. package/src/notifications/decision-engine.ts +3 -4
  95. package/src/notifications/deliveries-store.ts +0 -4
  96. package/src/notifications/destination-resolver.ts +4 -6
  97. package/src/notifications/deterministic-checks.ts +1 -6
  98. package/src/notifications/emit-signal.ts +4 -11
  99. package/src/notifications/events-store.ts +7 -17
  100. package/src/notifications/preference-summary.ts +2 -2
  101. package/src/notifications/preferences-store.ts +2 -9
  102. package/src/notifications/signal.ts +0 -1
  103. package/src/notifications/thread-candidates.ts +1 -11
  104. package/src/notifications/types.ts +0 -3
  105. package/src/runtime/access-request-helper.ts +3 -10
  106. package/src/runtime/actor-refresh-token-store.ts +0 -6
  107. package/src/runtime/actor-token-store.ts +3 -16
  108. package/src/runtime/actor-trust-resolver.ts +1 -4
  109. package/src/runtime/auth/__tests__/credential-service.test.ts +0 -9
  110. package/src/runtime/auth/credential-service.ts +1 -15
  111. package/src/runtime/auth/require-bound-guardian.ts +1 -4
  112. package/src/runtime/channel-guardian-service.ts +15 -46
  113. package/src/runtime/channel-invite-transport.ts +8 -0
  114. package/src/runtime/channel-invite-transports/email.ts +4 -0
  115. package/src/runtime/channel-invite-transports/slack.ts +6 -0
  116. package/src/runtime/channel-invite-transports/sms.ts +4 -0
  117. package/src/runtime/channel-invite-transports/telegram.ts +6 -0
  118. package/src/runtime/confirmation-request-guardian-bridge.ts +0 -1
  119. package/src/runtime/guardian-action-followup-executor.ts +3 -2
  120. package/src/runtime/guardian-action-grant-minter.ts +0 -1
  121. package/src/runtime/guardian-outbound-actions.ts +2 -12
  122. package/src/runtime/guardian-vellum-migration.ts +2 -3
  123. package/src/runtime/http-server.ts +3 -10
  124. package/src/runtime/http-types.ts +13 -1
  125. package/src/runtime/invite-redemption-service.ts +1 -14
  126. package/src/runtime/local-actor-identity.ts +2 -5
  127. package/src/runtime/routes/access-request-decision.ts +0 -1
  128. package/src/runtime/routes/approval-strategies/guardian-callback-strategy.ts +0 -9
  129. package/src/runtime/routes/channel-readiness-routes.ts +29 -18
  130. package/src/runtime/routes/contact-routes.ts +15 -40
  131. package/src/runtime/routes/conversation-attention-routes.ts +0 -2
  132. package/src/runtime/routes/global-search-routes.ts +0 -2
  133. package/src/runtime/routes/guardian-bootstrap-routes.ts +6 -7
  134. package/src/runtime/routes/guardian-expiry-sweep.ts +3 -2
  135. package/src/runtime/routes/inbound-message-handler.ts +0 -3
  136. package/src/runtime/routes/inbound-stages/acl-enforcement.ts +7 -43
  137. package/src/runtime/routes/inbound-stages/background-dispatch.ts +1 -4
  138. package/src/runtime/routes/inbound-stages/bootstrap-intercept.ts +1 -6
  139. package/src/runtime/routes/inbound-stages/escalation-intercept.ts +0 -1
  140. package/src/runtime/routes/inbound-stages/secret-ingress-check.ts +0 -1
  141. package/src/runtime/routes/inbound-stages/verification-intercept.ts +3 -7
  142. package/src/runtime/routes/pairing-routes.ts +4 -4
  143. package/src/runtime/routes/surface-content-routes.ts +104 -0
  144. package/src/runtime/tool-grant-request-helper.ts +0 -1
  145. package/src/tools/browser/browser-manager.ts +22 -21
  146. package/src/tools/browser/runtime-check.ts +111 -6
  147. package/src/tools/calls/call-start.ts +1 -3
  148. package/src/tools/followups/followup_create.ts +1 -2
  149. package/src/tools/tool-approval-handler.ts +0 -2
@@ -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(canonicalAssistantId, sourceChannel) ||
99
- !!findActiveSession(canonicalAssistantId, sourceChannel));
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(canonicalAssistantId, sourceChannel);
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(assistantId);
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({ assistantId, platform }, "Minted credentials during pairing");
55
+ log.info({ platform }, "Minted credentials during pairing");
56
56
  return {
57
57
  accessToken: credentials.accessToken,
58
58
  accessTokenExpiresAt: credentials.accessTokenExpiresAt,
@@ -0,0 +1,104 @@
1
+ /**
2
+ * Route handler for fetching surface content by ID.
3
+ *
4
+ * GET /v1/surfaces/:surfaceId — return the full surface payload from the
5
+ * session's in-memory surface state. Used by clients to re-hydrate surfaces
6
+ * whose data was stripped during memory compaction.
7
+ */
8
+ import type { SurfaceData, SurfaceType } from "../../daemon/ipc-contract/surfaces.js";
9
+ import { getLogger } from "../../util/logger.js";
10
+ import { httpError } from "../http-errors.js";
11
+ import type { RouteDefinition } from "../http-router.js";
12
+
13
+ const log = getLogger("surface-content-routes");
14
+
15
+ /** Narrow interface for looking up surface state from a session. */
16
+ interface SurfaceContentTarget {
17
+ surfaceState: Map<
18
+ string,
19
+ { surfaceType: SurfaceType; data: SurfaceData; title?: string }
20
+ >;
21
+ currentTurnSurfaces?: Array<{
22
+ surfaceId: string;
23
+ surfaceType: SurfaceType;
24
+ title?: string;
25
+ data: SurfaceData;
26
+ actions?: Array<{ id: string; label: string; style?: string }>;
27
+ }>;
28
+ }
29
+
30
+ export type SurfaceContentSessionLookup = (
31
+ sessionId: string,
32
+ ) => SurfaceContentTarget | undefined;
33
+
34
+ // ---------------------------------------------------------------------------
35
+ // Route definitions
36
+ // ---------------------------------------------------------------------------
37
+
38
+ export function surfaceContentRouteDefinitions(deps: {
39
+ findSession?: SurfaceContentSessionLookup;
40
+ }): RouteDefinition[] {
41
+ return [
42
+ {
43
+ endpoint: "surfaces/:surfaceId",
44
+ method: "GET",
45
+ handler: ({ url, params }) => {
46
+ if (!deps.findSession) {
47
+ return httpError(
48
+ "NOT_IMPLEMENTED",
49
+ "Surface content lookup not available",
50
+ 501,
51
+ );
52
+ }
53
+
54
+ const sessionId = url.searchParams.get("sessionId");
55
+ if (!sessionId) {
56
+ return httpError("BAD_REQUEST", "sessionId query parameter is required", 400);
57
+ }
58
+
59
+ const surfaceId = params.surfaceId;
60
+ if (!surfaceId) {
61
+ return httpError("BAD_REQUEST", "surfaceId path parameter is required", 400);
62
+ }
63
+
64
+ const session = deps.findSession(sessionId);
65
+ if (!session) {
66
+ return httpError(
67
+ "NOT_FOUND",
68
+ "No active session found for this sessionId",
69
+ 404,
70
+ );
71
+ }
72
+
73
+ // Look up the surface in the session's in-memory state.
74
+ const stored = session.surfaceState.get(surfaceId);
75
+ if (stored) {
76
+ log.info({ sessionId, surfaceId }, "Surface content served from surfaceState");
77
+ return Response.json({
78
+ surfaceId,
79
+ surfaceType: stored.surfaceType,
80
+ title: stored.title ?? null,
81
+ data: stored.data,
82
+ });
83
+ }
84
+
85
+ // Fall back to currentTurnSurfaces in case the surface hasn't been
86
+ // committed to surfaceState yet (e.g. mid-turn).
87
+ const turnSurface = session.currentTurnSurfaces?.find(
88
+ (s) => s.surfaceId === surfaceId,
89
+ );
90
+ if (turnSurface) {
91
+ log.info({ sessionId, surfaceId }, "Surface content served from currentTurnSurfaces");
92
+ return Response.json({
93
+ surfaceId,
94
+ surfaceType: turnSurface.surfaceType,
95
+ title: turnSurface.title ?? null,
96
+ data: turnSurface.data,
97
+ });
98
+ }
99
+
100
+ return httpError("NOT_FOUND", "Surface not found in session", 404);
101
+ },
102
+ },
103
+ ];
104
+ }
@@ -146,7 +146,6 @@ export function createOrReuseToolGrantRequest(
146
146
  sourceEventName: "guardian.question",
147
147
  sourceChannel,
148
148
  sourceSessionId: conversationId,
149
- assistantId,
150
149
  attentionHints: {
151
150
  requiresAction: true,
152
151
  urgency: "high",
@@ -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 { checkBrowserRuntime } from "./runtime-check.js";
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
- // Auto-install Chromium if needed (skip when test launcher is injected)
181
- if (!launchPersistentContext) {
182
- const status = await checkBrowserRuntime();
183
- if (status.playwrightAvailable && !status.chromiumInstalled) {
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
- // Check if playwright can be imported
110
+ // Diagnostic only no side effects (no playwright installation)
12
111
  let chromium: { executablePath: () => string };
13
112
  try {
14
- const pw = await import("playwright");
15
- // In compiled Bun binaries, CJS→ESM interop may place named exports
16
- // under .default instead of at the top level of the module namespace.
17
- chromium = pw.chromium ?? (pw.default as typeof pw | undefined)?.chromium;
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 assistantId = context.assistantId ?? DAEMON_INTERNAL_ASSISTANT_ID;
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, DAEMON_INTERNAL_ASSISTANT_ID);
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,