@vellumai/assistant 0.5.7 → 0.5.9
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 +2 -1
- package/docker-entrypoint.sh +9 -0
- package/docs/architecture/memory.md +13 -11
- package/eslint.config.mjs +0 -31
- package/node_modules/@vellumai/ces-contracts/src/error.ts +1 -1
- package/node_modules/@vellumai/ces-contracts/src/grants.ts +1 -1
- package/node_modules/@vellumai/ces-contracts/src/handles.ts +1 -1
- package/node_modules/@vellumai/ces-contracts/src/index.ts +1 -1
- package/node_modules/@vellumai/ces-contracts/src/rpc.ts +1 -1
- package/package.json +1 -1
- package/src/__tests__/approval-cascade.test.ts +0 -1
- package/src/__tests__/browser-fill-credential.test.ts +1 -1
- package/src/__tests__/call-controller.test.ts +0 -1
- package/src/__tests__/ces-rpc-credential-backend.test.ts +3 -3
- package/src/__tests__/ces-startup-timeout.test.ts +40 -0
- package/src/__tests__/config-schema-cmd.test.ts +0 -1
- package/src/__tests__/config-schema.test.ts +2 -0
- package/src/__tests__/conversation-abort-tool-results.test.ts +0 -1
- package/src/__tests__/conversation-agent-loop-overflow.test.ts +0 -2
- package/src/__tests__/conversation-agent-loop.test.ts +2 -4
- package/src/__tests__/conversation-confirmation-signals.test.ts +0 -1
- package/src/__tests__/conversation-error.test.ts +15 -1
- package/src/__tests__/conversation-messaging-secret-redirect.test.ts +1 -1
- package/src/__tests__/conversation-pre-run-repair.test.ts +0 -1
- package/src/__tests__/conversation-provider-retry-repair.test.ts +0 -1
- package/src/__tests__/conversation-queue.test.ts +0 -1
- package/src/__tests__/conversation-runtime-assembly.test.ts +227 -0
- package/src/__tests__/conversation-slash-queue.test.ts +0 -1
- package/src/__tests__/conversation-slash-unknown.test.ts +0 -1
- package/src/__tests__/conversation-workspace-injection.test.ts +0 -1
- package/src/__tests__/conversation-workspace-tool-tracking.test.ts +0 -1
- package/src/__tests__/credential-execution-client.test.ts +5 -2
- package/src/__tests__/credential-execution-feature-gates.test.ts +31 -16
- package/src/__tests__/credential-execution-managed-contract.test.ts +2 -2
- package/src/__tests__/credential-security-e2e.test.ts +1 -1
- package/src/__tests__/credential-security-invariants.test.ts +2 -5
- package/src/__tests__/credentials-cli.test.ts +4 -3
- package/src/__tests__/daemon-credential-client.test.ts +123 -0
- package/src/__tests__/deterministic-verification-control-plane.test.ts +1 -0
- package/src/__tests__/gateway-client-managed-outbound.test.ts +79 -1
- package/src/__tests__/journal-context.test.ts +335 -0
- package/src/__tests__/memory-context-benchmark.benchmark.test.ts +0 -3
- package/src/__tests__/memory-lifecycle-e2e.test.ts +70 -25
- package/src/__tests__/memory-recall-quality.test.ts +48 -17
- package/src/__tests__/memory-regressions.test.ts +408 -363
- package/src/__tests__/memory-retrieval.benchmark.test.ts +0 -3
- package/src/__tests__/non-member-access-request.test.ts +2 -2
- package/src/__tests__/notification-decision-strategy.test.ts +71 -0
- package/src/__tests__/oauth-cli.test.ts +5 -1
- package/src/__tests__/provider-commit-message-generator.test.ts +0 -37
- package/src/__tests__/provider-error-scenarios.test.ts +0 -267
- package/src/__tests__/provider-streaming.benchmark.test.ts +2 -81
- package/src/__tests__/relay-server.test.ts +1 -2
- package/src/__tests__/script-proxy-injection-runtime.test.ts +1 -1
- package/src/__tests__/secret-onetime-send.test.ts +1 -1
- package/src/__tests__/secure-keys.test.ts +18 -15
- package/src/__tests__/skill-memory.test.ts +17 -3
- package/src/__tests__/stale-approval-dedup.test.ts +171 -0
- package/src/__tests__/stt-hints.test.ts +437 -0
- package/src/__tests__/task-memory-cleanup.test.ts +14 -0
- package/src/__tests__/twilio-routes-twiml.test.ts +139 -1
- package/src/__tests__/voice-quality.test.ts +58 -0
- package/src/__tests__/voice-scoped-grant-consumer.test.ts +0 -1
- package/src/__tests__/workspace-migration-016-migrate-credentials-from-keychain.test.ts +5 -3
- package/src/acp/agent-process.ts +9 -1
- package/src/agent/loop.ts +1 -1
- package/src/approvals/guardian-request-resolvers.ts +164 -38
- package/src/calls/__tests__/tts-text-sanitizer.test.ts +254 -0
- package/src/calls/call-controller.ts +9 -5
- package/src/calls/fish-audio-client.ts +26 -14
- package/src/calls/stt-hints.ts +189 -0
- package/src/calls/tts-text-sanitizer.ts +61 -0
- package/src/calls/twilio-routes.ts +32 -4
- package/src/calls/voice-quality.ts +15 -3
- package/src/calls/voice-session-bridge.ts +1 -0
- package/src/cli/commands/avatar.ts +2 -2
- package/src/cli/commands/credentials.ts +110 -94
- package/src/cli/commands/doctor.ts +2 -2
- package/src/cli/commands/keys.ts +7 -7
- package/src/cli/commands/memory.ts +1 -1
- package/src/cli/commands/oauth/connections.ts +11 -29
- package/src/cli/commands/oauth/platform.ts +389 -43
- package/src/cli/lib/daemon-credential-client.ts +284 -0
- package/src/cli.ts +1 -1
- package/src/config/bundled-skills/AGENTS.md +34 -0
- package/src/config/bundled-skills/acp/SKILL.md +10 -0
- package/src/config/bundled-skills/app-builder/SKILL.md +0 -4
- package/src/config/bundled-skills/messaging/tools/messaging-analyze-style.ts +2 -2
- package/src/config/bundled-skills/playbooks/tools/playbook-create.ts +1 -0
- package/src/config/bundled-skills/playbooks/tools/playbook-update.ts +1 -0
- package/src/config/bundled-skills/settings/SKILL.md +15 -2
- package/src/config/bundled-skills/settings/TOOLS.json +46 -1
- package/src/config/bundled-skills/settings/tools/avatar-remove.ts +59 -0
- package/src/config/bundled-skills/settings/tools/avatar-update.ts +80 -0
- package/src/config/bundled-skills/slack/SKILL.md +1 -1
- package/src/config/bundled-tool-registry.ts +4 -0
- package/src/config/defaults.ts +0 -2
- package/src/config/env-registry.ts +4 -4
- package/src/config/env.ts +14 -1
- package/src/config/feature-flag-registry.json +1 -1
- package/src/config/loader.ts +8 -11
- package/src/config/schema.ts +5 -16
- package/src/config/schemas/calls.ts +17 -0
- package/src/config/schemas/inference.ts +2 -2
- package/src/config/schemas/journal.ts +16 -0
- package/src/config/schemas/memory-processing.ts +2 -2
- package/src/config/types.ts +1 -0
- package/src/contacts/contact-store.ts +2 -2
- package/src/credential-execution/executable-discovery.ts +1 -1
- package/src/credential-execution/startup-timeout.ts +36 -0
- package/src/daemon/approval-generators.ts +3 -9
- package/src/daemon/conversation-agent-loop.ts +6 -0
- package/src/daemon/conversation-error.ts +13 -1
- package/src/daemon/conversation-memory.ts +1 -2
- package/src/daemon/conversation-process.ts +18 -1
- package/src/daemon/conversation-runtime-assembly.ts +61 -1
- package/src/daemon/conversation-surfaces.ts +30 -1
- package/src/daemon/conversation.ts +20 -9
- package/src/daemon/guardian-action-generators.ts +3 -9
- package/src/daemon/lifecycle.ts +18 -11
- package/src/daemon/message-types/conversations.ts +1 -0
- package/src/daemon/server.ts +2 -3
- package/src/memory/app-store.ts +31 -0
- package/src/memory/db-init.ts +4 -0
- package/src/memory/indexer.ts +19 -10
- package/src/memory/items-extractor.ts +315 -322
- package/src/memory/job-handlers/summarization.ts +26 -16
- package/src/memory/jobs-store.ts +33 -1
- package/src/memory/journal-memory.ts +214 -0
- package/src/memory/migrations/193-add-source-type-columns.ts +81 -0
- package/src/memory/migrations/index.ts +1 -0
- package/src/memory/migrations/registry.ts +8 -0
- package/src/memory/retriever.test.ts +37 -25
- package/src/memory/retriever.ts +24 -49
- package/src/memory/schema/memory-core.ts +2 -0
- package/src/memory/search/formatting.ts +7 -44
- package/src/memory/search/staleness.ts +4 -0
- package/src/memory/search/tier-classifier.ts +10 -2
- package/src/memory/search/types.ts +2 -5
- package/src/memory/task-memory-cleanup.ts +4 -3
- package/src/notifications/adapters/slack.ts +168 -6
- package/src/notifications/broadcaster.ts +1 -0
- package/src/notifications/copy-composer.ts +59 -2
- package/src/notifications/signal.ts +2 -0
- package/src/notifications/types.ts +2 -0
- package/src/prompts/journal-context.ts +133 -0
- package/src/prompts/persona-resolver.ts +80 -24
- package/src/prompts/system-prompt.ts +30 -0
- package/src/prompts/templates/NOW.md +26 -0
- package/src/prompts/templates/SOUL.md +20 -0
- package/src/prompts/update-bulletin-format.ts +0 -2
- package/src/providers/provider-send-message.ts +3 -32
- package/src/providers/registry.ts +2 -139
- package/src/providers/types.ts +1 -1
- package/src/runtime/access-request-helper.ts +4 -0
- package/src/runtime/auth/__tests__/guard-tests.test.ts +9 -50
- package/src/runtime/auth/route-policy.ts +2 -0
- package/src/runtime/gateway-client.ts +47 -4
- package/src/runtime/guardian-decision-types.ts +45 -4
- package/src/runtime/http-server.ts +5 -2
- package/src/runtime/routes/access-request-decision.ts +2 -2
- package/src/runtime/routes/app-management-routes.ts +2 -1
- package/src/runtime/routes/approval-strategies/guardian-callback-strategy.ts +219 -30
- package/src/runtime/routes/approval-strategies/guardian-text-engine-strategy.ts +37 -14
- package/src/runtime/routes/channel-readiness-routes.ts +9 -4
- package/src/runtime/routes/debug-routes.ts +12 -9
- package/src/runtime/routes/guardian-approval-interception.ts +168 -11
- package/src/runtime/routes/guardian-approval-prompt.ts +6 -1
- package/src/runtime/routes/guardian-approval-reply-helpers.ts +103 -21
- package/src/runtime/routes/identity-routes.ts +1 -1
- package/src/runtime/routes/inbound-message-handler.ts +31 -1
- package/src/runtime/routes/inbound-stages/acl-enforcement.ts +64 -5
- package/src/runtime/routes/inbound-stages/background-dispatch.ts +52 -40
- package/src/runtime/routes/integrations/twilio.ts +52 -10
- package/src/runtime/routes/memory-item-routes.test.ts +3 -3
- package/src/runtime/routes/memory-item-routes.ts +25 -11
- package/src/runtime/routes/secret-routes.ts +141 -10
- package/src/runtime/routes/tts-routes.ts +11 -1
- package/src/security/ces-credential-client.ts +18 -9
- package/src/security/ces-rpc-credential-backend.ts +4 -3
- package/src/security/credential-backend.ts +10 -4
- package/src/security/secure-keys.ts +21 -4
- package/src/skills/catalog-install.ts +4 -36
- package/src/skills/inline-command-expansions.ts +7 -7
- package/src/skills/skill-memory.ts +1 -0
- package/src/subagent/manager.ts +2 -5
- package/src/tools/acp/spawn.ts +78 -1
- package/src/tools/credentials/vault.ts +5 -3
- package/src/tools/memory/definitions.ts +3 -2
- package/src/tools/memory/handlers.ts +10 -7
- package/src/tools/sensitive-output-placeholders.ts +2 -2
- package/src/tools/terminal/safe-env.ts +1 -0
- package/src/util/browser.ts +15 -0
- package/src/util/platform.ts +1 -1
- package/src/workspace/migrations/016-migrate-credentials-from-keychain.ts +4 -4
- package/src/workspace/migrations/017-seed-persona-dirs.ts +2 -1
- package/src/workspace/migrations/018-rekey-compound-credential-keys.ts +184 -0
- package/src/workspace/migrations/019-scope-journal-to-guardian.ts +103 -0
- package/src/workspace/migrations/migrate-to-workspace-volume.ts +4 -4
- package/src/workspace/migrations/registry.ts +4 -0
- package/src/workspace/provider-commit-message-generator.ts +12 -21
- package/src/__tests__/provider-fail-open-selection.test.ts +0 -271
- package/src/__tests__/provider-failover-actual-provider.test.ts +0 -66
- package/src/memory/search/lexical.ts +0 -48
- package/src/providers/failover.ts +0 -186
|
@@ -19,17 +19,16 @@ import {
|
|
|
19
19
|
getProviderBehavior,
|
|
20
20
|
resolveService,
|
|
21
21
|
} from "../../../oauth/provider-behaviors.js";
|
|
22
|
-
import { credentialKey } from "../../../security/credential-key.js";
|
|
23
|
-
import {
|
|
24
|
-
deleteSecureKeyAsync,
|
|
25
|
-
getSecureKeyAsync,
|
|
26
|
-
} from "../../../security/secure-keys.js";
|
|
27
22
|
import { withValidToken } from "../../../security/token-manager.js";
|
|
28
23
|
import {
|
|
29
24
|
assertMetadataWritable,
|
|
30
25
|
deleteCredentialMetadata,
|
|
31
26
|
} from "../../../tools/credentials/metadata-store.js";
|
|
32
|
-
import {
|
|
27
|
+
import { openInBrowser } from "../../../util/browser.js";
|
|
28
|
+
import {
|
|
29
|
+
deleteSecureKeyViaDaemon,
|
|
30
|
+
getSecureKeyViaDaemon,
|
|
31
|
+
} from "../../lib/daemon-credential-client.js";
|
|
33
32
|
import { getCliLogger } from "../../logger.js";
|
|
34
33
|
import { shouldOutputJson, writeOutput } from "../../output.js";
|
|
35
34
|
|
|
@@ -538,8 +537,10 @@ Examples:
|
|
|
538
537
|
"client_secret",
|
|
539
538
|
];
|
|
540
539
|
for (const field of legacyFields) {
|
|
541
|
-
const
|
|
542
|
-
|
|
540
|
+
const result = await deleteSecureKeyViaDaemon(
|
|
541
|
+
"credential",
|
|
542
|
+
`${providerKey}:${field}`,
|
|
543
|
+
);
|
|
543
544
|
if (result === "deleted") cleanedUp = true;
|
|
544
545
|
|
|
545
546
|
const metaDeleted = deleteCredentialMetadata(providerKey, field);
|
|
@@ -633,7 +634,7 @@ Examples:
|
|
|
633
634
|
|
|
634
635
|
if (dbApp) {
|
|
635
636
|
if (!clientId) clientId = dbApp.clientId;
|
|
636
|
-
const storedSecret = await
|
|
637
|
+
const storedSecret = await getSecureKeyViaDaemon(
|
|
637
638
|
dbApp.clientSecretCredentialPath,
|
|
638
639
|
);
|
|
639
640
|
if (storedSecret) clientSecret = storedSecret;
|
|
@@ -685,26 +686,7 @@ Examples:
|
|
|
685
686
|
clientId,
|
|
686
687
|
clientSecret,
|
|
687
688
|
isInteractive: !!opts.openBrowser,
|
|
688
|
-
openUrl: opts.openBrowser
|
|
689
|
-
? (url) => {
|
|
690
|
-
if (isMacOS()) {
|
|
691
|
-
Bun.spawn(["open", url], {
|
|
692
|
-
stdout: "ignore",
|
|
693
|
-
stderr: "ignore",
|
|
694
|
-
});
|
|
695
|
-
} else if (isLinux()) {
|
|
696
|
-
Bun.spawn(["xdg-open", url], {
|
|
697
|
-
stdout: "ignore",
|
|
698
|
-
stderr: "ignore",
|
|
699
|
-
});
|
|
700
|
-
} else {
|
|
701
|
-
// Fallback: print URL for manual opening (stderr to keep --json stdout clean)
|
|
702
|
-
process.stderr.write(
|
|
703
|
-
`Open this URL to authorize:\n\n${url}\n`,
|
|
704
|
-
);
|
|
705
|
-
}
|
|
706
|
-
}
|
|
707
|
-
: undefined,
|
|
689
|
+
openUrl: opts.openBrowser ? openInBrowser : undefined,
|
|
708
690
|
...(opts.scopes ? { requestedScopes: opts.scopes } : {}),
|
|
709
691
|
});
|
|
710
692
|
|
|
@@ -7,11 +7,27 @@ import {
|
|
|
7
7
|
} from "../../../config/schemas/services.js";
|
|
8
8
|
import { getProvider } from "../../../oauth/oauth-store.js";
|
|
9
9
|
import { VellumPlatformClient } from "../../../platform/client.js";
|
|
10
|
+
import { openInBrowser } from "../../../util/browser.js";
|
|
10
11
|
import { getCliLogger } from "../../logger.js";
|
|
11
12
|
import { shouldOutputJson, writeOutput } from "../../output.js";
|
|
12
13
|
|
|
13
14
|
const log = getCliLogger("cli");
|
|
14
15
|
|
|
16
|
+
// ---------------------------------------------------------------------------
|
|
17
|
+
// Shared types
|
|
18
|
+
// ---------------------------------------------------------------------------
|
|
19
|
+
|
|
20
|
+
interface PlatformConnectionEntry {
|
|
21
|
+
id: string;
|
|
22
|
+
account_label?: string;
|
|
23
|
+
scopes_granted?: string[];
|
|
24
|
+
status?: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// ---------------------------------------------------------------------------
|
|
28
|
+
// Shared helpers
|
|
29
|
+
// ---------------------------------------------------------------------------
|
|
30
|
+
|
|
15
31
|
/**
|
|
16
32
|
* Normalize a bare provider name (e.g. "google") into the canonical provider
|
|
17
33
|
* key used internally (e.g. "integration:google").
|
|
@@ -22,17 +38,144 @@ function toProviderKey(provider: string): string {
|
|
|
22
38
|
: `integration:${provider}`;
|
|
23
39
|
}
|
|
24
40
|
|
|
41
|
+
/**
|
|
42
|
+
* Extract the bare provider slug (e.g. "google") from either a raw CLI
|
|
43
|
+
* argument or a canonical provider key (e.g. "integration:google").
|
|
44
|
+
* Platform API paths expect the bare slug, not the internal key.
|
|
45
|
+
*/
|
|
46
|
+
function toBareProvider(provider: string): string {
|
|
47
|
+
return provider.startsWith("integration:")
|
|
48
|
+
? provider.slice("integration:".length)
|
|
49
|
+
: provider;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Validate that a provider supports managed OAuth (has a managedServiceConfigKey).
|
|
54
|
+
* Does NOT check whether managed mode is currently enabled — use this for
|
|
55
|
+
* operations that should work regardless of the current mode (e.g. disconnect).
|
|
56
|
+
* Returns the managed config key on success, or writes an error and returns null.
|
|
57
|
+
*/
|
|
58
|
+
function requireManagedCapableProvider(
|
|
59
|
+
provider: string,
|
|
60
|
+
cmd: Command,
|
|
61
|
+
): string | null {
|
|
62
|
+
const providerKey = toProviderKey(provider);
|
|
63
|
+
const providerRow = getProvider(providerKey);
|
|
64
|
+
|
|
65
|
+
const managedKey = providerRow?.managedServiceConfigKey;
|
|
66
|
+
if (!managedKey || !(managedKey in ServicesSchema.shape)) {
|
|
67
|
+
writeOutput(cmd, {
|
|
68
|
+
ok: false,
|
|
69
|
+
error: `Provider "${provider}" does not support platform-managed OAuth`,
|
|
70
|
+
});
|
|
71
|
+
process.exitCode = 1;
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return managedKey;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Validate that a provider supports managed OAuth AND that managed mode is
|
|
80
|
+
* currently enabled. Use this for operations that require active managed
|
|
81
|
+
* mode (e.g. connect). Returns the managed config key on success, or writes
|
|
82
|
+
* an error and returns null.
|
|
83
|
+
*/
|
|
84
|
+
function requireManagedProvider(provider: string, cmd: Command): string | null {
|
|
85
|
+
const managedKey = requireManagedCapableProvider(provider, cmd);
|
|
86
|
+
if (!managedKey) return null;
|
|
87
|
+
|
|
88
|
+
const services: Services = getConfig().services;
|
|
89
|
+
const managedEnabled =
|
|
90
|
+
services[managedKey as keyof Services].mode === "managed";
|
|
91
|
+
|
|
92
|
+
if (!managedEnabled) {
|
|
93
|
+
writeOutput(cmd, {
|
|
94
|
+
ok: false,
|
|
95
|
+
error: `Provider "${provider}" supports managed OAuth but is set to "your-own" mode`,
|
|
96
|
+
});
|
|
97
|
+
process.exitCode = 1;
|
|
98
|
+
return null;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return managedKey;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Create an authenticated platform client, or write an error and return null.
|
|
106
|
+
*/
|
|
107
|
+
async function requirePlatformClient(
|
|
108
|
+
cmd: Command,
|
|
109
|
+
): Promise<VellumPlatformClient | null> {
|
|
110
|
+
const client = await VellumPlatformClient.create();
|
|
111
|
+
if (!client || !client.platformAssistantId) {
|
|
112
|
+
writeOutput(cmd, {
|
|
113
|
+
ok: false,
|
|
114
|
+
error:
|
|
115
|
+
"Platform prerequisites not met (not logged in or missing assistant ID)",
|
|
116
|
+
});
|
|
117
|
+
process.exitCode = 1;
|
|
118
|
+
return null;
|
|
119
|
+
}
|
|
120
|
+
return client;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Fetch active platform connections for a provider. Returns the parsed entries
|
|
125
|
+
* or writes an error and returns null.
|
|
126
|
+
*/
|
|
127
|
+
async function fetchActiveConnections(
|
|
128
|
+
client: VellumPlatformClient,
|
|
129
|
+
provider: string,
|
|
130
|
+
cmd: Command,
|
|
131
|
+
): Promise<PlatformConnectionEntry[] | null> {
|
|
132
|
+
const params = new URLSearchParams();
|
|
133
|
+
params.set("provider", toBareProvider(provider));
|
|
134
|
+
params.set("status", "ACTIVE");
|
|
135
|
+
|
|
136
|
+
const path = `/v1/assistants/${encodeURIComponent(client.platformAssistantId)}/oauth/connections/?${params.toString()}`;
|
|
137
|
+
const response = await client.fetch(path);
|
|
138
|
+
|
|
139
|
+
if (!response.ok) {
|
|
140
|
+
writeOutput(cmd, {
|
|
141
|
+
ok: false,
|
|
142
|
+
error: `Platform returned HTTP ${response.status}`,
|
|
143
|
+
});
|
|
144
|
+
process.exitCode = 1;
|
|
145
|
+
return null;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const body = (await response.json()) as unknown;
|
|
149
|
+
|
|
150
|
+
// The platform returns either a flat array or a {results: [...]} wrapper.
|
|
151
|
+
return (
|
|
152
|
+
Array.isArray(body)
|
|
153
|
+
? body
|
|
154
|
+
: ((body as Record<string, unknown>).results ?? [])
|
|
155
|
+
) as PlatformConnectionEntry[];
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// ---------------------------------------------------------------------------
|
|
159
|
+
// Command registration
|
|
160
|
+
// ---------------------------------------------------------------------------
|
|
161
|
+
|
|
25
162
|
export function registerPlatformCommands(oauth: Command): void {
|
|
26
163
|
const platform = oauth
|
|
27
164
|
.command("platform")
|
|
28
165
|
.description(
|
|
29
|
-
"
|
|
166
|
+
"Manage platform-managed OAuth provider status and connections",
|
|
30
167
|
);
|
|
31
168
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
169
|
+
registerStatusCommand(platform);
|
|
170
|
+
registerConnectCommand(platform);
|
|
171
|
+
registerDisconnectCommand(platform);
|
|
172
|
+
}
|
|
35
173
|
|
|
174
|
+
// ---------------------------------------------------------------------------
|
|
175
|
+
// platform status <provider>
|
|
176
|
+
// ---------------------------------------------------------------------------
|
|
177
|
+
|
|
178
|
+
function registerStatusCommand(platform: Command): void {
|
|
36
179
|
platform
|
|
37
180
|
.command("status <provider>")
|
|
38
181
|
.description(
|
|
@@ -102,46 +245,15 @@ Examples:
|
|
|
102
245
|
}
|
|
103
246
|
|
|
104
247
|
// 3. Fetch active connections from the platform
|
|
105
|
-
const client = await
|
|
106
|
-
if (!client
|
|
107
|
-
writeOutput(cmd, {
|
|
108
|
-
ok: false,
|
|
109
|
-
error:
|
|
110
|
-
"Platform prerequisites not met (not logged in or missing assistant ID)",
|
|
111
|
-
});
|
|
112
|
-
process.exitCode = 1;
|
|
113
|
-
return;
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
const params = new URLSearchParams();
|
|
117
|
-
params.set("provider", provider);
|
|
118
|
-
params.set("status", "ACTIVE");
|
|
119
|
-
|
|
120
|
-
const path = `/v1/assistants/${encodeURIComponent(client.platformAssistantId)}/oauth/connections/?${params.toString()}`;
|
|
121
|
-
const response = await client.fetch(path);
|
|
122
|
-
|
|
123
|
-
if (!response.ok) {
|
|
124
|
-
writeOutput(cmd, {
|
|
125
|
-
ok: false,
|
|
126
|
-
error: `Platform returned HTTP ${response.status}`,
|
|
127
|
-
});
|
|
128
|
-
process.exitCode = 1;
|
|
129
|
-
return;
|
|
130
|
-
}
|
|
248
|
+
const client = await requirePlatformClient(cmd);
|
|
249
|
+
if (!client) return;
|
|
131
250
|
|
|
132
|
-
const
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
: ((body as Record<string, unknown>).results ?? [])
|
|
139
|
-
) as Array<{
|
|
140
|
-
id: string;
|
|
141
|
-
account_label?: string;
|
|
142
|
-
scopes_granted?: string[];
|
|
143
|
-
status?: string;
|
|
144
|
-
}>;
|
|
251
|
+
const rawEntries = await fetchActiveConnections(
|
|
252
|
+
client,
|
|
253
|
+
provider,
|
|
254
|
+
cmd,
|
|
255
|
+
);
|
|
256
|
+
if (!rawEntries) return;
|
|
145
257
|
|
|
146
258
|
const connections = rawEntries.map((c) => ({
|
|
147
259
|
id: c.id,
|
|
@@ -177,3 +289,237 @@ Examples:
|
|
|
177
289
|
},
|
|
178
290
|
);
|
|
179
291
|
}
|
|
292
|
+
|
|
293
|
+
// ---------------------------------------------------------------------------
|
|
294
|
+
// platform connect <provider>
|
|
295
|
+
// ---------------------------------------------------------------------------
|
|
296
|
+
|
|
297
|
+
function registerConnectCommand(platform: Command): void {
|
|
298
|
+
platform
|
|
299
|
+
.command("connect <provider>")
|
|
300
|
+
.description(
|
|
301
|
+
"Initiate a platform-managed OAuth flow for a provider and open the browser",
|
|
302
|
+
)
|
|
303
|
+
.option(
|
|
304
|
+
"--scopes <scopes...>",
|
|
305
|
+
"Exact OAuth scopes to request (must be a subset of the provider's allowed scopes)",
|
|
306
|
+
)
|
|
307
|
+
.addHelpText(
|
|
308
|
+
"after",
|
|
309
|
+
`
|
|
310
|
+
Arguments:
|
|
311
|
+
provider Provider name (e.g. google, slack, twitter)
|
|
312
|
+
|
|
313
|
+
Starts a platform-managed OAuth authorization flow by calling the platform's
|
|
314
|
+
/start/ endpoint, then opens the returned authorization URL in the user's
|
|
315
|
+
browser. The user completes consent in the browser; the platform handles the
|
|
316
|
+
callback, token exchange, and credential storage.
|
|
317
|
+
|
|
318
|
+
The provider must support managed OAuth and managed mode must be enabled in
|
|
319
|
+
the services config.
|
|
320
|
+
|
|
321
|
+
Scope behavior:
|
|
322
|
+
Without --scopes, the platform requests ALL of the provider's allowed scopes.
|
|
323
|
+
With --scopes, only the specified scopes are requested (no merging with
|
|
324
|
+
defaults). Each scope must be in the provider's allowed set or the platform
|
|
325
|
+
will reject it. Use full scope URLs where required (e.g. Google scopes use
|
|
326
|
+
https://www.googleapis.com/auth/... format).
|
|
327
|
+
|
|
328
|
+
Examples:
|
|
329
|
+
$ assistant oauth platform connect google
|
|
330
|
+
$ assistant oauth platform connect google --scopes https://www.googleapis.com/auth/calendar https://www.googleapis.com/auth/calendar.events
|
|
331
|
+
$ assistant oauth platform connect google --json`,
|
|
332
|
+
)
|
|
333
|
+
.action(
|
|
334
|
+
async (provider: string, opts: { scopes?: string[] }, cmd: Command) => {
|
|
335
|
+
try {
|
|
336
|
+
if (!requireManagedProvider(provider, cmd)) return;
|
|
337
|
+
|
|
338
|
+
const client = await requirePlatformClient(cmd);
|
|
339
|
+
if (!client) return;
|
|
340
|
+
|
|
341
|
+
// Call the platform's OAuth start endpoint
|
|
342
|
+
const startPath = `/v1/assistants/${encodeURIComponent(client.platformAssistantId)}/oauth/${encodeURIComponent(toBareProvider(provider))}/start/`;
|
|
343
|
+
|
|
344
|
+
const body: Record<string, unknown> = {};
|
|
345
|
+
if (opts.scopes && opts.scopes.length > 0) {
|
|
346
|
+
body.requested_scopes = opts.scopes;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
const response = await client.fetch(startPath, {
|
|
350
|
+
method: "POST",
|
|
351
|
+
headers: { "Content-Type": "application/json" },
|
|
352
|
+
body: JSON.stringify(body),
|
|
353
|
+
});
|
|
354
|
+
|
|
355
|
+
if (!response.ok) {
|
|
356
|
+
const errorText = await response.text().catch(() => "");
|
|
357
|
+
writeOutput(cmd, {
|
|
358
|
+
ok: false,
|
|
359
|
+
error: `Platform returned HTTP ${response.status}${errorText ? `: ${errorText}` : ""}`,
|
|
360
|
+
});
|
|
361
|
+
process.exitCode = 1;
|
|
362
|
+
return;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
const result = (await response.json()) as {
|
|
366
|
+
connect_url?: string;
|
|
367
|
+
};
|
|
368
|
+
|
|
369
|
+
if (!result.connect_url) {
|
|
370
|
+
writeOutput(cmd, {
|
|
371
|
+
ok: false,
|
|
372
|
+
error:
|
|
373
|
+
"Platform did not return a connect URL — the OAuth flow could not be started",
|
|
374
|
+
});
|
|
375
|
+
process.exitCode = 1;
|
|
376
|
+
return;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
openInBrowser(result.connect_url);
|
|
380
|
+
|
|
381
|
+
writeOutput(cmd, {
|
|
382
|
+
ok: true,
|
|
383
|
+
deferred: true,
|
|
384
|
+
provider,
|
|
385
|
+
connectUrl: result.connect_url,
|
|
386
|
+
});
|
|
387
|
+
|
|
388
|
+
if (!shouldOutputJson(cmd)) {
|
|
389
|
+
log.info(
|
|
390
|
+
`Opening browser to connect ${provider}. Complete the authorization in your browser.`,
|
|
391
|
+
);
|
|
392
|
+
}
|
|
393
|
+
} catch (err) {
|
|
394
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
395
|
+
writeOutput(cmd, { ok: false, error: message });
|
|
396
|
+
process.exitCode = 1;
|
|
397
|
+
}
|
|
398
|
+
},
|
|
399
|
+
);
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
// ---------------------------------------------------------------------------
|
|
403
|
+
// platform disconnect <provider>
|
|
404
|
+
// ---------------------------------------------------------------------------
|
|
405
|
+
|
|
406
|
+
function registerDisconnectCommand(platform: Command): void {
|
|
407
|
+
platform
|
|
408
|
+
.command("disconnect <provider>")
|
|
409
|
+
.description(
|
|
410
|
+
"Disconnect a platform-managed OAuth connection for a provider",
|
|
411
|
+
)
|
|
412
|
+
.option(
|
|
413
|
+
"--connection-id <id>",
|
|
414
|
+
"Specific connection ID to disconnect (required when multiple active connections exist)",
|
|
415
|
+
)
|
|
416
|
+
.addHelpText(
|
|
417
|
+
"after",
|
|
418
|
+
`
|
|
419
|
+
Arguments:
|
|
420
|
+
provider Provider name (e.g. google, slack, twitter)
|
|
421
|
+
|
|
422
|
+
Disconnects a platform-managed OAuth connection by calling the platform's
|
|
423
|
+
/disconnect/ endpoint, which revokes the connection.
|
|
424
|
+
|
|
425
|
+
If the provider has multiple active connections, use --connection-id to specify
|
|
426
|
+
which one to disconnect. Without --connection-id, the command disconnects the
|
|
427
|
+
sole active connection or fails with a list of active connections if there are
|
|
428
|
+
multiple.
|
|
429
|
+
|
|
430
|
+
Examples:
|
|
431
|
+
$ assistant oauth platform disconnect google
|
|
432
|
+
$ assistant oauth platform disconnect google --connection-id conn_abc123
|
|
433
|
+
$ assistant oauth platform disconnect google --json`,
|
|
434
|
+
)
|
|
435
|
+
.action(
|
|
436
|
+
async (
|
|
437
|
+
provider: string,
|
|
438
|
+
opts: { connectionId?: string },
|
|
439
|
+
cmd: Command,
|
|
440
|
+
) => {
|
|
441
|
+
try {
|
|
442
|
+
if (!requireManagedCapableProvider(provider, cmd)) return;
|
|
443
|
+
|
|
444
|
+
const client = await requirePlatformClient(cmd);
|
|
445
|
+
if (!client) return;
|
|
446
|
+
|
|
447
|
+
// Always fetch active connections for the provider so we can
|
|
448
|
+
// verify --connection-id belongs to it (prevents cross-provider
|
|
449
|
+
// disconnects from typos or stale IDs).
|
|
450
|
+
const entries = await fetchActiveConnections(client, provider, cmd);
|
|
451
|
+
if (!entries) return;
|
|
452
|
+
|
|
453
|
+
let connectionId = opts.connectionId;
|
|
454
|
+
|
|
455
|
+
if (connectionId) {
|
|
456
|
+
// Verify the supplied ID belongs to this provider
|
|
457
|
+
if (!entries.some((c) => c.id === connectionId)) {
|
|
458
|
+
writeOutput(cmd, {
|
|
459
|
+
ok: false,
|
|
460
|
+
error: `Connection "${connectionId}" is not an active ${provider} connection`,
|
|
461
|
+
});
|
|
462
|
+
process.exitCode = 1;
|
|
463
|
+
return;
|
|
464
|
+
}
|
|
465
|
+
} else {
|
|
466
|
+
if (entries.length === 0) {
|
|
467
|
+
writeOutput(cmd, {
|
|
468
|
+
ok: false,
|
|
469
|
+
error: `No active connections found for provider "${provider}"`,
|
|
470
|
+
});
|
|
471
|
+
process.exitCode = 1;
|
|
472
|
+
return;
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
if (entries.length > 1) {
|
|
476
|
+
const connectionList = entries.map((c) => ({
|
|
477
|
+
id: c.id,
|
|
478
|
+
accountLabel: c.account_label ?? null,
|
|
479
|
+
}));
|
|
480
|
+
writeOutput(cmd, {
|
|
481
|
+
ok: false,
|
|
482
|
+
error: `Multiple active connections for "${provider}". Use --connection-id to specify which one to disconnect.`,
|
|
483
|
+
connections: connectionList,
|
|
484
|
+
});
|
|
485
|
+
process.exitCode = 1;
|
|
486
|
+
return;
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
connectionId = entries[0].id;
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
// Disconnect the connection
|
|
493
|
+
const disconnectPath = `/v1/assistants/${encodeURIComponent(client.platformAssistantId)}/oauth/connections/${encodeURIComponent(connectionId)}/disconnect/`;
|
|
494
|
+
const disconnectResponse = await client.fetch(disconnectPath, {
|
|
495
|
+
method: "POST",
|
|
496
|
+
headers: { "Content-Type": "application/json" },
|
|
497
|
+
});
|
|
498
|
+
|
|
499
|
+
if (!disconnectResponse.ok) {
|
|
500
|
+
const errorText = await disconnectResponse.text().catch(() => "");
|
|
501
|
+
writeOutput(cmd, {
|
|
502
|
+
ok: false,
|
|
503
|
+
error: `Platform returned HTTP ${disconnectResponse.status}${errorText ? `: ${errorText}` : ""}`,
|
|
504
|
+
});
|
|
505
|
+
process.exitCode = 1;
|
|
506
|
+
return;
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
writeOutput(cmd, {
|
|
510
|
+
ok: true,
|
|
511
|
+
provider,
|
|
512
|
+
connectionId,
|
|
513
|
+
});
|
|
514
|
+
|
|
515
|
+
if (!shouldOutputJson(cmd)) {
|
|
516
|
+
log.info(`Disconnected ${provider} connection ${connectionId}`);
|
|
517
|
+
}
|
|
518
|
+
} catch (err) {
|
|
519
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
520
|
+
writeOutput(cmd, { ok: false, error: message });
|
|
521
|
+
process.exitCode = 1;
|
|
522
|
+
}
|
|
523
|
+
},
|
|
524
|
+
);
|
|
525
|
+
}
|