@vellumai/assistant 0.4.48 → 0.4.49
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 +2 -2
- package/README.md +2 -23
- package/docs/architecture/integrations.md +45 -41
- package/docs/architecture/keychain-broker.md +3 -3
- package/docs/runbook-trusted-contacts.md +3 -8
- package/hook-templates/debug-prompt-logger/hook.json +1 -1
- package/hook-templates/debug-prompt-logger/run.sh +1 -3
- package/package.json +1 -1
- package/src/__tests__/actor-token-service.test.ts +0 -1
- package/src/__tests__/anthropic-provider.test.ts +156 -0
- package/src/__tests__/approval-cascade.test.ts +810 -0
- package/src/__tests__/approval-primitive.test.ts +0 -1
- package/src/__tests__/approval-routes-http.test.ts +2 -0
- package/src/__tests__/assistant-attachments.test.ts +12 -34
- package/src/__tests__/assistant-feature-flag-guardrails.test.ts +76 -0
- package/src/__tests__/assistant-feature-flags-integration.test.ts +0 -1
- package/src/__tests__/browser-skill-baseline-tool-payload.test.ts +2 -2
- package/src/__tests__/channel-guardian.test.ts +0 -2
- package/src/__tests__/channel-readiness-routes.test.ts +15 -6
- package/src/__tests__/channel-readiness-service.test.ts +10 -9
- package/src/__tests__/checker.test.ts +9 -29
- package/src/__tests__/computer-use-skill-manifest-regression.test.ts +1 -1
- package/src/__tests__/computer-use-tools.test.ts +2 -19
- package/src/__tests__/config-watcher.test.ts +0 -1
- package/src/__tests__/confirmation-request-guardian-bridge.test.ts +0 -1
- package/src/__tests__/context-image-dimensions.test.ts +332 -0
- package/src/__tests__/context-token-estimator.test.ts +196 -13
- package/src/__tests__/conversation-attention-store.test.ts +0 -1
- package/src/__tests__/conversation-attention-telegram.test.ts +0 -1
- package/src/__tests__/conversation-routes-guardian-reply.test.ts +144 -0
- package/src/__tests__/conversation-routes-slash-commands.test.ts +1 -0
- package/src/__tests__/credential-metadata-store.test.ts +64 -73
- package/src/__tests__/credential-security-invariants.test.ts +13 -7
- package/src/__tests__/credential-vault-unit.test.ts +280 -49
- package/src/__tests__/credential-vault.test.ts +138 -16
- package/src/__tests__/credentials-cli.test.ts +71 -0
- package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +0 -1
- package/src/__tests__/ephemeral-permissions.test.ts +3 -3
- package/src/__tests__/gateway-only-guard.test.ts +0 -1
- package/src/__tests__/guardian-action-grant-mint-consume.test.ts +0 -1
- package/src/__tests__/guardian-decision-primitive-canonical.test.ts +0 -1
- package/src/__tests__/guardian-routing-invariants.test.ts +0 -1
- package/src/__tests__/guardian-verification-voice-binding.test.ts +0 -1
- package/src/__tests__/handlers-user-message-approval-consumption.test.ts +0 -39
- package/src/__tests__/heartbeat-service.test.ts +0 -1
- package/src/__tests__/host-cu-proxy.test.ts +629 -0
- package/src/__tests__/host-shell-tool.test.ts +27 -15
- package/src/__tests__/http-user-message-parity.test.ts +1 -0
- package/src/__tests__/ingress-url-consistency.test.ts +14 -21
- package/src/__tests__/integration-status.test.ts +32 -51
- package/src/__tests__/intent-routing.test.ts +0 -1
- package/src/__tests__/invite-routes-http.test.ts +10 -9
- package/src/__tests__/keychain-broker-client.test.ts +11 -43
- package/src/__tests__/notification-routing-intent.test.ts +0 -1
- package/src/__tests__/oauth-cli.test.ts +373 -14
- package/src/__tests__/oauth-provider-profiles.test.ts +9 -9
- package/src/__tests__/oauth-scope-policy.test.ts +4 -6
- package/src/__tests__/oauth-store.test.ts +756 -0
- package/src/__tests__/onboarding-starter-tasks.test.ts +0 -1
- package/src/__tests__/provider-error-scenarios.test.ts +0 -1
- package/src/__tests__/provider-streaming.benchmark.test.ts +0 -1
- package/src/__tests__/public-ingress-urls.test.ts +15 -21
- package/src/__tests__/recording-handler.test.ts +3 -4
- package/src/__tests__/registry.test.ts +2 -2
- package/src/__tests__/runtime-events-sse.test.ts +55 -7
- package/src/__tests__/schedule-store.test.ts +0 -1
- package/src/__tests__/scheduler-recurrence.test.ts +0 -1
- package/src/__tests__/scoped-approval-grants.test.ts +0 -1
- package/src/__tests__/scoped-grant-security-matrix.test.ts +0 -1
- package/src/__tests__/secret-ingress-handler.test.ts +0 -1
- package/src/__tests__/send-endpoint-busy.test.ts +21 -6
- package/src/__tests__/sequence-store.test.ts +0 -1
- package/src/__tests__/session-init.benchmark.test.ts +4 -5
- package/src/__tests__/skill-include-graph.test.ts +66 -0
- package/src/__tests__/skill-load-feature-flag.test.ts +0 -1
- package/src/__tests__/skill-load-tool.test.ts +149 -1
- package/src/__tests__/skill-projection-feature-flag.test.ts +0 -1
- package/src/__tests__/skills-uninstall.test.ts +1 -1
- package/src/__tests__/skills.test.ts +3 -3
- package/src/__tests__/slack-channel-config.test.ts +67 -3
- package/src/__tests__/slack-share-routes.test.ts +17 -19
- package/src/__tests__/system-prompt.test.ts +0 -1
- package/src/__tests__/telegram-invite-adapter.test.ts +18 -22
- package/src/__tests__/terminal-tools.test.ts +4 -3
- package/src/__tests__/test-support/computer-use-skill-harness.ts +3 -2
- package/src/__tests__/tool-approval-handler.test.ts +0 -1
- package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +0 -1
- package/src/__tests__/tool-executor-lifecycle-events.test.ts +0 -1
- package/src/__tests__/tool-executor-shell-integration.test.ts +0 -1
- package/src/__tests__/tool-executor.test.ts +0 -1
- package/src/__tests__/tool-grant-request-escalation.test.ts +0 -1
- package/src/__tests__/trust-store-pattern-matches.test.ts +29 -0
- package/src/__tests__/trust-store.test.ts +1 -22
- package/src/__tests__/trusted-contact-approval-notifier.test.ts +0 -1
- package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +0 -1
- package/src/__tests__/twilio-routes.test.ts +0 -16
- package/src/__tests__/verification-control-plane-policy.test.ts +0 -1
- package/src/__tests__/voice-scoped-grant-consumer.test.ts +0 -1
- package/src/agent/ax-tree-compaction.test.ts +235 -0
- package/src/agent/loop.ts +76 -130
- package/src/calls/call-domain.ts +1 -6
- package/src/calls/relay-server.ts +9 -13
- package/src/calls/twilio-config.ts +2 -7
- package/src/calls/twilio-routes.ts +1 -2
- package/src/calls/voice-ingress-preflight.ts +1 -1
- package/src/cli/commands/browser-relay.ts +18 -12
- package/src/cli/commands/completions.ts +0 -3
- package/src/cli/commands/credentials.ts +101 -15
- package/src/cli/commands/oauth/apps.ts +255 -0
- package/src/cli/commands/oauth/connections.ts +299 -0
- package/src/cli/commands/oauth/index.ts +52 -0
- package/src/cli/commands/oauth/providers.ts +242 -0
- package/src/cli/commands/skills.ts +4 -338
- package/src/cli/program.ts +1 -5
- package/src/cli/reference.ts +1 -3
- package/src/config/assistant-feature-flags.ts +0 -3
- package/src/config/bundled-skills/_shared/CLI_RETRIEVAL_PATTERN.md +1 -1
- package/src/config/bundled-skills/computer-use/SKILL.md +3 -6
- package/src/config/bundled-skills/computer-use/TOOLS.json +22 -4
- package/src/config/bundled-skills/google-calendar/calendar-client.ts +21 -16
- package/src/config/bundled-skills/messaging/tools/shared.ts +1 -4
- package/src/config/bundled-skills/settings/SKILL.md +1 -1
- package/src/config/bundled-skills/settings/TOOLS.json +2 -8
- package/src/config/bundled-skills/settings/tools/voice-config-update.ts +5 -33
- package/src/config/env-registry.ts +14 -83
- package/src/config/env.ts +11 -50
- package/src/config/feature-flag-registry.json +16 -16
- package/src/config/loader.ts +0 -6
- package/src/config/schema.ts +3 -1
- package/src/config/skills.ts +21 -2
- package/src/context/image-dimensions.ts +229 -0
- package/src/context/token-estimator.ts +75 -12
- package/src/context/window-manager.ts +49 -10
- package/src/daemon/assistant-attachments.ts +1 -13
- package/src/daemon/handlers/config-ingress.ts +8 -33
- package/src/daemon/handlers/config-slack-channel.ts +49 -46
- package/src/daemon/handlers/config-telegram.ts +32 -16
- package/src/daemon/handlers/sessions.ts +10 -24
- package/src/daemon/handlers/shared.ts +0 -130
- package/src/daemon/host-cu-proxy.ts +401 -0
- package/src/daemon/lifecycle.ts +36 -68
- package/src/daemon/message-protocol.ts +3 -0
- package/src/daemon/message-types/computer-use.ts +2 -119
- package/src/daemon/message-types/host-cu.ts +19 -0
- package/src/daemon/message-types/messages.ts +3 -0
- package/src/daemon/server.ts +14 -21
- package/src/daemon/session-agent-loop-handlers.ts +2 -0
- package/src/daemon/session-attachments.ts +1 -2
- package/src/daemon/session-slash.ts +1 -1
- package/src/daemon/session-surfaces.ts +40 -28
- package/src/daemon/session-tool-setup.ts +2 -9
- package/src/daemon/session.ts +138 -15
- package/src/daemon/tool-side-effects.ts +2 -8
- package/src/daemon/watch-handler.ts +2 -2
- package/src/events/tool-metrics-listener.ts +2 -2
- package/src/hooks/manager.ts +1 -4
- package/src/inbound/public-ingress-urls.ts +7 -7
- package/src/logfire.ts +16 -5
- package/src/memory/conversation-key-store.ts +21 -0
- package/src/memory/db-init.ts +4 -0
- package/src/memory/migrations/149-oauth-tables.ts +60 -0
- package/src/memory/migrations/index.ts +1 -0
- package/src/memory/schema/index.ts +1 -0
- package/src/memory/schema/oauth.ts +65 -0
- package/src/messaging/provider.ts +4 -4
- package/src/messaging/providers/gmail/client.ts +82 -2
- package/src/messaging/providers/gmail/people-client.ts +10 -10
- package/src/messaging/providers/telegram-bot/adapter.ts +17 -17
- package/src/messaging/providers/whatsapp/adapter.ts +11 -8
- package/src/messaging/registry.ts +2 -32
- package/src/notifications/copy-composer.ts +0 -5
- package/src/notifications/signal.ts +4 -5
- package/src/oauth/byo-connection.test.ts +126 -25
- package/src/oauth/byo-connection.ts +22 -6
- package/src/oauth/connect-orchestrator.ts +113 -57
- package/src/oauth/connect-types.ts +17 -23
- package/src/oauth/connection-resolver.ts +35 -11
- package/src/oauth/connection.ts +1 -1
- package/src/oauth/manual-token-connection.ts +104 -0
- package/src/oauth/oauth-store.ts +496 -0
- package/src/oauth/platform-connection.test.ts +29 -0
- package/src/oauth/platform-connection.ts +6 -5
- package/src/oauth/provider-behaviors.ts +124 -0
- package/src/oauth/scope-policy.ts +9 -2
- package/src/oauth/seed-providers.ts +161 -0
- package/src/oauth/token-persistence.ts +74 -78
- package/src/permissions/checker.ts +3 -3
- package/src/permissions/defaults.ts +0 -1
- package/src/permissions/prompter.ts +10 -1
- package/src/permissions/trust-store.ts +13 -0
- package/src/prompts/__tests__/build-cli-reference-section.test.ts +3 -1
- package/src/prompts/system-prompt.ts +28 -40
- package/src/providers/anthropic/client.ts +133 -24
- package/src/providers/retry.ts +1 -27
- package/src/runtime/auth/route-policy.ts +0 -3
- package/src/runtime/channel-reply-delivery.ts +0 -40
- package/src/runtime/gateway-client.ts +0 -7
- package/src/runtime/http-server.ts +8 -6
- package/src/runtime/http-types.ts +2 -2
- package/src/runtime/middleware/twilio-validation.ts +1 -11
- package/src/runtime/pending-interactions.ts +14 -12
- package/src/runtime/routes/channel-delivery-routes.ts +0 -1
- package/src/runtime/routes/conversation-routes.ts +73 -19
- package/src/runtime/routes/events-routes.ts +21 -11
- package/src/runtime/routes/host-cu-routes.ts +97 -0
- package/src/runtime/routes/inbound-stages/background-dispatch.ts +12 -111
- package/src/runtime/routes/integrations/slack/share.ts +6 -7
- package/src/runtime/routes/log-export-routes.ts +126 -8
- package/src/runtime/routes/settings-routes.ts +55 -48
- package/src/runtime/routes/surface-action-routes.ts +1 -1
- package/src/runtime/routes/watch-routes.ts +128 -0
- package/src/schedule/integration-status.ts +10 -9
- package/src/security/credential-key.ts +0 -156
- package/src/security/keychain-broker-client.ts +5 -6
- package/src/security/oauth2.ts +1 -1
- package/src/security/token-manager.ts +119 -46
- package/src/skills/catalog-install.ts +358 -0
- package/src/skills/include-graph.ts +32 -0
- package/src/telegram/bot-username.ts +2 -3
- package/src/tools/browser/network-recorder.ts +1 -1
- package/src/tools/browser/network-recording-types.ts +1 -1
- package/src/tools/computer-use/definitions.ts +46 -11
- package/src/tools/computer-use/registry.ts +4 -5
- package/src/tools/credentials/broker.ts +1 -2
- package/src/tools/credentials/metadata-store.ts +17 -121
- package/src/tools/credentials/vault.ts +94 -167
- package/src/tools/registry.ts +2 -7
- package/src/tools/skills/load.ts +62 -3
- package/src/tools/watch/watch-state.ts +0 -12
- package/src/util/logger.ts +7 -41
- package/src/util/platform.ts +9 -28
- package/src/watcher/providers/google-calendar.ts +2 -1
- package/src/__tests__/computer-use-session-compaction.test.ts +0 -143
- package/src/__tests__/computer-use-session-lifecycle.test.ts +0 -322
- package/src/__tests__/computer-use-session-working-dir.test.ts +0 -166
- package/src/__tests__/computer-use-skill-baseline.test.ts +0 -78
- package/src/__tests__/computer-use-skill-endstate.test.ts +0 -105
- package/src/__tests__/computer-use-skill-lifecycle-cleanup.test.ts +0 -249
- package/src/__tests__/ride-shotgun-handler.test.ts +0 -452
- package/src/cli/commands/dev.ts +0 -129
- package/src/cli/commands/map.ts +0 -391
- package/src/cli/commands/oauth.ts +0 -77
- package/src/config/bundled-skills/computer-use/tools/computer-use-request-control.ts +0 -16
- package/src/daemon/computer-use-session.ts +0 -1026
- package/src/daemon/ride-shotgun-handler.ts +0 -569
- package/src/oauth/provider-base-urls.ts +0 -21
- package/src/oauth/provider-profiles.ts +0 -192
- package/src/prompts/computer-use-prompt.ts +0 -98
- package/src/runtime/routes/computer-use-routes.ts +0 -641
- package/src/runtime/telegram-streaming-delivery.test.ts +0 -729
- package/src/runtime/telegram-streaming-delivery.ts +0 -393
- package/src/tools/computer-use/request-computer-control.ts +0 -56
|
@@ -171,6 +171,7 @@ describe("handleSendMessage canonical guardian reply interception", () => {
|
|
|
171
171
|
hasPendingConfirmation: () => false,
|
|
172
172
|
setHostBashProxy: () => {},
|
|
173
173
|
setHostFileProxy: () => {},
|
|
174
|
+
setHostCuProxy: () => {},
|
|
174
175
|
} as unknown as import("../daemon/session.js").Session;
|
|
175
176
|
|
|
176
177
|
const req = new Request("http://localhost/v1/messages", {
|
|
@@ -246,6 +247,7 @@ describe("handleSendMessage canonical guardian reply interception", () => {
|
|
|
246
247
|
hasPendingConfirmation: () => false,
|
|
247
248
|
setHostBashProxy: () => {},
|
|
248
249
|
setHostFileProxy: () => {},
|
|
250
|
+
setHostCuProxy: () => {},
|
|
249
251
|
} as unknown as import("../daemon/session.js").Session;
|
|
250
252
|
|
|
251
253
|
const req = new Request("http://localhost/v1/messages", {
|
|
@@ -317,6 +319,7 @@ describe("handleSendMessage canonical guardian reply interception", () => {
|
|
|
317
319
|
requestId === "tool-approval-live",
|
|
318
320
|
setHostBashProxy: () => {},
|
|
319
321
|
setHostFileProxy: () => {},
|
|
322
|
+
setHostCuProxy: () => {},
|
|
320
323
|
} as unknown as import("../daemon/session.js").Session;
|
|
321
324
|
|
|
322
325
|
const req = new Request("http://localhost/v1/messages", {
|
|
@@ -392,6 +395,7 @@ describe("handleSendMessage canonical guardian reply interception", () => {
|
|
|
392
395
|
hasPendingConfirmation: (id: string) => id === "tool-req-code-1",
|
|
393
396
|
setHostBashProxy: () => {},
|
|
394
397
|
setHostFileProxy: () => {},
|
|
398
|
+
setHostCuProxy: () => {},
|
|
395
399
|
} as unknown as import("../daemon/session.js").Session;
|
|
396
400
|
|
|
397
401
|
const req = new Request("http://localhost/v1/messages", {
|
|
@@ -463,6 +467,7 @@ describe("handleSendMessage canonical guardian reply interception", () => {
|
|
|
463
467
|
hasPendingConfirmation: (id: string) => id === "pending-reject-1",
|
|
464
468
|
setHostBashProxy: () => {},
|
|
465
469
|
setHostFileProxy: () => {},
|
|
470
|
+
setHostCuProxy: () => {},
|
|
466
471
|
} as unknown as import("../daemon/session.js").Session;
|
|
467
472
|
|
|
468
473
|
const req = new Request("http://localhost/v1/messages", {
|
|
@@ -528,6 +533,7 @@ describe("handleSendMessage canonical guardian reply interception", () => {
|
|
|
528
533
|
hasPendingConfirmation: (id: string) => id === "pending-1",
|
|
529
534
|
setHostBashProxy: () => {},
|
|
530
535
|
setHostFileProxy: () => {},
|
|
536
|
+
setHostCuProxy: () => {},
|
|
531
537
|
} as unknown as import("../daemon/session.js").Session;
|
|
532
538
|
|
|
533
539
|
const req = new Request("http://localhost/v1/messages", {
|
|
@@ -559,4 +565,142 @@ describe("handleSendMessage canonical guardian reply interception", () => {
|
|
|
559
565
|
expect(persistUserMessage).toHaveBeenCalledTimes(1);
|
|
560
566
|
expect(runAgentLoop).toHaveBeenCalledTimes(1);
|
|
561
567
|
});
|
|
568
|
+
|
|
569
|
+
test("desktop sessions do not pass approvalConversationGenerator to routeGuardianReply", async () => {
|
|
570
|
+
listPendingByDestinationMock.mockReturnValue([
|
|
571
|
+
{ id: "pending-1", kind: "access_request" },
|
|
572
|
+
]);
|
|
573
|
+
listCanonicalMock.mockReturnValue([]);
|
|
574
|
+
routeGuardianReplyMock.mockResolvedValue({
|
|
575
|
+
consumed: false,
|
|
576
|
+
decisionApplied: false,
|
|
577
|
+
type: "not_consumed",
|
|
578
|
+
});
|
|
579
|
+
|
|
580
|
+
const mockGenerator = mock(async () => ({}));
|
|
581
|
+
const persistUserMessage = mock(async () => "persisted-user-id");
|
|
582
|
+
const runAgentLoop = mock(async () => undefined);
|
|
583
|
+
const session = {
|
|
584
|
+
setTrustContext: () => {},
|
|
585
|
+
updateClient: () => {},
|
|
586
|
+
emitConfirmationStateChanged: () => {},
|
|
587
|
+
emitActivityState: () => {},
|
|
588
|
+
setTurnChannelContext: () => {},
|
|
589
|
+
setTurnInterfaceContext: () => {},
|
|
590
|
+
ensureActorScopedHistory: async () => {},
|
|
591
|
+
usageStats: { inputTokens: 0, outputTokens: 0, estimatedCost: 0 },
|
|
592
|
+
isProcessing: () => false,
|
|
593
|
+
hasAnyPendingConfirmation: () => false,
|
|
594
|
+
denyAllPendingConfirmations: () => {},
|
|
595
|
+
enqueueMessage: () => ({ queued: true, requestId: "queued-id" }),
|
|
596
|
+
persistUserMessage,
|
|
597
|
+
runAgentLoop,
|
|
598
|
+
getMessages: () => [] as unknown[],
|
|
599
|
+
assistantId: "self",
|
|
600
|
+
trustContext: undefined,
|
|
601
|
+
hasPendingConfirmation: () => false,
|
|
602
|
+
setHostBashProxy: () => {},
|
|
603
|
+
setHostFileProxy: () => {},
|
|
604
|
+
setHostCuProxy: () => {},
|
|
605
|
+
} as unknown as import("../daemon/session.js").Session;
|
|
606
|
+
|
|
607
|
+
const req = new Request("http://localhost/v1/messages", {
|
|
608
|
+
method: "POST",
|
|
609
|
+
headers: { "Content-Type": "application/json" },
|
|
610
|
+
body: JSON.stringify({
|
|
611
|
+
conversationKey: "guardian-thread-key",
|
|
612
|
+
content: "no sorry, beats 0 and 3 should be new threads",
|
|
613
|
+
sourceChannel: "vellum",
|
|
614
|
+
interface: "macos",
|
|
615
|
+
}),
|
|
616
|
+
});
|
|
617
|
+
|
|
618
|
+
await handleSendMessage(
|
|
619
|
+
req,
|
|
620
|
+
{
|
|
621
|
+
sendMessageDeps: {
|
|
622
|
+
getOrCreateSession: async () => session,
|
|
623
|
+
assistantEventHub: { publish: async () => {} } as any,
|
|
624
|
+
resolveAttachments: () => [],
|
|
625
|
+
},
|
|
626
|
+
approvalConversationGenerator: mockGenerator as any,
|
|
627
|
+
},
|
|
628
|
+
testAuthContext,
|
|
629
|
+
);
|
|
630
|
+
|
|
631
|
+
expect(routeGuardianReplyMock).toHaveBeenCalledTimes(1);
|
|
632
|
+
const routerCall = (routeGuardianReplyMock as any).mock
|
|
633
|
+
.calls[0][0] as Record<string, unknown>;
|
|
634
|
+
// Desktop (vellum) should suppress the NL engine
|
|
635
|
+
expect(routerCall.approvalConversationGenerator).toBeUndefined();
|
|
636
|
+
});
|
|
637
|
+
|
|
638
|
+
test("channel sessions pass approvalConversationGenerator to routeGuardianReply", async () => {
|
|
639
|
+
listPendingByDestinationMock.mockReturnValue([
|
|
640
|
+
{ id: "pending-1", kind: "access_request" },
|
|
641
|
+
]);
|
|
642
|
+
listCanonicalMock.mockReturnValue([]);
|
|
643
|
+
routeGuardianReplyMock.mockResolvedValue({
|
|
644
|
+
consumed: false,
|
|
645
|
+
decisionApplied: false,
|
|
646
|
+
type: "not_consumed",
|
|
647
|
+
});
|
|
648
|
+
|
|
649
|
+
const mockGenerator = mock(async () => ({}));
|
|
650
|
+
const persistUserMessage = mock(async () => "persisted-user-id");
|
|
651
|
+
const runAgentLoop = mock(async () => undefined);
|
|
652
|
+
const session = {
|
|
653
|
+
setTrustContext: () => {},
|
|
654
|
+
updateClient: () => {},
|
|
655
|
+
emitConfirmationStateChanged: () => {},
|
|
656
|
+
emitActivityState: () => {},
|
|
657
|
+
setTurnChannelContext: () => {},
|
|
658
|
+
setTurnInterfaceContext: () => {},
|
|
659
|
+
ensureActorScopedHistory: async () => {},
|
|
660
|
+
usageStats: { inputTokens: 0, outputTokens: 0, estimatedCost: 0 },
|
|
661
|
+
isProcessing: () => false,
|
|
662
|
+
hasAnyPendingConfirmation: () => false,
|
|
663
|
+
denyAllPendingConfirmations: () => {},
|
|
664
|
+
enqueueMessage: () => ({ queued: true, requestId: "queued-id" }),
|
|
665
|
+
persistUserMessage,
|
|
666
|
+
runAgentLoop,
|
|
667
|
+
getMessages: () => [] as unknown[],
|
|
668
|
+
assistantId: "self",
|
|
669
|
+
trustContext: undefined,
|
|
670
|
+
hasPendingConfirmation: () => false,
|
|
671
|
+
setHostBashProxy: () => {},
|
|
672
|
+
setHostFileProxy: () => {},
|
|
673
|
+
setHostCuProxy: () => {},
|
|
674
|
+
} as unknown as import("../daemon/session.js").Session;
|
|
675
|
+
|
|
676
|
+
const req = new Request("http://localhost/v1/messages", {
|
|
677
|
+
method: "POST",
|
|
678
|
+
headers: { "Content-Type": "application/json" },
|
|
679
|
+
body: JSON.stringify({
|
|
680
|
+
conversationKey: "guardian-thread-key",
|
|
681
|
+
content: "no sorry, beats 0 and 3 should be new threads",
|
|
682
|
+
sourceChannel: "telegram",
|
|
683
|
+
interface: "telegram",
|
|
684
|
+
}),
|
|
685
|
+
});
|
|
686
|
+
|
|
687
|
+
await handleSendMessage(
|
|
688
|
+
req,
|
|
689
|
+
{
|
|
690
|
+
sendMessageDeps: {
|
|
691
|
+
getOrCreateSession: async () => session,
|
|
692
|
+
assistantEventHub: { publish: async () => {} } as any,
|
|
693
|
+
resolveAttachments: () => [],
|
|
694
|
+
},
|
|
695
|
+
approvalConversationGenerator: mockGenerator as any,
|
|
696
|
+
},
|
|
697
|
+
testAuthContext,
|
|
698
|
+
);
|
|
699
|
+
|
|
700
|
+
expect(routeGuardianReplyMock).toHaveBeenCalledTimes(1);
|
|
701
|
+
const routerCall = (routeGuardianReplyMock as any).mock
|
|
702
|
+
.calls[0][0] as Record<string, unknown>;
|
|
703
|
+
// Channel sessions should receive the NL engine
|
|
704
|
+
expect(routerCall.approvalConversationGenerator).toBe(mockGenerator);
|
|
705
|
+
});
|
|
562
706
|
});
|
|
@@ -294,49 +294,35 @@ describe("credential metadata store", () => {
|
|
|
294
294
|
});
|
|
295
295
|
});
|
|
296
296
|
|
|
297
|
-
// ──
|
|
297
|
+
// ── v5 Schema: OAuth fields removed ─────────────────────────────────
|
|
298
298
|
|
|
299
|
-
describe("
|
|
300
|
-
test("
|
|
299
|
+
describe("v5 schema — OAuth fields removed from CredentialMetadata", () => {
|
|
300
|
+
test("CredentialMetadata does not include hasRefreshToken", () => {
|
|
301
301
|
const record = upsertCredentialMetadata("github", "access_token", {
|
|
302
|
-
|
|
302
|
+
allowedTools: ["api_request"],
|
|
303
303
|
});
|
|
304
|
-
|
|
304
|
+
// hasRefreshToken was removed in v5 — field should not exist
|
|
305
|
+
expect("hasRefreshToken" in record).toBe(false);
|
|
305
306
|
});
|
|
306
307
|
|
|
307
|
-
test("
|
|
308
|
-
const record = upsertCredentialMetadata("github", "access_token"
|
|
309
|
-
|
|
310
|
-
});
|
|
311
|
-
expect(record.hasRefreshToken).toBe(false);
|
|
308
|
+
test("CredentialMetadata does not include oauth2TokenUrl", () => {
|
|
309
|
+
const record = upsertCredentialMetadata("github", "access_token");
|
|
310
|
+
expect("oauth2TokenUrl" in record).toBe(false);
|
|
312
311
|
});
|
|
313
312
|
|
|
314
|
-
test("
|
|
313
|
+
test("CredentialMetadata does not include oauth2ClientId", () => {
|
|
315
314
|
const record = upsertCredentialMetadata("github", "access_token");
|
|
316
|
-
expect(record
|
|
315
|
+
expect("oauth2ClientId" in record).toBe(false);
|
|
317
316
|
});
|
|
318
317
|
|
|
319
|
-
test("
|
|
320
|
-
upsertCredentialMetadata("github", "access_token"
|
|
321
|
-
|
|
322
|
-
});
|
|
323
|
-
const updated = upsertCredentialMetadata("github", "access_token", {
|
|
324
|
-
hasRefreshToken: true,
|
|
325
|
-
});
|
|
326
|
-
expect(updated.hasRefreshToken).toBe(true);
|
|
318
|
+
test("CredentialMetadata does not include expiresAt", () => {
|
|
319
|
+
const record = upsertCredentialMetadata("github", "access_token");
|
|
320
|
+
expect("expiresAt" in record).toBe(false);
|
|
327
321
|
});
|
|
328
322
|
|
|
329
|
-
test("
|
|
330
|
-
upsertCredentialMetadata("github", "access_token"
|
|
331
|
-
|
|
332
|
-
allowedTools: ["api_request"],
|
|
333
|
-
});
|
|
334
|
-
|
|
335
|
-
// Re-read from disk
|
|
336
|
-
const loaded = getCredentialMetadata("github", "access_token");
|
|
337
|
-
expect(loaded).toBeDefined();
|
|
338
|
-
expect(loaded!.hasRefreshToken).toBe(true);
|
|
339
|
-
expect(loaded!.allowedTools).toEqual(["api_request"]);
|
|
323
|
+
test("CredentialMetadata does not include grantedScopes", () => {
|
|
324
|
+
const record = upsertCredentialMetadata("github", "access_token");
|
|
325
|
+
expect("grantedScopes" in record).toBe(false);
|
|
340
326
|
});
|
|
341
327
|
});
|
|
342
328
|
|
|
@@ -417,7 +403,7 @@ describe("credential metadata store", () => {
|
|
|
417
403
|
expect(record!.credentialId).toBe("cred-stable-id");
|
|
418
404
|
});
|
|
419
405
|
|
|
420
|
-
test("v2 file is migrated to
|
|
406
|
+
test("v2 file is migrated to v5 (strips oauth2ClientSecret and OAuth fields)", () => {
|
|
421
407
|
const v2Data = {
|
|
422
408
|
version: 2,
|
|
423
409
|
credentials: [
|
|
@@ -449,16 +435,16 @@ describe("credential metadata store", () => {
|
|
|
449
435
|
expect(record!.alias).toBe("fal-primary");
|
|
450
436
|
expect(record!.injectionTemplates).toHaveLength(1);
|
|
451
437
|
expect(record!.injectionTemplates![0].hostPattern).toBe("*.fal.ai");
|
|
452
|
-
// oauth2ClientSecret must be stripped by
|
|
438
|
+
// oauth2ClientSecret must be stripped by migration
|
|
453
439
|
expect("oauth2ClientSecret" in record!).toBe(false);
|
|
454
440
|
|
|
455
|
-
// On-disk file should be upgraded to
|
|
441
|
+
// On-disk file should be upgraded to v5
|
|
456
442
|
const raw = JSON.parse(readFileSync(META_PATH, "utf-8"));
|
|
457
|
-
expect(raw.version).toBe(
|
|
443
|
+
expect(raw.version).toBe(5);
|
|
458
444
|
expect(raw.credentials[0]).not.toHaveProperty("oauth2ClientSecret");
|
|
459
445
|
});
|
|
460
446
|
|
|
461
|
-
test("v3 file is migrated to
|
|
447
|
+
test("v3 file is migrated to v5 (removes ghost refresh_token records)", () => {
|
|
462
448
|
const v3Data = {
|
|
463
449
|
version: 3,
|
|
464
450
|
credentials: [
|
|
@@ -488,11 +474,12 @@ describe("credential metadata store", () => {
|
|
|
488
474
|
// Ghost refresh_token record removed
|
|
489
475
|
expect(records).toHaveLength(1);
|
|
490
476
|
expect(records[0].field).toBe("access_token");
|
|
491
|
-
|
|
477
|
+
// hasRefreshToken is stripped in v5 migration (OAuth fields moved to SQLite)
|
|
478
|
+
expect("hasRefreshToken" in records[0]).toBe(false);
|
|
492
479
|
|
|
493
|
-
// On-disk file should be upgraded to
|
|
480
|
+
// On-disk file should be upgraded to v5
|
|
494
481
|
const raw = JSON.parse(readFileSync(META_PATH, "utf-8"));
|
|
495
|
-
expect(raw.version).toBe(
|
|
482
|
+
expect(raw.version).toBe(5);
|
|
496
483
|
expect(raw.credentials).toHaveLength(1);
|
|
497
484
|
expect(raw.credentials[0].field).toBe("access_token");
|
|
498
485
|
});
|
|
@@ -528,11 +515,12 @@ describe("credential metadata store", () => {
|
|
|
528
515
|
expect(record!.alias).toBe("fal-primary");
|
|
529
516
|
expect(record!.injectionTemplates).toHaveLength(1);
|
|
530
517
|
expect(record!.injectionTemplates![0].hostPattern).toBe("*.fal.ai");
|
|
531
|
-
|
|
518
|
+
// hasRefreshToken is stripped in v5 migration
|
|
519
|
+
expect("hasRefreshToken" in record!).toBe(false);
|
|
532
520
|
|
|
533
|
-
// On-disk file should be upgraded to
|
|
521
|
+
// On-disk file should be upgraded to v5
|
|
534
522
|
const raw = JSON.parse(readFileSync(META_PATH, "utf-8"));
|
|
535
|
-
expect(raw.version).toBe(
|
|
523
|
+
expect(raw.version).toBe(5);
|
|
536
524
|
});
|
|
537
525
|
|
|
538
526
|
test("v3 migration handles multiple services with ghost records", () => {
|
|
@@ -592,16 +580,16 @@ describe("credential metadata store", () => {
|
|
|
592
580
|
// refresh_token records removed, only access_token records remain
|
|
593
581
|
expect(records).toHaveLength(3);
|
|
594
582
|
expect(records.every((r) => r.field !== "refresh_token")).toBe(true);
|
|
595
|
-
//
|
|
583
|
+
// hasRefreshToken is stripped in v5 migration — none should have it
|
|
596
584
|
const github = records.find((r) => r.service === "github");
|
|
597
585
|
const slack = records.find((r) => r.service === "slack");
|
|
598
586
|
const stripe = records.find((r) => r.service === "stripe");
|
|
599
|
-
expect(github
|
|
600
|
-
expect(slack
|
|
601
|
-
expect(stripe
|
|
587
|
+
expect("hasRefreshToken" in github!).toBe(false);
|
|
588
|
+
expect("hasRefreshToken" in slack!).toBe(false);
|
|
589
|
+
expect("hasRefreshToken" in stripe!).toBe(false);
|
|
602
590
|
});
|
|
603
591
|
|
|
604
|
-
test("v4 file is
|
|
592
|
+
test("v4 file is migrated to v5 (strips hasRefreshToken and OAuth fields)", () => {
|
|
605
593
|
const v4Data = {
|
|
606
594
|
version: 4,
|
|
607
595
|
credentials: [
|
|
@@ -623,10 +611,15 @@ describe("credential metadata store", () => {
|
|
|
623
611
|
const record = getCredentialMetadata("fal-ai", "api_key");
|
|
624
612
|
expect(record).toBeDefined();
|
|
625
613
|
expect(record!.alias).toBe("fal-primary");
|
|
626
|
-
|
|
614
|
+
// hasRefreshToken is stripped in v5 migration
|
|
615
|
+
expect("hasRefreshToken" in record!).toBe(false);
|
|
616
|
+
|
|
617
|
+
// On-disk file should be upgraded to v5
|
|
618
|
+
const raw = JSON.parse(readFileSync(META_PATH, "utf-8"));
|
|
619
|
+
expect(raw.version).toBe(5);
|
|
627
620
|
});
|
|
628
621
|
|
|
629
|
-
test("future version (
|
|
622
|
+
test("future version (v6+) returns unknown version and refuses writes", () => {
|
|
630
623
|
const futureData = {
|
|
631
624
|
version: 99,
|
|
632
625
|
credentials: [],
|
|
@@ -661,7 +654,7 @@ describe("credential metadata store", () => {
|
|
|
661
654
|
},
|
|
662
655
|
);
|
|
663
656
|
|
|
664
|
-
test("upsert on migrated v1 file saves as
|
|
657
|
+
test("upsert on migrated v1 file saves as v5", () => {
|
|
665
658
|
const v1Data = {
|
|
666
659
|
version: 1,
|
|
667
660
|
credentials: [
|
|
@@ -681,13 +674,13 @@ describe("credential metadata store", () => {
|
|
|
681
674
|
// Upsert triggers load (migration) + save (at current version)
|
|
682
675
|
upsertCredentialMetadata("github", "token", { alias: "gh-main" });
|
|
683
676
|
|
|
684
|
-
// Verify on-disk file is now
|
|
677
|
+
// Verify on-disk file is now v5
|
|
685
678
|
const raw = JSON.parse(readFileSync(META_PATH, "utf-8"));
|
|
686
|
-
expect(raw.version).toBe(
|
|
679
|
+
expect(raw.version).toBe(5);
|
|
687
680
|
expect(raw.credentials[0].alias).toBe("gh-main");
|
|
688
681
|
});
|
|
689
682
|
|
|
690
|
-
test("v1 load auto-persists as
|
|
683
|
+
test("v1 load auto-persists as v5 on disk without requiring a write", () => {
|
|
691
684
|
const v1Data = {
|
|
692
685
|
version: 1,
|
|
693
686
|
credentials: [
|
|
@@ -704,15 +697,15 @@ describe("credential metadata store", () => {
|
|
|
704
697
|
};
|
|
705
698
|
writeFileSync(META_PATH, JSON.stringify(v1Data, null, 2), "utf-8");
|
|
706
699
|
|
|
707
|
-
// A read-only operation should still persist the
|
|
700
|
+
// A read-only operation should still persist the v5 upgrade
|
|
708
701
|
listCredentialMetadata();
|
|
709
702
|
|
|
710
703
|
const raw = JSON.parse(readFileSync(META_PATH, "utf-8"));
|
|
711
|
-
expect(raw.version).toBe(
|
|
704
|
+
expect(raw.version).toBe(5);
|
|
712
705
|
expect(raw.credentials[0].credentialId).toBe("cred-autopersist");
|
|
713
706
|
});
|
|
714
707
|
|
|
715
|
-
test("v1 file with multiple credentials migrates all records", () => {
|
|
708
|
+
test("v1 file with multiple credentials migrates all records (strips OAuth fields)", () => {
|
|
716
709
|
const v1Data = {
|
|
717
710
|
version: 1,
|
|
718
711
|
credentials: [
|
|
@@ -761,13 +754,11 @@ describe("credential metadata store", () => {
|
|
|
761
754
|
expect(r.injectionTemplates).toBeUndefined();
|
|
762
755
|
}
|
|
763
756
|
|
|
764
|
-
//
|
|
765
|
-
expect(records[0]
|
|
766
|
-
expect(records[1]
|
|
767
|
-
expect(records[2]
|
|
768
|
-
|
|
769
|
-
);
|
|
770
|
-
expect(records[2].oauth2ClientId).toBe("ca_test123");
|
|
757
|
+
// OAuth-specific fields are stripped by v5 migration
|
|
758
|
+
expect("grantedScopes" in records[0]).toBe(false);
|
|
759
|
+
expect("expiresAt" in records[1]).toBe(false);
|
|
760
|
+
expect("oauth2TokenUrl" in records[2]).toBe(false);
|
|
761
|
+
expect("oauth2ClientId" in records[2]).toBe(false);
|
|
771
762
|
});
|
|
772
763
|
});
|
|
773
764
|
|
|
@@ -808,20 +799,20 @@ describe("credential metadata store", () => {
|
|
|
808
799
|
test("file with non-array credentials field is treated as empty list", () => {
|
|
809
800
|
writeFileSync(
|
|
810
801
|
META_PATH,
|
|
811
|
-
JSON.stringify({ version:
|
|
802
|
+
JSON.stringify({ version: 5, credentials: "not-an-array" }),
|
|
812
803
|
"utf-8",
|
|
813
804
|
);
|
|
814
805
|
expect(listCredentialMetadata()).toEqual([]);
|
|
815
806
|
});
|
|
816
807
|
|
|
817
808
|
test("file with missing credentials field is treated as empty list", () => {
|
|
818
|
-
writeFileSync(META_PATH, JSON.stringify({ version:
|
|
809
|
+
writeFileSync(META_PATH, JSON.stringify({ version: 5 }), "utf-8");
|
|
819
810
|
expect(listCredentialMetadata()).toEqual([]);
|
|
820
811
|
});
|
|
821
812
|
|
|
822
813
|
test("malformed records within credentials array are filtered out", () => {
|
|
823
814
|
const data = {
|
|
824
|
-
version:
|
|
815
|
+
version: 5,
|
|
825
816
|
credentials: [
|
|
826
817
|
// Valid record
|
|
827
818
|
{
|
|
@@ -931,7 +922,7 @@ describe("credential metadata store", () => {
|
|
|
931
922
|
|
|
932
923
|
const raw = readFileSync(META_PATH, "utf-8");
|
|
933
924
|
const parsed = JSON.parse(raw);
|
|
934
|
-
expect(parsed.version).toBe(
|
|
925
|
+
expect(parsed.version).toBe(5);
|
|
935
926
|
expect(parsed.credentials).toHaveLength(1);
|
|
936
927
|
expect(parsed.credentials[0].service).toBe("slack");
|
|
937
928
|
});
|
|
@@ -939,23 +930,23 @@ describe("credential metadata store", () => {
|
|
|
939
930
|
test("file written by saveFile has version field", () => {
|
|
940
931
|
upsertCredentialMetadata("test", "key");
|
|
941
932
|
const raw = JSON.parse(readFileSync(META_PATH, "utf-8"));
|
|
942
|
-
expect(raw.version).toBe(
|
|
933
|
+
expect(raw.version).toBe(5);
|
|
943
934
|
});
|
|
944
935
|
});
|
|
945
936
|
|
|
946
937
|
// ── Empty credential lists ────────────────────────────────────────
|
|
947
938
|
|
|
948
939
|
describe("empty credential lists", () => {
|
|
949
|
-
test("empty
|
|
940
|
+
test("empty v5 file returns empty array", () => {
|
|
950
941
|
writeFileSync(
|
|
951
942
|
META_PATH,
|
|
952
|
-
JSON.stringify({ version:
|
|
943
|
+
JSON.stringify({ version: 5, credentials: [] }, null, 2),
|
|
953
944
|
"utf-8",
|
|
954
945
|
);
|
|
955
946
|
expect(listCredentialMetadata()).toEqual([]);
|
|
956
947
|
});
|
|
957
948
|
|
|
958
|
-
test("empty v1 file is migrated to
|
|
949
|
+
test("empty v1 file is migrated to v5 with empty credentials", () => {
|
|
959
950
|
writeFileSync(
|
|
960
951
|
META_PATH,
|
|
961
952
|
JSON.stringify({ version: 1, credentials: [] }, null, 2),
|
|
@@ -963,9 +954,9 @@ describe("credential metadata store", () => {
|
|
|
963
954
|
);
|
|
964
955
|
expect(listCredentialMetadata()).toEqual([]);
|
|
965
956
|
|
|
966
|
-
// Should be persisted as
|
|
957
|
+
// Should be persisted as v5
|
|
967
958
|
const raw = JSON.parse(readFileSync(META_PATH, "utf-8"));
|
|
968
|
-
expect(raw.version).toBe(
|
|
959
|
+
expect(raw.version).toBe(5);
|
|
969
960
|
expect(raw.credentials).toEqual([]);
|
|
970
961
|
});
|
|
971
962
|
|
|
@@ -229,10 +229,13 @@ describe("Invariant 2: no generic plaintext secret read API", () => {
|
|
|
229
229
|
"runtime/routes/integrations/slack/share.ts", // Slack share routes credential lookup
|
|
230
230
|
"mcp/client.ts", // MCP client cached-token lookup
|
|
231
231
|
"oauth/token-persistence.ts", // OAuth token persistence (set/delete tokens)
|
|
232
|
+
"oauth/connection-resolver.ts", // resolve OAuthConnection from oauth-store (access_token lookup)
|
|
232
233
|
"runtime/routes/secret-routes.ts", // HTTP secret management routes (set/delete secrets)
|
|
233
|
-
"daemon/ride-shotgun-handler.ts", // learn session cookie persistence
|
|
234
234
|
"daemon/session-messaging.ts", // credential storage during session messaging
|
|
235
235
|
"runtime/routes/settings-routes.ts", // settings routes OAuth credential lookup (client_secret)
|
|
236
|
+
"oauth/oauth-store.ts", // OAuth provider disconnect (delete stored tokens)
|
|
237
|
+
"cli/commands/oauth/connections.ts", // CLI OAuth connection delete (legacy credential cleanup)
|
|
238
|
+
"oauth/manual-token-connection.ts", // manual-token provider backfill (keychain credential existence check)
|
|
236
239
|
]);
|
|
237
240
|
|
|
238
241
|
const thisDir = dirname(fileURLToPath(import.meta.url));
|
|
@@ -499,16 +502,17 @@ describe("Invariant 6: oauth2ClientSecret not in metadata, only in secure store"
|
|
|
499
502
|
rmSync(TEST_DIR, { recursive: true, force: true });
|
|
500
503
|
});
|
|
501
504
|
|
|
502
|
-
test("upsertCredentialMetadata does not accept oauth2ClientSecret", () => {
|
|
505
|
+
test("upsertCredentialMetadata does not accept oauth2ClientSecret or other OAuth fields", () => {
|
|
503
506
|
const record = upsertCredentialMetadata(
|
|
504
507
|
"integration:gmail",
|
|
505
508
|
"access_token",
|
|
506
509
|
{
|
|
507
|
-
|
|
508
|
-
oauth2ClientId: "test-client-id",
|
|
510
|
+
allowedTools: ["api_request"],
|
|
509
511
|
},
|
|
510
512
|
);
|
|
511
513
|
expect("oauth2ClientSecret" in record).toBe(false);
|
|
514
|
+
expect("oauth2TokenUrl" in record).toBe(false);
|
|
515
|
+
expect("oauth2ClientId" in record).toBe(false);
|
|
512
516
|
});
|
|
513
517
|
|
|
514
518
|
test("client secret is read from secure store, not metadata", () => {
|
|
@@ -517,13 +521,15 @@ describe("Invariant 6: oauth2ClientSecret not in metadata, only in secure store"
|
|
|
517
521
|
"my-secret",
|
|
518
522
|
);
|
|
519
523
|
upsertCredentialMetadata("integration:gmail", "access_token", {
|
|
520
|
-
|
|
521
|
-
oauth2ClientId: "test-client-id",
|
|
524
|
+
allowedTools: ["api_request"],
|
|
522
525
|
});
|
|
523
526
|
|
|
524
527
|
const meta = getCredentialMetadata("integration:gmail", "access_token");
|
|
525
528
|
expect(meta).toBeDefined();
|
|
526
529
|
expect("oauth2ClientSecret" in meta!).toBe(false);
|
|
530
|
+
// OAuth-specific fields are no longer in metadata (v5)
|
|
531
|
+
expect("oauth2TokenUrl" in meta!).toBe(false);
|
|
532
|
+
expect("oauth2ClientId" in meta!).toBe(false);
|
|
527
533
|
|
|
528
534
|
// Secret is in secure store
|
|
529
535
|
expect(
|
|
@@ -564,7 +570,7 @@ describe("Invariant 6: oauth2ClientSecret not in metadata, only in secure store"
|
|
|
564
570
|
readFileSync(join(TEST_DIR, "metadata.json"), "utf-8"),
|
|
565
571
|
);
|
|
566
572
|
expect(raw.credentials[0]).not.toHaveProperty("oauth2ClientSecret");
|
|
567
|
-
expect(raw.version).toBe(
|
|
573
|
+
expect(raw.version).toBe(5);
|
|
568
574
|
});
|
|
569
575
|
});
|
|
570
576
|
|