@vellumai/assistant 0.4.31 → 0.4.33
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 +1 -1
- package/docs/architecture/memory.md +1 -1
- package/package.json +1 -1
- package/src/__tests__/__snapshots__/ipc-snapshot.test.ts.snap +0 -7
- package/src/__tests__/access-request-decision.test.ts +83 -1
- package/src/__tests__/actor-token-service.test.ts +0 -1
- package/src/__tests__/anthropic-provider.test.ts +86 -1
- package/src/__tests__/approval-routes-http.test.ts +0 -1
- package/src/__tests__/assistant-feature-flags-integration.test.ts +2 -2
- package/src/__tests__/call-controller.test.ts +0 -1
- package/src/__tests__/call-routes-http.test.ts +0 -1
- package/src/__tests__/channel-guardian.test.ts +0 -1
- package/src/__tests__/channel-invite-transport.test.ts +52 -40
- package/src/__tests__/checker.test.ts +37 -98
- package/src/__tests__/commit-message-enrichment-service.test.ts +4 -23
- package/src/__tests__/computer-use-session-working-dir.test.ts +0 -1
- package/src/__tests__/config-schema.test.ts +6 -5
- package/src/__tests__/credential-security-invariants.test.ts +2 -0
- package/src/__tests__/daemon-server-session-init.test.ts +1 -19
- package/src/__tests__/deterministic-verification-control-plane.test.ts +0 -1
- package/src/__tests__/followup-tools.test.ts +0 -30
- package/src/__tests__/gemini-provider.test.ts +79 -1
- package/src/__tests__/guardian-action-followup-executor.test.ts +0 -1
- package/src/__tests__/guardian-dispatch.test.ts +0 -1
- package/src/__tests__/guardian-outbound-http.test.ts +0 -1
- package/src/__tests__/handlers-telegram-config.test.ts +0 -1
- package/src/__tests__/inbound-invite-redemption.test.ts +1 -4
- package/src/__tests__/ingress-reconcile.test.ts +3 -36
- package/src/__tests__/ipc-snapshot.test.ts +0 -4
- package/src/__tests__/managed-proxy-context.test.ts +163 -0
- package/src/__tests__/memory-lifecycle-e2e.test.ts +2 -2
- package/src/__tests__/memory-regressions.test.ts +6 -6
- package/src/__tests__/migration-cross-version-compatibility.test.ts +0 -1
- package/src/__tests__/migration-export-http.test.ts +0 -1
- package/src/__tests__/migration-import-commit-http.test.ts +0 -1
- package/src/__tests__/migration-import-preflight-http.test.ts +0 -1
- package/src/__tests__/migration-validate-http.test.ts +0 -1
- package/src/__tests__/non-member-access-request.test.ts +0 -1
- package/src/__tests__/notification-guardian-path.test.ts +0 -1
- package/src/__tests__/notification-telegram-adapter.test.ts +0 -4
- package/src/__tests__/openai-provider.test.ts +82 -0
- package/src/__tests__/provider-fail-open-selection.test.ts +134 -1
- package/src/__tests__/provider-managed-proxy-integration.test.ts +269 -0
- package/src/__tests__/recurrence-types.test.ts +0 -15
- package/src/__tests__/relay-server.test.ts +145 -2
- package/src/__tests__/sandbox-host-parity.test.ts +5 -2
- package/src/__tests__/schedule-tools.test.ts +28 -44
- package/src/__tests__/session-init.benchmark.test.ts +0 -2
- package/src/__tests__/skill-feature-flags.test.ts +2 -2
- package/src/__tests__/slack-channel-config.test.ts +0 -1
- package/src/__tests__/slack-inbound-verification.test.ts +0 -1
- package/src/__tests__/sms-messaging-provider.test.ts +0 -4
- package/src/__tests__/task-management-tools.test.ts +111 -0
- package/src/__tests__/terminal-tools.test.ts +5 -2
- package/src/__tests__/trusted-contact-approval-notifier.test.ts +66 -74
- package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +0 -1
- package/src/__tests__/trusted-contact-lifecycle-notifications.test.ts +0 -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 +0 -3
- package/src/__tests__/twilio-routes.test.ts +0 -1
- package/src/__tests__/update-bulletin.test.ts +0 -2
- package/src/__tests__/user-reference.test.ts +47 -1
- package/src/__tests__/voice-scoped-grant-consumer.test.ts +0 -1
- package/src/__tests__/workspace-git-service.test.ts +2 -2
- package/src/amazon/session.ts +30 -91
- package/src/calls/call-controller.ts +423 -571
- package/src/calls/finalize-call.ts +20 -0
- package/src/calls/relay-access-wait.ts +340 -0
- package/src/calls/relay-server.ts +271 -956
- package/src/calls/relay-setup-router.ts +307 -0
- package/src/calls/relay-verification.ts +280 -0
- package/src/calls/twilio-config.ts +1 -8
- package/src/calls/voice-control-protocol.ts +184 -0
- package/src/calls/voice-session-bridge.ts +1 -8
- package/src/channels/config.ts +41 -2
- package/src/config/agent-schema.ts +1 -1
- package/src/config/bundled-skills/followups/TOOLS.json +0 -4
- package/src/config/bundled-skills/schedule/SKILL.md +1 -1
- package/src/config/bundled-skills/schedule/TOOLS.json +2 -10
- package/src/config/bundled-skills/slack/SKILL.md +2 -0
- package/src/config/bundled-skills/slack-digest-setup/SKILL.md +164 -0
- package/src/config/core-schema.ts +1 -1
- package/src/config/env.ts +0 -14
- package/src/config/feature-flag-registry.json +5 -5
- package/src/config/loader.ts +19 -0
- package/src/config/schema.ts +2 -2
- package/src/config/user-reference.ts +47 -9
- package/src/daemon/handlers/config-channels.ts +11 -10
- package/src/daemon/handlers/contacts.ts +5 -1
- package/src/daemon/handlers/session-history.ts +398 -0
- package/src/daemon/handlers/session-user-message.ts +982 -0
- package/src/daemon/handlers/sessions.ts +9 -1338
- package/src/daemon/ipc-contract/sessions.ts +0 -6
- package/src/daemon/ipc-contract-inventory.json +0 -1
- package/src/daemon/lifecycle.ts +18 -55
- package/src/home-base/app-link-store.ts +0 -7
- package/src/memory/channel-delivery-store.ts +1 -0
- package/src/memory/conversation-attention-store.ts +1 -1
- package/src/memory/conversation-store.ts +0 -51
- package/src/memory/db-init.ts +9 -1
- package/src/memory/delivery-crud.ts +13 -0
- package/src/memory/invite-store.ts +71 -1
- package/src/memory/job-handlers/conflict.ts +24 -0
- package/src/memory/migrations/040-invite-code-hash-column.ts +16 -0
- package/src/memory/migrations/105-contacts-and-triage.ts +4 -7
- package/src/memory/migrations/134-contacts-notes-column.ts +50 -33
- package/src/memory/migrations/index.ts +1 -0
- package/src/memory/migrations/registry.ts +6 -0
- package/src/memory/recall-cache.ts +0 -5
- package/src/memory/schema/calls.ts +274 -0
- package/src/memory/schema/contacts.ts +127 -0
- package/src/memory/schema/conversations.ts +129 -0
- package/src/memory/schema/guardian.ts +172 -0
- package/src/memory/schema/index.ts +8 -0
- package/src/memory/schema/infrastructure.ts +205 -0
- package/src/memory/schema/memory-core.ts +196 -0
- package/src/memory/schema/notifications.ts +191 -0
- package/src/memory/schema/tasks.ts +78 -0
- package/src/memory/schema.ts +1 -1385
- package/src/memory/slack-thread-store.ts +0 -69
- package/src/notifications/decisions-store.ts +2 -105
- package/src/notifications/deliveries-store.ts +0 -11
- package/src/notifications/preferences-store.ts +1 -58
- package/src/permissions/checker.ts +6 -17
- package/src/providers/anthropic/client.ts +6 -2
- package/src/providers/gemini/client.ts +13 -2
- package/src/providers/managed-proxy/constants.ts +55 -0
- package/src/providers/managed-proxy/context.ts +77 -0
- package/src/providers/registry.ts +112 -0
- package/src/runtime/auth/__tests__/guard-tests.test.ts +52 -26
- package/src/runtime/auth/token-service.ts +50 -0
- package/src/runtime/channel-guardian-service.ts +1 -3
- package/src/runtime/channel-invite-transport.ts +121 -34
- package/src/runtime/channel-invite-transports/email.ts +50 -0
- package/src/runtime/channel-invite-transports/slack.ts +81 -0
- package/src/runtime/channel-invite-transports/sms.ts +70 -0
- package/src/runtime/channel-invite-transports/telegram.ts +29 -11
- package/src/runtime/channel-invite-transports/voice.ts +12 -12
- package/src/runtime/http-server.ts +83 -722
- package/src/runtime/http-types.ts +0 -16
- package/src/runtime/invite-redemption-service.ts +193 -0
- package/src/runtime/invite-redemption-templates.ts +6 -6
- package/src/runtime/invite-service.ts +81 -11
- package/src/runtime/middleware/auth.ts +0 -12
- package/src/runtime/routes/access-request-decision.ts +52 -6
- package/src/runtime/routes/app-routes.ts +33 -0
- package/src/runtime/routes/approval-routes.ts +32 -0
- package/src/runtime/routes/approval-strategies/guardian-callback-strategy.ts +6 -0
- package/src/runtime/routes/attachment-routes.ts +32 -0
- package/src/runtime/routes/brain-graph-routes.ts +27 -0
- package/src/runtime/routes/call-routes.ts +41 -0
- package/src/runtime/routes/channel-readiness-routes.ts +20 -0
- package/src/runtime/routes/channel-routes.ts +70 -0
- package/src/runtime/routes/contact-routes.ts +96 -6
- package/src/runtime/routes/conversation-attention-routes.ts +15 -0
- package/src/runtime/routes/conversation-routes.ts +190 -193
- package/src/runtime/routes/debug-routes.ts +15 -0
- package/src/runtime/routes/events-routes.ts +16 -0
- package/src/runtime/routes/global-search-routes.ts +15 -0
- package/src/runtime/routes/guardian-action-routes.ts +22 -0
- package/src/runtime/routes/guardian-bootstrap-routes.ts +21 -6
- package/src/runtime/routes/guardian-refresh-routes.ts +20 -0
- package/src/runtime/routes/identity-routes.ts +20 -0
- package/src/runtime/routes/inbound-message-handler.ts +9 -3
- package/src/runtime/routes/inbound-stages/acl-enforcement.ts +295 -10
- package/src/runtime/routes/inbound-stages/background-dispatch.ts +9 -42
- package/src/runtime/routes/inbound-stages/edit-intercept.ts +10 -0
- package/src/runtime/routes/integration-routes.ts +83 -0
- package/src/runtime/routes/invite-routes.ts +32 -0
- package/src/runtime/routes/migration-routes.ts +30 -0
- package/src/runtime/routes/pairing-routes.ts +18 -0
- package/src/runtime/routes/secret-routes.ts +20 -0
- package/src/runtime/routes/surface-action-routes.ts +26 -0
- package/src/runtime/routes/trust-rules-routes.ts +31 -0
- package/src/runtime/routes/twilio-routes.ts +79 -0
- package/src/schedule/recurrence-types.ts +1 -11
- package/src/tools/browser/browser-manager.ts +10 -1
- package/src/tools/browser/runtime-check.ts +3 -1
- package/src/tools/followups/followup_create.ts +9 -3
- package/src/tools/mcp/mcp-tool-factory.ts +0 -17
- package/src/tools/memory/definitions.ts +0 -6
- package/src/tools/network/script-proxy/session-manager.ts +38 -3
- package/src/tools/schedule/create.ts +1 -3
- package/src/tools/schedule/update.ts +9 -6
- package/src/tools/shared/shell-output.ts +7 -2
- package/src/twitter/session.ts +29 -77
- package/src/util/cookie-session.ts +114 -0
- package/src/util/platform.ts +0 -4
- package/src/workspace/git-service.ts +10 -4
- package/src/__tests__/conversation-routes.test.ts +0 -99
- package/src/__tests__/task-tools.test.ts +0 -685
- package/src/contacts/startup-migration.ts +0 -21
package/ARCHITECTURE.md
CHANGED
|
@@ -883,7 +883,7 @@ graph LR
|
|
|
883
883
|
C5["user_message<br/>text, attachments"]
|
|
884
884
|
C6["confirmation_response<br/>decision"]
|
|
885
885
|
C7["cancel / undo"]
|
|
886
|
-
C8["model_get / model_set
|
|
886
|
+
C8["model_get / model_set"]
|
|
887
887
|
C9["ping"]
|
|
888
888
|
C10["ipc_blob_probe<br/>probeId, nonceSha256"]
|
|
889
889
|
C11["work_items_list / work_item_get<br/>work_item_create / work_item_update<br/>work_item_complete / work_item_run_task<br/>(planned)"]
|
|
@@ -487,7 +487,7 @@ graph TB
|
|
|
487
487
|
|
|
488
488
|
### How it works
|
|
489
489
|
|
|
490
|
-
1. **Lazy initialization**: The git repository is created on first use, not at workspace creation. When `ensureInitialized()` is called, it checks for a `.git` directory. If absent, it runs `git init`, creates a `.gitignore` (excluding `data/`, `logs/`, `*.log`, `*.sock`, `*.pid`, `session-token
|
|
490
|
+
1. **Lazy initialization**: The git repository is created on first use, not at workspace creation. When `ensureInitialized()` is called, it checks for a `.git` directory. If absent, it runs `git init`, creates a `.gitignore` (excluding `data/`, `logs/`, `*.log`, `*.sock`, `*.pid`, `session-token`), sets the git identity to "Vellum Assistant", and creates an initial baseline commit capturing any pre-existing files. The baseline commit is intentional — it makes `git log`, `git diff`, and `git revert` work cleanly from the start. Both new and existing workspaces get the same treatment. For existing repos (e.g. created by older versions or external tools), `.gitignore` rules and git identity are set idempotently on each init, ensuring proper configuration regardless of how the repo was originally created.
|
|
491
491
|
|
|
492
492
|
2. **Turn-boundary commits**: After each conversation turn (user message + assistant response cycle), `session.ts` commits workspace changes via `commitTurnChanges(workspaceDir, sessionId, turnNumber)`. The commit runs in the `finally` block of `runAgentLoop`, guarded by a `turnStarted` flag that is set once the agent loop begins executing. This guarantees a commit attempt even when post-processing (e.g. `resolveAssistantAttachments`) throws, or when the user cancels mid-turn. The commit is raced against a configurable timeout (`workspaceGit.turnCommitMaxWaitMs`, default 4s) via `Promise.race`. If the commit exceeds the timeout, the turn proceeds immediately while the commit continues in the background. Note: the background commit is NOT awaited before the next turn starts, so brief cross-turn file attribution windows are possible but accepted as a tradeoff for responsiveness. Commit outcomes are logged with structured fields (`sessionId`, `turnNumber`, `filesChanged`, `durationMs`) for observability.
|
|
493
493
|
|
package/package.json
CHANGED
|
@@ -145,13 +145,6 @@ exports[`IPC message snapshots ClientMessage types usage_request serializes to e
|
|
|
145
145
|
}
|
|
146
146
|
`;
|
|
147
147
|
|
|
148
|
-
exports[`IPC message snapshots ClientMessage types sandbox_set serializes to expected JSON 1`] = `
|
|
149
|
-
{
|
|
150
|
-
"enabled": true,
|
|
151
|
-
"type": "sandbox_set",
|
|
152
|
-
}
|
|
153
|
-
`;
|
|
154
|
-
|
|
155
148
|
exports[`IPC message snapshots ClientMessage types cu_session_create serializes to expected JSON 1`] = `
|
|
156
149
|
{
|
|
157
150
|
"screenHeight": 1080,
|
|
@@ -30,7 +30,6 @@ mock.module("../util/platform.js", () => ({
|
|
|
30
30
|
getDbPath: () => join(testDir, "test.db"),
|
|
31
31
|
getLogPath: () => join(testDir, "test.log"),
|
|
32
32
|
ensureDataDir: () => {},
|
|
33
|
-
readHttpToken: () => "test-bearer-token",
|
|
34
33
|
}));
|
|
35
34
|
|
|
36
35
|
mock.module("../util/logger.js", () => ({
|
|
@@ -331,4 +330,87 @@ describe("access request notification delivery", () => {
|
|
|
331
330
|
expect(text).toContain("unable to deliver");
|
|
332
331
|
expect(text).toContain("try again");
|
|
333
332
|
});
|
|
333
|
+
|
|
334
|
+
test("slack approval notification is sent as DM using requesterExternalUserId", async () => {
|
|
335
|
+
await notifyRequesterOfApproval({
|
|
336
|
+
replyCallbackUrl:
|
|
337
|
+
"http://localhost:7830/deliver/slack?threadTs=1234.5678",
|
|
338
|
+
requesterChatId: "C12345-channel",
|
|
339
|
+
requesterExternalUserId: "U98765-user",
|
|
340
|
+
channel: "slack",
|
|
341
|
+
assistantId: "self",
|
|
342
|
+
bearerToken: "test-token",
|
|
343
|
+
});
|
|
344
|
+
|
|
345
|
+
expect(deliverReplyCalls.length).toBe(1);
|
|
346
|
+
const call = deliverReplyCalls[0];
|
|
347
|
+
// Should target the user ID (DM) not the channel
|
|
348
|
+
expect(call.payload.chatId).toBe("U98765-user");
|
|
349
|
+
// threadTs should be stripped — it belongs to the guardian's channel thread
|
|
350
|
+
expect(call.url).not.toContain("threadTs");
|
|
351
|
+
});
|
|
352
|
+
|
|
353
|
+
test("slack denial notification is sent as DM using requesterExternalUserId", async () => {
|
|
354
|
+
await notifyRequesterOfDenial({
|
|
355
|
+
replyCallbackUrl:
|
|
356
|
+
"http://localhost:7830/deliver/slack?threadTs=1234.5678",
|
|
357
|
+
requesterChatId: "C12345-channel",
|
|
358
|
+
requesterExternalUserId: "U98765-user",
|
|
359
|
+
channel: "slack",
|
|
360
|
+
assistantId: "self",
|
|
361
|
+
bearerToken: "test-token",
|
|
362
|
+
});
|
|
363
|
+
|
|
364
|
+
expect(deliverReplyCalls.length).toBe(1);
|
|
365
|
+
const call = deliverReplyCalls[0];
|
|
366
|
+
expect(call.payload.chatId).toBe("U98765-user");
|
|
367
|
+
expect(call.url).not.toContain("threadTs");
|
|
368
|
+
});
|
|
369
|
+
|
|
370
|
+
test("slack delivery failure notification is sent as DM using requesterExternalUserId", async () => {
|
|
371
|
+
await notifyRequesterOfDeliveryFailure({
|
|
372
|
+
replyCallbackUrl:
|
|
373
|
+
"http://localhost:7830/deliver/slack?threadTs=1234.5678",
|
|
374
|
+
requesterChatId: "C12345-channel",
|
|
375
|
+
requesterExternalUserId: "U98765-user",
|
|
376
|
+
channel: "slack",
|
|
377
|
+
assistantId: "self",
|
|
378
|
+
bearerToken: "test-token",
|
|
379
|
+
});
|
|
380
|
+
|
|
381
|
+
expect(deliverReplyCalls.length).toBe(1);
|
|
382
|
+
const call = deliverReplyCalls[0];
|
|
383
|
+
expect(call.payload.chatId).toBe("U98765-user");
|
|
384
|
+
expect(call.url).not.toContain("threadTs");
|
|
385
|
+
});
|
|
386
|
+
|
|
387
|
+
test("non-slack channels still use requesterChatId and preserve threadTs", async () => {
|
|
388
|
+
await notifyRequesterOfApproval({
|
|
389
|
+
replyCallbackUrl:
|
|
390
|
+
"http://localhost:7830/deliver/telegram?threadTs=1234.5678",
|
|
391
|
+
requesterChatId: "chat-123",
|
|
392
|
+
requesterExternalUserId: "user-456",
|
|
393
|
+
channel: "telegram",
|
|
394
|
+
assistantId: "self",
|
|
395
|
+
bearerToken: "test-token",
|
|
396
|
+
});
|
|
397
|
+
|
|
398
|
+
expect(deliverReplyCalls.length).toBe(1);
|
|
399
|
+
expect(deliverReplyCalls[0].payload.chatId).toBe("chat-123");
|
|
400
|
+
// threadTs should be preserved for non-slack channels
|
|
401
|
+
expect(deliverReplyCalls[0].url).toContain("threadTs=1234.5678");
|
|
402
|
+
});
|
|
403
|
+
|
|
404
|
+
test("slack without requesterExternalUserId falls back to requesterChatId", async () => {
|
|
405
|
+
await notifyRequesterOfApproval({
|
|
406
|
+
replyCallbackUrl: "http://localhost:7830/deliver/slack",
|
|
407
|
+
requesterChatId: "C12345-channel",
|
|
408
|
+
channel: "slack",
|
|
409
|
+
assistantId: "self",
|
|
410
|
+
bearerToken: "test-token",
|
|
411
|
+
});
|
|
412
|
+
|
|
413
|
+
expect(deliverReplyCalls.length).toBe(1);
|
|
414
|
+
expect(deliverReplyCalls[0].payload.chatId).toBe("C12345-channel");
|
|
415
|
+
});
|
|
334
416
|
});
|
|
@@ -39,7 +39,6 @@ mock.module("../config/env.js", () => ({
|
|
|
39
39
|
isHttpAuthDisabled: () => false,
|
|
40
40
|
getInternalGatewayTarget: () => "http://localhost:7822",
|
|
41
41
|
getGatewayBaseUrl: () => "http://localhost:7822",
|
|
42
|
-
getRuntimeProxyBearerToken: () => undefined,
|
|
43
42
|
getRuntimeGatewayOriginSecret: () => undefined,
|
|
44
43
|
isHttpAuthDisabledWithoutSafetyGate: () => false,
|
|
45
44
|
getEnableMonitoring: () => false,
|
|
@@ -8,6 +8,7 @@ import type { Message, ToolDefinition } from "../providers/types.js";
|
|
|
8
8
|
|
|
9
9
|
let lastStreamParams: Record<string, unknown> | null = null;
|
|
10
10
|
let _lastStreamOptions: Record<string, unknown> | null = null;
|
|
11
|
+
let lastConstructorArgs: Record<string, unknown> | null = null;
|
|
11
12
|
|
|
12
13
|
const fakeResponse = {
|
|
13
14
|
content: [{ type: "text", text: "Hello" }],
|
|
@@ -33,7 +34,9 @@ class FakeAPIError extends Error {
|
|
|
33
34
|
mock.module("@anthropic-ai/sdk", () => ({
|
|
34
35
|
default: class MockAnthropic {
|
|
35
36
|
static APIError = FakeAPIError;
|
|
36
|
-
constructor() {
|
|
37
|
+
constructor(args: Record<string, unknown>) {
|
|
38
|
+
lastConstructorArgs = { ...args };
|
|
39
|
+
}
|
|
37
40
|
messages = {
|
|
38
41
|
stream: (
|
|
39
42
|
params: Record<string, unknown>,
|
|
@@ -127,6 +130,7 @@ describe("AnthropicProvider — Cache-Control Characterization", () => {
|
|
|
127
130
|
beforeEach(() => {
|
|
128
131
|
lastStreamParams = null;
|
|
129
132
|
_lastStreamOptions = null;
|
|
133
|
+
lastConstructorArgs = null;
|
|
130
134
|
provider = new AnthropicProvider("sk-ant-test", "claude-sonnet-4-6");
|
|
131
135
|
});
|
|
132
136
|
|
|
@@ -935,3 +939,84 @@ describe("AnthropicProvider — Cache-Control Characterization", () => {
|
|
|
935
939
|
expect(userMsgs[2].content[1].cache_control).toEqual({ type: "ephemeral" });
|
|
936
940
|
});
|
|
937
941
|
});
|
|
942
|
+
|
|
943
|
+
// ---------------------------------------------------------------------------
|
|
944
|
+
// Tests — Managed Proxy Fallback
|
|
945
|
+
// ---------------------------------------------------------------------------
|
|
946
|
+
|
|
947
|
+
describe("AnthropicProvider — Managed Proxy Fallback", () => {
|
|
948
|
+
beforeEach(() => {
|
|
949
|
+
lastStreamParams = null;
|
|
950
|
+
_lastStreamOptions = null;
|
|
951
|
+
lastConstructorArgs = null;
|
|
952
|
+
});
|
|
953
|
+
|
|
954
|
+
test("constructor passes baseURL to Anthropic SDK when provided", () => {
|
|
955
|
+
new AnthropicProvider("managed-key", "claude-sonnet-4-6", {
|
|
956
|
+
baseURL: "https://platform.example.com/v1/runtime-proxy/anthropic",
|
|
957
|
+
});
|
|
958
|
+
|
|
959
|
+
expect(lastConstructorArgs).not.toBeNull();
|
|
960
|
+
expect(lastConstructorArgs!.apiKey).toBe("managed-key");
|
|
961
|
+
expect(lastConstructorArgs!.baseURL).toBe(
|
|
962
|
+
"https://platform.example.com/v1/runtime-proxy/anthropic",
|
|
963
|
+
);
|
|
964
|
+
});
|
|
965
|
+
|
|
966
|
+
test("constructor does not set baseURL when option is omitted", () => {
|
|
967
|
+
new AnthropicProvider("sk-ant-user-key", "claude-sonnet-4-6");
|
|
968
|
+
|
|
969
|
+
expect(lastConstructorArgs).not.toBeNull();
|
|
970
|
+
expect(lastConstructorArgs!.apiKey).toBe("sk-ant-user-key");
|
|
971
|
+
expect(lastConstructorArgs!.baseURL).toBeUndefined();
|
|
972
|
+
});
|
|
973
|
+
|
|
974
|
+
test("managed mode provider preserves tool-pairing behavior", async () => {
|
|
975
|
+
const provider = new AnthropicProvider("managed-key", "claude-sonnet-4-6", {
|
|
976
|
+
baseURL: "https://platform.example.com/v1/runtime-proxy/anthropic",
|
|
977
|
+
});
|
|
978
|
+
|
|
979
|
+
const messages: Message[] = [
|
|
980
|
+
userMsg("Read file"),
|
|
981
|
+
toolUseMsg("tu_1", "file_read"),
|
|
982
|
+
toolResultMsg("tu_1", "file contents"),
|
|
983
|
+
];
|
|
984
|
+
await provider.sendMessage(messages);
|
|
985
|
+
|
|
986
|
+
const sent = lastStreamParams!.messages as Array<{
|
|
987
|
+
role: string;
|
|
988
|
+
content: Array<{ type: string; tool_use_id?: string }>;
|
|
989
|
+
}>;
|
|
990
|
+
|
|
991
|
+
expect(sent).toHaveLength(3);
|
|
992
|
+
const toolResults = sent[2].content.filter((b) => b.type === "tool_result");
|
|
993
|
+
expect(toolResults).toHaveLength(1);
|
|
994
|
+
expect(toolResults[0].tool_use_id).toBe("tu_1");
|
|
995
|
+
});
|
|
996
|
+
|
|
997
|
+
test("managed mode provider preserves cache-control behavior", async () => {
|
|
998
|
+
const provider = new AnthropicProvider("managed-key", "claude-sonnet-4-6", {
|
|
999
|
+
baseURL: "https://platform.example.com/v1/runtime-proxy/anthropic",
|
|
1000
|
+
});
|
|
1001
|
+
|
|
1002
|
+
await provider.sendMessage(
|
|
1003
|
+
[userMsg("Hi")],
|
|
1004
|
+
sampleTools,
|
|
1005
|
+
"You are helpful.",
|
|
1006
|
+
);
|
|
1007
|
+
|
|
1008
|
+
// System prompt cache control
|
|
1009
|
+
const system = lastStreamParams!.system as Array<{
|
|
1010
|
+
cache_control?: { type: string };
|
|
1011
|
+
}>;
|
|
1012
|
+
expect(system[0].cache_control).toEqual({ type: "ephemeral" });
|
|
1013
|
+
|
|
1014
|
+
// Last tool cache control
|
|
1015
|
+
const tools = lastStreamParams!.tools as Array<{
|
|
1016
|
+
cache_control?: { type: string };
|
|
1017
|
+
}>;
|
|
1018
|
+
expect(tools[tools.length - 1].cache_control).toEqual({
|
|
1019
|
+
type: "ephemeral",
|
|
1020
|
+
});
|
|
1021
|
+
});
|
|
1022
|
+
});
|
|
@@ -57,7 +57,6 @@ mock.module("../config/env.js", () => ({
|
|
|
57
57
|
getGatewayPort: () => 7830,
|
|
58
58
|
getRuntimeHttpPort: () => 7821,
|
|
59
59
|
getRuntimeHttpHost: () => "127.0.0.1",
|
|
60
|
-
getRuntimeProxyBearerToken: () => undefined,
|
|
61
60
|
getRuntimeGatewayOriginSecret: () => undefined,
|
|
62
61
|
getIngressPublicBaseUrl: () => undefined,
|
|
63
62
|
setIngressPublicBaseUrl: () => {},
|
|
@@ -241,8 +241,8 @@ describe("buildSystemPrompt assistant feature flag filtering", () => {
|
|
|
241
241
|
|
|
242
242
|
const result = buildSystemPrompt();
|
|
243
243
|
|
|
244
|
-
// browser is declared in the registry with defaultEnabled:
|
|
245
|
-
expect(result).
|
|
244
|
+
// browser is declared in the registry with defaultEnabled: true
|
|
245
|
+
expect(result).toContain('id="browser"');
|
|
246
246
|
});
|
|
247
247
|
});
|
|
248
248
|
|
|
@@ -21,7 +21,6 @@ mock.module("../util/platform.js", () => ({
|
|
|
21
21
|
getDbPath: () => join(testDir, "test.db"),
|
|
22
22
|
getLogPath: () => join(testDir, "test.log"),
|
|
23
23
|
ensureDataDir: () => {},
|
|
24
|
-
readHttpToken: () => "test-bearer-token",
|
|
25
24
|
}));
|
|
26
25
|
|
|
27
26
|
mock.module("../util/logger.js", () => ({
|
|
@@ -17,19 +17,19 @@ mock.module("../tools/credentials/metadata-store.js", () => ({
|
|
|
17
17
|
|
|
18
18
|
import {
|
|
19
19
|
_resetRegistry,
|
|
20
|
-
type
|
|
20
|
+
type ChannelInviteAdapter,
|
|
21
|
+
getInviteAdapterRegistry,
|
|
21
22
|
getTransport,
|
|
22
23
|
registerTransport,
|
|
23
24
|
} from "../runtime/channel-invite-transport.js";
|
|
24
|
-
|
|
25
|
-
import { telegramInviteTransport } from "../runtime/channel-invite-transports/telegram.js";
|
|
25
|
+
import { telegramInviteAdapter } from "../runtime/channel-invite-transports/telegram.js";
|
|
26
26
|
|
|
27
27
|
describe("channel-invite-transport", () => {
|
|
28
28
|
beforeEach(() => {
|
|
29
29
|
_resetRegistry();
|
|
30
30
|
mockBotUsername = "test_invite_bot";
|
|
31
31
|
// Re-register after reset so Telegram tests work
|
|
32
|
-
registerTransport(
|
|
32
|
+
registerTransport(telegramInviteAdapter);
|
|
33
33
|
});
|
|
34
34
|
|
|
35
35
|
// =========================================================================
|
|
@@ -37,46 +37,58 @@ describe("channel-invite-transport", () => {
|
|
|
37
37
|
// =========================================================================
|
|
38
38
|
|
|
39
39
|
describe("registry", () => {
|
|
40
|
-
test("returns the Telegram
|
|
41
|
-
const
|
|
42
|
-
expect(
|
|
43
|
-
expect(
|
|
40
|
+
test("returns the Telegram adapter for telegram channel", () => {
|
|
41
|
+
const adapter = getTransport("telegram");
|
|
42
|
+
expect(adapter).toBeDefined();
|
|
43
|
+
expect(adapter!.channel).toBe("telegram");
|
|
44
44
|
});
|
|
45
45
|
|
|
46
46
|
test("returns undefined for an unregistered channel", () => {
|
|
47
|
-
const
|
|
48
|
-
expect(
|
|
47
|
+
const adapter = getTransport("sms");
|
|
48
|
+
expect(adapter).toBeUndefined();
|
|
49
49
|
});
|
|
50
50
|
|
|
51
|
-
test("overwrites a previously registered
|
|
52
|
-
const custom:
|
|
51
|
+
test("overwrites a previously registered adapter for the same channel", () => {
|
|
52
|
+
const custom: ChannelInviteAdapter = {
|
|
53
53
|
channel: "telegram",
|
|
54
|
-
|
|
54
|
+
buildShareLink: () => ({ url: "custom", displayText: "custom" }),
|
|
55
55
|
extractInboundToken: () => undefined,
|
|
56
56
|
};
|
|
57
57
|
registerTransport(custom);
|
|
58
|
-
const
|
|
58
|
+
const adapter = getTransport("telegram");
|
|
59
59
|
expect(
|
|
60
|
-
|
|
60
|
+
adapter!.buildShareLink!({
|
|
61
61
|
rawToken: "x",
|
|
62
62
|
sourceChannel: "telegram",
|
|
63
63
|
}).url,
|
|
64
64
|
).toBe("custom");
|
|
65
65
|
});
|
|
66
66
|
|
|
67
|
-
test("_resetRegistry clears all
|
|
67
|
+
test("_resetRegistry clears all adapters", () => {
|
|
68
68
|
_resetRegistry();
|
|
69
69
|
expect(getTransport("telegram")).toBeUndefined();
|
|
70
70
|
});
|
|
71
|
+
|
|
72
|
+
test("getInviteAdapterRegistry returns the singleton registry", () => {
|
|
73
|
+
const registry = getInviteAdapterRegistry();
|
|
74
|
+
expect(registry.get("telegram")).toBeDefined();
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
test("registry.getAll returns all registered adapters", () => {
|
|
78
|
+
const registry = getInviteAdapterRegistry();
|
|
79
|
+
const all = registry.getAll();
|
|
80
|
+
expect(all.length).toBeGreaterThanOrEqual(1);
|
|
81
|
+
expect(all.some((a) => a.channel === "telegram")).toBe(true);
|
|
82
|
+
});
|
|
71
83
|
});
|
|
72
84
|
|
|
73
85
|
// =========================================================================
|
|
74
|
-
// Telegram adapter —
|
|
86
|
+
// Telegram adapter — buildShareLink
|
|
75
87
|
// =========================================================================
|
|
76
88
|
|
|
77
|
-
describe("telegram
|
|
89
|
+
describe("telegram buildShareLink", () => {
|
|
78
90
|
test("produces a valid Telegram deep link", () => {
|
|
79
|
-
const result =
|
|
91
|
+
const result = telegramInviteAdapter.buildShareLink!({
|
|
80
92
|
rawToken: "abc123_test-token",
|
|
81
93
|
sourceChannel: "telegram",
|
|
82
94
|
});
|
|
@@ -90,11 +102,11 @@ describe("channel-invite-transport", () => {
|
|
|
90
102
|
});
|
|
91
103
|
|
|
92
104
|
test("deep link is deterministic for the same token", () => {
|
|
93
|
-
const a =
|
|
105
|
+
const a = telegramInviteAdapter.buildShareLink!({
|
|
94
106
|
rawToken: "tok1",
|
|
95
107
|
sourceChannel: "telegram",
|
|
96
108
|
});
|
|
97
|
-
const b =
|
|
109
|
+
const b = telegramInviteAdapter.buildShareLink!({
|
|
98
110
|
rawToken: "tok1",
|
|
99
111
|
sourceChannel: "telegram",
|
|
100
112
|
});
|
|
@@ -104,7 +116,7 @@ describe("channel-invite-transport", () => {
|
|
|
104
116
|
|
|
105
117
|
test("uses the configured bot username", () => {
|
|
106
118
|
mockBotUsername = "my_custom_bot";
|
|
107
|
-
const result =
|
|
119
|
+
const result = telegramInviteAdapter.buildShareLink!({
|
|
108
120
|
rawToken: "token",
|
|
109
121
|
sourceChannel: "telegram",
|
|
110
122
|
});
|
|
@@ -118,7 +130,7 @@ describe("channel-invite-transport", () => {
|
|
|
118
130
|
delete process.env.TELEGRAM_BOT_USERNAME;
|
|
119
131
|
try {
|
|
120
132
|
expect(() =>
|
|
121
|
-
|
|
133
|
+
telegramInviteAdapter.buildShareLink!({
|
|
122
134
|
rawToken: "token",
|
|
123
135
|
sourceChannel: "telegram",
|
|
124
136
|
}),
|
|
@@ -133,7 +145,7 @@ describe("channel-invite-transport", () => {
|
|
|
133
145
|
const prev = process.env.TELEGRAM_BOT_USERNAME;
|
|
134
146
|
process.env.TELEGRAM_BOT_USERNAME = "env_bot";
|
|
135
147
|
try {
|
|
136
|
-
const result =
|
|
148
|
+
const result = telegramInviteAdapter.buildShareLink!({
|
|
137
149
|
rawToken: "token",
|
|
138
150
|
sourceChannel: "telegram",
|
|
139
151
|
});
|
|
@@ -154,7 +166,7 @@ describe("channel-invite-transport", () => {
|
|
|
154
166
|
|
|
155
167
|
describe("telegram extractInboundToken", () => {
|
|
156
168
|
test("extracts token from structured commandIntent", () => {
|
|
157
|
-
const token =
|
|
169
|
+
const token = telegramInviteAdapter.extractInboundToken!({
|
|
158
170
|
commandIntent: { type: "start", payload: "iv_abc123" },
|
|
159
171
|
content: "/start iv_abc123",
|
|
160
172
|
});
|
|
@@ -162,7 +174,7 @@ describe("channel-invite-transport", () => {
|
|
|
162
174
|
});
|
|
163
175
|
|
|
164
176
|
test("extracts base64url token from commandIntent", () => {
|
|
165
|
-
const token =
|
|
177
|
+
const token = telegramInviteAdapter.extractInboundToken!({
|
|
166
178
|
commandIntent: { type: "start", payload: "iv_YWJjMTIz-_test" },
|
|
167
179
|
content: "/start iv_YWJjMTIz-_test",
|
|
168
180
|
});
|
|
@@ -170,7 +182,7 @@ describe("channel-invite-transport", () => {
|
|
|
170
182
|
});
|
|
171
183
|
|
|
172
184
|
test("returns undefined when commandIntent has no payload", () => {
|
|
173
|
-
const token =
|
|
185
|
+
const token = telegramInviteAdapter.extractInboundToken!({
|
|
174
186
|
commandIntent: { type: "start" },
|
|
175
187
|
content: "/start",
|
|
176
188
|
});
|
|
@@ -178,7 +190,7 @@ describe("channel-invite-transport", () => {
|
|
|
178
190
|
});
|
|
179
191
|
|
|
180
192
|
test("returns undefined when commandIntent payload has wrong prefix (gv_)", () => {
|
|
181
|
-
const token =
|
|
193
|
+
const token = telegramInviteAdapter.extractInboundToken!({
|
|
182
194
|
commandIntent: { type: "start", payload: "gv_abc123" },
|
|
183
195
|
content: "/start gv_abc123",
|
|
184
196
|
});
|
|
@@ -186,7 +198,7 @@ describe("channel-invite-transport", () => {
|
|
|
186
198
|
});
|
|
187
199
|
|
|
188
200
|
test("returns undefined when commandIntent payload has no prefix", () => {
|
|
189
|
-
const token =
|
|
201
|
+
const token = telegramInviteAdapter.extractInboundToken!({
|
|
190
202
|
commandIntent: { type: "start", payload: "abc123" },
|
|
191
203
|
content: "/start abc123",
|
|
192
204
|
});
|
|
@@ -194,7 +206,7 @@ describe("channel-invite-transport", () => {
|
|
|
194
206
|
});
|
|
195
207
|
|
|
196
208
|
test("returns undefined when commandIntent type is not start", () => {
|
|
197
|
-
const token =
|
|
209
|
+
const token = telegramInviteAdapter.extractInboundToken!({
|
|
198
210
|
commandIntent: { type: "help", payload: "iv_abc123" },
|
|
199
211
|
content: "/help iv_abc123",
|
|
200
212
|
});
|
|
@@ -202,7 +214,7 @@ describe("channel-invite-transport", () => {
|
|
|
202
214
|
});
|
|
203
215
|
|
|
204
216
|
test("returns undefined when commandIntent payload is iv_ with empty token", () => {
|
|
205
|
-
const token =
|
|
217
|
+
const token = telegramInviteAdapter.extractInboundToken!({
|
|
206
218
|
commandIntent: { type: "start", payload: "iv_" },
|
|
207
219
|
content: "/start iv_",
|
|
208
220
|
});
|
|
@@ -210,7 +222,7 @@ describe("channel-invite-transport", () => {
|
|
|
210
222
|
});
|
|
211
223
|
|
|
212
224
|
test("returns undefined when commandIntent payload is iv_ with whitespace-only token", () => {
|
|
213
|
-
const token =
|
|
225
|
+
const token = telegramInviteAdapter.extractInboundToken!({
|
|
214
226
|
commandIntent: { type: "start", payload: "iv_ " },
|
|
215
227
|
content: "/start iv_ ",
|
|
216
228
|
});
|
|
@@ -218,49 +230,49 @@ describe("channel-invite-transport", () => {
|
|
|
218
230
|
});
|
|
219
231
|
|
|
220
232
|
test("extracts token from raw content fallback", () => {
|
|
221
|
-
const token =
|
|
233
|
+
const token = telegramInviteAdapter.extractInboundToken!({
|
|
222
234
|
content: "/start iv_abc123",
|
|
223
235
|
});
|
|
224
236
|
expect(token).toBe("abc123");
|
|
225
237
|
});
|
|
226
238
|
|
|
227
239
|
test("extracts token from raw content with extra whitespace", () => {
|
|
228
|
-
const token =
|
|
240
|
+
const token = telegramInviteAdapter.extractInboundToken!({
|
|
229
241
|
content: "/start iv_token123",
|
|
230
242
|
});
|
|
231
243
|
expect(token).toBe("token123");
|
|
232
244
|
});
|
|
233
245
|
|
|
234
246
|
test("returns undefined for empty content", () => {
|
|
235
|
-
const token =
|
|
247
|
+
const token = telegramInviteAdapter.extractInboundToken!({
|
|
236
248
|
content: "",
|
|
237
249
|
});
|
|
238
250
|
expect(token).toBeUndefined();
|
|
239
251
|
});
|
|
240
252
|
|
|
241
253
|
test("returns undefined for content without /start", () => {
|
|
242
|
-
const token =
|
|
254
|
+
const token = telegramInviteAdapter.extractInboundToken!({
|
|
243
255
|
content: "hello world",
|
|
244
256
|
});
|
|
245
257
|
expect(token).toBeUndefined();
|
|
246
258
|
});
|
|
247
259
|
|
|
248
260
|
test("returns undefined for /start without iv_ prefix in content", () => {
|
|
249
|
-
const token =
|
|
261
|
+
const token = telegramInviteAdapter.extractInboundToken!({
|
|
250
262
|
content: "/start gv_abc123",
|
|
251
263
|
});
|
|
252
264
|
expect(token).toBeUndefined();
|
|
253
265
|
});
|
|
254
266
|
|
|
255
267
|
test("returns undefined for malformed /start with only iv_ in content", () => {
|
|
256
|
-
const token =
|
|
268
|
+
const token = telegramInviteAdapter.extractInboundToken!({
|
|
257
269
|
content: "/start iv_",
|
|
258
270
|
});
|
|
259
271
|
expect(token).toBeUndefined();
|
|
260
272
|
});
|
|
261
273
|
|
|
262
274
|
test("prefers commandIntent over raw content", () => {
|
|
263
|
-
const token =
|
|
275
|
+
const token = telegramInviteAdapter.extractInboundToken!({
|
|
264
276
|
commandIntent: { type: "start", payload: "iv_from_intent" },
|
|
265
277
|
content: "/start iv_from_content",
|
|
266
278
|
});
|
|
@@ -269,7 +281,7 @@ describe("channel-invite-transport", () => {
|
|
|
269
281
|
|
|
270
282
|
test("returns undefined when commandIntent rejects, even if content has token", () => {
|
|
271
283
|
// commandIntent present but payload has wrong prefix
|
|
272
|
-
const token =
|
|
284
|
+
const token = telegramInviteAdapter.extractInboundToken!({
|
|
273
285
|
commandIntent: { type: "start", payload: "gv_abc123" },
|
|
274
286
|
content: "/start iv_valid_token",
|
|
275
287
|
});
|