@vellumai/assistant 0.4.2 → 0.4.4
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/.env.example +3 -0
- package/ARCHITECTURE.md +124 -10
- package/README.md +43 -35
- package/docs/trusted-contact-access.md +20 -0
- package/package.json +1 -1
- package/scripts/ipc/generate-swift.ts +1 -0
- package/src/__tests__/__snapshots__/ipc-snapshot.test.ts.snap +58 -120
- package/src/__tests__/access-request-decision.test.ts +0 -1
- package/src/__tests__/actor-token-service.test.ts +1099 -0
- package/src/__tests__/agent-loop.test.ts +51 -0
- package/src/__tests__/approval-routes-http.test.ts +2 -0
- package/src/__tests__/assistant-events-sse-hardening.test.ts +7 -5
- package/src/__tests__/assistant-id-boundary-guard.test.ts +415 -0
- package/src/__tests__/call-controller.test.ts +49 -0
- package/src/__tests__/call-pointer-message-composer.test.ts +171 -0
- package/src/__tests__/call-pointer-messages.test.ts +93 -3
- package/src/__tests__/call-pointer-no-hardcoded-copy.guard.test.ts +42 -0
- package/src/__tests__/call-routes-http.test.ts +0 -25
- package/src/__tests__/callback-handoff-copy.test.ts +186 -0
- package/src/__tests__/channel-approval-routes.test.ts +133 -12
- package/src/__tests__/channel-guardian.test.ts +0 -86
- package/src/__tests__/channel-readiness-service.test.ts +10 -16
- package/src/__tests__/checker.test.ts +33 -12
- package/src/__tests__/config-schema.test.ts +6 -0
- package/src/__tests__/confirmation-request-guardian-bridge.test.ts +410 -0
- package/src/__tests__/conversation-routes-guardian-reply.test.ts +256 -0
- package/src/__tests__/conversation-routes.test.ts +12 -3
- package/src/__tests__/credential-security-invariants.test.ts +1 -1
- package/src/__tests__/daemon-server-session-init.test.ts +4 -0
- package/src/__tests__/deterministic-verification-control-plane.test.ts +0 -1
- package/src/__tests__/guardian-actions-endpoint.test.ts +39 -13
- package/src/__tests__/guardian-dispatch.test.ts +8 -0
- package/src/__tests__/guardian-outbound-http.test.ts +4 -5
- package/src/__tests__/guardian-question-mode.test.ts +200 -0
- package/src/__tests__/guardian-routing-invariants.test.ts +178 -0
- package/src/__tests__/guardian-routing-state.test.ts +525 -0
- package/src/__tests__/handle-user-message-secret-resume.test.ts +2 -0
- package/src/__tests__/handlers-telegram-config.test.ts +0 -83
- package/src/__tests__/handlers-user-message-approval-consumption.test.ts +55 -0
- package/src/__tests__/headless-browser-navigate.test.ts +2 -0
- package/src/__tests__/inbound-invite-redemption.test.ts +0 -1
- package/src/__tests__/ingress-routes-http.test.ts +55 -0
- package/src/__tests__/ipc-snapshot.test.ts +18 -51
- package/src/__tests__/non-member-access-request.test.ts +159 -9
- package/src/__tests__/notification-decision-fallback.test.ts +129 -4
- package/src/__tests__/notification-decision-strategy.test.ts +106 -2
- package/src/__tests__/notification-guardian-path.test.ts +3 -0
- package/src/__tests__/recording-intent-handler.test.ts +1 -0
- package/src/__tests__/relay-server.test.ts +1475 -33
- package/src/__tests__/send-endpoint-busy.test.ts +5 -0
- package/src/__tests__/session-agent-loop.test.ts +1 -0
- package/src/__tests__/session-confirmation-signals.test.ts +523 -0
- package/src/__tests__/session-init.benchmark.test.ts +0 -2
- package/src/__tests__/session-runtime-assembly.test.ts +4 -1
- package/src/__tests__/session-surfaces-task-progress.test.ts +44 -1
- package/src/__tests__/session-tool-setup-app-refresh.test.ts +81 -2
- package/src/__tests__/session-tool-setup-memory-scope.test.ts +1 -1
- package/src/__tests__/session-tool-setup-side-effect-flag.test.ts +1 -1
- package/src/__tests__/tool-executor.test.ts +21 -2
- package/src/__tests__/tool-grant-request-escalation.test.ts +333 -27
- package/src/__tests__/trusted-contact-approval-notifier.test.ts +678 -0
- package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +1064 -0
- package/src/__tests__/trusted-contact-lifecycle-notifications.test.ts +11 -1
- package/src/__tests__/trusted-contact-multichannel.test.ts +0 -1
- package/src/__tests__/trusted-contact-verification.test.ts +0 -1
- package/src/__tests__/twilio-config.test.ts +2 -13
- package/src/__tests__/twilio-routes.test.ts +4 -3
- package/src/__tests__/update-bulletin.test.ts +0 -1
- package/src/agent/loop.ts +1 -1
- package/src/approvals/guardian-decision-primitive.ts +12 -3
- package/src/approvals/guardian-request-resolvers.ts +169 -11
- package/src/calls/call-constants.ts +29 -0
- package/src/calls/call-controller.ts +11 -3
- package/src/calls/call-domain.ts +33 -11
- package/src/calls/call-pointer-message-composer.ts +154 -0
- package/src/calls/call-pointer-messages.ts +106 -27
- package/src/calls/guardian-dispatch.ts +4 -2
- package/src/calls/relay-server.ts +921 -112
- package/src/calls/twilio-config.ts +4 -11
- package/src/calls/twilio-routes.ts +4 -6
- package/src/calls/types.ts +3 -1
- package/src/calls/voice-session-bridge.ts +4 -3
- package/src/cli/core-commands.ts +7 -4
- package/src/cli.ts +5 -4
- package/src/config/bundled-skills/agentmail/SKILL.md +4 -0
- package/src/config/bundled-skills/app-builder/SKILL.md +309 -10
- package/src/config/bundled-skills/app-builder/TOOLS.json +1 -1
- package/src/config/bundled-skills/email-setup/SKILL.md +1 -1
- package/src/config/bundled-skills/google-oauth-setup/SKILL.md +105 -81
- package/src/config/bundled-skills/messaging/SKILL.md +61 -12
- package/src/config/bundled-skills/messaging/TOOLS.json +58 -0
- package/src/config/bundled-skills/messaging/tools/gmail-sender-digest.ts +6 -1
- package/src/config/bundled-skills/messaging/tools/messaging-archive-by-sender.ts +35 -0
- package/src/config/bundled-skills/messaging/tools/messaging-sender-digest.ts +52 -0
- package/src/config/bundled-skills/phone-calls/SKILL.md +30 -39
- package/src/config/bundled-skills/twitter/SKILL.md +3 -3
- package/src/config/bundled-skills/vercel-token-setup/SKILL.md +215 -0
- package/src/config/calls-schema.ts +36 -0
- package/src/config/env.ts +22 -0
- package/src/config/feature-flag-registry.json +8 -8
- package/src/config/schema.ts +2 -2
- package/src/config/skills.ts +11 -0
- package/src/config/system-prompt.ts +11 -1
- package/src/config/templates/SOUL.md +2 -0
- package/src/config/vellum-skills/sms-setup/SKILL.md +71 -82
- package/src/config/vellum-skills/trusted-contacts/SKILL.md +8 -1
- package/src/config/vellum-skills/twilio-setup/SKILL.md +88 -73
- package/src/daemon/call-pointer-generators.ts +59 -0
- package/src/daemon/computer-use-session.ts +2 -5
- package/src/daemon/handlers/apps.ts +76 -20
- package/src/daemon/handlers/config-channels.ts +9 -61
- package/src/daemon/handlers/config-inbox.ts +11 -3
- package/src/daemon/handlers/config-ingress.ts +28 -3
- package/src/daemon/handlers/config-telegram.ts +12 -0
- package/src/daemon/handlers/config.ts +2 -6
- package/src/daemon/handlers/index.ts +2 -1
- package/src/daemon/handlers/pairing.ts +2 -0
- package/src/daemon/handlers/publish.ts +11 -46
- package/src/daemon/handlers/sessions.ts +59 -5
- package/src/daemon/handlers/shared.ts +17 -2
- package/src/daemon/ipc-contract/apps.ts +1 -0
- package/src/daemon/ipc-contract/inbox.ts +4 -0
- package/src/daemon/ipc-contract/integrations.ts +1 -97
- package/src/daemon/ipc-contract/messages.ts +47 -1
- package/src/daemon/ipc-contract/notifications.ts +11 -0
- package/src/daemon/ipc-contract-inventory.json +2 -4
- package/src/daemon/lifecycle.ts +17 -0
- package/src/daemon/server.ts +16 -2
- package/src/daemon/session-agent-loop-handlers.ts +20 -0
- package/src/daemon/session-agent-loop.ts +24 -12
- package/src/daemon/session-lifecycle.ts +1 -1
- package/src/daemon/session-process.ts +11 -1
- package/src/daemon/session-runtime-assembly.ts +6 -1
- package/src/daemon/session-surfaces.ts +32 -3
- package/src/daemon/session.ts +88 -1
- package/src/daemon/tool-side-effects.ts +22 -0
- package/src/home-base/prebuilt/brain-graph.html +1483 -0
- package/src/home-base/prebuilt/index.html +40 -0
- package/src/inbound/platform-callback-registration.ts +157 -0
- package/src/memory/canonical-guardian-store.ts +1 -1
- package/src/memory/conversation-crud.ts +2 -1
- package/src/memory/conversation-title-service.ts +16 -2
- package/src/memory/db-init.ts +8 -0
- package/src/memory/delivery-crud.ts +2 -1
- package/src/memory/guardian-action-store.ts +2 -1
- package/src/memory/guardian-approvals.ts +3 -2
- package/src/memory/ingress-invite-store.ts +12 -2
- package/src/memory/ingress-member-store.ts +4 -3
- package/src/memory/migrations/038-actor-token-records.ts +39 -0
- package/src/memory/migrations/124-voice-invite-display-metadata.ts +14 -0
- package/src/memory/migrations/index.ts +2 -0
- package/src/memory/schema.ts +26 -5
- package/src/messaging/provider-types.ts +24 -0
- package/src/messaging/provider.ts +7 -0
- package/src/messaging/providers/gmail/adapter.ts +127 -0
- package/src/messaging/providers/sms/adapter.ts +40 -37
- package/src/notifications/adapters/macos.ts +45 -2
- package/src/notifications/broadcaster.ts +16 -0
- package/src/notifications/copy-composer.ts +50 -2
- package/src/notifications/decision-engine.ts +22 -9
- package/src/notifications/destination-resolver.ts +16 -2
- package/src/notifications/emit-signal.ts +18 -9
- package/src/notifications/guardian-question-mode.ts +419 -0
- package/src/notifications/signal.ts +14 -3
- package/src/permissions/checker.ts +13 -1
- package/src/permissions/prompter.ts +14 -0
- package/src/providers/anthropic/client.ts +20 -0
- package/src/providers/provider-send-message.ts +15 -3
- package/src/runtime/access-request-helper.ts +82 -4
- package/src/runtime/actor-token-service.ts +234 -0
- package/src/runtime/actor-token-store.ts +236 -0
- package/src/runtime/actor-trust-resolver.ts +2 -2
- package/src/runtime/assistant-scope.ts +10 -0
- package/src/runtime/channel-approvals.ts +5 -3
- package/src/runtime/channel-readiness-service.ts +23 -64
- package/src/runtime/channel-readiness-types.ts +3 -4
- package/src/runtime/channel-retry-sweep.ts +4 -1
- package/src/runtime/confirmation-request-guardian-bridge.ts +197 -0
- package/src/runtime/guardian-action-followup-executor.ts +1 -1
- package/src/runtime/guardian-context-resolver.ts +82 -0
- package/src/runtime/guardian-outbound-actions.ts +5 -7
- package/src/runtime/guardian-reply-router.ts +67 -30
- package/src/runtime/guardian-vellum-migration.ts +57 -0
- package/src/runtime/http-server.ts +75 -31
- package/src/runtime/http-types.ts +13 -0
- package/src/runtime/ingress-service.ts +14 -0
- package/src/runtime/invite-redemption-service.ts +10 -1
- package/src/runtime/local-actor-identity.ts +76 -0
- package/src/runtime/middleware/actor-token.ts +271 -0
- package/src/runtime/middleware/twilio-validation.ts +2 -4
- package/src/runtime/routes/approval-routes.ts +82 -7
- package/src/runtime/routes/brain-graph-routes.ts +222 -0
- package/src/runtime/routes/call-routes.ts +2 -1
- package/src/runtime/routes/channel-readiness-routes.ts +71 -0
- package/src/runtime/routes/channel-route-shared.ts +3 -3
- package/src/runtime/routes/conversation-attention-routes.ts +2 -1
- package/src/runtime/routes/conversation-routes.ts +142 -53
- package/src/runtime/routes/events-routes.ts +22 -8
- package/src/runtime/routes/guardian-action-routes.ts +45 -3
- package/src/runtime/routes/guardian-approval-interception.ts +29 -0
- package/src/runtime/routes/guardian-bootstrap-routes.ts +145 -0
- package/src/runtime/routes/inbound-conversation.ts +4 -3
- package/src/runtime/routes/inbound-message-handler.ts +147 -5
- package/src/runtime/routes/ingress-routes.ts +2 -0
- package/src/runtime/routes/integration-routes.ts +7 -15
- package/src/runtime/routes/pairing-routes.ts +163 -0
- package/src/runtime/routes/twilio-routes.ts +934 -0
- package/src/runtime/tool-grant-request-helper.ts +3 -1
- package/src/security/oauth2.ts +27 -2
- package/src/security/token-manager.ts +46 -10
- package/src/tools/browser/browser-execution.ts +4 -3
- package/src/tools/browser/browser-handoff.ts +10 -18
- package/src/tools/browser/browser-manager.ts +80 -25
- package/src/tools/browser/browser-screencast.ts +35 -119
- package/src/tools/calls/call-start.ts +2 -1
- package/src/tools/permission-checker.ts +15 -4
- package/src/tools/terminal/parser.ts +12 -0
- package/src/tools/tool-approval-handler.ts +244 -19
- package/src/workspace/git-service.ts +19 -0
- package/src/__tests__/handlers-twilio-config.test.ts +0 -1928
- package/src/daemon/handlers/config-twilio.ts +0 -1082
|
@@ -0,0 +1,525 @@
|
|
|
1
|
+
import { mkdtempSync, rmSync } from 'node:fs';
|
|
2
|
+
import { tmpdir } from 'node:os';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
|
|
5
|
+
import { afterAll, beforeEach, describe, expect, mock, test } from 'bun:test';
|
|
6
|
+
import { eq } from 'drizzle-orm';
|
|
7
|
+
|
|
8
|
+
// ---------------------------------------------------------------------------
|
|
9
|
+
// Test isolation: in-memory SQLite via temp directory
|
|
10
|
+
// ---------------------------------------------------------------------------
|
|
11
|
+
|
|
12
|
+
const testDir = mkdtempSync(join(tmpdir(), 'guardian-routing-state-test-'));
|
|
13
|
+
|
|
14
|
+
mock.module('../util/platform.js', () => ({
|
|
15
|
+
getRootDir: () => testDir,
|
|
16
|
+
getDataDir: () => testDir,
|
|
17
|
+
isMacOS: () => process.platform === 'darwin',
|
|
18
|
+
isLinux: () => process.platform === 'linux',
|
|
19
|
+
isWindows: () => process.platform === 'win32',
|
|
20
|
+
getSocketPath: () => join(testDir, 'test.sock'),
|
|
21
|
+
getPidPath: () => join(testDir, 'test.pid'),
|
|
22
|
+
getDbPath: () => join(testDir, 'test.db'),
|
|
23
|
+
getLogPath: () => join(testDir, 'test.log'),
|
|
24
|
+
ensureDataDir: () => {},
|
|
25
|
+
}));
|
|
26
|
+
|
|
27
|
+
mock.module('../util/logger.js', () => ({
|
|
28
|
+
getLogger: () => new Proxy({} as Record<string, unknown>, {
|
|
29
|
+
get: () => () => {},
|
|
30
|
+
}),
|
|
31
|
+
}));
|
|
32
|
+
|
|
33
|
+
// Mock security check to always pass
|
|
34
|
+
mock.module('../security/secret-ingress.js', () => ({
|
|
35
|
+
checkIngressForSecrets: () => ({ blocked: false }),
|
|
36
|
+
}));
|
|
37
|
+
|
|
38
|
+
// Mock ingress member store with a configurable member lookup.
|
|
39
|
+
// By default returns an active member so ACL passes.
|
|
40
|
+
let mockFindMember: (() => unknown) | null = null;
|
|
41
|
+
mock.module('../memory/ingress-member-store.js', () => ({
|
|
42
|
+
findMember: (..._args: unknown[]) => {
|
|
43
|
+
if (mockFindMember) return mockFindMember();
|
|
44
|
+
return {
|
|
45
|
+
id: 'member-test-default',
|
|
46
|
+
assistantId: 'self',
|
|
47
|
+
sourceChannel: 'telegram',
|
|
48
|
+
externalUserId: 'telegram-user-default',
|
|
49
|
+
externalChatId: null,
|
|
50
|
+
displayName: null,
|
|
51
|
+
username: null,
|
|
52
|
+
status: 'active',
|
|
53
|
+
policy: 'allow',
|
|
54
|
+
inviteId: null,
|
|
55
|
+
createdBySessionId: null,
|
|
56
|
+
revokedReason: null,
|
|
57
|
+
blockedReason: null,
|
|
58
|
+
lastSeenAt: null,
|
|
59
|
+
createdAt: Date.now(),
|
|
60
|
+
updatedAt: Date.now(),
|
|
61
|
+
};
|
|
62
|
+
},
|
|
63
|
+
updateLastSeen: () => {},
|
|
64
|
+
upsertMember: () => {},
|
|
65
|
+
}));
|
|
66
|
+
|
|
67
|
+
import * as channelDeliveryStore from '../memory/channel-delivery-store.js';
|
|
68
|
+
import { createBinding } from '../memory/channel-guardian-store.js';
|
|
69
|
+
import { getDb, initializeDb, resetDb } from '../memory/db.js';
|
|
70
|
+
import { channelInboundEvents, messages } from '../memory/schema.js';
|
|
71
|
+
import { sweepFailedEvents } from '../runtime/channel-retry-sweep.js';
|
|
72
|
+
import {
|
|
73
|
+
type GuardianContext,
|
|
74
|
+
resolveRoutingState,
|
|
75
|
+
resolveRoutingStateFromRuntime,
|
|
76
|
+
} from '../runtime/guardian-context-resolver.js';
|
|
77
|
+
import { handleChannelInbound } from '../runtime/routes/channel-routes.js';
|
|
78
|
+
|
|
79
|
+
initializeDb();
|
|
80
|
+
|
|
81
|
+
afterAll(() => {
|
|
82
|
+
resetDb();
|
|
83
|
+
try { rmSync(testDir, { recursive: true }); } catch { /* best effort */ }
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
function resetTables(): void {
|
|
87
|
+
const db = getDb();
|
|
88
|
+
db.run('DELETE FROM channel_inbound_events');
|
|
89
|
+
db.run('DELETE FROM channel_guardian_bindings');
|
|
90
|
+
db.run('DELETE FROM channel_guardian_approval_requests');
|
|
91
|
+
db.run('DELETE FROM canonical_guardian_requests');
|
|
92
|
+
db.run('DELETE FROM conversation_keys');
|
|
93
|
+
db.run('DELETE FROM messages');
|
|
94
|
+
db.run('DELETE FROM conversations');
|
|
95
|
+
db.run('DELETE FROM assistant_ingress_members');
|
|
96
|
+
db.run('DELETE FROM external_conversation_bindings');
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
100
|
+
// Unit tests: resolveRoutingState
|
|
101
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
102
|
+
|
|
103
|
+
describe('resolveRoutingState', () => {
|
|
104
|
+
test('guardian actors are always interactive and route-resolvable', () => {
|
|
105
|
+
const ctx: GuardianContext = {
|
|
106
|
+
trustClass: 'guardian',
|
|
107
|
+
guardianExternalUserId: 'guardian-123',
|
|
108
|
+
guardianChatId: 'chat-123',
|
|
109
|
+
};
|
|
110
|
+
const state = resolveRoutingState(ctx);
|
|
111
|
+
expect(state).toEqual({
|
|
112
|
+
canBeInteractive: true,
|
|
113
|
+
guardianRouteResolvable: true,
|
|
114
|
+
promptWaitingAllowed: true,
|
|
115
|
+
});
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
test('guardian actors are interactive even without guardianExternalUserId', () => {
|
|
119
|
+
// Edge case: guardian is chatting in their own chat, no separate binding needed
|
|
120
|
+
const ctx: GuardianContext = {
|
|
121
|
+
trustClass: 'guardian',
|
|
122
|
+
};
|
|
123
|
+
const state = resolveRoutingState(ctx);
|
|
124
|
+
expect(state.canBeInteractive).toBe(true);
|
|
125
|
+
expect(state.promptWaitingAllowed).toBe(true);
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
test('trusted contact with resolvable guardian route is interactive', () => {
|
|
129
|
+
const ctx: GuardianContext = {
|
|
130
|
+
trustClass: 'trusted_contact',
|
|
131
|
+
guardianExternalUserId: 'guardian-456',
|
|
132
|
+
guardianChatId: 'guardian-chat-456',
|
|
133
|
+
};
|
|
134
|
+
const state = resolveRoutingState(ctx);
|
|
135
|
+
expect(state).toEqual({
|
|
136
|
+
canBeInteractive: true,
|
|
137
|
+
guardianRouteResolvable: true,
|
|
138
|
+
promptWaitingAllowed: true,
|
|
139
|
+
});
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
test('trusted contact without guardian route is NOT interactive (fail-fast)', () => {
|
|
143
|
+
const ctx: GuardianContext = {
|
|
144
|
+
trustClass: 'trusted_contact',
|
|
145
|
+
// No guardianExternalUserId — no guardian binding for this channel
|
|
146
|
+
};
|
|
147
|
+
const state = resolveRoutingState(ctx);
|
|
148
|
+
expect(state).toEqual({
|
|
149
|
+
canBeInteractive: true,
|
|
150
|
+
guardianRouteResolvable: false,
|
|
151
|
+
promptWaitingAllowed: false,
|
|
152
|
+
});
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
test('unknown actors are never interactive regardless of guardian route', () => {
|
|
156
|
+
const withRoute: GuardianContext = {
|
|
157
|
+
trustClass: 'unknown',
|
|
158
|
+
guardianExternalUserId: 'guardian-789',
|
|
159
|
+
};
|
|
160
|
+
const withoutRoute: GuardianContext = {
|
|
161
|
+
trustClass: 'unknown',
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
expect(resolveRoutingState(withRoute).promptWaitingAllowed).toBe(false);
|
|
165
|
+
expect(resolveRoutingState(withRoute).canBeInteractive).toBe(false);
|
|
166
|
+
expect(resolveRoutingState(withoutRoute).promptWaitingAllowed).toBe(false);
|
|
167
|
+
});
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
describe('resolveRoutingStateFromRuntime', () => {
|
|
171
|
+
test('produces same result as resolveRoutingState for guardian runtime context', () => {
|
|
172
|
+
const runtimeCtx = {
|
|
173
|
+
sourceChannel: 'telegram' as const,
|
|
174
|
+
trustClass: 'trusted_contact' as const,
|
|
175
|
+
guardianExternalUserId: 'guardian-rt-1',
|
|
176
|
+
};
|
|
177
|
+
const state = resolveRoutingStateFromRuntime(runtimeCtx);
|
|
178
|
+
expect(state.promptWaitingAllowed).toBe(true);
|
|
179
|
+
expect(state.guardianRouteResolvable).toBe(true);
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
test('trusted contact runtime context without guardian binding is not interactive', () => {
|
|
183
|
+
const runtimeCtx = {
|
|
184
|
+
sourceChannel: 'telegram' as const,
|
|
185
|
+
trustClass: 'trusted_contact' as const,
|
|
186
|
+
// No guardianExternalUserId
|
|
187
|
+
};
|
|
188
|
+
const state = resolveRoutingStateFromRuntime(runtimeCtx);
|
|
189
|
+
expect(state.promptWaitingAllowed).toBe(false);
|
|
190
|
+
expect(state.guardianRouteResolvable).toBe(false);
|
|
191
|
+
});
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
195
|
+
// Integration tests: inbound message handler interactivity
|
|
196
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
197
|
+
|
|
198
|
+
describe('inbound-message-handler trusted-contact interactivity', () => {
|
|
199
|
+
beforeEach(() => {
|
|
200
|
+
resetTables();
|
|
201
|
+
mockFindMember = null;
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
function makeInboundRequest(overrides: Record<string, unknown> = {}): Request {
|
|
205
|
+
return new Request('http://localhost/channels/inbound', {
|
|
206
|
+
method: 'POST',
|
|
207
|
+
headers: {
|
|
208
|
+
'Content-Type': 'application/json',
|
|
209
|
+
'X-Gateway-Origin': 'test-token',
|
|
210
|
+
},
|
|
211
|
+
body: JSON.stringify({
|
|
212
|
+
sourceChannel: 'telegram',
|
|
213
|
+
interface: 'telegram',
|
|
214
|
+
externalChatId: 'chat-123',
|
|
215
|
+
externalMessageId: `msg-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
|
|
216
|
+
content: 'hello',
|
|
217
|
+
senderExternalUserId: 'telegram-user-default',
|
|
218
|
+
replyCallbackUrl: 'https://gateway.test/deliver/telegram',
|
|
219
|
+
...overrides,
|
|
220
|
+
}),
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
test('trusted contact with guardian binding gets interactive turn', async () => {
|
|
225
|
+
// Create guardian binding so the trusted contact has a resolvable route
|
|
226
|
+
createBinding({
|
|
227
|
+
assistantId: 'self',
|
|
228
|
+
channel: 'telegram',
|
|
229
|
+
guardianExternalUserId: 'guardian-user-for-tc',
|
|
230
|
+
guardianDeliveryChatId: 'guardian-chat-for-tc',
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
const processCalls: Array<{ options?: Record<string, unknown> }> = [];
|
|
234
|
+
const processMessage = mock(async (
|
|
235
|
+
conversationId: string,
|
|
236
|
+
_content: string,
|
|
237
|
+
_attachmentIds?: string[],
|
|
238
|
+
options?: Record<string, unknown>,
|
|
239
|
+
) => {
|
|
240
|
+
processCalls.push({ options });
|
|
241
|
+
const messageId = `msg-tc-interactive-${Date.now()}`;
|
|
242
|
+
const db = getDb();
|
|
243
|
+
db.insert(messages).values({
|
|
244
|
+
id: messageId,
|
|
245
|
+
conversationId,
|
|
246
|
+
role: 'user',
|
|
247
|
+
content: JSON.stringify([{ type: 'text', text: 'hello' }]),
|
|
248
|
+
createdAt: Date.now(),
|
|
249
|
+
}).run();
|
|
250
|
+
return { messageId };
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
const req = makeInboundRequest({
|
|
254
|
+
externalMessageId: `msg-tc-interactive-${Date.now()}`,
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
const res = await handleChannelInbound(req, processMessage as any, 'test-token');
|
|
258
|
+
const body = await res.json() as Record<string, unknown>;
|
|
259
|
+
expect(body.accepted).toBe(true);
|
|
260
|
+
|
|
261
|
+
// Wait for background processing
|
|
262
|
+
await new Promise((resolve) => setTimeout(resolve, 300));
|
|
263
|
+
|
|
264
|
+
expect(processCalls.length).toBeGreaterThan(0);
|
|
265
|
+
expect(processCalls[0].options?.isInteractive).toBe(true);
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
test('trusted contact WITHOUT guardian binding gets non-interactive turn (fail-fast)', async () => {
|
|
269
|
+
// No guardian binding created — trusted contact has no guardian route
|
|
270
|
+
// but findMember still returns an active member (trusted_contact trust class)
|
|
271
|
+
|
|
272
|
+
const processCalls: Array<{ options?: Record<string, unknown> }> = [];
|
|
273
|
+
const processMessage = mock(async (
|
|
274
|
+
conversationId: string,
|
|
275
|
+
_content: string,
|
|
276
|
+
_attachmentIds?: string[],
|
|
277
|
+
options?: Record<string, unknown>,
|
|
278
|
+
) => {
|
|
279
|
+
processCalls.push({ options });
|
|
280
|
+
const messageId = `msg-tc-noroute-${Date.now()}`;
|
|
281
|
+
const db = getDb();
|
|
282
|
+
db.insert(messages).values({
|
|
283
|
+
id: messageId,
|
|
284
|
+
conversationId,
|
|
285
|
+
role: 'user',
|
|
286
|
+
content: JSON.stringify([{ type: 'text', text: 'hello' }]),
|
|
287
|
+
createdAt: Date.now(),
|
|
288
|
+
}).run();
|
|
289
|
+
return { messageId };
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
const req = makeInboundRequest({
|
|
293
|
+
externalMessageId: `msg-tc-noroute-${Date.now()}`,
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
const res = await handleChannelInbound(req, processMessage as any, 'test-token');
|
|
297
|
+
const body = await res.json() as Record<string, unknown>;
|
|
298
|
+
expect(body.accepted).toBe(true);
|
|
299
|
+
|
|
300
|
+
await new Promise((resolve) => setTimeout(resolve, 300));
|
|
301
|
+
|
|
302
|
+
expect(processCalls.length).toBeGreaterThan(0);
|
|
303
|
+
// Trusted contact without a guardian binding should NOT be interactive
|
|
304
|
+
// to prevent dead-end 300s prompt waits
|
|
305
|
+
expect(processCalls[0].options?.isInteractive).toBe(false);
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
test('guardian actors remain interactive regardless', async () => {
|
|
309
|
+
// Guardian binding matches the sender
|
|
310
|
+
createBinding({
|
|
311
|
+
assistantId: 'self',
|
|
312
|
+
channel: 'telegram',
|
|
313
|
+
guardianExternalUserId: 'telegram-user-default',
|
|
314
|
+
guardianDeliveryChatId: 'chat-123',
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
const processCalls: Array<{ options?: Record<string, unknown> }> = [];
|
|
318
|
+
const processMessage = mock(async (
|
|
319
|
+
conversationId: string,
|
|
320
|
+
_content: string,
|
|
321
|
+
_attachmentIds?: string[],
|
|
322
|
+
options?: Record<string, unknown>,
|
|
323
|
+
) => {
|
|
324
|
+
processCalls.push({ options });
|
|
325
|
+
const messageId = `msg-guardian-${Date.now()}`;
|
|
326
|
+
const db = getDb();
|
|
327
|
+
db.insert(messages).values({
|
|
328
|
+
id: messageId,
|
|
329
|
+
conversationId,
|
|
330
|
+
role: 'user',
|
|
331
|
+
content: JSON.stringify([{ type: 'text', text: 'hello' }]),
|
|
332
|
+
createdAt: Date.now(),
|
|
333
|
+
}).run();
|
|
334
|
+
return { messageId };
|
|
335
|
+
});
|
|
336
|
+
|
|
337
|
+
const req = makeInboundRequest({
|
|
338
|
+
externalMessageId: `msg-guardian-${Date.now()}`,
|
|
339
|
+
});
|
|
340
|
+
|
|
341
|
+
const res = await handleChannelInbound(req, processMessage as any, 'test-token');
|
|
342
|
+
const body = await res.json() as Record<string, unknown>;
|
|
343
|
+
expect(body.accepted).toBe(true);
|
|
344
|
+
|
|
345
|
+
await new Promise((resolve) => setTimeout(resolve, 300));
|
|
346
|
+
|
|
347
|
+
expect(processCalls.length).toBeGreaterThan(0);
|
|
348
|
+
expect(processCalls[0].options?.isInteractive).toBe(true);
|
|
349
|
+
});
|
|
350
|
+
|
|
351
|
+
test('unknown actors remain non-interactive', async () => {
|
|
352
|
+
// No guardian binding, no member record => unknown trust class
|
|
353
|
+
mockFindMember = () => null;
|
|
354
|
+
|
|
355
|
+
const processCalls: Array<{ options?: Record<string, unknown> }> = [];
|
|
356
|
+
const processMessage = mock(async (
|
|
357
|
+
conversationId: string,
|
|
358
|
+
_content: string,
|
|
359
|
+
_attachmentIds?: string[],
|
|
360
|
+
options?: Record<string, unknown>,
|
|
361
|
+
) => {
|
|
362
|
+
processCalls.push({ options });
|
|
363
|
+
const messageId = `msg-unknown-${Date.now()}`;
|
|
364
|
+
const db = getDb();
|
|
365
|
+
db.insert(messages).values({
|
|
366
|
+
id: messageId,
|
|
367
|
+
conversationId,
|
|
368
|
+
role: 'user',
|
|
369
|
+
content: JSON.stringify([{ type: 'text', text: 'hello' }]),
|
|
370
|
+
createdAt: Date.now(),
|
|
371
|
+
}).run();
|
|
372
|
+
return { messageId };
|
|
373
|
+
});
|
|
374
|
+
|
|
375
|
+
const req = makeInboundRequest({
|
|
376
|
+
externalMessageId: `msg-unknown-${Date.now()}`,
|
|
377
|
+
// No senderExternalUserId => unknown trust class
|
|
378
|
+
senderExternalUserId: undefined,
|
|
379
|
+
});
|
|
380
|
+
|
|
381
|
+
const res = await handleChannelInbound(req, processMessage as any, 'test-token');
|
|
382
|
+
const body = await res.json() as Record<string, unknown>;
|
|
383
|
+
expect(body.accepted).toBe(true);
|
|
384
|
+
|
|
385
|
+
await new Promise((resolve) => setTimeout(resolve, 300));
|
|
386
|
+
|
|
387
|
+
expect(processCalls.length).toBeGreaterThan(0);
|
|
388
|
+
expect(processCalls[0].options?.isInteractive).toBe(false);
|
|
389
|
+
});
|
|
390
|
+
});
|
|
391
|
+
|
|
392
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
393
|
+
// Integration tests: channel-retry-sweep routing state
|
|
394
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
395
|
+
|
|
396
|
+
describe('channel-retry-sweep routing state', () => {
|
|
397
|
+
beforeEach(() => {
|
|
398
|
+
resetTables();
|
|
399
|
+
mockFindMember = null;
|
|
400
|
+
});
|
|
401
|
+
|
|
402
|
+
function seedFailedEvent(trustClass: 'guardian' | 'trusted_contact' | 'unknown', guardianExternalUserId?: string): string {
|
|
403
|
+
const inbound = channelDeliveryStore.recordInbound('telegram', `chat-${trustClass}`, `msg-${trustClass}-${Date.now()}`);
|
|
404
|
+
channelDeliveryStore.storePayload(inbound.eventId, {
|
|
405
|
+
content: 'retry me',
|
|
406
|
+
sourceChannel: 'telegram',
|
|
407
|
+
interface: 'telegram',
|
|
408
|
+
guardianCtx: {
|
|
409
|
+
trustClass,
|
|
410
|
+
sourceChannel: 'telegram',
|
|
411
|
+
requesterExternalUserId: 'test-user',
|
|
412
|
+
requesterChatId: `chat-${trustClass}`,
|
|
413
|
+
...(guardianExternalUserId ? { guardianExternalUserId } : {}),
|
|
414
|
+
},
|
|
415
|
+
});
|
|
416
|
+
|
|
417
|
+
const db = getDb();
|
|
418
|
+
db.update(channelInboundEvents)
|
|
419
|
+
.set({
|
|
420
|
+
processingStatus: 'failed',
|
|
421
|
+
processingAttempts: 1,
|
|
422
|
+
retryAfter: Date.now() - 1,
|
|
423
|
+
})
|
|
424
|
+
.where(eq(channelInboundEvents.id, inbound.eventId))
|
|
425
|
+
.run();
|
|
426
|
+
|
|
427
|
+
return inbound.eventId;
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
test('trusted_contact with guardian binding replays as interactive', async () => {
|
|
431
|
+
seedFailedEvent('trusted_contact', 'guardian-for-sweep');
|
|
432
|
+
let capturedOptions: { isInteractive?: boolean } | undefined;
|
|
433
|
+
|
|
434
|
+
await sweepFailedEvents(
|
|
435
|
+
async (conversationId, _content, _attachmentIds, options) => {
|
|
436
|
+
capturedOptions = options as { isInteractive?: boolean };
|
|
437
|
+
const messageId = `message-tc-sweep-${Date.now()}`;
|
|
438
|
+
const db = getDb();
|
|
439
|
+
db.insert(messages).values({
|
|
440
|
+
id: messageId,
|
|
441
|
+
conversationId,
|
|
442
|
+
role: 'user',
|
|
443
|
+
content: JSON.stringify([{ type: 'text', text: 'retry me' }]),
|
|
444
|
+
createdAt: Date.now(),
|
|
445
|
+
}).run();
|
|
446
|
+
return { messageId };
|
|
447
|
+
},
|
|
448
|
+
undefined,
|
|
449
|
+
);
|
|
450
|
+
|
|
451
|
+
expect(capturedOptions?.isInteractive).toBe(true);
|
|
452
|
+
});
|
|
453
|
+
|
|
454
|
+
test('trusted_contact without guardian binding replays as non-interactive', async () => {
|
|
455
|
+
seedFailedEvent('trusted_contact');
|
|
456
|
+
let capturedOptions: { isInteractive?: boolean } | undefined;
|
|
457
|
+
|
|
458
|
+
await sweepFailedEvents(
|
|
459
|
+
async (conversationId, _content, _attachmentIds, options) => {
|
|
460
|
+
capturedOptions = options as { isInteractive?: boolean };
|
|
461
|
+
const messageId = `message-tc-no-binding-${Date.now()}`;
|
|
462
|
+
const db = getDb();
|
|
463
|
+
db.insert(messages).values({
|
|
464
|
+
id: messageId,
|
|
465
|
+
conversationId,
|
|
466
|
+
role: 'user',
|
|
467
|
+
content: JSON.stringify([{ type: 'text', text: 'retry me' }]),
|
|
468
|
+
createdAt: Date.now(),
|
|
469
|
+
}).run();
|
|
470
|
+
return { messageId };
|
|
471
|
+
},
|
|
472
|
+
undefined,
|
|
473
|
+
);
|
|
474
|
+
|
|
475
|
+
expect(capturedOptions?.isInteractive).toBe(false);
|
|
476
|
+
});
|
|
477
|
+
|
|
478
|
+
test('guardian replays as interactive', async () => {
|
|
479
|
+
seedFailedEvent('guardian', 'guardian-self');
|
|
480
|
+
let capturedOptions: { isInteractive?: boolean } | undefined;
|
|
481
|
+
|
|
482
|
+
await sweepFailedEvents(
|
|
483
|
+
async (conversationId, _content, _attachmentIds, options) => {
|
|
484
|
+
capturedOptions = options as { isInteractive?: boolean };
|
|
485
|
+
const messageId = `message-guardian-sweep-${Date.now()}`;
|
|
486
|
+
const db = getDb();
|
|
487
|
+
db.insert(messages).values({
|
|
488
|
+
id: messageId,
|
|
489
|
+
conversationId,
|
|
490
|
+
role: 'user',
|
|
491
|
+
content: JSON.stringify([{ type: 'text', text: 'retry me' }]),
|
|
492
|
+
createdAt: Date.now(),
|
|
493
|
+
}).run();
|
|
494
|
+
return { messageId };
|
|
495
|
+
},
|
|
496
|
+
undefined,
|
|
497
|
+
);
|
|
498
|
+
|
|
499
|
+
expect(capturedOptions?.isInteractive).toBe(true);
|
|
500
|
+
});
|
|
501
|
+
|
|
502
|
+
test('unknown replays as non-interactive', async () => {
|
|
503
|
+
seedFailedEvent('unknown');
|
|
504
|
+
let capturedOptions: { isInteractive?: boolean } | undefined;
|
|
505
|
+
|
|
506
|
+
await sweepFailedEvents(
|
|
507
|
+
async (conversationId, _content, _attachmentIds, options) => {
|
|
508
|
+
capturedOptions = options as { isInteractive?: boolean };
|
|
509
|
+
const messageId = `message-unknown-sweep-${Date.now()}`;
|
|
510
|
+
const db = getDb();
|
|
511
|
+
db.insert(messages).values({
|
|
512
|
+
id: messageId,
|
|
513
|
+
conversationId,
|
|
514
|
+
role: 'user',
|
|
515
|
+
content: JSON.stringify([{ type: 'text', text: 'retry me' }]),
|
|
516
|
+
createdAt: Date.now(),
|
|
517
|
+
}).run();
|
|
518
|
+
return { messageId };
|
|
519
|
+
},
|
|
520
|
+
undefined,
|
|
521
|
+
);
|
|
522
|
+
|
|
523
|
+
expect(capturedOptions?.isInteractive).toBe(false);
|
|
524
|
+
});
|
|
525
|
+
});
|
|
@@ -40,6 +40,8 @@ describe('handleUserMessage secret redirect continuation', () => {
|
|
|
40
40
|
setChannelCapabilities: () => {},
|
|
41
41
|
setGuardianContext: () => {},
|
|
42
42
|
setCommandIntent: () => {},
|
|
43
|
+
updateClient: () => {},
|
|
44
|
+
emitActivityState: () => {},
|
|
43
45
|
processMessage: (content: string, _attachments: unknown[], _onEvent: unknown, requestId: string) => {
|
|
44
46
|
processCalls.push({ content, requestId });
|
|
45
47
|
return Promise.resolve();
|
|
@@ -908,8 +908,6 @@ describe('Telegram config handler', () => {
|
|
|
908
908
|
|
|
909
909
|
import { handleGuardianVerification } from '../daemon/handlers/config.js';
|
|
910
910
|
import type { GuardianVerificationRequest } from '../daemon/ipc-contract.js';
|
|
911
|
-
import { createBinding } from '../memory/channel-guardian-store.js';
|
|
912
|
-
|
|
913
911
|
describe('Guardian verification IPC actions', () => {
|
|
914
912
|
beforeEach(() => {
|
|
915
913
|
secureKeyStore = {};
|
|
@@ -971,85 +969,4 @@ describe('Guardian verification IPC actions', () => {
|
|
|
971
969
|
expect(res.error).toContain('Unknown action');
|
|
972
970
|
});
|
|
973
971
|
|
|
974
|
-
test('create_challenge with explicit assistantId scopes challenge to that assistant', () => {
|
|
975
|
-
const msg: GuardianVerificationRequest = {
|
|
976
|
-
type: 'guardian_verification',
|
|
977
|
-
action: 'create_challenge',
|
|
978
|
-
channel: 'telegram',
|
|
979
|
-
assistantId: 'asst-ipc-X',
|
|
980
|
-
};
|
|
981
|
-
|
|
982
|
-
const { ctx, sent } = createTestContext();
|
|
983
|
-
handleGuardianVerification(msg, {} as net.Socket, ctx);
|
|
984
|
-
|
|
985
|
-
expect(sent).toHaveLength(1);
|
|
986
|
-
const res = sent[0] as { type: string; success: boolean; secret?: string; instruction?: string };
|
|
987
|
-
expect(res.success).toBe(true);
|
|
988
|
-
expect(res.secret).toBeDefined();
|
|
989
|
-
expect(res.instruction).toContain('send');
|
|
990
|
-
expect(res.instruction).toContain('code');
|
|
991
|
-
});
|
|
992
|
-
|
|
993
|
-
test('status action with explicit assistantId checks binding for that assistant', () => {
|
|
994
|
-
// Create a control binding for a known assistant so we can verify
|
|
995
|
-
// that querying a *different* assistantId actually returns bound=false
|
|
996
|
-
// (not just because no bindings exist at all).
|
|
997
|
-
createBinding({
|
|
998
|
-
assistantId: 'asst-ipc-bound',
|
|
999
|
-
channel: 'telegram',
|
|
1000
|
-
guardianExternalUserId: 'guardian-user-1',
|
|
1001
|
-
guardianDeliveryChatId: 'guardian-chat-1',
|
|
1002
|
-
});
|
|
1003
|
-
|
|
1004
|
-
// Querying a different assistant should return bound=false
|
|
1005
|
-
const unboundMsg: GuardianVerificationRequest = {
|
|
1006
|
-
type: 'guardian_verification',
|
|
1007
|
-
action: 'status',
|
|
1008
|
-
channel: 'telegram',
|
|
1009
|
-
assistantId: 'asst-ipc-Y',
|
|
1010
|
-
};
|
|
1011
|
-
|
|
1012
|
-
const { ctx: ctx1, sent: sent1 } = createTestContext();
|
|
1013
|
-
handleGuardianVerification(unboundMsg, {} as net.Socket, ctx1);
|
|
1014
|
-
|
|
1015
|
-
expect(sent1).toHaveLength(1);
|
|
1016
|
-
const unboundRes = sent1[0] as { type: string; success: boolean; bound: boolean };
|
|
1017
|
-
expect(unboundRes.success).toBe(true);
|
|
1018
|
-
expect(unboundRes.bound).toBe(false);
|
|
1019
|
-
|
|
1020
|
-
// Querying the bound assistant should return bound=true
|
|
1021
|
-
const boundMsg: GuardianVerificationRequest = {
|
|
1022
|
-
type: 'guardian_verification',
|
|
1023
|
-
action: 'status',
|
|
1024
|
-
channel: 'telegram',
|
|
1025
|
-
assistantId: 'asst-ipc-bound',
|
|
1026
|
-
};
|
|
1027
|
-
|
|
1028
|
-
const { ctx: ctx2, sent: sent2 } = createTestContext();
|
|
1029
|
-
handleGuardianVerification(boundMsg, {} as net.Socket, ctx2);
|
|
1030
|
-
|
|
1031
|
-
expect(sent2).toHaveLength(1);
|
|
1032
|
-
const boundRes = sent2[0] as { type: string; success: boolean; bound: boolean; guardianExternalUserId?: string };
|
|
1033
|
-
expect(boundRes.success).toBe(true);
|
|
1034
|
-
expect(boundRes.bound).toBe(true);
|
|
1035
|
-
expect(boundRes.guardianExternalUserId).toBe('guardian-user-1');
|
|
1036
|
-
});
|
|
1037
|
-
|
|
1038
|
-
test('assistantId defaults to "self" when not provided', () => {
|
|
1039
|
-
// create_challenge without assistantId should scope to 'self'
|
|
1040
|
-
const createMsg: GuardianVerificationRequest = {
|
|
1041
|
-
type: 'guardian_verification',
|
|
1042
|
-
action: 'create_challenge',
|
|
1043
|
-
channel: 'telegram',
|
|
1044
|
-
// assistantId intentionally omitted
|
|
1045
|
-
};
|
|
1046
|
-
|
|
1047
|
-
const { ctx: ctx1, sent: sent1 } = createTestContext();
|
|
1048
|
-
handleGuardianVerification(createMsg, {} as net.Socket, ctx1);
|
|
1049
|
-
|
|
1050
|
-
expect(sent1).toHaveLength(1);
|
|
1051
|
-
const createRes = sent1[0] as { type: string; success: boolean; secret?: string };
|
|
1052
|
-
expect(createRes.success).toBe(true);
|
|
1053
|
-
expect(createRes.secret).toBeDefined();
|
|
1054
|
-
});
|
|
1055
972
|
});
|