@vellumai/assistant 0.3.15 → 0.3.18
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 +211 -12
- package/Dockerfile +1 -1
- package/README.md +11 -5
- package/docs/architecture/http-token-refresh.md +274 -0
- package/docs/architecture/memory.md +5 -4
- package/docs/architecture/scheduling.md +4 -88
- package/docs/runbook-trusted-contacts.md +283 -0
- package/docs/trusted-contact-access.md +247 -0
- package/package.json +1 -1
- package/scripts/ipc/check-swift-decoder-drift.ts +2 -0
- package/src/__tests__/__snapshots__/ipc-snapshot.test.ts.snap +2 -6
- package/src/__tests__/access-request-decision.test.ts +328 -0
- package/src/__tests__/asset-materialize-tool.test.ts +7 -7
- package/src/__tests__/asset-search-tool.test.ts +15 -15
- package/src/__tests__/attachments-store.test.ts +13 -13
- package/src/__tests__/call-controller.test.ts +150 -4
- package/src/__tests__/call-conversation-messages.test.ts +2 -2
- package/src/__tests__/call-pointer-messages.test.ts +28 -0
- package/src/__tests__/call-start-guardian-guard.test.ts +93 -0
- package/src/__tests__/channel-approval-routes.test.ts +108 -12
- package/src/__tests__/channel-guardian.test.ts +19 -15
- package/src/__tests__/checker.test.ts +103 -48
- package/src/__tests__/computer-use-skill-manifest-regression.test.ts +2 -2
- package/src/__tests__/config-watcher.test.ts +356 -0
- package/src/__tests__/conversation-pairing.test.ts +127 -27
- package/src/__tests__/conversation-store.test.ts +36 -36
- package/src/__tests__/date-context.test.ts +179 -1
- package/src/__tests__/db-migration-rollback.test.ts +4 -7
- package/src/__tests__/deterministic-verification-control-plane.test.ts +5 -5
- package/src/__tests__/emit-signal-routing-intent.test.ts +179 -0
- package/src/__tests__/gateway-only-guard.test.ts +188 -0
- package/src/__tests__/guardian-action-conversation-turn.test.ts +451 -0
- package/src/__tests__/guardian-action-copy-generator.test.ts +197 -0
- package/src/__tests__/guardian-action-followup-executor.test.ts +379 -0
- package/src/__tests__/guardian-action-followup-store.test.ts +376 -0
- package/src/__tests__/guardian-action-late-reply.test.ts +425 -0
- package/src/__tests__/guardian-action-no-hardcoded-copy.test.ts +71 -0
- package/src/__tests__/guardian-action-store.test.ts +182 -0
- package/src/__tests__/guardian-action-sweep.test.ts +9 -9
- package/src/__tests__/guardian-dispatch.test.ts +120 -0
- package/src/__tests__/guardian-outbound-http.test.ts +194 -2
- package/src/__tests__/guardian-verification-intent-routing.test.ts +179 -0
- package/src/__tests__/guardian-verify-setup-skill-regression.test.ts +141 -0
- package/src/__tests__/handlers-telegram-config.test.ts +6 -6
- package/src/__tests__/hooks-runner.test.ts +13 -4
- package/src/__tests__/ingress-routes-http.test.ts +443 -0
- package/src/__tests__/intent-routing.test.ts +14 -0
- package/src/__tests__/ipc-snapshot.test.ts +23 -5
- package/src/__tests__/media-reuse-story.e2e.test.ts +7 -7
- package/src/__tests__/memory-regressions.test.ts +16 -12
- package/src/__tests__/non-member-access-request.test.ts +281 -0
- package/src/__tests__/notification-broadcaster.test.ts +115 -4
- package/src/__tests__/notification-decision-strategy.test.ts +138 -1
- package/src/__tests__/notification-deep-link.test.ts +44 -1
- package/src/__tests__/notification-guardian-path.test.ts +157 -0
- package/src/__tests__/notification-routing-intent.test.ts +11 -1
- package/src/__tests__/notification-thread-candidate-validation.test.ts +215 -0
- package/src/__tests__/notification-thread-candidates.test.ts +166 -0
- package/src/__tests__/recording-intent.test.ts +1 -0
- package/src/__tests__/recording-state-machine.test.ts +328 -17
- package/src/__tests__/registry.test.ts +17 -8
- package/src/__tests__/relay-server.test.ts +105 -0
- package/src/__tests__/reminder.test.ts +13 -0
- package/src/__tests__/runtime-attachment-metadata.test.ts +4 -4
- package/src/__tests__/scheduler-recurrence.test.ts +50 -0
- package/src/__tests__/server-history-render.test.ts +8 -8
- package/src/__tests__/session-agent-loop.test.ts +1 -0
- package/src/__tests__/session-runtime-assembly.test.ts +49 -0
- package/src/__tests__/session-skill-tools.test.ts +1 -0
- package/src/__tests__/skill-projection.benchmark.test.ts +11 -3
- package/src/__tests__/slack-channel-config.test.ts +230 -0
- package/src/__tests__/subagent-manager-notify.test.ts +4 -4
- package/src/__tests__/swarm-session-integration.test.ts +2 -2
- package/src/__tests__/system-prompt.test.ts +43 -0
- package/src/__tests__/task-management-tools.test.ts +3 -3
- package/src/__tests__/task-tools.test.ts +3 -3
- package/src/__tests__/trust-store.test.ts +38 -22
- package/src/__tests__/trusted-contact-lifecycle-notifications.test.ts +489 -0
- package/src/__tests__/trusted-contact-multichannel.test.ts +405 -0
- package/src/__tests__/trusted-contact-verification.test.ts +360 -0
- package/src/__tests__/update-bulletin-format.test.ts +119 -0
- package/src/__tests__/update-bulletin-state.test.ts +129 -0
- package/src/__tests__/update-bulletin.test.ts +323 -0
- package/src/__tests__/update-template-contract.test.ts +24 -0
- package/src/__tests__/voice-session-bridge.test.ts +109 -9
- package/src/agent/loop.ts +2 -2
- package/src/amazon/client.ts +2 -3
- package/src/calls/call-controller.ts +241 -39
- package/src/calls/call-conversation-messages.ts +2 -2
- package/src/calls/call-domain.ts +10 -3
- package/src/calls/call-pointer-messages.ts +17 -5
- package/src/calls/guardian-action-sweep.ts +77 -36
- package/src/calls/guardian-dispatch.ts +8 -0
- package/src/calls/relay-server.ts +51 -12
- package/src/calls/twilio-routes.ts +3 -1
- package/src/calls/types.ts +1 -1
- package/src/calls/voice-session-bridge.ts +8 -6
- package/src/cli/core-commands.ts +43 -3
- package/src/cli/map.ts +8 -5
- package/src/config/bundled-skills/phone-calls/SKILL.md +16 -1
- package/src/config/bundled-skills/tasks/SKILL.md +1 -1
- package/src/config/bundled-skills/tasks/TOOLS.json +4 -4
- package/src/config/bundled-skills/time-based-actions/SKILL.md +11 -1
- package/src/config/computer-use-prompt.ts +1 -0
- package/src/config/core-schema.ts +16 -0
- package/src/config/env-registry.ts +1 -0
- package/src/config/env.ts +16 -1
- package/src/config/memory-schema.ts +5 -0
- package/src/config/schema.ts +4 -0
- package/src/config/system-prompt.ts +69 -2
- package/src/config/templates/BOOTSTRAP.md +1 -1
- package/src/config/templates/IDENTITY.md +8 -4
- package/src/config/templates/SOUL.md +14 -0
- package/src/config/templates/UPDATES.md +15 -0
- package/src/config/templates/USER.md +5 -1
- package/src/config/types.ts +1 -0
- package/src/config/update-bulletin-format.ts +54 -0
- package/src/config/update-bulletin-state.ts +49 -0
- package/src/config/update-bulletin-template-path.ts +6 -0
- package/src/config/update-bulletin.ts +97 -0
- package/src/config/vellum-skills/catalog.json +6 -0
- package/src/config/vellum-skills/chatgpt-import/tools/chatgpt-import.ts +1 -1
- package/src/config/vellum-skills/guardian-verify-setup/SKILL.md +44 -10
- package/src/config/vellum-skills/telegram-setup/SKILL.md +4 -4
- package/src/config/vellum-skills/trusted-contacts/SKILL.md +147 -0
- package/src/config/vellum-skills/twilio-setup/SKILL.md +2 -2
- package/src/context/window-manager.ts +43 -3
- package/src/daemon/config-watcher.ts +4 -2
- package/src/daemon/connection-policy.ts +21 -1
- package/src/daemon/daemon-control.ts +219 -8
- package/src/daemon/date-context.ts +174 -1
- package/src/daemon/guardian-action-generators.ts +175 -0
- package/src/daemon/guardian-verification-intent.ts +120 -0
- package/src/daemon/handlers/apps.ts +1 -3
- package/src/daemon/handlers/config-channels.ts +2 -2
- package/src/daemon/handlers/config-heartbeat.ts +1 -1
- package/src/daemon/handlers/config-inbox.ts +55 -159
- package/src/daemon/handlers/config-ingress.ts +1 -1
- package/src/daemon/handlers/config-integrations.ts +1 -1
- package/src/daemon/handlers/config-platform.ts +1 -1
- package/src/daemon/handlers/config-scheduling.ts +2 -2
- package/src/daemon/handlers/config-slack-channel.ts +190 -0
- package/src/daemon/handlers/config-telegram.ts +1 -1
- package/src/daemon/handlers/config-twilio.ts +1 -1
- package/src/daemon/handlers/config-voice.ts +100 -0
- package/src/daemon/handlers/config.ts +3 -0
- package/src/daemon/handlers/identity.ts +45 -25
- package/src/daemon/handlers/misc.ts +83 -5
- package/src/daemon/handlers/navigate-settings.ts +27 -0
- package/src/daemon/handlers/recording.ts +270 -144
- package/src/daemon/handlers/sessions.ts +100 -17
- package/src/daemon/handlers/subagents.ts +3 -3
- package/src/daemon/handlers/work-items.ts +10 -7
- package/src/daemon/ipc-contract/integrations.ts +9 -1
- package/src/daemon/ipc-contract/messages.ts +4 -0
- package/src/daemon/ipc-contract/sessions.ts +1 -1
- package/src/daemon/ipc-contract/settings.ts +26 -0
- package/src/daemon/ipc-contract/shared.ts +2 -0
- package/src/daemon/ipc-contract/work-items.ts +1 -7
- package/src/daemon/ipc-contract/workspace.ts +12 -1
- package/src/daemon/ipc-contract-inventory.json +6 -1
- package/src/daemon/ipc-contract.ts +5 -1
- package/src/daemon/lifecycle.ts +314 -266
- package/src/daemon/recording-intent.ts +0 -41
- package/src/daemon/response-tier.ts +2 -2
- package/src/daemon/server.ts +31 -9
- package/src/daemon/session-agent-loop-handlers.ts +34 -9
- package/src/daemon/session-agent-loop.ts +15 -8
- package/src/daemon/session-history.ts +3 -2
- package/src/daemon/session-media-retry.ts +3 -0
- package/src/daemon/session-messaging.ts +38 -4
- package/src/daemon/session-notifiers.ts +2 -2
- package/src/daemon/session-process.ts +546 -59
- package/src/daemon/session-queue-manager.ts +2 -0
- package/src/daemon/session-runtime-assembly.ts +39 -0
- package/src/daemon/session-skill-tools.ts +13 -4
- package/src/daemon/session-tool-setup.ts +5 -6
- package/src/daemon/session.ts +19 -8
- package/src/daemon/tls-certs.ts +60 -13
- package/src/daemon/tool-side-effects.ts +13 -5
- package/src/gallery/default-gallery.ts +32 -9
- package/src/influencer/client.ts +2 -1
- package/src/memory/channel-delivery-store.ts +35 -567
- package/src/memory/channel-guardian-store.ts +63 -1317
- package/src/memory/conflict-store.ts +4 -4
- package/src/memory/conversation-attention-store.ts +0 -3
- package/src/memory/conversation-crud.ts +668 -0
- package/src/memory/conversation-queries.ts +361 -0
- package/src/memory/conversation-store.ts +44 -983
- package/src/memory/db-connection.ts +3 -0
- package/src/memory/db-init.ts +33 -0
- package/src/memory/delivery-channels.ts +175 -0
- package/src/memory/delivery-crud.ts +211 -0
- package/src/memory/delivery-status.ts +199 -0
- package/src/memory/embedding-backend.ts +70 -4
- package/src/memory/embedding-local.ts +12 -2
- package/src/memory/entity-extractor.ts +3 -8
- package/src/memory/fts-reconciler.ts +136 -0
- package/src/memory/guardian-action-store.ts +418 -5
- package/src/memory/guardian-approvals.ts +569 -0
- package/src/memory/guardian-bindings.ts +130 -0
- package/src/memory/guardian-rate-limits.ts +196 -0
- package/src/memory/guardian-verification.ts +521 -0
- package/src/memory/job-handlers/index-maintenance.ts +2 -1
- package/src/memory/job-utils.ts +8 -5
- package/src/memory/jobs-store.ts +66 -6
- package/src/memory/jobs-worker.ts +23 -1
- package/src/memory/migrations/030-guardian-action-followup.ts +21 -0
- package/src/memory/migrations/030-guardian-verification-purpose.ts +17 -0
- package/src/memory/migrations/031-conversations-thread-type-index.ts +5 -0
- package/src/memory/migrations/032-guardian-delivery-conversation-index.ts +15 -0
- package/src/memory/migrations/032-notification-delivery-thread-decision.ts +20 -0
- package/src/memory/migrations/100-core-tables.ts +1 -1
- package/src/memory/migrations/101-watchers-and-logs.ts +4 -0
- package/src/memory/migrations/108-tasks-and-work-items.ts +1 -1
- package/src/memory/migrations/112-assistant-inbox.ts +1 -1
- package/src/memory/migrations/113-late-migrations.ts +1 -1
- package/src/memory/migrations/116-messages-fts.ts +13 -0
- package/src/memory/migrations/119-schema-indexes-and-columns.ts +37 -0
- package/src/memory/migrations/120-fk-cascade-rebuilds.ts +161 -0
- package/src/memory/migrations/index.ts +10 -3
- package/src/memory/migrations/validate-migration-state.ts +114 -15
- package/src/memory/qdrant-circuit-breaker.ts +105 -0
- package/src/memory/retriever.ts +46 -13
- package/src/memory/schema-migration.ts +4 -0
- package/src/memory/schema.ts +31 -8
- package/src/memory/search/semantic.ts +8 -90
- package/src/notifications/README.md +159 -18
- package/src/notifications/broadcaster.ts +69 -33
- package/src/notifications/conversation-pairing.ts +99 -21
- package/src/notifications/decision-engine.ts +176 -8
- package/src/notifications/deliveries-store.ts +39 -8
- package/src/notifications/emit-signal.ts +1 -0
- package/src/notifications/preferences-store.ts +7 -7
- package/src/notifications/thread-candidates.ts +269 -0
- package/src/notifications/types.ts +19 -0
- package/src/permissions/checker.ts +1 -16
- package/src/permissions/defaults.ts +25 -5
- package/src/permissions/prompter.ts +17 -0
- package/src/permissions/trust-store.ts +2 -0
- package/src/providers/failover.ts +19 -0
- package/src/providers/registry.ts +46 -1
- package/src/runtime/approval-message-composer.ts +1 -1
- package/src/runtime/channel-guardian-service.ts +15 -3
- package/src/runtime/channel-retry-sweep.ts +7 -2
- package/src/runtime/guardian-action-conversation-turn.ts +85 -0
- package/src/runtime/guardian-action-followup-executor.ts +301 -0
- package/src/runtime/guardian-action-message-composer.ts +245 -0
- package/src/runtime/guardian-outbound-actions.ts +26 -6
- package/src/runtime/guardian-verification-templates.ts +15 -9
- package/src/runtime/http-errors.ts +93 -0
- package/src/runtime/http-server.ts +133 -44
- package/src/runtime/http-types.ts +53 -0
- package/src/runtime/ingress-service.ts +237 -0
- package/src/runtime/middleware/error-handler.ts +4 -3
- package/src/runtime/middleware/rate-limiter.ts +160 -0
- package/src/runtime/middleware/request-logger.ts +71 -0
- package/src/runtime/middleware/twilio-validation.ts +7 -6
- package/src/runtime/pending-interactions.ts +12 -0
- package/src/runtime/routes/access-request-decision.ts +215 -0
- package/src/runtime/routes/app-routes.ts +25 -18
- package/src/runtime/routes/approval-routes.ts +18 -47
- package/src/runtime/routes/attachment-routes.ts +15 -41
- package/src/runtime/routes/call-routes.ts +20 -20
- package/src/runtime/routes/channel-delivery-routes.ts +6 -5
- package/src/runtime/routes/contact-routes.ts +4 -9
- package/src/runtime/routes/conversation-attention-routes.ts +2 -1
- package/src/runtime/routes/conversation-routes.ts +26 -57
- package/src/runtime/routes/debug-routes.ts +71 -0
- package/src/runtime/routes/events-routes.ts +3 -2
- package/src/runtime/routes/guardian-approval-interception.ts +221 -0
- package/src/runtime/routes/identity-routes.ts +14 -10
- package/src/runtime/routes/inbound-conversation.ts +3 -2
- package/src/runtime/routes/inbound-message-handler.ts +527 -62
- package/src/runtime/routes/ingress-routes.ts +174 -0
- package/src/runtime/routes/integration-routes.ts +78 -16
- package/src/runtime/routes/pairing-routes.ts +11 -10
- package/src/runtime/routes/secret-routes.ts +10 -18
- package/src/runtime/verification-rate-limiter.ts +83 -0
- package/src/schedule/schedule-store.ts +13 -1
- package/src/schedule/scheduler.ts +1 -1
- package/src/security/secret-ingress.ts +5 -2
- package/src/security/secret-scanner.ts +72 -6
- package/src/subagent/manager.ts +6 -4
- package/src/swarm/plan-validator.ts +4 -1
- package/src/tasks/task-runner.ts +3 -1
- package/src/tools/browser/api-map.ts +9 -6
- package/src/tools/calls/call-start.ts +20 -0
- package/src/tools/executor.ts +50 -568
- package/src/tools/permission-checker.ts +271 -0
- package/src/tools/registry.ts +14 -6
- package/src/tools/reminder/reminder-store.ts +7 -7
- package/src/tools/reminder/reminder.ts +6 -3
- package/src/tools/secret-detection-handler.ts +301 -0
- package/src/tools/subagent/message.ts +1 -1
- package/src/tools/system/voice-config.ts +62 -0
- package/src/tools/tasks/index.ts +3 -3
- package/src/tools/tasks/work-item-list.ts +3 -3
- package/src/tools/tasks/work-item-update.ts +4 -5
- package/src/tools/tool-approval-handler.ts +192 -0
- package/src/tools/tool-manifest.ts +2 -0
- package/src/version.ts +29 -2
- package/src/watcher/watcher-store.ts +9 -9
- package/src/work-items/work-item-runner.ts +9 -6
- /package/src/memory/migrations/{026-embeddings-nullable-vector-json.ts → 026a-embeddings-nullable-vector-json.ts} +0 -0
- /package/src/memory/migrations/{027-guardian-bootstrap-token.ts → 027a-guardian-bootstrap-token.ts} +0 -0
|
@@ -0,0 +1,323 @@
|
|
|
1
|
+
import * as fs from 'node:fs';
|
|
2
|
+
import { existsSync, mkdirSync, readdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
|
|
3
|
+
import { tmpdir } from 'node:os';
|
|
4
|
+
import { join } from 'node:path';
|
|
5
|
+
|
|
6
|
+
import { afterEach, beforeEach, describe, expect, it, mock, spyOn } from 'bun:test';
|
|
7
|
+
|
|
8
|
+
// --- In-memory checkpoint store ---
|
|
9
|
+
const store = new Map<string, string>();
|
|
10
|
+
|
|
11
|
+
mock.module('../memory/checkpoints.js', () => ({
|
|
12
|
+
getMemoryCheckpoint: mock((key: string) => store.get(key) ?? null),
|
|
13
|
+
setMemoryCheckpoint: mock((key: string, value: string) => store.set(key, value)),
|
|
14
|
+
}));
|
|
15
|
+
|
|
16
|
+
// --- Temp directory for workspace paths ---
|
|
17
|
+
let tempDir: string;
|
|
18
|
+
|
|
19
|
+
// --- Temp directory for template files ---
|
|
20
|
+
// Avoids mutating the real source-controlled UPDATES.md template, preventing
|
|
21
|
+
// race conditions with parallel test execution and working tree corruption
|
|
22
|
+
// if the test process crashes.
|
|
23
|
+
let tempTemplateDir: string;
|
|
24
|
+
|
|
25
|
+
// Mock platform to avoid env-registry transitive imports.
|
|
26
|
+
// All needed exports are stubbed; getWorkspacePromptPath is the only one
|
|
27
|
+
// exercised by update-bulletin.ts.
|
|
28
|
+
mock.module('../util/platform.js', () => ({
|
|
29
|
+
getWorkspacePromptPath: mock((file: string) => join(tempDir, file)),
|
|
30
|
+
getWorkspaceDir: () => tempDir,
|
|
31
|
+
getRootDir: () => tempDir,
|
|
32
|
+
getDataDir: () => join(tempDir, 'data'),
|
|
33
|
+
getPlatformName: () => 'darwin',
|
|
34
|
+
isMacOS: () => false,
|
|
35
|
+
isLinux: () => false,
|
|
36
|
+
isWindows: () => false,
|
|
37
|
+
ensureDataDir: () => {},
|
|
38
|
+
getDbPath: () => '',
|
|
39
|
+
getLogPath: () => '',
|
|
40
|
+
getHistoryPath: () => '',
|
|
41
|
+
getHooksDir: () => '',
|
|
42
|
+
getSocketPath: () => '',
|
|
43
|
+
getSessionTokenPath: () => '',
|
|
44
|
+
getHttpTokenPath: () => '',
|
|
45
|
+
getPlatformTokenPath: () => '',
|
|
46
|
+
getPidPath: () => '',
|
|
47
|
+
getWorkspaceConfigPath: () => '',
|
|
48
|
+
getWorkspaceSkillsDir: () => '',
|
|
49
|
+
getWorkspaceHooksDir: () => '',
|
|
50
|
+
getIpcBlobDir: () => '',
|
|
51
|
+
getSandboxRootDir: () => '',
|
|
52
|
+
getSandboxWorkingDir: () => '',
|
|
53
|
+
getInterfacesDir: () => '',
|
|
54
|
+
getClipboardCommand: () => null,
|
|
55
|
+
readLockfile: () => null,
|
|
56
|
+
normalizeAssistantId: (id: string) => id,
|
|
57
|
+
writeLockfile: () => {},
|
|
58
|
+
readPlatformToken: () => null,
|
|
59
|
+
readSessionToken: () => null,
|
|
60
|
+
readHttpToken: () => null,
|
|
61
|
+
removeSocketFile: () => {},
|
|
62
|
+
getTCPPort: () => 8765,
|
|
63
|
+
isTCPEnabled: () => false,
|
|
64
|
+
getTCPHost: () => '127.0.0.1',
|
|
65
|
+
isIOSPairingEnabled: () => false,
|
|
66
|
+
migrateToDataLayout: () => {},
|
|
67
|
+
migratePath: () => {},
|
|
68
|
+
migrateToWorkspaceLayout: () => {},
|
|
69
|
+
}));
|
|
70
|
+
|
|
71
|
+
// Mock system-prompt to provide only stripCommentLines without pulling in
|
|
72
|
+
// the rest of the system-prompt transitive dependency tree.
|
|
73
|
+
mock.module('../config/system-prompt.js', () => {
|
|
74
|
+
// Inline a minimal implementation of stripCommentLines matching production behavior.
|
|
75
|
+
function stripCommentLines(content: string): string {
|
|
76
|
+
const normalized = content.replace(/\r\n/g, '\n');
|
|
77
|
+
let openFenceChar: string | null = null;
|
|
78
|
+
const filtered = normalized.split('\n').filter((line) => {
|
|
79
|
+
const fenceMatch = line.match(/^ {0,3}(`{3,}|~{3,})/);
|
|
80
|
+
if (fenceMatch) {
|
|
81
|
+
const char = fenceMatch[1][0];
|
|
82
|
+
if (!openFenceChar) {
|
|
83
|
+
openFenceChar = char;
|
|
84
|
+
} else if (char === openFenceChar) {
|
|
85
|
+
openFenceChar = null;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
if (openFenceChar) return true;
|
|
89
|
+
return !line.trimStart().startsWith('_');
|
|
90
|
+
});
|
|
91
|
+
return filtered
|
|
92
|
+
.join('\n')
|
|
93
|
+
.replace(/\n{3,}/g, '\n\n')
|
|
94
|
+
.trim();
|
|
95
|
+
}
|
|
96
|
+
return { stripCommentLines };
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
mock.module('../version.js', () => ({
|
|
100
|
+
APP_VERSION: '1.0.0',
|
|
101
|
+
}));
|
|
102
|
+
|
|
103
|
+
// Mock the template path module so tests read from a temp directory instead
|
|
104
|
+
// of the real source-controlled template file.
|
|
105
|
+
mock.module('../config/update-bulletin-template-path.js', () => ({
|
|
106
|
+
getTemplatePath: () => join(tempTemplateDir, 'UPDATES.md'),
|
|
107
|
+
}));
|
|
108
|
+
|
|
109
|
+
const { syncUpdateBulletinOnStartup } = await import('../config/update-bulletin.js');
|
|
110
|
+
|
|
111
|
+
const TEST_TEMPLATE = '## What\'s New\n\nTest release notes.\n';
|
|
112
|
+
const COMMENT_ONLY_TEMPLATE = '_ This is a comment-only template.\n_ No real content here.\n';
|
|
113
|
+
|
|
114
|
+
describe('syncUpdateBulletinOnStartup', () => {
|
|
115
|
+
beforeEach(() => {
|
|
116
|
+
store.clear();
|
|
117
|
+
tempDir = join(tmpdir(), `update-bulletin-test-${Date.now()}-${Math.random().toString(36).slice(2)}`);
|
|
118
|
+
mkdirSync(tempDir, { recursive: true });
|
|
119
|
+
tempTemplateDir = join(tmpdir(), `update-bulletin-tpl-${Date.now()}-${Math.random().toString(36).slice(2)}`);
|
|
120
|
+
mkdirSync(tempTemplateDir, { recursive: true });
|
|
121
|
+
// Write a test template with real content so materialization proceeds
|
|
122
|
+
writeFileSync(join(tempTemplateDir, 'UPDATES.md'), TEST_TEMPLATE, 'utf-8');
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
afterEach(() => {
|
|
126
|
+
rmSync(tempDir, { recursive: true, force: true });
|
|
127
|
+
rmSync(tempTemplateDir, { recursive: true, force: true });
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
it('creates workspace file on first eligible run', () => {
|
|
131
|
+
const workspacePath = join(tempDir, 'UPDATES.md');
|
|
132
|
+
expect(existsSync(workspacePath)).toBe(false);
|
|
133
|
+
|
|
134
|
+
syncUpdateBulletinOnStartup();
|
|
135
|
+
|
|
136
|
+
expect(existsSync(workspacePath)).toBe(true);
|
|
137
|
+
const content = readFileSync(workspacePath, 'utf-8');
|
|
138
|
+
expect(content).toContain('<!-- vellum-update-release:1.0.0 -->');
|
|
139
|
+
expect(content).toContain("What's New");
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
it('appends release block when workspace file exists without current marker', () => {
|
|
143
|
+
const workspacePath = join(tempDir, 'UPDATES.md');
|
|
144
|
+
const preExisting = '<!-- vellum-update-release:0.9.0 -->\nOld release notes.\n';
|
|
145
|
+
writeFileSync(workspacePath, preExisting, 'utf-8');
|
|
146
|
+
|
|
147
|
+
syncUpdateBulletinOnStartup();
|
|
148
|
+
|
|
149
|
+
const content = readFileSync(workspacePath, 'utf-8');
|
|
150
|
+
expect(content).toContain('<!-- vellum-update-release:0.9.0 -->');
|
|
151
|
+
expect(content).toContain('<!-- vellum-update-release:1.0.0 -->');
|
|
152
|
+
expect(content).toContain('Old release notes.');
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
it('does not duplicate same marker on repeated runs', () => {
|
|
156
|
+
syncUpdateBulletinOnStartup();
|
|
157
|
+
const workspacePath = join(tempDir, 'UPDATES.md');
|
|
158
|
+
const afterFirst = readFileSync(workspacePath, 'utf-8');
|
|
159
|
+
|
|
160
|
+
syncUpdateBulletinOnStartup();
|
|
161
|
+
const afterSecond = readFileSync(workspacePath, 'utf-8');
|
|
162
|
+
|
|
163
|
+
expect(afterSecond).toBe(afterFirst);
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
it('skips completed release', () => {
|
|
167
|
+
store.set('updates:completed_releases', JSON.stringify(['1.0.0']));
|
|
168
|
+
const workspacePath = join(tempDir, 'UPDATES.md');
|
|
169
|
+
|
|
170
|
+
syncUpdateBulletinOnStartup();
|
|
171
|
+
|
|
172
|
+
expect(existsSync(workspacePath)).toBe(false);
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
it('adds current release to active set', () => {
|
|
176
|
+
syncUpdateBulletinOnStartup();
|
|
177
|
+
|
|
178
|
+
const raw = store.get('updates:active_releases');
|
|
179
|
+
expect(raw).toBeDefined();
|
|
180
|
+
const active: string[] = JSON.parse(raw!);
|
|
181
|
+
expect(active).toContain('1.0.0');
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
it('marks active releases as completed when UPDATES.md is deleted', () => {
|
|
185
|
+
// Pre-populate active releases in the store
|
|
186
|
+
store.set('updates:active_releases', JSON.stringify(['0.8.0', '0.9.0']));
|
|
187
|
+
|
|
188
|
+
// Workspace file does not exist — simulates the assistant having deleted it
|
|
189
|
+
const workspacePath = join(tempDir, 'UPDATES.md');
|
|
190
|
+
expect(existsSync(workspacePath)).toBe(false);
|
|
191
|
+
|
|
192
|
+
syncUpdateBulletinOnStartup();
|
|
193
|
+
|
|
194
|
+
// Active set should be cleared (except for the newly-added current release)
|
|
195
|
+
const activeRaw = store.get('updates:active_releases');
|
|
196
|
+
expect(activeRaw).toBeDefined();
|
|
197
|
+
const active: string[] = JSON.parse(activeRaw!);
|
|
198
|
+
// The old releases should not be in the active set
|
|
199
|
+
expect(active).not.toContain('0.8.0');
|
|
200
|
+
expect(active).not.toContain('0.9.0');
|
|
201
|
+
|
|
202
|
+
// The old releases should now be completed
|
|
203
|
+
const completedRaw = store.get('updates:completed_releases');
|
|
204
|
+
expect(completedRaw).toBeDefined();
|
|
205
|
+
const completed: string[] = JSON.parse(completedRaw!);
|
|
206
|
+
expect(completed).toContain('0.8.0');
|
|
207
|
+
expect(completed).toContain('0.9.0');
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
it('does not recreate completed release after deletion', () => {
|
|
211
|
+
// First run — creates the workspace file and marks 1.0.0 active
|
|
212
|
+
syncUpdateBulletinOnStartup();
|
|
213
|
+
const workspacePath = join(tempDir, 'UPDATES.md');
|
|
214
|
+
expect(existsSync(workspacePath)).toBe(true);
|
|
215
|
+
|
|
216
|
+
// Simulate assistant deleting the file to signal completion
|
|
217
|
+
rmSync(workspacePath);
|
|
218
|
+
expect(existsSync(workspacePath)).toBe(false);
|
|
219
|
+
|
|
220
|
+
// Second run — deletion-completion should mark 1.0.0 completed
|
|
221
|
+
syncUpdateBulletinOnStartup();
|
|
222
|
+
|
|
223
|
+
// The file should NOT be recreated since the release is now completed
|
|
224
|
+
expect(existsSync(workspacePath)).toBe(false);
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
it('merges pending old block with new release block', () => {
|
|
228
|
+
const workspacePath = join(tempDir, 'UPDATES.md');
|
|
229
|
+
// Pre-create workspace file with an old release block
|
|
230
|
+
const oldContent =
|
|
231
|
+
'<!-- vellum-update-release:0.9.0 -->\nOld release notes for 0.9.0.\n<!-- /vellum-update-release:0.9.0 -->\n';
|
|
232
|
+
writeFileSync(workspacePath, oldContent, 'utf-8');
|
|
233
|
+
|
|
234
|
+
syncUpdateBulletinOnStartup();
|
|
235
|
+
|
|
236
|
+
const content = readFileSync(workspacePath, 'utf-8');
|
|
237
|
+
// Both old and new release blocks should be present
|
|
238
|
+
expect(content).toContain('<!-- vellum-update-release:0.9.0 -->');
|
|
239
|
+
expect(content).toContain('Old release notes for 0.9.0.');
|
|
240
|
+
expect(content).toContain('<!-- vellum-update-release:1.0.0 -->');
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
it('idempotent on repeated sync calls', () => {
|
|
244
|
+
// First call
|
|
245
|
+
syncUpdateBulletinOnStartup();
|
|
246
|
+
const workspacePath = join(tempDir, 'UPDATES.md');
|
|
247
|
+
const afterFirst = readFileSync(workspacePath, 'utf-8');
|
|
248
|
+
|
|
249
|
+
// Second call
|
|
250
|
+
syncUpdateBulletinOnStartup();
|
|
251
|
+
const afterSecond = readFileSync(workspacePath, 'utf-8');
|
|
252
|
+
|
|
253
|
+
expect(afterSecond).toBe(afterFirst);
|
|
254
|
+
|
|
255
|
+
// Third call for good measure
|
|
256
|
+
syncUpdateBulletinOnStartup();
|
|
257
|
+
const afterThird = readFileSync(workspacePath, 'utf-8');
|
|
258
|
+
|
|
259
|
+
expect(afterThird).toBe(afterFirst);
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
it('write path produces valid UTF-8 with trailing newline', () => {
|
|
263
|
+
syncUpdateBulletinOnStartup();
|
|
264
|
+
const workspacePath = join(tempDir, 'UPDATES.md');
|
|
265
|
+
const content = readFileSync(workspacePath, 'utf-8');
|
|
266
|
+
|
|
267
|
+
expect(content.length).toBeGreaterThan(0);
|
|
268
|
+
expect(content.endsWith('\n')).toBe(true);
|
|
269
|
+
|
|
270
|
+
// Verify round-trip through Buffer produces identical content (valid UTF-8)
|
|
271
|
+
const roundTripped = Buffer.from(content, 'utf-8').toString('utf-8');
|
|
272
|
+
expect(roundTripped).toBe(content);
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
it('no temp file leftovers after successful write', () => {
|
|
276
|
+
syncUpdateBulletinOnStartup();
|
|
277
|
+
|
|
278
|
+
const entries = readdirSync(tempDir);
|
|
279
|
+
const tmpFiles = entries.filter((e) => e.includes('.tmp.'));
|
|
280
|
+
expect(tmpFiles).toHaveLength(0);
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
it('skips materialization when template is comment-only', () => {
|
|
284
|
+
// Write a comment-only template fixture (no real content after stripping)
|
|
285
|
+
writeFileSync(join(tempTemplateDir, 'UPDATES.md'), COMMENT_ONLY_TEMPLATE, 'utf-8');
|
|
286
|
+
|
|
287
|
+
const workspacePath = join(tempDir, 'UPDATES.md');
|
|
288
|
+
syncUpdateBulletinOnStartup();
|
|
289
|
+
|
|
290
|
+
expect(existsSync(workspacePath)).toBe(false);
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
it('preserves existing file when atomic write fails', () => {
|
|
294
|
+
const workspacePath = join(tempDir, 'UPDATES.md');
|
|
295
|
+
const originalContent = '<!-- vellum-update-release:0.9.0 -->\nOriginal content.\n';
|
|
296
|
+
writeFileSync(workspacePath, originalContent, 'utf-8');
|
|
297
|
+
|
|
298
|
+
// Mock writeFileSync to throw when writing the temp file, simulating a
|
|
299
|
+
// disk-full or permission error deterministically (chmod-based approaches
|
|
300
|
+
// are unreliable when running as root or with CAP_DAC_OVERRIDE).
|
|
301
|
+
const originalWriteFileSync = fs.writeFileSync;
|
|
302
|
+
const spy = spyOn(fs, 'writeFileSync').mockImplementation((...args: Parameters<typeof fs.writeFileSync>) => {
|
|
303
|
+
if (typeof args[0] === 'string' && args[0].includes('.tmp.')) {
|
|
304
|
+
throw new Error('Simulated write failure');
|
|
305
|
+
}
|
|
306
|
+
return originalWriteFileSync(...args);
|
|
307
|
+
});
|
|
308
|
+
try {
|
|
309
|
+
expect(() => syncUpdateBulletinOnStartup()).toThrow('Simulated write failure');
|
|
310
|
+
} finally {
|
|
311
|
+
spy.mockRestore();
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// Original content should be preserved (atomic write never renamed over it)
|
|
315
|
+
const content = readFileSync(workspacePath, 'utf-8');
|
|
316
|
+
expect(content).toBe(originalContent);
|
|
317
|
+
|
|
318
|
+
// No temp file leftovers
|
|
319
|
+
const entries = readdirSync(tempDir);
|
|
320
|
+
const tmpFiles = entries.filter((e: string) => e.includes('.tmp.'));
|
|
321
|
+
expect(tmpFiles).toHaveLength(0);
|
|
322
|
+
});
|
|
323
|
+
});
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Contract test: ensures the bundled UPDATES.md template exists and is readable.
|
|
3
|
+
*
|
|
4
|
+
* The template may be comment-only (no real content) for no-op releases —
|
|
5
|
+
* the bulletin system treats an empty-after-stripping template as a skip signal.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
9
|
+
import { join } from 'node:path';
|
|
10
|
+
|
|
11
|
+
import { describe, expect, test } from 'bun:test';
|
|
12
|
+
|
|
13
|
+
const TEMPLATE_PATH = join(import.meta.dirname, '..', 'config', 'templates', 'UPDATES.md');
|
|
14
|
+
|
|
15
|
+
describe('UPDATES.md template contract', () => {
|
|
16
|
+
test('template file exists', () => {
|
|
17
|
+
expect(existsSync(TEMPLATE_PATH)).toBe(true);
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
test('template is a readable UTF-8 file', () => {
|
|
21
|
+
const content = readFileSync(TEMPLATE_PATH, 'utf-8');
|
|
22
|
+
expect(typeof content).toBe('string');
|
|
23
|
+
});
|
|
24
|
+
});
|
|
@@ -8,6 +8,18 @@ import type { ServerMessage } from '../daemon/ipc-protocol.js';
|
|
|
8
8
|
import type { Session } from '../daemon/session.js';
|
|
9
9
|
|
|
10
10
|
const testDir = mkdtempSync(join(tmpdir(), 'voice-bridge-test-'));
|
|
11
|
+
let mockedConfig: {
|
|
12
|
+
secretDetection: { enabled: boolean };
|
|
13
|
+
calls: { disclosure: { enabled: boolean; text: string } };
|
|
14
|
+
} = {
|
|
15
|
+
secretDetection: { enabled: false },
|
|
16
|
+
calls: {
|
|
17
|
+
disclosure: {
|
|
18
|
+
enabled: false,
|
|
19
|
+
text: '',
|
|
20
|
+
},
|
|
21
|
+
},
|
|
22
|
+
};
|
|
11
23
|
|
|
12
24
|
mock.module('../util/platform.js', () => ({
|
|
13
25
|
getRootDir: () => testDir,
|
|
@@ -29,15 +41,7 @@ mock.module('../util/logger.js', () => ({
|
|
|
29
41
|
}));
|
|
30
42
|
|
|
31
43
|
mock.module('../config/loader.js', () => ({
|
|
32
|
-
getConfig: () =>
|
|
33
|
-
secretDetection: { enabled: false },
|
|
34
|
-
calls: {
|
|
35
|
-
disclosure: {
|
|
36
|
-
enabled: false,
|
|
37
|
-
text: '',
|
|
38
|
-
},
|
|
39
|
-
},
|
|
40
|
-
}),
|
|
44
|
+
getConfig: () => mockedConfig,
|
|
41
45
|
}));
|
|
42
46
|
|
|
43
47
|
import { setVoiceBridgeDeps, startVoiceTurn } from '../calls/voice-session-bridge.js';
|
|
@@ -85,6 +89,15 @@ function injectDeps(sessionFactory: () => Session): void {
|
|
|
85
89
|
|
|
86
90
|
describe('voice-session-bridge', () => {
|
|
87
91
|
beforeEach(() => {
|
|
92
|
+
mockedConfig = {
|
|
93
|
+
secretDetection: { enabled: false },
|
|
94
|
+
calls: {
|
|
95
|
+
disclosure: {
|
|
96
|
+
enabled: false,
|
|
97
|
+
text: '',
|
|
98
|
+
},
|
|
99
|
+
},
|
|
100
|
+
};
|
|
88
101
|
const db = getDb();
|
|
89
102
|
db.run('DELETE FROM messages');
|
|
90
103
|
db.run('DELETE FROM conversations');
|
|
@@ -415,6 +428,93 @@ describe('voice-session-bridge', () => {
|
|
|
415
428
|
expect(capturedGuardianContext).toEqual(guardianCtx);
|
|
416
429
|
});
|
|
417
430
|
|
|
431
|
+
test('inbound non-guardian opener prompt uses pickup framing instead of outbound phrasing', async () => {
|
|
432
|
+
const conversation = createConversation('voice bridge inbound opener framing test');
|
|
433
|
+
const events: ServerMessage[] = [
|
|
434
|
+
{ type: 'message_complete', sessionId: conversation.id },
|
|
435
|
+
];
|
|
436
|
+
|
|
437
|
+
let capturedPrompt: string | null = null;
|
|
438
|
+
const session = {
|
|
439
|
+
...makeStreamingSession(events),
|
|
440
|
+
setVoiceCallControlPrompt: (prompt: string | null) => {
|
|
441
|
+
if (prompt != null) capturedPrompt = prompt;
|
|
442
|
+
},
|
|
443
|
+
} as unknown as Session;
|
|
444
|
+
|
|
445
|
+
injectDeps(() => session);
|
|
446
|
+
|
|
447
|
+
await startVoiceTurn({
|
|
448
|
+
conversationId: conversation.id,
|
|
449
|
+
content: 'Hello there',
|
|
450
|
+
isInbound: true,
|
|
451
|
+
guardianContext: {
|
|
452
|
+
sourceChannel: 'voice',
|
|
453
|
+
actorRole: 'non-guardian',
|
|
454
|
+
},
|
|
455
|
+
onTextDelta: () => {},
|
|
456
|
+
onComplete: () => {},
|
|
457
|
+
onError: () => {},
|
|
458
|
+
});
|
|
459
|
+
|
|
460
|
+
await new Promise((r) => setTimeout(r, 50));
|
|
461
|
+
if (!capturedPrompt) throw new Error('Expected voice call control prompt to be set');
|
|
462
|
+
const prompt: string = capturedPrompt;
|
|
463
|
+
|
|
464
|
+
expect(prompt).toContain('this is an inbound call you are answering (not a call you initiated)');
|
|
465
|
+
expect(prompt).toContain('Introduce yourself once at the start using your assistant name if you know it');
|
|
466
|
+
expect(prompt).toContain('If your assistant name is not known, skip the name and just identify yourself as the guardian\'s assistant.');
|
|
467
|
+
expect(prompt).toContain('Do NOT say "I\'m calling" or "I\'m calling on behalf of".');
|
|
468
|
+
});
|
|
469
|
+
|
|
470
|
+
test('inbound disclosure guidance is rewritten for pickup context', async () => {
|
|
471
|
+
mockedConfig = {
|
|
472
|
+
secretDetection: { enabled: false },
|
|
473
|
+
calls: {
|
|
474
|
+
disclosure: {
|
|
475
|
+
enabled: true,
|
|
476
|
+
text: 'At the very beginning of the call, introduce yourself as an assistant calling on behalf of the person you represent.',
|
|
477
|
+
},
|
|
478
|
+
},
|
|
479
|
+
};
|
|
480
|
+
|
|
481
|
+
const conversation = createConversation('voice bridge inbound disclosure rewrite test');
|
|
482
|
+
const events: ServerMessage[] = [
|
|
483
|
+
{ type: 'message_complete', sessionId: conversation.id },
|
|
484
|
+
];
|
|
485
|
+
|
|
486
|
+
let capturedPrompt: string | null = null;
|
|
487
|
+
const session = {
|
|
488
|
+
...makeStreamingSession(events),
|
|
489
|
+
setVoiceCallControlPrompt: (prompt: string | null) => {
|
|
490
|
+
if (prompt != null) capturedPrompt = prompt;
|
|
491
|
+
},
|
|
492
|
+
} as unknown as Session;
|
|
493
|
+
|
|
494
|
+
injectDeps(() => session);
|
|
495
|
+
|
|
496
|
+
await startVoiceTurn({
|
|
497
|
+
conversationId: conversation.id,
|
|
498
|
+
content: 'Hi',
|
|
499
|
+
isInbound: true,
|
|
500
|
+
guardianContext: {
|
|
501
|
+
sourceChannel: 'voice',
|
|
502
|
+
actorRole: 'non-guardian',
|
|
503
|
+
},
|
|
504
|
+
onTextDelta: () => {},
|
|
505
|
+
onComplete: () => {},
|
|
506
|
+
onError: () => {},
|
|
507
|
+
});
|
|
508
|
+
|
|
509
|
+
await new Promise((r) => setTimeout(r, 50));
|
|
510
|
+
if (!capturedPrompt) throw new Error('Expected voice call control prompt to be set');
|
|
511
|
+
const prompt: string = capturedPrompt;
|
|
512
|
+
|
|
513
|
+
expect(prompt).toContain('At the very beginning of the call, introduce yourself as an assistant calling on behalf of the person you represent.');
|
|
514
|
+
expect(prompt).toContain('rewrite any disclosure naturally for pickup context');
|
|
515
|
+
expect(prompt).toContain('Do NOT say "I\'m calling", "I called you", or "I\'m calling on behalf of".');
|
|
516
|
+
});
|
|
517
|
+
|
|
418
518
|
test('auto-denies confirmation requests for non-guardian voice turns', async () => {
|
|
419
519
|
const conversation = createConversation('voice bridge auto-deny non-guardian test');
|
|
420
520
|
|
package/src/agent/loop.ts
CHANGED
|
@@ -85,7 +85,7 @@ export class AgentLoop {
|
|
|
85
85
|
|
|
86
86
|
async run(
|
|
87
87
|
messages: Message[],
|
|
88
|
-
onEvent: (event: AgentEvent) => void
|
|
88
|
+
onEvent: (event: AgentEvent) => void | Promise<void>,
|
|
89
89
|
signal?: AbortSignal,
|
|
90
90
|
requestId?: string,
|
|
91
91
|
onCheckpoint?: (checkpoint: CheckpointInfo) => CheckpointDecision,
|
|
@@ -244,7 +244,7 @@ export class AgentLoop {
|
|
|
244
244
|
};
|
|
245
245
|
history.push(assistantMessage);
|
|
246
246
|
|
|
247
|
-
onEvent({ type: 'message_complete', message: assistantMessage });
|
|
247
|
+
await onEvent({ type: 'message_complete', message: assistantMessage });
|
|
248
248
|
|
|
249
249
|
// Check for tool use
|
|
250
250
|
toolUseBlocks = response.content.filter(
|
package/src/amazon/client.ts
CHANGED
|
@@ -50,7 +50,7 @@
|
|
|
50
50
|
|
|
51
51
|
import type { ExtensionCommand, ExtensionResponse } from '../browser-extension-relay/protocol.js';
|
|
52
52
|
import { extensionRelayServer } from '../browser-extension-relay/server.js';
|
|
53
|
-
import {
|
|
53
|
+
import { getGatewayInternalBaseUrl } from '../config/env.js';
|
|
54
54
|
import type { ExtractedCredential } from '../tools/browser/network-recording-types.js';
|
|
55
55
|
import { readHttpToken } from '../util/platform.js';
|
|
56
56
|
import {
|
|
@@ -81,8 +81,7 @@ export async function sendRelayCommand(command: Record<string, unknown>): Promis
|
|
|
81
81
|
throw new Error('Browser extension relay is not connected and no HTTP token found. Is the daemon running?');
|
|
82
82
|
}
|
|
83
83
|
|
|
84
|
-
const
|
|
85
|
-
const resp = await fetch(`http://127.0.0.1:${port}/v1/browser-relay/command`, {
|
|
84
|
+
const resp = await fetch(`${getGatewayInternalBaseUrl()}/v1/browser-relay/command`, {
|
|
86
85
|
method: 'POST',
|
|
87
86
|
headers: {
|
|
88
87
|
'Content-Type': 'application/json',
|