@vellumai/assistant 0.3.28 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/ARCHITECTURE.md +33 -3
- package/bun.lock +4 -1
- package/docs/trusted-contact-access.md +9 -2
- package/package.json +6 -3
- package/scripts/ipc/generate-swift.ts +3 -3
- package/src/__tests__/__snapshots__/ipc-snapshot.test.ts.snap +80 -0
- package/src/__tests__/agent-loop-thinking.test.ts +1 -1
- package/src/__tests__/approval-routes-http.test.ts +13 -5
- package/src/__tests__/asset-materialize-tool.test.ts +2 -0
- package/src/__tests__/asset-search-tool.test.ts +2 -0
- package/src/__tests__/assistant-events-sse-hardening.test.ts +4 -2
- package/src/__tests__/attachments-store.test.ts +2 -0
- package/src/__tests__/browser-skill-endstate.test.ts +3 -3
- package/src/__tests__/call-controller.test.ts +30 -29
- package/src/__tests__/call-routes-http.test.ts +34 -32
- package/src/__tests__/call-start-guardian-guard.test.ts +2 -0
- package/src/__tests__/channel-invite-transport.test.ts +6 -6
- package/src/__tests__/channel-reply-delivery.test.ts +19 -0
- package/src/__tests__/channel-retry-sweep.test.ts +130 -0
- package/src/__tests__/clarification-resolver.test.ts +2 -0
- package/src/__tests__/claude-code-skill-regression.test.ts +2 -0
- package/src/__tests__/claude-code-tool-profiles.test.ts +2 -0
- package/src/__tests__/commit-message-enrichment-service.test.ts +9 -1
- package/src/__tests__/computer-use-session-lifecycle.test.ts +2 -0
- package/src/__tests__/computer-use-session-working-dir.test.ts +1 -0
- package/src/__tests__/computer-use-skill-lifecycle-cleanup.test.ts +2 -0
- package/src/__tests__/config-schema.test.ts +5 -5
- package/src/__tests__/config-watcher.test.ts +3 -1
- package/src/__tests__/connection-policy.test.ts +14 -5
- package/src/__tests__/contacts-tools.test.ts +3 -1
- package/src/__tests__/contradiction-checker.test.ts +2 -0
- package/src/__tests__/conversation-pairing.test.ts +10 -0
- package/src/__tests__/conversation-routes.test.ts +1 -1
- package/src/__tests__/credential-security-invariants.test.ts +16 -6
- package/src/__tests__/credential-vault-unit.test.ts +2 -2
- package/src/__tests__/credential-vault.test.ts +5 -4
- package/src/__tests__/daemon-lifecycle.test.ts +9 -0
- package/src/__tests__/daemon-server-session-init.test.ts +27 -0
- package/src/__tests__/elevenlabs-config.test.ts +2 -0
- package/src/__tests__/encrypted-store.test.ts +10 -5
- package/src/__tests__/followup-tools.test.ts +3 -1
- package/src/__tests__/gateway-only-enforcement.test.ts +21 -21
- package/src/__tests__/gmail-integration.test.ts +0 -1
- package/src/__tests__/guardian-control-plane-policy.test.ts +19 -19
- package/src/__tests__/guardian-dispatch.test.ts +2 -0
- package/src/__tests__/guardian-grant-minting.test.ts +68 -1
- package/src/__tests__/guardian-outbound-http.test.ts +12 -9
- package/src/__tests__/guardian-routing-invariants.test.ts +138 -0
- package/src/__tests__/handle-user-message-secret-resume.test.ts +1 -0
- package/src/__tests__/handlers-slack-config.test.ts +3 -1
- package/src/__tests__/handlers-telegram-config.test.ts +3 -1
- package/src/__tests__/handlers-twilio-config.test.ts +3 -1
- package/src/__tests__/handlers-twitter-config.test.ts +3 -1
- package/src/__tests__/handlers-user-message-approval-consumption.test.ts +318 -0
- package/src/__tests__/heartbeat-service.test.ts +20 -0
- package/src/__tests__/inbound-invite-redemption.test.ts +33 -0
- package/src/__tests__/ingress-reconcile.test.ts +3 -1
- package/src/__tests__/ingress-routes-http.test.ts +231 -4
- package/src/__tests__/intent-routing.test.ts +2 -0
- package/src/__tests__/ipc-snapshot.test.ts +13 -0
- package/src/__tests__/media-generate-image.test.ts +21 -0
- package/src/__tests__/media-reuse-story.e2e.test.ts +2 -0
- package/src/__tests__/memory-regressions.test.ts +20 -20
- package/src/__tests__/non-member-access-request.test.ts +183 -9
- package/src/__tests__/notification-decision-fallback.test.ts +2 -0
- package/src/__tests__/notification-decision-strategy.test.ts +61 -0
- package/src/__tests__/notification-guardian-path.test.ts +2 -0
- package/src/__tests__/oauth-connect-handler.test.ts +3 -1
- package/src/__tests__/oauth2-gateway-transport.test.ts +2 -0
- package/src/__tests__/onboarding-starter-tasks.test.ts +4 -4
- package/src/__tests__/pairing-routes.test.ts +171 -0
- package/src/__tests__/playbook-execution.test.ts +3 -1
- package/src/__tests__/playbook-tools.test.ts +3 -1
- package/src/__tests__/provider-error-scenarios.test.ts +59 -8
- package/src/__tests__/proxy-approval-callback.test.ts +2 -0
- package/src/__tests__/recording-handler.test.ts +11 -0
- package/src/__tests__/recording-intent-handler.test.ts +15 -0
- package/src/__tests__/recording-state-machine.test.ts +13 -2
- package/src/__tests__/registry.test.ts +7 -3
- package/src/__tests__/relay-server.test.ts +148 -28
- package/src/__tests__/runtime-attachment-metadata.test.ts +4 -2
- package/src/__tests__/runtime-events-sse-parity.test.ts +21 -0
- package/src/__tests__/runtime-events-sse.test.ts +4 -2
- package/src/__tests__/sandbox-diagnostics.test.ts +2 -0
- package/src/__tests__/schedule-tools.test.ts +3 -1
- package/src/__tests__/send-endpoint-busy.test.ts +4 -0
- package/src/__tests__/session-abort-tool-results.test.ts +23 -0
- package/src/__tests__/session-agent-loop.test.ts +16 -0
- package/src/__tests__/session-conflict-gate.test.ts +21 -0
- package/src/__tests__/session-load-history-repair.test.ts +27 -17
- package/src/__tests__/session-pre-run-repair.test.ts +23 -0
- package/src/__tests__/session-profile-injection.test.ts +21 -0
- package/src/__tests__/session-provider-retry-repair.test.ts +20 -0
- package/src/__tests__/session-queue.test.ts +23 -0
- package/src/__tests__/session-runtime-assembly.test.ts +50 -12
- package/src/__tests__/session-skill-tools.test.ts +27 -5
- package/src/__tests__/session-slash-known.test.ts +23 -0
- package/src/__tests__/session-slash-queue.test.ts +23 -0
- package/src/__tests__/session-slash-unknown.test.ts +23 -0
- package/src/__tests__/session-workspace-cache-state.test.ts +7 -0
- package/src/__tests__/session-workspace-injection.test.ts +21 -0
- package/src/__tests__/session-workspace-tool-tracking.test.ts +21 -0
- package/src/__tests__/shell-credential-ref.test.ts +2 -0
- package/src/__tests__/skill-feature-flags-integration.test.ts +6 -6
- package/src/__tests__/skill-load-feature-flag.test.ts +5 -4
- package/src/__tests__/skill-projection-feature-flag.test.ts +22 -0
- package/src/__tests__/skills.test.ts +8 -4
- package/src/__tests__/slack-channel-config.test.ts +3 -1
- package/src/__tests__/subagent-tools.test.ts +19 -0
- package/src/__tests__/swarm-recursion.test.ts +2 -0
- package/src/__tests__/swarm-session-integration.test.ts +2 -0
- package/src/__tests__/swarm-tool.test.ts +2 -0
- package/src/__tests__/system-prompt.test.ts +3 -1
- package/src/__tests__/task-compiler.test.ts +3 -1
- package/src/__tests__/task-management-tools.test.ts +3 -1
- package/src/__tests__/task-tools.test.ts +3 -1
- package/src/__tests__/terminal-sandbox.test.ts +13 -12
- package/src/__tests__/terminal-tools.test.ts +2 -0
- package/src/__tests__/tool-approval-handler.test.ts +15 -15
- package/src/__tests__/tool-execution-abort-cleanup.test.ts +2 -0
- package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +2 -0
- package/src/__tests__/tool-grant-request-escalation.test.ts +7 -7
- package/src/__tests__/trusted-contact-lifecycle-notifications.test.ts +48 -0
- package/src/__tests__/trusted-contact-multichannel.test.ts +22 -19
- package/src/__tests__/trusted-contact-verification.test.ts +91 -0
- package/src/__tests__/twilio-routes-elevenlabs.test.ts +2 -0
- package/src/__tests__/twitter-auth-handler.test.ts +3 -1
- package/src/__tests__/twitter-cli-routing.test.ts +3 -1
- package/src/__tests__/view-image-tool.test.ts +3 -1
- package/src/__tests__/voice-invite-redemption.test.ts +329 -0
- package/src/__tests__/voice-scoped-grant-consumer.test.ts +7 -5
- package/src/__tests__/voice-session-bridge.test.ts +10 -10
- package/src/__tests__/work-item-output.test.ts +3 -1
- package/src/__tests__/workspace-lifecycle.test.ts +13 -2
- package/src/calls/call-controller.ts +26 -23
- package/src/calls/guardian-action-sweep.ts +10 -2
- package/src/calls/relay-server.ts +216 -27
- package/src/calls/types.ts +1 -1
- package/src/calls/voice-session-bridge.ts +3 -3
- package/src/cli.ts +12 -0
- package/src/config/agent-schema.ts +14 -3
- package/src/config/calls-schema.ts +6 -6
- package/src/config/core-schema.ts +3 -3
- package/src/config/feature-flag-registry.json +8 -0
- package/src/config/mcp-schema.ts +1 -1
- package/src/config/memory-schema.ts +27 -19
- package/src/config/schema.ts +21 -21
- package/src/config/skills-schema.ts +7 -7
- package/src/config/vellum-skills/trusted-contacts/SKILL.md +139 -16
- package/src/daemon/handlers/config-inbox.ts +4 -4
- package/src/daemon/handlers/sessions.ts +148 -4
- package/src/daemon/ipc-contract/messages.ts +16 -0
- package/src/daemon/ipc-contract-inventory.json +1 -0
- package/src/daemon/lifecycle.ts +19 -0
- package/src/daemon/pairing-store.ts +86 -3
- package/src/daemon/session-agent-loop.ts +5 -5
- package/src/daemon/session-lifecycle.ts +25 -17
- package/src/daemon/session-memory.ts +2 -2
- package/src/daemon/session-process.ts +1 -20
- package/src/daemon/session-runtime-assembly.ts +28 -22
- package/src/daemon/session-tool-setup.ts +2 -2
- package/src/daemon/session.ts +3 -3
- package/src/memory/canonical-guardian-store.ts +63 -1
- package/src/memory/channel-guardian-store.ts +1 -0
- package/src/memory/conversation-crud.ts +7 -7
- package/src/memory/db-init.ts +4 -0
- package/src/memory/embedding-local.ts +257 -39
- package/src/memory/embedding-runtime-manager.ts +471 -0
- package/src/memory/guardian-bindings.ts +25 -1
- package/src/memory/indexer.ts +3 -3
- package/src/memory/ingress-invite-store.ts +45 -0
- package/src/memory/job-handlers/backfill.ts +16 -9
- package/src/memory/migrations/037-voice-invite-columns.ts +16 -0
- package/src/memory/migrations/index.ts +1 -0
- package/src/memory/qdrant-client.ts +31 -22
- package/src/memory/schema.ts +4 -0
- package/src/notifications/copy-composer.ts +15 -0
- package/src/runtime/access-request-helper.ts +43 -7
- package/src/runtime/actor-trust-resolver.ts +46 -50
- package/src/runtime/channel-invite-transports/voice.ts +58 -0
- package/src/runtime/channel-retry-sweep.ts +18 -6
- package/src/runtime/guardian-context-resolver.ts +38 -96
- package/src/runtime/guardian-reply-router.ts +31 -1
- package/src/runtime/ingress-service.ts +80 -3
- package/src/runtime/invite-redemption-service.ts +141 -2
- package/src/runtime/routes/channel-route-shared.ts +1 -1
- package/src/runtime/routes/channel-routes.ts +1 -1
- package/src/runtime/routes/conversation-routes.ts +2 -2
- package/src/runtime/routes/guardian-approval-interception.ts +17 -6
- package/src/runtime/routes/inbound-message-handler.ts +41 -10
- package/src/runtime/routes/ingress-routes.ts +52 -4
- package/src/runtime/routes/pairing-routes.ts +3 -0
- package/src/tools/guardian-control-plane-policy.ts +2 -2
- package/src/tools/tool-approval-handler.ts +11 -11
- package/src/tools/types.ts +2 -2
- package/src/util/logger.ts +20 -8
- package/src/util/platform.ts +10 -0
- package/src/util/voice-code.ts +29 -0
- package/src/daemon/guardian-invite-intent.ts +0 -124
|
@@ -19,6 +19,8 @@ mock.module('../util/logger.js', () => ({
|
|
|
19
19
|
|
|
20
20
|
mock.module('../config/loader.js', () => ({
|
|
21
21
|
getConfig: () => ({
|
|
22
|
+
ui: {},
|
|
23
|
+
|
|
22
24
|
daemon: { standaloneRecording: true },
|
|
23
25
|
provider: 'mock-provider',
|
|
24
26
|
model: 'mock-model',
|
|
@@ -233,6 +235,14 @@ mock.module('../daemon/handlers/recording.js', () => ({
|
|
|
233
235
|
// ── Mock conversation store ────────────────────────────────────────────────
|
|
234
236
|
|
|
235
237
|
mock.module('../memory/conversation-store.js', () => ({
|
|
238
|
+
getConversationThreadType: () => 'default',
|
|
239
|
+
setConversationOriginChannelIfUnset: () => {},
|
|
240
|
+
updateConversationContextWindow: () => {},
|
|
241
|
+
deleteMessageById: () => {},
|
|
242
|
+
updateConversationUsage: () => {},
|
|
243
|
+
provenanceFromGuardianContext: () => ({ source: 'user', guardianContext: undefined }),
|
|
244
|
+
getConversationOriginInterface: () => null,
|
|
245
|
+
getConversationOriginChannel: () => null,
|
|
236
246
|
getMessages: () => [],
|
|
237
247
|
addMessage: () => ({ id: 'msg-mock', role: 'assistant', content: '' }),
|
|
238
248
|
createConversation: (titleOrOpts?: string | { title?: string }) => {
|
|
@@ -269,6 +279,7 @@ mock.module('../security/secret-ingress.js', () => ({
|
|
|
269
279
|
|
|
270
280
|
mock.module('../security/secret-scanner.js', () => ({
|
|
271
281
|
redactSecrets: (text: string) => text,
|
|
282
|
+
compileCustomPatterns: () => [],
|
|
272
283
|
}));
|
|
273
284
|
|
|
274
285
|
// ── Mock classifier (for task_submit fallthrough) ──────────────────────────
|
|
@@ -307,6 +318,7 @@ mock.module('../providers/provider-send-message.js', () => ({
|
|
|
307
318
|
|
|
308
319
|
mock.module('../memory/external-conversation-store.js', () => ({
|
|
309
320
|
getBindingsForConversations: () => new Map(),
|
|
321
|
+
upsertBinding: () => {},
|
|
310
322
|
}));
|
|
311
323
|
|
|
312
324
|
// ── Mock subagent manager ──────────────────────────────────────────────────
|
|
@@ -376,6 +388,7 @@ function createCtx(overrides?: Partial<HandlerContext>): {
|
|
|
376
388
|
setTurnChannelContext: noop,
|
|
377
389
|
setTurnInterfaceContext: noop,
|
|
378
390
|
setAssistantId: noop,
|
|
391
|
+
setChannelCapabilities: noop,
|
|
379
392
|
setGuardianContext: noop,
|
|
380
393
|
setCommandIntent: noop,
|
|
381
394
|
processMessage: async () => {},
|
|
@@ -386,6 +399,8 @@ function createCtx(overrides?: Partial<HandlerContext>): {
|
|
|
386
399
|
dispose: noop,
|
|
387
400
|
hasPendingConfirmation: () => false,
|
|
388
401
|
hasPendingSecret: () => false,
|
|
402
|
+
isProcessing: () => false,
|
|
403
|
+
messages: [] as any[],
|
|
389
404
|
};
|
|
390
405
|
|
|
391
406
|
const sessions = new Map<string, any>();
|
|
@@ -16,6 +16,8 @@ mock.module('../util/logger.js', () => ({
|
|
|
16
16
|
|
|
17
17
|
mock.module('../config/loader.js', () => ({
|
|
18
18
|
getConfig: () => ({
|
|
19
|
+
ui: {},
|
|
20
|
+
|
|
19
21
|
daemon: { standaloneRecording: true },
|
|
20
22
|
provider: 'mock-provider',
|
|
21
23
|
permissions: { mode: 'legacy' },
|
|
@@ -48,6 +50,15 @@ const mockMessages: Array<{ id: string; role: string; content: string }> = [];
|
|
|
48
50
|
let mockMessageIdCounter = 0;
|
|
49
51
|
|
|
50
52
|
mock.module('../memory/conversation-store.js', () => ({
|
|
53
|
+
getConversationThreadType: () => 'default',
|
|
54
|
+
setConversationOriginChannelIfUnset: () => {},
|
|
55
|
+
updateConversationContextWindow: () => {},
|
|
56
|
+
deleteMessageById: () => {},
|
|
57
|
+
updateConversationTitle: () => {},
|
|
58
|
+
updateConversationUsage: () => {},
|
|
59
|
+
provenanceFromGuardianContext: () => ({ source: 'user', guardianContext: undefined }),
|
|
60
|
+
getConversationOriginInterface: () => null,
|
|
61
|
+
getConversationOriginChannel: () => null,
|
|
51
62
|
getMessages: () => mockMessages,
|
|
52
63
|
addMessage: (_convId: string, role: string, content: string) => {
|
|
53
64
|
const msg = { id: `msg-${++mockMessageIdCounter}`, role, content };
|
|
@@ -417,7 +428,7 @@ describe('stale completion guard (operation token)', () => {
|
|
|
417
428
|
expect(getActiveRestartToken()).toBeNull();
|
|
418
429
|
});
|
|
419
430
|
|
|
420
|
-
test('allows tokenless recording_status during active restart (old recording ack)', () => {
|
|
431
|
+
test('allows tokenless recording_status during active restart (old recording ack)', async () => {
|
|
421
432
|
const { ctx, sent, fakeSocket } = createCtx();
|
|
422
433
|
const conversationId = 'conv-tokenless-1';
|
|
423
434
|
ctx.socketToSession.set(fakeSocket, conversationId);
|
|
@@ -442,7 +453,7 @@ describe('stale completion guard (operation token)', () => {
|
|
|
442
453
|
attachToConversationId: conversationId,
|
|
443
454
|
// No operationToken — from old recording, should be allowed
|
|
444
455
|
};
|
|
445
|
-
recordingHandlers.recording_status(tokenlessStatus, fakeSocket, ctx);
|
|
456
|
+
await recordingHandlers.recording_status(tokenlessStatus, fakeSocket, ctx);
|
|
446
457
|
|
|
447
458
|
// Should have triggered the deferred restart start
|
|
448
459
|
const newStartMsgs = sent.filter((m) => m.type === 'recording_start');
|
|
@@ -160,15 +160,19 @@ describe('tool manifest', () => {
|
|
|
160
160
|
});
|
|
161
161
|
|
|
162
162
|
test('manifest declares expected core lazy tools', () => {
|
|
163
|
+
// bash and swarm_delegate moved from lazy to eager registration
|
|
163
164
|
const lazyNames = new Set(lazyTools.map((t) => t.name));
|
|
164
|
-
expect(lazyNames.has('bash')).toBe(
|
|
165
|
+
expect(lazyNames.has('bash')).toBe(false);
|
|
165
166
|
expect(lazyNames.has('evaluate_typescript_code')).toBe(false);
|
|
166
167
|
expect(lazyNames.has('claude_code')).toBe(false);
|
|
167
|
-
expect(lazyNames.has('swarm_delegate')).toBe(
|
|
168
|
+
expect(lazyNames.has('swarm_delegate')).toBe(false);
|
|
169
|
+
// Verify they are in eager tools instead
|
|
170
|
+
expect(eagerModuleToolNames).toContain('bash');
|
|
171
|
+
expect(eagerModuleToolNames).toContain('swarm_delegate');
|
|
168
172
|
});
|
|
169
173
|
|
|
170
174
|
test('eager module tool names list contains expected count', () => {
|
|
171
|
-
expect(eagerModuleToolNames.length).toBe(
|
|
175
|
+
expect(eagerModuleToolNames.length).toBe(15);
|
|
172
176
|
});
|
|
173
177
|
|
|
174
178
|
test('explicit tools list includes memory, credential, watch, and catalog tools', () => {
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
* - destroy cleanup
|
|
14
14
|
* - Malformed message resilience
|
|
15
15
|
*/
|
|
16
|
+
import { createHash, randomUUID } from 'node:crypto';
|
|
16
17
|
import { mkdtempSync, rmSync } from 'node:fs';
|
|
17
18
|
import { tmpdir } from 'node:os';
|
|
18
19
|
import { join } from 'node:path';
|
|
@@ -49,6 +50,7 @@ const mockConfig = {
|
|
|
49
50
|
provider: 'anthropic',
|
|
50
51
|
providerOrder: ['anthropic'],
|
|
51
52
|
apiKeys: { anthropic: 'test-key' },
|
|
53
|
+
secretDetection: { enabled: false },
|
|
52
54
|
calls: {
|
|
53
55
|
enabled: true,
|
|
54
56
|
provider: 'twilio',
|
|
@@ -132,12 +134,14 @@ import {
|
|
|
132
134
|
} from '../calls/call-store.js';
|
|
133
135
|
import type { RelayWebSocketData } from '../calls/relay-server.js';
|
|
134
136
|
import { activeRelayConnections,RelayConnection } from '../calls/relay-server.js';
|
|
135
|
-
import {
|
|
136
|
-
import {
|
|
137
|
+
import { setVoiceBridgeDeps } from '../calls/voice-session-bridge.js';
|
|
138
|
+
import { createBinding, createChallenge } from '../memory/channel-guardian-store.js';
|
|
139
|
+
import { addMessage, getMessages } from '../memory/conversation-store.js';
|
|
137
140
|
import { getDb, initializeDb, resetDb } from '../memory/db.js';
|
|
141
|
+
import { upsertMember } from '../memory/ingress-member-store.js';
|
|
138
142
|
import { conversations } from '../memory/schema.js';
|
|
139
143
|
import {
|
|
140
|
-
|
|
144
|
+
createOutboundSession,
|
|
141
145
|
getGuardianBinding,
|
|
142
146
|
} from '../runtime/channel-guardian-service.js';
|
|
143
147
|
|
|
@@ -200,12 +204,51 @@ function resetTables() {
|
|
|
200
204
|
db.run('DELETE FROM tool_invocations');
|
|
201
205
|
db.run('DELETE FROM messages');
|
|
202
206
|
db.run('DELETE FROM conversations');
|
|
207
|
+
db.run('DELETE FROM assistant_ingress_members');
|
|
203
208
|
db.run('DELETE FROM channel_guardian_verification_challenges');
|
|
204
209
|
db.run('DELETE FROM channel_guardian_bindings');
|
|
205
210
|
db.run('DELETE FROM channel_guardian_rate_limits');
|
|
206
211
|
ensuredConvIds = new Set();
|
|
207
212
|
}
|
|
208
213
|
|
|
214
|
+
function addTrustedVoiceContact(phoneNumber: string, assistantId: string = 'self'): void {
|
|
215
|
+
upsertMember({
|
|
216
|
+
assistantId,
|
|
217
|
+
sourceChannel: 'voice',
|
|
218
|
+
externalUserId: phoneNumber,
|
|
219
|
+
externalChatId: phoneNumber,
|
|
220
|
+
status: 'active',
|
|
221
|
+
policy: 'allow',
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
function createVoiceVerificationSession(
|
|
226
|
+
assistantId: string,
|
|
227
|
+
expectedPhoneE164: string,
|
|
228
|
+
sessionId?: string,
|
|
229
|
+
): string {
|
|
230
|
+
const { secret } = createOutboundSession({
|
|
231
|
+
assistantId,
|
|
232
|
+
channel: 'voice',
|
|
233
|
+
expectedExternalUserId: expectedPhoneE164,
|
|
234
|
+
expectedChatId: expectedPhoneE164,
|
|
235
|
+
expectedPhoneE164,
|
|
236
|
+
sessionId,
|
|
237
|
+
});
|
|
238
|
+
return secret;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
function createPendingVoiceGuardianChallenge(assistantId: string, secret: string = '123456'): string {
|
|
242
|
+
createChallenge({
|
|
243
|
+
id: randomUUID(),
|
|
244
|
+
assistantId,
|
|
245
|
+
channel: 'voice',
|
|
246
|
+
challengeHash: createHash('sha256').update(secret).digest('hex'),
|
|
247
|
+
expiresAt: Date.now() + 10 * 60 * 1000,
|
|
248
|
+
});
|
|
249
|
+
return secret;
|
|
250
|
+
}
|
|
251
|
+
|
|
209
252
|
function getLatestAssistantText(conversationId: string): string | null {
|
|
210
253
|
const messages = getMessages(conversationId).filter((m) => m.role === 'assistant');
|
|
211
254
|
if (messages.length === 0) return null;
|
|
@@ -235,17 +278,88 @@ describe('relay-server', () => {
|
|
|
235
278
|
mockConfig.calls.verification.maxAttempts = 3;
|
|
236
279
|
mockConfig.calls.verification.codeLength = 6;
|
|
237
280
|
mockConfig.calls.callerIdentity.userNumber = undefined;
|
|
281
|
+
setVoiceBridgeDeps({
|
|
282
|
+
getOrCreateSession: async (conversationId) => {
|
|
283
|
+
const session = {
|
|
284
|
+
callSessionId: undefined as string | undefined,
|
|
285
|
+
currentRequestId: undefined as string | undefined,
|
|
286
|
+
memoryPolicy: { scopeId: 'default', includeDefaultFallback: false, strictSideEffects: false },
|
|
287
|
+
isProcessing: () => false,
|
|
288
|
+
persistUserMessage: async (content: string, _attachments: unknown[], requestId?: string) => {
|
|
289
|
+
session.currentRequestId = requestId;
|
|
290
|
+
const message = await addMessage(
|
|
291
|
+
conversationId,
|
|
292
|
+
'user',
|
|
293
|
+
JSON.stringify([{ type: 'text', text: content }]),
|
|
294
|
+
{
|
|
295
|
+
userMessageChannel: 'voice',
|
|
296
|
+
assistantMessageChannel: 'voice',
|
|
297
|
+
userMessageInterface: 'voice',
|
|
298
|
+
assistantMessageInterface: 'voice',
|
|
299
|
+
},
|
|
300
|
+
);
|
|
301
|
+
return message.id;
|
|
302
|
+
},
|
|
303
|
+
setChannelCapabilities: () => {},
|
|
304
|
+
setAssistantId: () => {},
|
|
305
|
+
setGuardianContext: () => {},
|
|
306
|
+
setCommandIntent: () => {},
|
|
307
|
+
setTurnChannelContext: () => {},
|
|
308
|
+
setVoiceCallControlPrompt: () => {},
|
|
309
|
+
updateClient: () => {},
|
|
310
|
+
handleConfirmationResponse: () => {},
|
|
311
|
+
handleSecretResponse: () => {},
|
|
312
|
+
abort: () => {},
|
|
313
|
+
runAgentLoop: async (
|
|
314
|
+
_content: string,
|
|
315
|
+
_messageId: string,
|
|
316
|
+
onEvent: (event: { type: string; sessionId?: string; text?: string }) => void,
|
|
317
|
+
) => {
|
|
318
|
+
const tokens: string[] = [];
|
|
319
|
+
await mockSendMessage([], [], '', {
|
|
320
|
+
onEvent: (event: { type: string; text?: string }) => {
|
|
321
|
+
if (event.type !== 'text_delta' || typeof event.text !== 'string') return;
|
|
322
|
+
tokens.push(event.text);
|
|
323
|
+
onEvent({ type: 'assistant_text_delta', sessionId: conversationId, text: event.text });
|
|
324
|
+
},
|
|
325
|
+
});
|
|
326
|
+
|
|
327
|
+
const fullText = tokens.join('');
|
|
328
|
+
if (fullText.length > 0) {
|
|
329
|
+
await addMessage(
|
|
330
|
+
conversationId,
|
|
331
|
+
'assistant',
|
|
332
|
+
JSON.stringify([{ type: 'text', text: fullText }]),
|
|
333
|
+
{
|
|
334
|
+
userMessageChannel: 'voice',
|
|
335
|
+
assistantMessageChannel: 'voice',
|
|
336
|
+
userMessageInterface: 'voice',
|
|
337
|
+
assistantMessageInterface: 'voice',
|
|
338
|
+
},
|
|
339
|
+
);
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
onEvent({ type: 'message_complete', sessionId: conversationId });
|
|
343
|
+
},
|
|
344
|
+
};
|
|
345
|
+
return session as unknown as import('../daemon/session.js').Session;
|
|
346
|
+
},
|
|
347
|
+
resolveAttachments: () => [],
|
|
348
|
+
deriveDefaultStrictSideEffects: () => false,
|
|
349
|
+
});
|
|
238
350
|
});
|
|
239
351
|
|
|
240
352
|
// ── Setup message handling ──────────────────────────────────────
|
|
241
353
|
|
|
242
354
|
test('handleMessage: setup message associates callSid and records event', async () => {
|
|
243
355
|
ensureConversation('conv-relay-1');
|
|
356
|
+
ensureConversation('conv-relay-1-origin');
|
|
244
357
|
const session = createCallSession({
|
|
245
358
|
conversationId: 'conv-relay-1',
|
|
246
359
|
provider: 'twilio',
|
|
247
360
|
fromNumber: '+15551111111',
|
|
248
361
|
toNumber: '+15552222222',
|
|
362
|
+
initiatedFromConversationId: 'conv-relay-1-origin',
|
|
249
363
|
});
|
|
250
364
|
|
|
251
365
|
const { relay } = createMockWs(session.id);
|
|
@@ -277,12 +391,14 @@ describe('relay-server', () => {
|
|
|
277
391
|
|
|
278
392
|
test('handleMessage: setup triggers initial assistant greeting turn', async () => {
|
|
279
393
|
ensureConversation('conv-relay-setup-greet');
|
|
394
|
+
ensureConversation('conv-relay-setup-greet-origin');
|
|
280
395
|
const session = createCallSession({
|
|
281
396
|
conversationId: 'conv-relay-setup-greet',
|
|
282
397
|
provider: 'twilio',
|
|
283
398
|
fromNumber: '+15551111111',
|
|
284
399
|
toNumber: '+15552222222',
|
|
285
400
|
task: 'Confirm appointment time',
|
|
401
|
+
initiatedFromConversationId: 'conv-relay-setup-greet-origin',
|
|
286
402
|
});
|
|
287
403
|
|
|
288
404
|
mockSendMessage.mockImplementation(createMockProviderResponse(['Hello, I am calling to confirm your appointment.']));
|
|
@@ -398,11 +514,13 @@ describe('relay-server', () => {
|
|
|
398
514
|
|
|
399
515
|
test('handleMessage: final prompt routes to orchestrator and records event', async () => {
|
|
400
516
|
ensureConversation('conv-relay-prompt');
|
|
517
|
+
ensureConversation('conv-relay-prompt-origin');
|
|
401
518
|
const session = createCallSession({
|
|
402
519
|
conversationId: 'conv-relay-prompt',
|
|
403
520
|
provider: 'twilio',
|
|
404
521
|
fromNumber: '+15551111111',
|
|
405
522
|
toNumber: '+15552222222',
|
|
523
|
+
initiatedFromConversationId: 'conv-relay-prompt-origin',
|
|
406
524
|
});
|
|
407
525
|
|
|
408
526
|
const { ws, relay } = createMockWs(session.id);
|
|
@@ -858,6 +976,7 @@ describe('relay-server', () => {
|
|
|
858
976
|
|
|
859
977
|
// Enable verification to prove inbound calls skip it
|
|
860
978
|
mockConfig.calls.verification.enabled = true;
|
|
979
|
+
addTrustedVoiceContact('+15559999999');
|
|
861
980
|
|
|
862
981
|
mockSendMessage.mockImplementation(createMockProviderResponse(['Hello, how can I help you today?']));
|
|
863
982
|
|
|
@@ -896,6 +1015,7 @@ describe('relay-server', () => {
|
|
|
896
1015
|
});
|
|
897
1016
|
|
|
898
1017
|
mockSendMessage.mockImplementation(createMockProviderResponse(['Sure, let me help with that.']));
|
|
1018
|
+
addTrustedVoiceContact('+15559999999');
|
|
899
1019
|
|
|
900
1020
|
const { relay } = createMockWs(session.id);
|
|
901
1021
|
|
|
@@ -941,6 +1061,7 @@ describe('relay-server', () => {
|
|
|
941
1061
|
});
|
|
942
1062
|
|
|
943
1063
|
let turnCount = 0;
|
|
1064
|
+
addTrustedVoiceContact('+15559999999');
|
|
944
1065
|
mockSendMessage.mockImplementation(async (_messages: unknown[], _tools: unknown[], _systemPrompt: unknown, options?: { onEvent?: (event: { type: string; text?: string }) => void }) => {
|
|
945
1066
|
turnCount++;
|
|
946
1067
|
let tokens: string[];
|
|
@@ -1016,8 +1137,7 @@ describe('relay-server', () => {
|
|
|
1016
1137
|
});
|
|
1017
1138
|
|
|
1018
1139
|
// Create a pending voice guardian challenge
|
|
1019
|
-
const
|
|
1020
|
-
const secret = challenge.secret;
|
|
1140
|
+
const secret = createPendingVoiceGuardianChallenge('test-assistant');
|
|
1021
1141
|
|
|
1022
1142
|
mockSendMessage.mockImplementation(createMockProviderResponse(['Hello, how can I help you?']));
|
|
1023
1143
|
|
|
@@ -1079,8 +1199,7 @@ describe('relay-server', () => {
|
|
|
1079
1199
|
assistantId: 'test-assistant',
|
|
1080
1200
|
});
|
|
1081
1201
|
|
|
1082
|
-
const
|
|
1083
|
-
const secret = challenge.secret;
|
|
1202
|
+
const secret = createPendingVoiceGuardianChallenge('test-assistant');
|
|
1084
1203
|
|
|
1085
1204
|
mockSendMessage.mockImplementation(createMockProviderResponse(['Hello, verified caller!']));
|
|
1086
1205
|
|
|
@@ -1151,15 +1270,15 @@ describe('relay-server', () => {
|
|
|
1151
1270
|
to: '+15551111111',
|
|
1152
1271
|
}));
|
|
1153
1272
|
|
|
1154
|
-
const runtimeContext = (relay.getController() as unknown as { guardianContext?: { sourceChannel?: string;
|
|
1273
|
+
const runtimeContext = (relay.getController() as unknown as { guardianContext?: { sourceChannel?: string; trustClass?: string; guardianExternalUserId?: string } })?.guardianContext;
|
|
1155
1274
|
expect(runtimeContext?.sourceChannel).toBe('voice');
|
|
1156
|
-
expect(runtimeContext?.
|
|
1275
|
+
expect(runtimeContext?.trustClass).toBe('guardian');
|
|
1157
1276
|
expect(runtimeContext?.guardianExternalUserId).toBe('+15550001111');
|
|
1158
1277
|
|
|
1159
1278
|
relay.destroy();
|
|
1160
1279
|
});
|
|
1161
1280
|
|
|
1162
|
-
test('inbound call: caller not matching voice guardian binding is classified as
|
|
1281
|
+
test('inbound call: caller not matching voice guardian binding is classified as trusted contact', async () => {
|
|
1163
1282
|
ensureConversation('conv-guardian-role-mismatch');
|
|
1164
1283
|
const session = createCallSession({
|
|
1165
1284
|
conversationId: 'conv-guardian-role-mismatch',
|
|
@@ -1175,6 +1294,7 @@ describe('relay-server', () => {
|
|
|
1175
1294
|
guardianExternalUserId: '+15550009999',
|
|
1176
1295
|
guardianDeliveryChatId: '+15550009999',
|
|
1177
1296
|
});
|
|
1297
|
+
addTrustedVoiceContact('+15550002222', 'test-assistant');
|
|
1178
1298
|
|
|
1179
1299
|
mockSendMessage.mockImplementation(createMockProviderResponse(['Hello there.']));
|
|
1180
1300
|
|
|
@@ -1190,13 +1310,13 @@ describe('relay-server', () => {
|
|
|
1190
1310
|
const runtimeContext = (relay.getController() as unknown as {
|
|
1191
1311
|
guardianContext?: {
|
|
1192
1312
|
sourceChannel?: string;
|
|
1193
|
-
|
|
1313
|
+
trustClass?: string;
|
|
1194
1314
|
guardianExternalUserId?: string;
|
|
1195
1315
|
requesterExternalUserId?: string;
|
|
1196
1316
|
};
|
|
1197
1317
|
})?.guardianContext;
|
|
1198
1318
|
expect(runtimeContext?.sourceChannel).toBe('voice');
|
|
1199
|
-
expect(runtimeContext?.
|
|
1319
|
+
expect(runtimeContext?.trustClass).toBe('trusted_contact');
|
|
1200
1320
|
expect(runtimeContext?.guardianExternalUserId).toBe('+15550009999');
|
|
1201
1321
|
expect(runtimeContext?.requesterExternalUserId).toBe('+15550002222');
|
|
1202
1322
|
|
|
@@ -1236,12 +1356,12 @@ describe('relay-server', () => {
|
|
|
1236
1356
|
const runtimeContext = (relay.getController() as unknown as {
|
|
1237
1357
|
guardianContext?: {
|
|
1238
1358
|
sourceChannel?: string;
|
|
1239
|
-
|
|
1359
|
+
trustClass?: string;
|
|
1240
1360
|
guardianExternalUserId?: string;
|
|
1241
1361
|
};
|
|
1242
1362
|
})?.guardianContext;
|
|
1243
1363
|
expect(runtimeContext?.sourceChannel).toBe('voice');
|
|
1244
|
-
expect(runtimeContext?.
|
|
1364
|
+
expect(runtimeContext?.trustClass).toBe('guardian');
|
|
1245
1365
|
expect(runtimeContext?.guardianExternalUserId).toBe('+15550001111');
|
|
1246
1366
|
|
|
1247
1367
|
relay.destroy();
|
|
@@ -1283,11 +1403,11 @@ describe('relay-server', () => {
|
|
|
1283
1403
|
const runtimeContext = (relay.getController() as unknown as {
|
|
1284
1404
|
guardianContext?: {
|
|
1285
1405
|
sourceChannel?: string;
|
|
1286
|
-
|
|
1406
|
+
trustClass?: string;
|
|
1287
1407
|
};
|
|
1288
1408
|
})?.guardianContext;
|
|
1289
1409
|
expect(runtimeContext?.sourceChannel).toBe('voice');
|
|
1290
|
-
expect(runtimeContext?.
|
|
1410
|
+
expect(runtimeContext?.trustClass).toBe('unknown');
|
|
1291
1411
|
|
|
1292
1412
|
relay.destroy();
|
|
1293
1413
|
});
|
|
@@ -1302,8 +1422,8 @@ describe('relay-server', () => {
|
|
|
1302
1422
|
assistantId: 'test-assistant',
|
|
1303
1423
|
});
|
|
1304
1424
|
|
|
1305
|
-
const
|
|
1306
|
-
const spokenCode =
|
|
1425
|
+
const secret = createPendingVoiceGuardianChallenge('test-assistant');
|
|
1426
|
+
const spokenCode = secret.split('').join(' ');
|
|
1307
1427
|
|
|
1308
1428
|
const { relay } = createMockWs(session.id);
|
|
1309
1429
|
|
|
@@ -1315,9 +1435,9 @@ describe('relay-server', () => {
|
|
|
1315
1435
|
}));
|
|
1316
1436
|
|
|
1317
1437
|
const preVerify = (relay.getController() as unknown as {
|
|
1318
|
-
guardianContext?: {
|
|
1438
|
+
guardianContext?: { trustClass?: string };
|
|
1319
1439
|
})?.guardianContext;
|
|
1320
|
-
expect(preVerify?.
|
|
1440
|
+
expect(preVerify?.trustClass).toBe('unknown');
|
|
1321
1441
|
|
|
1322
1442
|
await relay.handleMessage(JSON.stringify({
|
|
1323
1443
|
type: 'prompt',
|
|
@@ -1329,10 +1449,10 @@ describe('relay-server', () => {
|
|
|
1329
1449
|
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
1330
1450
|
|
|
1331
1451
|
const postVerify = (relay.getController() as unknown as {
|
|
1332
|
-
guardianContext?: { sourceChannel?: string;
|
|
1452
|
+
guardianContext?: { sourceChannel?: string; trustClass?: string; guardianExternalUserId?: string };
|
|
1333
1453
|
})?.guardianContext;
|
|
1334
1454
|
expect(postVerify?.sourceChannel).toBe('voice');
|
|
1335
|
-
expect(postVerify?.
|
|
1455
|
+
expect(postVerify?.trustClass).toBe('guardian');
|
|
1336
1456
|
expect(postVerify?.guardianExternalUserId).toBe(session.fromNumber);
|
|
1337
1457
|
|
|
1338
1458
|
relay.destroy();
|
|
@@ -1348,7 +1468,7 @@ describe('relay-server', () => {
|
|
|
1348
1468
|
assistantId: 'test-assistant',
|
|
1349
1469
|
});
|
|
1350
1470
|
|
|
1351
|
-
|
|
1471
|
+
createPendingVoiceGuardianChallenge('test-assistant');
|
|
1352
1472
|
|
|
1353
1473
|
const { ws, relay } = createMockWs(session.id);
|
|
1354
1474
|
|
|
@@ -1389,7 +1509,7 @@ describe('relay-server', () => {
|
|
|
1389
1509
|
assistantId: 'test-assistant',
|
|
1390
1510
|
});
|
|
1391
1511
|
|
|
1392
|
-
|
|
1512
|
+
createPendingVoiceGuardianChallenge('test-assistant');
|
|
1393
1513
|
|
|
1394
1514
|
const { ws, relay } = createMockWs(session.id);
|
|
1395
1515
|
|
|
@@ -1451,6 +1571,7 @@ describe('relay-server', () => {
|
|
|
1451
1571
|
// Do NOT create any pending challenge
|
|
1452
1572
|
|
|
1453
1573
|
mockSendMessage.mockImplementation(createMockProviderResponse(['Welcome to the line.']));
|
|
1574
|
+
addTrustedVoiceContact('+15559999999', 'test-assistant');
|
|
1454
1575
|
|
|
1455
1576
|
const { ws, relay } = createMockWs(session.id);
|
|
1456
1577
|
|
|
@@ -1486,7 +1607,7 @@ describe('relay-server', () => {
|
|
|
1486
1607
|
assistantId: 'test-assistant',
|
|
1487
1608
|
});
|
|
1488
1609
|
|
|
1489
|
-
|
|
1610
|
+
createPendingVoiceGuardianChallenge('test-assistant');
|
|
1490
1611
|
|
|
1491
1612
|
const { ws, relay } = createMockWs(session.id);
|
|
1492
1613
|
|
|
@@ -1536,8 +1657,7 @@ describe('relay-server', () => {
|
|
|
1536
1657
|
initiatedFromConversationId: 'conv-gv-pointer-success-origin',
|
|
1537
1658
|
});
|
|
1538
1659
|
|
|
1539
|
-
const
|
|
1540
|
-
const secret = challenge.secret;
|
|
1660
|
+
const secret = createVoiceVerificationSession('test-assistant', '+15559999999', 'gv-session-ptr-success');
|
|
1541
1661
|
|
|
1542
1662
|
const { relay } = createMockWs(session.id);
|
|
1543
1663
|
|
|
@@ -1586,7 +1706,7 @@ describe('relay-server', () => {
|
|
|
1586
1706
|
initiatedFromConversationId: 'conv-gv-pointer-fail-origin',
|
|
1587
1707
|
});
|
|
1588
1708
|
|
|
1589
|
-
|
|
1709
|
+
createVoiceVerificationSession('test-assistant', '+15559999999', 'gv-session-ptr-fail');
|
|
1590
1710
|
|
|
1591
1711
|
const { relay } = createMockWs(session.id);
|
|
1592
1712
|
|
|
@@ -27,6 +27,8 @@ mock.module('../util/logger.js', () => ({
|
|
|
27
27
|
|
|
28
28
|
mock.module('../config/loader.js', () => ({
|
|
29
29
|
getConfig: () => ({
|
|
30
|
+
ui: {},
|
|
31
|
+
|
|
30
32
|
model: 'test',
|
|
31
33
|
provider: 'test',
|
|
32
34
|
apiKeys: {},
|
|
@@ -182,9 +184,9 @@ describe('Runtime attachment metadata', () => {
|
|
|
182
184
|
`http://127.0.0.1:${port}/v1/attachments/nonexistent-id`,
|
|
183
185
|
{ headers: AUTH_HEADERS },
|
|
184
186
|
);
|
|
185
|
-
const body = await res.json() as { error: string };
|
|
187
|
+
const body = await res.json() as { error: { message: string; code?: string } };
|
|
186
188
|
|
|
187
189
|
expect(res.status).toBe(404);
|
|
188
|
-
expect(body.error).toBe('Attachment not found');
|
|
190
|
+
expect(body.error.message).toBe('Attachment not found');
|
|
189
191
|
});
|
|
190
192
|
});
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
* - tool_input_delta
|
|
11
11
|
* - tool_output_chunk
|
|
12
12
|
* - tool_result
|
|
13
|
+
* - message_request_complete (request-level terminal)
|
|
13
14
|
* - message_complete (terminal)
|
|
14
15
|
* - generation_handoff (terminal)
|
|
15
16
|
* - generation_cancelled (terminal)
|
|
@@ -43,6 +44,8 @@ mock.module('../util/logger.js', () => ({
|
|
|
43
44
|
|
|
44
45
|
mock.module('../config/loader.js', () => ({
|
|
45
46
|
getConfig: () => ({
|
|
47
|
+
ui: {},
|
|
48
|
+
|
|
46
49
|
model: 'test',
|
|
47
50
|
provider: 'test',
|
|
48
51
|
apiKeys: {},
|
|
@@ -274,6 +277,24 @@ describe('SSE IPC parity — streaming/delta message types', () => {
|
|
|
274
277
|
expect(event.message.type).toBe('message_complete');
|
|
275
278
|
});
|
|
276
279
|
|
|
280
|
+
// ── message_request_complete (request-level terminal) ───────────────────
|
|
281
|
+
|
|
282
|
+
test('preserves message_request_complete payload', async () => {
|
|
283
|
+
const msg = {
|
|
284
|
+
type: 'message_request_complete' as const,
|
|
285
|
+
sessionId: 'conv-msg-request-complete',
|
|
286
|
+
requestId: 'req-123',
|
|
287
|
+
runStillActive: true,
|
|
288
|
+
};
|
|
289
|
+
const event = await publishAndReadFrame('parity-message-request-complete', msg);
|
|
290
|
+
|
|
291
|
+
expect(event.message.type).toBe('message_request_complete');
|
|
292
|
+
const m = event.message as typeof msg;
|
|
293
|
+
expect(m.sessionId).toBe('conv-msg-request-complete');
|
|
294
|
+
expect(m.requestId).toBe('req-123');
|
|
295
|
+
expect(m.runStillActive).toBe(true);
|
|
296
|
+
});
|
|
297
|
+
|
|
277
298
|
// ── generation_handoff (terminal) ────────────────────────────────────────
|
|
278
299
|
|
|
279
300
|
test('preserves generation_handoff payload', async () => {
|
|
@@ -35,6 +35,8 @@ mock.module('../util/logger.js', () => ({
|
|
|
35
35
|
|
|
36
36
|
mock.module('../config/loader.js', () => ({
|
|
37
37
|
getConfig: () => ({
|
|
38
|
+
ui: {},
|
|
39
|
+
|
|
38
40
|
model: 'test',
|
|
39
41
|
provider: 'test',
|
|
40
42
|
apiKeys: {},
|
|
@@ -116,8 +118,8 @@ describe('SSE assistant-events endpoint', () => {
|
|
|
116
118
|
|
|
117
119
|
const res = await fetch(eventsUrl(), { headers: AUTH_HEADERS });
|
|
118
120
|
expect(res.status).toBe(400);
|
|
119
|
-
const body = await res.json() as { error: string };
|
|
120
|
-
expect(body.error).toContain('conversationKey');
|
|
121
|
+
const body = await res.json() as { error: { message: string; code?: string } };
|
|
122
|
+
expect(body.error.message).toContain('conversationKey');
|
|
121
123
|
|
|
122
124
|
await stopServer();
|
|
123
125
|
});
|
|
@@ -38,6 +38,8 @@ mock.module('../util/logger.js', () => ({
|
|
|
38
38
|
|
|
39
39
|
mock.module('../config/loader.js', () => ({
|
|
40
40
|
getConfig: () => ({
|
|
41
|
+
ui: {},
|
|
42
|
+
|
|
41
43
|
model: 'test',
|
|
42
44
|
provider: 'test',
|
|
43
45
|
apiKeys: {},
|
|
@@ -83,6 +85,7 @@ function makeCompletingSession(): Session {
|
|
|
83
85
|
},
|
|
84
86
|
handleConfirmationResponse: () => {},
|
|
85
87
|
handleSecretResponse: () => {},
|
|
88
|
+
hasAnyPendingConfirmation: () => false,
|
|
86
89
|
} as unknown as Session;
|
|
87
90
|
}
|
|
88
91
|
|
|
@@ -114,6 +117,7 @@ function makeHangingSession(): Session {
|
|
|
114
117
|
},
|
|
115
118
|
handleConfirmationResponse: () => {},
|
|
116
119
|
handleSecretResponse: () => {},
|
|
120
|
+
hasAnyPendingConfirmation: () => false,
|
|
117
121
|
_enqueuedMessages: enqueuedMessages,
|
|
118
122
|
} as unknown as Session;
|
|
119
123
|
}
|