@vellumai/assistant 0.5.11 → 0.5.13
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/Dockerfile +42 -9
- package/docs/architecture/integrations.md +34 -32
- package/node_modules/@vellumai/ces-contracts/src/__tests__/grants.test.ts +7 -7
- package/node_modules/@vellumai/ces-contracts/src/handles.ts +5 -4
- package/node_modules/@vellumai/ces-contracts/src/index.ts +7 -0
- package/node_modules/@vellumai/ces-contracts/src/rpc.ts +5 -0
- package/node_modules/@vellumai/credential-storage/src/index.ts +1 -1
- package/openapi.yaml +87 -9
- package/package.json +1 -1
- package/src/__tests__/catalog-cache.test.ts +164 -0
- package/src/__tests__/catalog-search.test.ts +61 -0
- package/src/__tests__/cli-command-risk-guard.test.ts +181 -6
- package/src/__tests__/conversation-delete-schedule-cleanup.test.ts +396 -0
- package/src/__tests__/conversation-error.test.ts +3 -2
- package/src/__tests__/credential-security-invariants.test.ts +9 -15
- package/src/__tests__/credential-vault-unit.test.ts +32 -34
- package/src/__tests__/credential-vault.test.ts +25 -33
- package/src/__tests__/credentials-cli.test.ts +3 -3
- package/src/__tests__/daemon-credential-client.test.ts +2 -2
- package/src/__tests__/first-greeting.test.ts +7 -0
- package/src/__tests__/host-bash-proxy.test.ts +79 -0
- package/src/__tests__/host-cu-proxy.test.ts +90 -0
- package/src/__tests__/host-file-proxy.test.ts +89 -0
- package/src/__tests__/integration-status.test.ts +5 -5
- package/src/__tests__/list-messages-attachments.test.ts +171 -0
- package/src/__tests__/mcp-abort-signal.test.ts +205 -0
- package/src/__tests__/messaging-send-tool.test.ts +5 -5
- package/src/__tests__/navigate-settings-tab.test.ts +6 -2
- package/src/__tests__/notification-telegram-adapter.test.ts +125 -0
- package/src/__tests__/oauth-cli.test.ts +126 -119
- package/src/__tests__/oauth-provider-profiles.test.ts +55 -20
- package/src/__tests__/oauth-scope-policy.test.ts +4 -6
- package/src/__tests__/onboarding-template-contract.test.ts +2 -2
- package/src/__tests__/platform.test.ts +3 -168
- package/src/__tests__/secret-routes-managed-proxy.test.ts +78 -0
- package/src/__tests__/secure-keys-managed-failover.test.ts +73 -0
- package/src/__tests__/skill-feature-flags.test.ts +8 -0
- package/src/__tests__/skill-secret-handling-guard.test.ts +212 -0
- package/src/__tests__/skills-uninstall.test.ts +2 -2
- package/src/__tests__/slack-messaging-token-resolution.test.ts +22 -24
- package/src/__tests__/slack-share-routes.test.ts +5 -5
- package/src/__tests__/system-prompt.test.ts +39 -0
- package/src/__tests__/token-estimator-accuracy.benchmark.test.ts +1 -1
- package/src/__tests__/workspace-migration-backfill-installation-id.test.ts +5 -4
- package/src/cli/AGENTS.md +47 -7
- package/src/cli/commands/browser-relay.ts +2 -17
- package/src/cli/commands/contacts.ts +6 -4
- package/src/cli/commands/conversations.ts +13 -1
- package/src/cli/commands/credential-execution.ts +16 -1
- package/src/cli/commands/credentials.ts +2 -8
- package/src/cli/commands/oauth/__tests__/connect.test.ts +29 -108
- package/src/cli/commands/oauth/__tests__/disconnect.test.ts +13 -87
- package/src/cli/commands/oauth/__tests__/mode.test.ts +22 -69
- package/src/cli/commands/oauth/__tests__/ping.test.ts +20 -79
- package/src/cli/commands/oauth/__tests__/providers-delete.test.ts +574 -0
- package/src/cli/commands/oauth/__tests__/providers-update.test.ts +416 -0
- package/src/cli/commands/oauth/__tests__/status.test.ts +12 -40
- package/src/cli/commands/oauth/__tests__/token.test.ts +3 -50
- package/src/cli/commands/oauth/apps.ts +63 -44
- package/src/cli/commands/oauth/connect.ts +187 -155
- package/src/cli/commands/oauth/disconnect.ts +27 -75
- package/src/cli/commands/oauth/index.ts +36 -46
- package/src/cli/commands/oauth/mode.ts +22 -34
- package/src/cli/commands/oauth/ping.ts +19 -45
- package/src/cli/commands/oauth/providers.ts +569 -62
- package/src/cli/commands/oauth/request.ts +36 -48
- package/src/cli/commands/oauth/shared.ts +1 -19
- package/src/cli/commands/oauth/status.ts +14 -25
- package/src/cli/commands/oauth/token.ts +25 -34
- package/src/cli/commands/platform/__tests__/connect.test.ts +224 -0
- package/src/cli/commands/platform/__tests__/disconnect.test.ts +237 -0
- package/src/cli/commands/platform/__tests__/status.test.ts +246 -0
- package/src/cli/commands/platform/connect.ts +104 -0
- package/src/cli/commands/platform/disconnect.ts +118 -0
- package/src/cli/commands/{platform.ts → platform/index.ts} +108 -38
- package/src/cli/commands/sequence.ts +5 -4
- package/src/cli/commands/shotgun.ts +16 -0
- package/src/cli/commands/skills.ts +173 -41
- package/src/cli/commands/usage.ts +5 -11
- package/src/cli/lib/daemon-credential-client.ts +22 -38
- package/src/cli/program.ts +1 -1
- package/src/config/assistant-feature-flags.ts +3 -7
- package/src/config/bundled-skills/contacts/tools/google-contacts.ts +1 -1
- package/src/config/bundled-skills/conversations/SKILL.md +20 -0
- package/src/config/bundled-skills/conversations/TOOLS.json +23 -0
- package/src/config/bundled-skills/conversations/tools/rename-conversation.ts +66 -0
- package/src/config/bundled-skills/gmail/SKILL.md +13 -13
- package/src/config/bundled-skills/gmail/tools/gmail-archive.ts +3 -3
- package/src/config/bundled-skills/gmail/tools/gmail-attachments.ts +2 -2
- package/src/config/bundled-skills/gmail/tools/gmail-draft.ts +1 -1
- package/src/config/bundled-skills/gmail/tools/gmail-filters.ts +1 -1
- package/src/config/bundled-skills/gmail/tools/gmail-follow-up.ts +1 -1
- package/src/config/bundled-skills/gmail/tools/gmail-forward.ts +1 -1
- package/src/config/bundled-skills/gmail/tools/gmail-label.ts +2 -2
- package/src/config/bundled-skills/gmail/tools/gmail-outreach-scan.ts +1 -1
- package/src/config/bundled-skills/gmail/tools/gmail-send-draft.ts +1 -1
- package/src/config/bundled-skills/gmail/tools/gmail-sender-digest.ts +1 -1
- package/src/config/bundled-skills/gmail/tools/gmail-trash.ts +1 -1
- package/src/config/bundled-skills/gmail/tools/gmail-unsubscribe.ts +1 -1
- package/src/config/bundled-skills/gmail/tools/gmail-vacation.ts +1 -1
- package/src/config/bundled-skills/google-calendar/SKILL.md +10 -4
- package/src/config/bundled-skills/google-calendar/tools/shared.ts +1 -1
- package/src/config/bundled-skills/messaging/SKILL.md +7 -7
- package/src/config/bundled-skills/messaging/tools/messaging-send.ts +5 -2
- package/src/config/bundled-skills/messaging/tools/shared.ts +5 -6
- package/src/config/bundled-skills/settings/TOOLS.json +5 -3
- package/src/config/bundled-skills/settings/tools/navigate-settings-tab.ts +4 -2
- package/src/config/bundled-tool-registry.ts +5 -0
- package/src/config/feature-flag-registry.json +2 -2
- package/src/credential-execution/client.ts +15 -3
- package/src/daemon/conversation-agent-loop.ts +2 -0
- package/src/daemon/conversation-error.ts +36 -6
- package/src/daemon/conversation-messaging.ts +9 -0
- package/src/daemon/conversation-runtime-assembly.ts +33 -0
- package/src/daemon/conversation-surfaces.ts +120 -14
- package/src/daemon/conversation.ts +5 -0
- package/src/daemon/first-greeting.ts +6 -1
- package/src/daemon/handlers/skills.ts +148 -3
- package/src/daemon/host-bash-proxy.ts +16 -0
- package/src/daemon/host-cu-proxy.ts +16 -0
- package/src/daemon/host-file-proxy.ts +16 -0
- package/src/daemon/lifecycle.ts +56 -5
- package/src/daemon/message-types/conversations.ts +1 -0
- package/src/daemon/message-types/guardian-actions.ts +2 -0
- package/src/daemon/message-types/host-bash.ts +6 -1
- package/src/daemon/message-types/host-cu.ts +6 -1
- package/src/daemon/message-types/host-file.ts +6 -1
- package/src/daemon/message-types/integrations.ts +0 -1
- package/src/daemon/server.ts +29 -2
- package/src/hooks/cli.ts +74 -0
- package/src/inbound/platform-callback-registration.ts +7 -12
- package/src/index.ts +0 -12
- package/src/mcp/client.ts +6 -1
- package/src/mcp/manager.ts +2 -1
- package/src/memory/conversation-crud.ts +92 -3
- package/src/memory/conversation-key-store.ts +26 -0
- package/src/memory/conversation-queries.ts +6 -6
- package/src/memory/db-init.ts +16 -0
- package/src/memory/journal-memory.ts +8 -2
- package/src/memory/migrations/196-messages-conversation-created-at-index.ts +9 -0
- package/src/memory/migrations/196-strip-integration-prefix-from-provider-keys.ts +186 -0
- package/src/memory/migrations/197-oauth-providers-behavior-columns.ts +29 -0
- package/src/memory/migrations/198-drop-setup-skill-id-column.ts +11 -0
- package/src/memory/migrations/index.ts +4 -0
- package/src/memory/migrations/registry.ts +8 -0
- package/src/memory/schema/oauth.ts +11 -0
- package/src/messaging/provider.ts +13 -12
- package/src/messaging/providers/gmail/adapter.ts +44 -35
- package/src/messaging/providers/slack/adapter.ts +63 -33
- package/src/messaging/providers/telegram-bot/adapter.ts +6 -8
- package/src/messaging/providers/whatsapp/adapter.ts +6 -8
- package/src/notifications/adapters/telegram.ts +78 -2
- package/src/oauth/__tests__/identity-verifier.test.ts +464 -0
- package/src/oauth/byo-connection.test.ts +22 -24
- package/src/oauth/connect-orchestrator.ts +37 -76
- package/src/oauth/connect-types.ts +7 -65
- package/src/oauth/connection-resolver.test.ts +13 -13
- package/src/oauth/connection-resolver.ts +3 -4
- package/src/oauth/identity-verifier.ts +177 -0
- package/src/oauth/oauth-store.ts +228 -3
- package/src/oauth/platform-connection.test.ts +56 -6
- package/src/oauth/platform-connection.ts +8 -1
- package/src/oauth/seed-providers.ts +247 -34
- package/src/permissions/checker.ts +127 -1
- package/src/prompts/journal-context.ts +4 -1
- package/src/prompts/system-prompt.ts +54 -9
- package/src/prompts/templates/BOOTSTRAP.md +16 -5
- package/src/providers/anthropic/client.ts +2 -33
- package/src/runtime/guardian-action-service.ts +7 -2
- package/src/runtime/http-server.ts +12 -18
- package/src/runtime/http-types.ts +8 -1
- package/src/runtime/migrations/rebind-secrets-screen.ts +2 -2
- package/src/runtime/routes/conversation-management-routes.ts +31 -0
- package/src/runtime/routes/conversation-routes.ts +79 -4
- package/src/runtime/routes/guardian-action-routes.ts +15 -2
- package/src/runtime/routes/inbound-stages/acl-enforcement.ts +21 -8
- package/src/runtime/routes/integrations/slack/share.ts +1 -1
- package/src/runtime/routes/oauth-apps.ts +2 -1
- package/src/runtime/routes/secret-routes.ts +45 -15
- package/src/runtime/routes/settings-routes.ts +12 -19
- package/src/runtime/routes/skills-routes.ts +45 -4
- package/src/schedule/integration-status.ts +2 -2
- package/src/security/ces-rpc-credential-backend.ts +19 -16
- package/src/security/oauth-completion-page.ts +153 -0
- package/src/security/oauth2.ts +3 -17
- package/src/security/secure-keys.ts +207 -7
- package/src/security/token-manager.ts +3 -6
- package/src/signals/bash.ts +6 -1
- package/src/skills/catalog-cache.ts +44 -0
- package/src/skills/catalog-search.ts +18 -0
- package/src/tools/browser/browser-manager.ts +2 -2
- package/src/tools/credentials/post-connect-hooks.ts +1 -1
- package/src/tools/credentials/vault.ts +34 -45
- package/src/tools/host-terminal/host-shell.ts +16 -3
- package/src/tools/mcp/mcp-tool-factory.ts +2 -1
- package/src/tools/skills/sandbox-runner.ts +16 -3
- package/src/tools/terminal/shell.ts +16 -3
- package/src/util/logger.ts +11 -1
- package/src/util/platform.ts +1 -91
- package/src/util/sentry-log-stream.ts +51 -0
- package/src/watcher/providers/github.ts +2 -2
- package/src/watcher/providers/gmail.ts +1 -1
- package/src/watcher/providers/google-calendar.ts +1 -1
- package/src/watcher/providers/linear.ts +2 -2
- package/src/workspace/migrations/011-backfill-installation-id.ts +5 -3
- package/src/workspace/migrations/020-rename-oauth-skill-dirs.ts +119 -0
- package/src/workspace/migrations/registry.ts +2 -0
- package/src/cli/commands/oauth/connections.ts +0 -255
- package/src/oauth/provider-behaviors.ts +0 -634
package/src/security/oauth2.ts
CHANGED
|
@@ -20,6 +20,7 @@ import { createHash, randomBytes } from "node:crypto";
|
|
|
20
20
|
import { createServer, type Server } from "node:http";
|
|
21
21
|
|
|
22
22
|
import { getLogger } from "../util/logger.js";
|
|
23
|
+
import { renderOAuthCompletionPage as renderLoopbackPage } from "./oauth-completion-page.js";
|
|
23
24
|
|
|
24
25
|
const log = getLogger("oauth2");
|
|
25
26
|
|
|
@@ -359,7 +360,7 @@ function startLoopbackServerAndWaitForCode(
|
|
|
359
360
|
res.writeHead(200, { "Content-Type": "text/html" });
|
|
360
361
|
res.end(
|
|
361
362
|
renderLoopbackPage(
|
|
362
|
-
"
|
|
363
|
+
"You can close this tab and return to your assistant.",
|
|
363
364
|
true,
|
|
364
365
|
),
|
|
365
366
|
);
|
|
@@ -433,21 +434,6 @@ function startLoopbackServerAndWaitForCode(
|
|
|
433
434
|
});
|
|
434
435
|
}
|
|
435
436
|
|
|
436
|
-
function escapeHtml(s: string): string {
|
|
437
|
-
return s
|
|
438
|
-
.replace(/&/g, "&")
|
|
439
|
-
.replace(/</g, "<")
|
|
440
|
-
.replace(/>/g, ">")
|
|
441
|
-
.replace(/"/g, """)
|
|
442
|
-
.replace(/'/g, "'");
|
|
443
|
-
}
|
|
444
|
-
|
|
445
|
-
function renderLoopbackPage(message: string, success: boolean): string {
|
|
446
|
-
const title = success ? "Authorization Successful" : "Authorization Failed";
|
|
447
|
-
const color = success ? "#4CAF50" : "#f44336";
|
|
448
|
-
return `<!DOCTYPE html><html><head><title>${escapeHtml(title)}</title><style>body{font-family:system-ui,sans-serif;display:flex;justify-content:center;align-items:center;min-height:100vh;margin:0;background:#f5f5f5}div{text-align:center;padding:2rem;background:white;border-radius:8px;box-shadow:0 2px 4px rgba(0,0,0,0.1)}h1{color:${color}}</style></head><body><div><h1>${escapeHtml(title)}</h1><p>${escapeHtml(message)}</p></div></body></html>`;
|
|
449
|
-
}
|
|
450
|
-
|
|
451
437
|
// ---------------------------------------------------------------------------
|
|
452
438
|
// Public API
|
|
453
439
|
// ---------------------------------------------------------------------------
|
|
@@ -640,7 +626,7 @@ function startLoopbackServerForPreparedFlow(
|
|
|
640
626
|
res.writeHead(200, { "Content-Type": "text/html" });
|
|
641
627
|
res.end(
|
|
642
628
|
renderLoopbackPage(
|
|
643
|
-
"
|
|
629
|
+
"You can close this tab and return to your assistant.",
|
|
644
630
|
true,
|
|
645
631
|
),
|
|
646
632
|
);
|
|
@@ -5,13 +5,16 @@
|
|
|
5
5
|
* Backend selection (`resolveBackendAsync`) is the single async decision point:
|
|
6
6
|
* 1. CES RPC (primary) — injected via `setCesClient()`: delegates credential
|
|
7
7
|
* operations to the CES process over stdio RPC. This is the default path
|
|
8
|
-
* for
|
|
8
|
+
* for local modes and the managed bootstrap handshake path.
|
|
9
9
|
* 2. CES HTTP — containerized mode (IS_CONTAINERIZED + CES_CREDENTIAL_URL):
|
|
10
|
-
* delegates to the CES sidecar over HTTP. Used in Docker/managed mode
|
|
10
|
+
* delegates to the CES sidecar over HTTP. Used in Docker/managed mode,
|
|
11
|
+
* including failover when the bootstrap RPC transport dies later.
|
|
11
12
|
* 3. Encrypted file store (fallback) — used when CES is unavailable.
|
|
12
13
|
*
|
|
13
14
|
* All operations (reads, writes, lists, deletes) go to exactly one backend.
|
|
14
|
-
* There are no cross-
|
|
15
|
+
* There are no cross-store fallbacks or merges. The only transport failover is
|
|
16
|
+
* CES RPC → CES HTTP in managed mode; both backends target the same CES
|
|
17
|
+
* sidecar and credential data.
|
|
15
18
|
*/
|
|
16
19
|
|
|
17
20
|
import type {
|
|
@@ -56,12 +59,59 @@ let _encryptedStore: CredentialBackend | undefined;
|
|
|
56
59
|
let _resolvedBackend: CredentialBackend | undefined;
|
|
57
60
|
let _resolvePromise: Promise<CredentialBackend> | undefined;
|
|
58
61
|
|
|
62
|
+
/**
|
|
63
|
+
* Optional callback that attempts to re-establish a CES connection when
|
|
64
|
+
* the current transport dies. Set by lifecycle.ts after initial CES startup.
|
|
65
|
+
* Returns a new CesClient on success, or undefined if reconnection failed.
|
|
66
|
+
*/
|
|
67
|
+
let _cesReconnect: (() => Promise<CesClient | undefined>) | undefined;
|
|
68
|
+
|
|
69
|
+
/** Optional listener invoked whenever setCesClient() updates the client. */
|
|
70
|
+
let _cesClientListener: ((client: CesClient | undefined) => void) | undefined;
|
|
71
|
+
|
|
72
|
+
/** Epoch ms of the last reconnection attempt. Used for cooldown. */
|
|
73
|
+
let _lastReconnectAttempt = 0;
|
|
74
|
+
|
|
75
|
+
/** In-flight reconnection promise — concurrent callers share the same attempt. */
|
|
76
|
+
let _reconnectInFlight: Promise<boolean> | undefined;
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Set to true when a ces-http operation returns an unreachable result.
|
|
80
|
+
* Triggers CES RPC reconnection on the next resolveBackendAsync() call so
|
|
81
|
+
* we don't keep routing to a dead HTTP endpoint. Cleared on reconnection or
|
|
82
|
+
* backend reset.
|
|
83
|
+
*/
|
|
84
|
+
let _cesHttpUnreachable = false;
|
|
85
|
+
|
|
86
|
+
/** Minimum interval between CES reconnection attempts. */
|
|
87
|
+
const RECONNECT_COOLDOWN_MS = 3_000;
|
|
88
|
+
|
|
59
89
|
/** Inject a CES RPC client for credential routing. Resets the resolved backend. */
|
|
60
90
|
export function setCesClient(client: CesClient | undefined): void {
|
|
61
91
|
_cesClient = client;
|
|
62
92
|
// Reset resolved backend so next call picks up CES
|
|
63
93
|
_resolvedBackend = undefined;
|
|
64
94
|
_resolvePromise = undefined;
|
|
95
|
+
_cesHttpUnreachable = false;
|
|
96
|
+
_cesClientListener?.(client);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Register a listener that is called whenever setCesClient() updates the
|
|
101
|
+
* CES client reference. Used by lifecycle.ts to keep DaemonServer in sync
|
|
102
|
+
* after a successful reconnection.
|
|
103
|
+
*/
|
|
104
|
+
export function onCesClientChanged(
|
|
105
|
+
fn: ((client: CesClient | undefined) => void) | undefined,
|
|
106
|
+
): void {
|
|
107
|
+
_cesClientListener = fn;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/** Register a callback for reconnecting to CES when the transport dies. */
|
|
111
|
+
export function setCesReconnect(
|
|
112
|
+
fn: (() => Promise<CesClient | undefined>) | undefined,
|
|
113
|
+
): void {
|
|
114
|
+
_cesReconnect = fn;
|
|
65
115
|
}
|
|
66
116
|
|
|
67
117
|
function getEncryptedStoreBackend(): CredentialBackend {
|
|
@@ -77,17 +127,141 @@ function getEncryptedStoreBackend(): CredentialBackend {
|
|
|
77
127
|
* 2. Containerized + CES_CREDENTIAL_URL → CES HTTP client (Docker/managed).
|
|
78
128
|
* 3. Encrypted file store → fallback when CES is unavailable.
|
|
79
129
|
*
|
|
80
|
-
* Once resolved, the backend
|
|
130
|
+
* Once resolved, the backend is cached. If it becomes unavailable (e.g. the
|
|
131
|
+
* CES transport dies), we attempt to reconnect via `_cesReconnect` rather
|
|
132
|
+
* than falling back to a different backend. In managed cloud mode CES is the
|
|
133
|
+
* primary credential source — falling back to the encrypted file store would
|
|
134
|
+
* silently serve stale or empty data.
|
|
135
|
+
*
|
|
136
|
+
* In managed mode, if the CES bootstrap RPC transport dies, we first fail
|
|
137
|
+
* over from CES RPC to the CES HTTP credential API exposed by the same
|
|
138
|
+
* sidecar. This avoids pinning credential reads to a dead bootstrap socket
|
|
139
|
+
* when the in-pod HTTP interface is still healthy.
|
|
140
|
+
*
|
|
141
|
+
* If HTTP failover is unavailable, we attempt CES RPC reconnection. When
|
|
142
|
+
* reconnection succeeds the cache is refreshed with the new client.
|
|
143
|
+
*
|
|
144
|
+
* If neither recovery path succeeds, the existing unavailable backend is
|
|
145
|
+
* returned — its methods short-circuit via `isAvailable()` guards and return
|
|
146
|
+
* `unreachable` results so callers can degrade gracefully.
|
|
147
|
+
*
|
|
148
|
+
* Additionally, if CES failed on initial startup (so the encrypted file
|
|
149
|
+
* store became the resolved backend) but the reconnection callback is
|
|
150
|
+
* registered, we periodically attempt to upgrade to CES — ensuring managed
|
|
151
|
+
* cloud deployments don't stay on the (potentially stale) file store.
|
|
152
|
+
*
|
|
81
153
|
* Call `_resetBackend()` in tests to clear the cached resolution.
|
|
82
154
|
*/
|
|
83
155
|
async function resolveBackendAsync(): Promise<CredentialBackend> {
|
|
84
|
-
if (_resolvedBackend)
|
|
156
|
+
if (_resolvedBackend) {
|
|
157
|
+
if (!_resolvedBackend.isAvailable()) {
|
|
158
|
+
const cesHttpFallback = tryFailoverToCesHttpBackend(_resolvedBackend);
|
|
159
|
+
if (cesHttpFallback) {
|
|
160
|
+
return cesHttpFallback;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// Backend is no longer reachable — attempt CES reconnection.
|
|
164
|
+
const reconnected = await attemptCesReconnection();
|
|
165
|
+
if (reconnected) {
|
|
166
|
+
// setCesClient() cleared the cache — fall through to re-resolve
|
|
167
|
+
// with the fresh client.
|
|
168
|
+
} else {
|
|
169
|
+
// Reconnection failed or on cooldown — return the existing (dead)
|
|
170
|
+
// backend. Its methods short-circuit via isAvailable() guards and
|
|
171
|
+
// return unreachable results. Callers like getProviderKeyAsync fall
|
|
172
|
+
// back to env vars, and embedding backend selection uses cached
|
|
173
|
+
// backends.
|
|
174
|
+
return _resolvedBackend;
|
|
175
|
+
}
|
|
176
|
+
} else if (
|
|
177
|
+
_cesReconnect &&
|
|
178
|
+
(_resolvedBackend.name === "encrypted-store" ||
|
|
179
|
+
(_resolvedBackend.name === "ces-http" && _cesHttpUnreachable))
|
|
180
|
+
) {
|
|
181
|
+
// CES RPC is the preferred backend. Attempt to reconnect when:
|
|
182
|
+
// - We fell back to the encrypted store (CES startup failed), or
|
|
183
|
+
// - We're on ces-http but an operation returned unreachable (HTTP
|
|
184
|
+
// endpoint is actually down even though isAvailable() returned true,
|
|
185
|
+
// since it only checks env vars, not actual connectivity).
|
|
186
|
+
const reconnected = await attemptCesReconnection();
|
|
187
|
+
if (reconnected) {
|
|
188
|
+
// setCesClient() cleared the cache — fall through to re-resolve.
|
|
189
|
+
} else {
|
|
190
|
+
// Reconnection failed or on cooldown — continue with current backend.
|
|
191
|
+
return _resolvedBackend;
|
|
192
|
+
}
|
|
193
|
+
} else {
|
|
194
|
+
return _resolvedBackend;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
85
197
|
if (!_resolvePromise) {
|
|
86
198
|
_resolvePromise = doResolveBackend();
|
|
87
199
|
}
|
|
88
200
|
return _resolvePromise;
|
|
89
201
|
}
|
|
90
202
|
|
|
203
|
+
function tryFailoverToCesHttpBackend(
|
|
204
|
+
backend: CredentialBackend,
|
|
205
|
+
): CredentialBackend | undefined {
|
|
206
|
+
if (backend.name !== "ces-rpc") return undefined;
|
|
207
|
+
if (!getIsContainerized() || !process.env.CES_CREDENTIAL_URL) {
|
|
208
|
+
return undefined;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
const cesHttp = createCesCredentialBackend();
|
|
212
|
+
if (!cesHttp.isAvailable()) return undefined;
|
|
213
|
+
|
|
214
|
+
_resolvedBackend = cesHttp;
|
|
215
|
+
_resolvePromise = undefined;
|
|
216
|
+
log.warn(
|
|
217
|
+
"CES RPC credential backend became unavailable — failing over to CES HTTP backend",
|
|
218
|
+
);
|
|
219
|
+
return cesHttp;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Try to re-establish a CES connection when the current transport has died.
|
|
224
|
+
* Returns true if reconnection succeeded (setCesClient was called with a
|
|
225
|
+
* new client), false otherwise.
|
|
226
|
+
*
|
|
227
|
+
* Concurrent callers share the same in-flight reconnection attempt to avoid
|
|
228
|
+
* racing on the same process manager. A timestamp cooldown prevents rapid
|
|
229
|
+
* back-to-back attempts after completion.
|
|
230
|
+
*/
|
|
231
|
+
async function attemptCesReconnection(): Promise<boolean> {
|
|
232
|
+
if (!_cesReconnect) return false;
|
|
233
|
+
|
|
234
|
+
// If a reconnection is already in flight, share it.
|
|
235
|
+
if (_reconnectInFlight) return _reconnectInFlight;
|
|
236
|
+
|
|
237
|
+
// Cooldown — don't retry immediately after a completed attempt.
|
|
238
|
+
if (Date.now() - _lastReconnectAttempt < RECONNECT_COOLDOWN_MS) return false;
|
|
239
|
+
|
|
240
|
+
_lastReconnectAttempt = Date.now();
|
|
241
|
+
log.warn("Credential backend unavailable — attempting CES reconnection");
|
|
242
|
+
|
|
243
|
+
_reconnectInFlight = (async () => {
|
|
244
|
+
try {
|
|
245
|
+
const newClient = await _cesReconnect!();
|
|
246
|
+
if (newClient) {
|
|
247
|
+
setCesClient(newClient);
|
|
248
|
+
log.info("CES reconnection successful — credential backend restored");
|
|
249
|
+
return true;
|
|
250
|
+
}
|
|
251
|
+
log.warn("CES reconnection returned no client");
|
|
252
|
+
} catch (err) {
|
|
253
|
+
log.warn({ err }, "CES reconnection failed");
|
|
254
|
+
}
|
|
255
|
+
return false;
|
|
256
|
+
})();
|
|
257
|
+
|
|
258
|
+
try {
|
|
259
|
+
return await _reconnectInFlight;
|
|
260
|
+
} finally {
|
|
261
|
+
_reconnectInFlight = undefined;
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
91
265
|
async function doResolveBackend(): Promise<CredentialBackend> {
|
|
92
266
|
// 1. CES RPC — primary credential backend for all local modes
|
|
93
267
|
if (_cesClient) {
|
|
@@ -119,6 +293,21 @@ async function doResolveBackend(): Promise<CredentialBackend> {
|
|
|
119
293
|
return _resolvedBackend;
|
|
120
294
|
}
|
|
121
295
|
|
|
296
|
+
/**
|
|
297
|
+
* Update the ces-http reachability latch after any get/list operation.
|
|
298
|
+
* Sets `_cesHttpUnreachable = true` on failure so the next
|
|
299
|
+
* resolveBackendAsync() call triggers a CES RPC reconnection attempt.
|
|
300
|
+
* Clears it on success so a transient blip doesn't cause endless churn.
|
|
301
|
+
*/
|
|
302
|
+
function updateCesHttpReachability(
|
|
303
|
+
backend: CredentialBackend,
|
|
304
|
+
unreachable: boolean,
|
|
305
|
+
): void {
|
|
306
|
+
if (backend.name === "ces-http") {
|
|
307
|
+
_cesHttpUnreachable = unreachable;
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
122
311
|
/**
|
|
123
312
|
* List all account names from the resolved backend (async).
|
|
124
313
|
*
|
|
@@ -126,7 +315,9 @@ async function doResolveBackend(): Promise<CredentialBackend> {
|
|
|
126
315
|
*/
|
|
127
316
|
export async function listSecureKeysAsync(): Promise<CredentialListResult> {
|
|
128
317
|
const backend = await resolveBackendAsync();
|
|
129
|
-
|
|
318
|
+
const result = await backend.list();
|
|
319
|
+
updateCesHttpReachability(backend, result.unreachable);
|
|
320
|
+
return result;
|
|
130
321
|
}
|
|
131
322
|
|
|
132
323
|
// ---------------------------------------------------------------------------
|
|
@@ -147,6 +338,7 @@ export async function getSecureKeyResultAsync(
|
|
|
147
338
|
): Promise<SecureKeyResult> {
|
|
148
339
|
const backend = await resolveBackendAsync();
|
|
149
340
|
const result = await backend.get(account);
|
|
341
|
+
updateCesHttpReachability(backend, result.unreachable);
|
|
150
342
|
if (result.value != null) {
|
|
151
343
|
return { value: result.value, unreachable: false };
|
|
152
344
|
}
|
|
@@ -180,6 +372,7 @@ export async function setSecureKeyAsync(
|
|
|
180
372
|
"Credential backend set failed",
|
|
181
373
|
);
|
|
182
374
|
}
|
|
375
|
+
updateCesHttpReachability(backend, !ok);
|
|
183
376
|
return ok;
|
|
184
377
|
}
|
|
185
378
|
|
|
@@ -192,7 +385,9 @@ export async function deleteSecureKeyAsync(
|
|
|
192
385
|
account: string,
|
|
193
386
|
): Promise<DeleteResult> {
|
|
194
387
|
const backend = await resolveBackendAsync();
|
|
195
|
-
|
|
388
|
+
const result = await backend.delete(account);
|
|
389
|
+
updateCesHttpReachability(backend, result === "error");
|
|
390
|
+
return result;
|
|
196
391
|
}
|
|
197
392
|
|
|
198
393
|
// ---------------------------------------------------------------------------
|
|
@@ -264,4 +459,9 @@ export function _resetBackend(): void {
|
|
|
264
459
|
_encryptedStore = undefined;
|
|
265
460
|
_resolvedBackend = undefined;
|
|
266
461
|
_resolvePromise = undefined;
|
|
462
|
+
_cesReconnect = undefined;
|
|
463
|
+
_cesClientListener = undefined;
|
|
464
|
+
_lastReconnectAttempt = 0;
|
|
465
|
+
_reconnectInFlight = undefined;
|
|
466
|
+
_cesHttpUnreachable = false;
|
|
267
467
|
}
|
|
@@ -35,16 +35,13 @@ import { getSecureKeyAsync, setSecureKeyAsync } from "./secure-keys.js";
|
|
|
35
35
|
|
|
36
36
|
const log = getLogger("token-manager");
|
|
37
37
|
|
|
38
|
-
const MESSAGING_SERVICES = new Set(["
|
|
38
|
+
const MESSAGING_SERVICES = new Set(["google", "slack"]);
|
|
39
39
|
|
|
40
40
|
function recoveryHint(service: string): string {
|
|
41
|
-
const shortName = service.startsWith("integration:")
|
|
42
|
-
? service.slice("integration:".length)
|
|
43
|
-
: service;
|
|
44
41
|
if (MESSAGING_SERVICES.has(service)) {
|
|
45
|
-
return ` Reconnect ${
|
|
42
|
+
return ` Reconnect ${service} — follow the Error Recovery steps in the messaging skill. Do not present options or explain the error to the user.`;
|
|
46
43
|
}
|
|
47
|
-
return ` Re-authorization required for ${
|
|
44
|
+
return ` Re-authorization required for ${service}. Do not present options or explain the error to the user.`;
|
|
48
45
|
}
|
|
49
46
|
|
|
50
47
|
// ── Shared circuit breaker & deduplication instances ──────────────────
|
package/src/signals/bash.ts
CHANGED
|
@@ -138,11 +138,16 @@ export function handleBashSignal(filename: string): void {
|
|
|
138
138
|
const child = spawn("bash", ["-c", command], {
|
|
139
139
|
cwd: getWorkspaceDir(),
|
|
140
140
|
stdio: ["ignore", "pipe", "pipe"],
|
|
141
|
+
detached: true,
|
|
141
142
|
});
|
|
142
143
|
|
|
143
144
|
const timer = setTimeout(() => {
|
|
144
145
|
timedOut = true;
|
|
145
|
-
|
|
146
|
+
try {
|
|
147
|
+
process.kill(-child.pid!, "SIGKILL");
|
|
148
|
+
} catch {
|
|
149
|
+
// Process group may have already exited.
|
|
150
|
+
}
|
|
146
151
|
}, effectiveTimeout);
|
|
147
152
|
|
|
148
153
|
child.stdout.on("data", (data: Buffer) => {
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { getLogger } from "../util/logger.js";
|
|
2
|
+
import type { CatalogSkill } from "./catalog-install.js";
|
|
3
|
+
import {
|
|
4
|
+
fetchCatalog,
|
|
5
|
+
getRepoSkillsDir,
|
|
6
|
+
readLocalCatalog,
|
|
7
|
+
} from "./catalog-install.js";
|
|
8
|
+
|
|
9
|
+
const log = getLogger("catalog-cache");
|
|
10
|
+
const CACHE_TTL_MS = 5 * 60 * 1000; // 5 minutes
|
|
11
|
+
|
|
12
|
+
let cachedCatalog: CatalogSkill[] | null = null;
|
|
13
|
+
let cacheTimestamp = 0;
|
|
14
|
+
|
|
15
|
+
/** Resolve the Vellum catalog with in-memory caching. */
|
|
16
|
+
export async function getCatalog(): Promise<CatalogSkill[]> {
|
|
17
|
+
if (cachedCatalog && Date.now() - cacheTimestamp < CACHE_TTL_MS) {
|
|
18
|
+
return cachedCatalog;
|
|
19
|
+
}
|
|
20
|
+
const repoSkillsDir = getRepoSkillsDir();
|
|
21
|
+
let catalog: CatalogSkill[];
|
|
22
|
+
if (repoSkillsDir) {
|
|
23
|
+
catalog = readLocalCatalog(repoSkillsDir);
|
|
24
|
+
} else {
|
|
25
|
+
try {
|
|
26
|
+
catalog = await fetchCatalog();
|
|
27
|
+
} catch (err) {
|
|
28
|
+
log.warn(
|
|
29
|
+
{ err },
|
|
30
|
+
"Failed to fetch Vellum catalog, using stale cache or empty",
|
|
31
|
+
);
|
|
32
|
+
return cachedCatalog ?? [];
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
cachedCatalog = catalog;
|
|
36
|
+
cacheTimestamp = Date.now();
|
|
37
|
+
return catalog;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** Invalidate the cache (for testing or forced refresh). */
|
|
41
|
+
export function invalidateCatalogCache(): void {
|
|
42
|
+
cachedCatalog = null;
|
|
43
|
+
cacheTimestamp = 0;
|
|
44
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared catalog text-search helper.
|
|
3
|
+
*
|
|
4
|
+
* Both the CLI `skills search` command and the daemon `searchSkills` handler
|
|
5
|
+
* need case-insensitive substring matching across multiple fields. This module
|
|
6
|
+
* provides a single generic implementation to prevent the two from drifting.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
export function filterByQuery<T>(
|
|
10
|
+
items: T[],
|
|
11
|
+
query: string,
|
|
12
|
+
fields: ((item: T) => string)[],
|
|
13
|
+
): T[] {
|
|
14
|
+
const lower = query.toLowerCase();
|
|
15
|
+
return items.filter((item) =>
|
|
16
|
+
fields.some((fn) => fn(item).toLowerCase().includes(lower)),
|
|
17
|
+
);
|
|
18
|
+
}
|
|
@@ -242,13 +242,13 @@ class BrowserManager {
|
|
|
242
242
|
if (!chromiumInstalled) {
|
|
243
243
|
log.info("Chromium not installed, installing via playwright...");
|
|
244
244
|
const proc = Bun.spawn(
|
|
245
|
-
["bunx", "playwright", "install", "chromium"],
|
|
245
|
+
["bunx", "playwright", "install", "--with-deps", "chromium"],
|
|
246
246
|
{
|
|
247
247
|
stdout: "pipe",
|
|
248
248
|
stderr: "pipe",
|
|
249
249
|
},
|
|
250
250
|
);
|
|
251
|
-
const timeoutMs =
|
|
251
|
+
const timeoutMs = 300_000;
|
|
252
252
|
let timer: ReturnType<typeof setTimeout>;
|
|
253
253
|
const exitCode = await Promise.race([
|
|
254
254
|
proc.exited.finally(() => clearTimeout(timer)),
|
|
@@ -48,7 +48,7 @@ async function slackWelcomeDM(ctx: PostConnectHookContext): Promise<void> {
|
|
|
48
48
|
// ---------------------------------------------------------------------------
|
|
49
49
|
|
|
50
50
|
const POST_CONNECT_HOOKS: Record<string, PostConnectHook> = {
|
|
51
|
-
|
|
51
|
+
slack: slackWelcomeDM,
|
|
52
52
|
};
|
|
53
53
|
|
|
54
54
|
/**
|
|
@@ -11,12 +11,8 @@ import {
|
|
|
11
11
|
getAppByProviderAndClientId,
|
|
12
12
|
getMostRecentAppByProvider,
|
|
13
13
|
getProvider,
|
|
14
|
+
listProviders,
|
|
14
15
|
} from "../../oauth/oauth-store.js";
|
|
15
|
-
import {
|
|
16
|
-
getProviderBehavior,
|
|
17
|
-
resolveService,
|
|
18
|
-
SERVICE_ALIASES,
|
|
19
|
-
} from "../../oauth/provider-behaviors.js";
|
|
20
16
|
import { RiskLevel } from "../../permissions/types.js";
|
|
21
17
|
import type { ToolDefinition } from "../../providers/types.js";
|
|
22
18
|
import { buildAssistantEvent } from "../../runtime/assistant-event.js";
|
|
@@ -103,11 +99,11 @@ class CredentialStoreTool implements Tool {
|
|
|
103
99
|
"describe",
|
|
104
100
|
],
|
|
105
101
|
description:
|
|
106
|
-
'The operation to perform. Use "prompt" to ask the user for a secret via secure UI - the value never enters the conversation. Use "oauth2_connect" to connect an OAuth2 service via browser authorization. Use "describe" to get setup metadata for a well-known OAuth service (dashboard URL, scopes, redirect URI, etc.). For well-known services (
|
|
102
|
+
'The operation to perform. Use "prompt" to ask the user for a secret via secure UI - the value never enters the conversation. Use "oauth2_connect" to connect an OAuth2 service via browser authorization. Use "describe" to get setup metadata for a well-known OAuth service (dashboard URL, scopes, redirect URI, etc.). For well-known services (google, slack), only the service name is required - endpoints, scopes, and stored client credentials are resolved automatically.',
|
|
107
103
|
},
|
|
108
104
|
service: {
|
|
109
105
|
type: "string",
|
|
110
|
-
description: "Service name, e.g.
|
|
106
|
+
description: "Service name, e.g. google, github",
|
|
111
107
|
},
|
|
112
108
|
account: {
|
|
113
109
|
type: "string",
|
|
@@ -158,7 +154,7 @@ class CredentialStoreTool implements Tool {
|
|
|
158
154
|
type: "array",
|
|
159
155
|
items: { type: "string" },
|
|
160
156
|
description:
|
|
161
|
-
"OAuth2 scopes to request (only for oauth2_connect action). Auto-filled for well-known services (
|
|
157
|
+
"OAuth2 scopes to request (only for oauth2_connect action). Auto-filled for well-known services (google, slack).",
|
|
162
158
|
},
|
|
163
159
|
client_id: {
|
|
164
160
|
type: "string",
|
|
@@ -822,18 +818,13 @@ class CredentialStoreTool implements Tool {
|
|
|
822
818
|
}
|
|
823
819
|
|
|
824
820
|
case "oauth2_connect": {
|
|
825
|
-
const
|
|
826
|
-
if (!
|
|
821
|
+
const service = input.service as string | undefined;
|
|
822
|
+
if (!service)
|
|
827
823
|
return {
|
|
828
824
|
content: "Error: service is required for oauth2_connect action",
|
|
829
825
|
isError: true,
|
|
830
826
|
};
|
|
831
827
|
|
|
832
|
-
// Resolve aliases (e.g. "gmail" → "integration:google")
|
|
833
|
-
const service = resolveService(rawService);
|
|
834
|
-
|
|
835
|
-
// Code-side behavioral fields (identityVerifier, setup, etc.)
|
|
836
|
-
const behavior = getProviderBehavior(service);
|
|
837
828
|
// Protocol-level config from the DB (authUrl, tokenUrl, scopes, etc.)
|
|
838
829
|
const providerRow = getProvider(service);
|
|
839
830
|
|
|
@@ -880,16 +871,10 @@ class CredentialStoreTool implements Tool {
|
|
|
880
871
|
// Fail early when client_secret is required but missing - guide the
|
|
881
872
|
// agent to collect it from the user rather than letting it improvise
|
|
882
873
|
// browser-automation workarounds that inevitably fail.
|
|
883
|
-
const requiresSecret =
|
|
884
|
-
behavior?.setup?.requiresClientSecret ??
|
|
885
|
-
!!(providerRow.tokenEndpointAuthMethod || providerRow.extraParams);
|
|
874
|
+
const requiresSecret = !!providerRow.requiresClientSecret;
|
|
886
875
|
if (requiresSecret && !clientSecret) {
|
|
887
|
-
const skillId = behavior?.setupSkillId;
|
|
888
|
-
const skillHint = skillId
|
|
889
|
-
? `\n\nLoad the "${skillId}" skill for provider-specific instructions on obtaining the client secret.`
|
|
890
|
-
: '\n\nUse credential_store with action "prompt" to securely collect the client_secret from the user before calling oauth2_connect again.';
|
|
891
876
|
return {
|
|
892
|
-
content: `Error: client_secret is required for ${
|
|
877
|
+
content: `Error: client_secret is required for ${service} but not found in the vault.\n\nUse credential_store with action "prompt" to securely collect the client_secret from the user before calling oauth2_connect again.`,
|
|
893
878
|
isError: true,
|
|
894
879
|
};
|
|
895
880
|
}
|
|
@@ -897,7 +882,7 @@ class CredentialStoreTool implements Tool {
|
|
|
897
882
|
// Delegate to the shared orchestrator - it resolves authUrl, tokenUrl,
|
|
898
883
|
// extraParams, userinfoUrl, and tokenEndpointAuthMethod from the DB.
|
|
899
884
|
const result = await orchestrateOAuthConnect({
|
|
900
|
-
service
|
|
885
|
+
service,
|
|
901
886
|
clientId,
|
|
902
887
|
clientSecret,
|
|
903
888
|
isInteractive: !!context.isInteractive,
|
|
@@ -949,7 +934,7 @@ class CredentialStoreTool implements Tool {
|
|
|
949
934
|
|
|
950
935
|
if (result.deferred) {
|
|
951
936
|
return {
|
|
952
|
-
content: `To connect ${
|
|
937
|
+
content: `To connect ${service}, open this link and authorize access:\n\n${result.authUrl}\n\nOnce you authorize, the connection will be set up automatically. You can verify by asking me to check your inbox.`,
|
|
953
938
|
isError: false,
|
|
954
939
|
};
|
|
955
940
|
}
|
|
@@ -963,34 +948,30 @@ class CredentialStoreTool implements Tool {
|
|
|
963
948
|
}
|
|
964
949
|
|
|
965
950
|
case "describe": {
|
|
966
|
-
const
|
|
967
|
-
if (!
|
|
951
|
+
const descService = (input.service as string | undefined) ?? "";
|
|
952
|
+
if (!descService) {
|
|
968
953
|
return {
|
|
969
954
|
content: "Error: service is required for describe action",
|
|
970
955
|
isError: true,
|
|
971
956
|
};
|
|
972
957
|
}
|
|
973
|
-
const
|
|
974
|
-
const descProviderRow = getProvider(resolvedService);
|
|
958
|
+
const descProviderRow = getProvider(descService);
|
|
975
959
|
if (!descProviderRow) {
|
|
960
|
+
const availableServices = listProviders().map((p) => p.providerKey);
|
|
976
961
|
return {
|
|
977
|
-
content: `No well-known OAuth config found for "${
|
|
978
|
-
SERVICE_ALIASES,
|
|
979
|
-
).join(", ")}`,
|
|
962
|
+
content: `No well-known OAuth config found for "${descService}". Available services: ${availableServices.join(", ")}`,
|
|
980
963
|
isError: false,
|
|
981
964
|
};
|
|
982
965
|
}
|
|
983
966
|
|
|
984
|
-
const descBehavior = getProviderBehavior(resolvedService);
|
|
985
|
-
|
|
986
967
|
// Compute the redirect URI based on callback transport
|
|
987
968
|
let redirectUri: string;
|
|
988
969
|
const transport =
|
|
989
970
|
(descProviderRow.callbackTransport as
|
|
990
971
|
| "loopback"
|
|
991
972
|
| "gateway"
|
|
992
|
-
| null) ?? "
|
|
993
|
-
const loopbackPort =
|
|
973
|
+
| null) ?? "loopback";
|
|
974
|
+
const loopbackPort = descProviderRow.loopbackPort;
|
|
994
975
|
if (transport === "loopback" && loopbackPort) {
|
|
995
976
|
redirectUri = `http://localhost:${loopbackPort}/oauth/callback`;
|
|
996
977
|
} else if (transport === "loopback") {
|
|
@@ -1010,20 +991,14 @@ class CredentialStoreTool implements Tool {
|
|
|
1010
991
|
}
|
|
1011
992
|
}
|
|
1012
993
|
|
|
1013
|
-
|
|
1014
|
-
const requiresClientSecret =
|
|
1015
|
-
descBehavior?.setup?.requiresClientSecret ??
|
|
1016
|
-
!!(
|
|
1017
|
-
descProviderRow.tokenEndpointAuthMethod ||
|
|
1018
|
-
descProviderRow.extraParams
|
|
1019
|
-
);
|
|
994
|
+
const requiresClientSecret = !!descProviderRow.requiresClientSecret;
|
|
1020
995
|
|
|
1021
996
|
const descDefaultScopes: string[] = descProviderRow.defaultScopes
|
|
1022
997
|
? JSON.parse(descProviderRow.defaultScopes)
|
|
1023
998
|
: [];
|
|
1024
999
|
|
|
1025
1000
|
const info: Record<string, unknown> = {
|
|
1026
|
-
service:
|
|
1001
|
+
service: descService,
|
|
1027
1002
|
authUrl: descProviderRow.authUrl,
|
|
1028
1003
|
tokenUrl: descProviderRow.tokenUrl,
|
|
1029
1004
|
scopes: descDefaultScopes,
|
|
@@ -1031,7 +1006,21 @@ class CredentialStoreTool implements Tool {
|
|
|
1031
1006
|
redirectUri,
|
|
1032
1007
|
requiresClientSecret,
|
|
1033
1008
|
};
|
|
1034
|
-
if (
|
|
1009
|
+
if (
|
|
1010
|
+
descProviderRow.displayName &&
|
|
1011
|
+
descProviderRow.dashboardUrl &&
|
|
1012
|
+
descProviderRow.appType
|
|
1013
|
+
) {
|
|
1014
|
+
info.setup = {
|
|
1015
|
+
displayName: descProviderRow.displayName,
|
|
1016
|
+
dashboardUrl: descProviderRow.dashboardUrl,
|
|
1017
|
+
appType: descProviderRow.appType,
|
|
1018
|
+
requiresClientSecret: !!descProviderRow.requiresClientSecret,
|
|
1019
|
+
...(descProviderRow.setupNotes
|
|
1020
|
+
? { notes: JSON.parse(descProviderRow.setupNotes) }
|
|
1021
|
+
: {}),
|
|
1022
|
+
};
|
|
1023
|
+
}
|
|
1035
1024
|
if (descProviderRow.extraParams) {
|
|
1036
1025
|
try {
|
|
1037
1026
|
info.extraParams = JSON.parse(descProviderRow.extraParams);
|