@vellumai/assistant 0.7.2 → 0.7.3
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 +16 -1
- package/docs/architecture/memory.md +5 -2
- package/node_modules/@vellumai/gateway-client/src/ipc-client.ts +13 -4
- package/node_modules/@vellumai/skill-host-contracts/src/assistant-event.ts +0 -9
- package/node_modules/@vellumai/slack-text/src/index.test.ts +18 -35
- package/node_modules/@vellumai/slack-text/src/index.ts +2 -48
- package/openapi.yaml +449 -22
- package/package.json +1 -1
- package/src/__tests__/app-control-flow.test.ts +21 -11
- package/src/__tests__/assistant-event-hub.test.ts +48 -0
- package/src/__tests__/assistant-event.test.ts +0 -10
- package/src/__tests__/assistant-events-sse-hardening.test.ts +2 -7
- package/src/__tests__/assistant-feature-flags-integration.test.ts +18 -0
- package/src/__tests__/auto-analysis-end-to-end.test.ts +62 -1
- package/src/__tests__/background-workers-disk-pressure.test.ts +268 -0
- package/src/__tests__/call-conversation-messages.test.ts +8 -2
- package/src/__tests__/channel-inbound-disk-pressure.test.ts +537 -0
- package/src/__tests__/channel-readiness-service.test.ts +4 -2
- package/src/__tests__/config-loader-backfill.test.ts +379 -0
- package/src/__tests__/config-schema.test.ts +1 -0
- package/src/__tests__/config-watcher-cleanup-throttle.test.ts +18 -9
- package/src/__tests__/config-watcher.test.ts +140 -69
- package/src/__tests__/context-search-agent-runner.test.ts +61 -3
- package/src/__tests__/context-search-conversations-source.test.ts +0 -24
- package/src/__tests__/context-search-fanout.test.ts +0 -1
- package/src/__tests__/context-search-memory-source.test.ts +3 -7
- package/src/__tests__/context-search-memory-v2-source.test.ts +0 -2
- package/src/__tests__/context-search-pkb-source.test.ts +0 -1
- package/src/__tests__/context-search-workspace-source.test.ts +0 -1
- package/src/__tests__/conversation-abort-tool-results.test.ts +6 -0
- package/src/__tests__/conversation-agent-loop-disk-pressure.test.ts +223 -0
- package/src/__tests__/conversation-agent-loop.test.ts +454 -5
- package/src/__tests__/conversation-error.test.ts +150 -3
- package/src/__tests__/conversation-process-callsite.test.ts +43 -0
- package/src/__tests__/conversation-provider-retry-repair.test.ts +6 -0
- package/src/__tests__/conversation-runtime-assembly.test.ts +65 -0
- package/src/__tests__/conversation-slash-unknown.test.ts +6 -0
- package/src/__tests__/conversation-speed-override.test.ts +0 -3
- package/src/__tests__/conversation-store.test.ts +0 -18
- package/src/__tests__/conversation-surfaces-app-control.test.ts +15 -4
- package/src/__tests__/conversation-surfaces-data-persist.test.ts +404 -0
- package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +2 -5
- package/src/__tests__/conversation-workspace-injection.test.ts +6 -0
- package/src/__tests__/conversation-workspace-tool-tracking.test.ts +6 -0
- package/src/__tests__/credentials-cli.test.ts +7 -0
- package/src/__tests__/cu-unified-flow.test.ts +176 -10
- package/src/__tests__/date-context.test.ts +164 -2
- package/src/__tests__/disk-pressure-guard.test.ts +262 -0
- package/src/__tests__/disk-pressure-lifecycle.test.ts +168 -0
- package/src/__tests__/disk-pressure-policy.test.ts +241 -0
- package/src/__tests__/disk-pressure-routes.test.ts +379 -0
- package/src/__tests__/disk-pressure-tools.test.ts +277 -0
- package/src/__tests__/disk-usage.test.ts +150 -0
- package/src/__tests__/events-client-registration.test.ts +52 -0
- package/src/__tests__/events-dev-bypass-actor.test.ts +162 -0
- package/src/__tests__/file-write-tool.test.ts +4 -10
- package/src/__tests__/filing-service.test.ts +3 -4
- package/src/__tests__/heartbeat-disk-pressure.test.ts +183 -0
- package/src/__tests__/heartbeat-service.test.ts +260 -11
- package/src/__tests__/host-app-control-proxy.test.ts +195 -25
- package/src/__tests__/host-bash-proxy.test.ts +227 -34
- package/src/__tests__/host-bash-routes.test.ts +178 -13
- package/src/__tests__/host-cu-proxy.test.ts +210 -3
- package/src/__tests__/host-cu-routes-targeted.test.ts +141 -12
- package/src/__tests__/host-file-proxy-targeted.test.ts +48 -9
- package/src/__tests__/host-file-proxy.test.ts +268 -6
- package/src/__tests__/host-file-routes-targeted.test.ts +175 -17
- package/src/__tests__/host-transfer-proxy-targeted.test.ts +408 -59
- package/src/__tests__/host-transfer-routes-targeted.test.ts +232 -17
- package/src/__tests__/http-user-message-parity.test.ts +107 -1
- package/src/__tests__/injector-chain.test.ts +18 -6
- package/src/__tests__/injector-disk-pressure.test.ts +224 -0
- package/src/__tests__/managed-profile-guard.test.ts +18 -0
- package/src/__tests__/mcp-abort-signal.test.ts +130 -0
- package/src/__tests__/memory-admin-recall.test.ts +3 -11
- package/src/__tests__/memory-retrieval-pipeline.test.ts +22 -1
- package/src/__tests__/normalize-onboarding.test.ts +180 -0
- package/src/__tests__/oauth-connect-routes.test.ts +316 -0
- package/src/__tests__/oauth-provider-seed-logos.test.ts +24 -2
- package/src/__tests__/onboarding-persona-write.test.ts +308 -0
- package/src/__tests__/openai-provider.test.ts +45 -8
- package/src/__tests__/persist-onboarding-artifacts.test.ts +44 -64
- package/src/__tests__/platform-callback-registration.test.ts +21 -4
- package/src/__tests__/platform.test.ts +2 -1
- package/src/__tests__/playbook-execution.test.ts +0 -43
- package/src/__tests__/plugin-tool-contribution.test.ts +47 -0
- package/src/__tests__/prechat-onboarding-contract.test.ts +214 -27
- package/src/__tests__/provider-tool-name.test.ts +23 -0
- package/src/__tests__/relay-server.test.ts +15 -4
- package/src/__tests__/runtime-events-sse.test.ts +4 -8
- package/src/__tests__/scheduler-disk-pressure.test.ts +148 -0
- package/src/__tests__/secret-ingress-http.test.ts +0 -1
- package/src/__tests__/suggestion-routes.test.ts +46 -0
- package/src/__tests__/twilio-validation.test.ts +2 -2
- package/src/__tests__/workspace-migration-065-bump-stale-heartbeat-interval.test.ts +122 -0
- package/src/__tests__/workspace-migration-066-seed-heartbeat-callsite-cost-default.test.ts +285 -0
- package/src/__tests__/workspace-migration-068-release-notes-local-timezone.test.ts +90 -0
- package/src/__tests__/workspace-migration-safe-storage-limits-release.test.ts +90 -0
- package/src/approvals/guardian-decision-primitive.ts +13 -0
- package/src/approvals/guardian-request-resolvers.ts +16 -17
- package/src/backup/snapshot-lock.ts +2 -27
- package/src/bundler/compiler-tools.ts +3 -2
- package/src/calls/call-conversation-messages.ts +46 -10
- package/src/cli/commands/__tests__/webhooks.test.ts +0 -4
- package/src/cli/commands/bash.ts +35 -108
- package/src/cli/commands/contacts.ts +64 -25
- package/src/cli/commands/credentials.ts +56 -0
- package/src/cli/commands/memory-v2.ts +7 -6
- package/src/cli/commands/oauth/__tests__/connect.test.ts +437 -1
- package/src/cli/commands/oauth/connect.ts +127 -1
- package/src/cli/commands/platform/__tests__/callback-routes-list.test.ts +0 -3
- package/src/cli/commands/platform/__tests__/connect.test.ts +7 -1
- package/src/cli/commands/platform/__tests__/disconnect.test.ts +7 -1
- package/src/cli/commands/platform/__tests__/status.test.ts +103 -6
- package/src/cli/commands/platform/index.ts +16 -7
- package/src/cli/commands/status.ts +57 -0
- package/src/cli/program.ts +4 -2
- package/src/config/assistant-feature-flags.ts +13 -3
- package/src/config/bundled-skills/messaging/tools/messaging-analyze-style.ts +4 -3
- package/src/config/bundled-skills/phone-calls/references/TROUBLESHOOTING.md +13 -7
- package/src/config/bundled-skills/playbooks/tools/playbook-create.ts +2 -2
- package/src/config/bundled-skills/playbooks/tools/playbook-delete.ts +2 -2
- package/src/config/bundled-skills/playbooks/tools/playbook-list.ts +2 -2
- package/src/config/bundled-skills/playbooks/tools/playbook-update.ts +2 -2
- package/src/config/env.ts +0 -8
- package/src/config/feature-flag-registry.json +27 -3
- package/src/config/loader.ts +127 -8
- package/src/config/schemas/__tests__/memory-v2.test.ts +10 -5
- package/src/config/schemas/call-site-catalog.ts +14 -0
- package/src/config/schemas/channels.ts +0 -5
- package/src/config/schemas/heartbeat.ts +1 -1
- package/src/config/schemas/llm.ts +2 -0
- package/src/config/schemas/memory-lifecycle.ts +13 -0
- package/src/config/schemas/memory-v2.ts +75 -11
- package/src/config/schemas/platform.ts +43 -3
- package/src/config/schemas/services.ts +28 -0
- package/src/config/seed-inference-profiles.ts +230 -33
- package/src/contacts/contact-store.ts +0 -25
- package/src/daemon/__tests__/conversation-tool-setup.test.ts +86 -25
- package/src/daemon/assistant-attachments.ts +4 -4
- package/src/daemon/config-watcher.ts +85 -57
- package/src/daemon/conversation-agent-loop-handlers.ts +6 -0
- package/src/daemon/conversation-agent-loop.ts +170 -33
- package/src/daemon/conversation-error.ts +87 -15
- package/src/daemon/conversation-lifecycle.ts +1 -3
- package/src/daemon/conversation-process.ts +8 -0
- package/src/daemon/conversation-runtime-assembly.ts +26 -0
- package/src/daemon/conversation-store.ts +2 -2
- package/src/daemon/conversation-surfaces.ts +195 -15
- package/src/daemon/conversation-tool-setup.ts +57 -14
- package/src/daemon/conversation.ts +17 -22
- package/src/daemon/date-context.ts +71 -22
- package/src/daemon/disk-pressure-background-gate.ts +73 -0
- package/src/daemon/disk-pressure-guard.ts +343 -0
- package/src/daemon/disk-pressure-policy.ts +163 -0
- package/src/daemon/handlers/shared.ts +0 -1
- package/src/daemon/handlers/skills.ts +3 -4
- package/src/daemon/host-app-control-proxy.ts +137 -41
- package/src/daemon/host-bash-proxy.ts +46 -21
- package/src/daemon/host-cu-proxy.ts +49 -3
- package/src/daemon/host-file-proxy.ts +43 -7
- package/src/daemon/host-transfer-proxy.ts +95 -4
- package/src/daemon/lifecycle.ts +79 -28
- package/src/daemon/meet-host-supervisor.ts +4 -4
- package/src/daemon/meet-manifest-loader.ts +0 -1
- package/src/daemon/memory-v2-startup.ts +14 -4
- package/src/daemon/message-protocol.ts +3 -0
- package/src/daemon/message-types/conversations.ts +4 -0
- package/src/daemon/message-types/disk-pressure.ts +9 -0
- package/src/daemon/message-types/messages.ts +3 -0
- package/src/daemon/profiler-run-store.ts +5 -5
- package/src/daemon/tool-setup-types.ts +2 -2
- package/src/documents/document-store.ts +85 -0
- package/src/filing/filing-service.ts +30 -5
- package/src/heartbeat/__tests__/heartbeat-feed-event.test.ts +9 -16
- package/src/heartbeat/__tests__/heartbeat-run-store.test.ts +36 -0
- package/src/heartbeat/heartbeat-run-store.ts +13 -0
- package/src/heartbeat/heartbeat-service.ts +205 -31
- package/src/home/feed-scheduler.ts +18 -0
- package/src/inbound/platform-callback-registration.ts +8 -15
- package/src/ipc/__tests__/clients-list-ipc.test.ts +169 -0
- package/src/ipc/assistant-server.ts +56 -2
- package/src/ipc/gateway-client.ts +37 -3
- package/src/live-voice/live-voice-archive.ts +4 -4
- package/src/live-voice/protocol.ts +5 -7
- package/src/media/image-service.ts +1 -7
- package/src/memory/__tests__/fixtures/memory-v2-activation-fixtures.ts +21 -13
- package/src/memory/__tests__/jobs-worker-v2-schedule.test.ts +52 -22
- package/src/memory/__tests__/memory-v2-activation-log-store.test.ts +0 -6
- package/src/memory/__tests__/memory-v2-concept-frequency.test.ts +272 -0
- package/src/memory/admin.ts +5 -9
- package/src/memory/context-search/agent-runner.ts +19 -2
- package/src/memory/context-search/sources/conversations.ts +2 -11
- package/src/memory/context-search/sources/memory-v2.ts +5 -4
- package/src/memory/context-search/sources/memory.ts +0 -1
- package/src/memory/context-search/types.ts +0 -1
- package/src/memory/conversation-crud.ts +4 -12
- package/src/memory/db-init.ts +2 -0
- package/src/memory/embedding-runtime-manager.ts +119 -5
- package/src/memory/graph/__tests__/conversation-graph-memory-v2-routing.test.ts +32 -21
- package/src/memory/graph/conversation-graph-memory.ts +42 -54
- package/src/memory/graph/extraction.ts +1 -3
- package/src/memory/graph/graph-search.test.ts +10 -67
- package/src/memory/graph/graph-search.ts +1 -20
- package/src/memory/graph/retriever.test.ts +6 -0
- package/src/memory/graph/retriever.ts +6 -10
- package/src/memory/indexer.ts +54 -45
- package/src/memory/job-handlers/backfill.ts +2 -11
- package/src/memory/job-handlers/cleanup.ts +43 -0
- package/src/memory/job-handlers/embedding.ts +6 -8
- package/src/memory/job-handlers/summarization.ts +2 -7
- package/src/memory/jobs-store.ts +48 -0
- package/src/memory/jobs-worker.ts +81 -43
- package/src/memory/memory-v2-activation-log-store.ts +32 -14
- package/src/memory/memory-v2-concept-frequency.ts +169 -0
- package/src/memory/migrations/239-trace-events-created-at-index.ts +18 -0
- package/src/memory/migrations/index.ts +1 -0
- package/src/memory/pkb/pkb-search.test.ts +6 -0
- package/src/memory/qdrant-client.ts +0 -13
- package/src/memory/rerank-local.ts +374 -0
- package/src/memory/search/semantic.ts +6 -67
- package/src/memory/trace-event-store.ts +1 -17
- package/src/memory/v2/__tests__/activation.test.ts +311 -250
- package/src/memory/v2/__tests__/consolidation-job.test.ts +40 -8
- package/src/memory/v2/__tests__/injection.test.ts +157 -167
- package/src/memory/v2/__tests__/prompts-consolidation.test.ts +61 -2
- package/src/memory/v2/__tests__/qdrant.test.ts +16 -0
- package/src/memory/v2/__tests__/reranker.test.ts +338 -0
- package/src/memory/v2/__tests__/sim.test.ts +5 -199
- package/src/memory/v2/__tests__/skill-store.test.ts +71 -65
- package/src/memory/v2/__tests__/static-context.test.ts +76 -1
- package/src/memory/v2/activation.ts +149 -156
- package/src/memory/v2/consolidation-job.ts +62 -12
- package/src/memory/v2/injection.ts +47 -60
- package/src/memory/v2/prompts/consolidation.ts +36 -1
- package/src/memory/v2/qdrant.ts +99 -0
- package/src/memory/v2/reranker.ts +177 -0
- package/src/memory/v2/sim.ts +10 -84
- package/src/memory/v2/skill-content.ts +4 -3
- package/src/memory/v2/skill-store.ts +82 -59
- package/src/memory/v2/static-context.ts +22 -0
- package/src/memory/v2/types.ts +10 -10
- package/src/notifications/copy-composer.ts +13 -0
- package/src/notifications/signal.ts +4 -0
- package/src/oauth/AGENTS.md +3 -1
- package/src/oauth/__tests__/oauth-connect-state.test.ts +137 -0
- package/src/oauth/connect-orchestrator.ts +2 -0
- package/src/oauth/connection-resolver.test.ts +66 -1
- package/src/oauth/connection-resolver.ts +55 -1
- package/src/oauth/oauth-connect-state.ts +77 -0
- package/src/oauth/seed-providers.ts +58 -1
- package/src/plugins/defaults/injectors.ts +35 -2
- package/src/plugins/defaults/memory-retrieval.ts +5 -6
- package/src/plugins/types.ts +7 -0
- package/src/proactive-artifact/aux-message-injector.ts +74 -0
- package/src/proactive-artifact/decision.test.ts +226 -0
- package/src/proactive-artifact/decision.ts +165 -0
- package/src/proactive-artifact/index.ts +7 -0
- package/src/proactive-artifact/job.test.ts +867 -0
- package/src/proactive-artifact/job.ts +352 -0
- package/src/proactive-artifact/message-copy.ts +41 -0
- package/src/proactive-artifact/trigger-state.test.ts +277 -0
- package/src/proactive-artifact/trigger-state.ts +119 -0
- package/src/prompts/normalize-onboarding.ts +80 -0
- package/src/prompts/persona-resolver.ts +101 -9
- package/src/prompts/system-prompt.ts +21 -7
- package/src/prompts/templates/BOOTSTRAP.md +13 -5
- package/src/providers/__tests__/retry-callsite.test.ts +222 -1
- package/src/providers/model-intents.ts +7 -0
- package/src/providers/openrouter/client.ts +8 -0
- package/src/providers/retry.ts +50 -0
- package/src/providers/types.ts +1 -0
- package/src/runtime/__tests__/agent-wake.test.ts +456 -3
- package/src/runtime/agent-wake.ts +238 -100
- package/src/runtime/assistant-event-hub.ts +36 -6
- package/src/runtime/assistant-event.ts +0 -1
- package/src/runtime/auth/__tests__/route-policy.test.ts +64 -0
- package/src/runtime/auth/route-policy.ts +14 -1
- package/src/runtime/auth/same-actor.ts +216 -0
- package/src/runtime/channel-retry-sweep.ts +65 -1
- package/src/runtime/guardian-reply-router.ts +10 -0
- package/src/runtime/local-actor-identity.ts +52 -11
- package/src/runtime/pending-interactions.ts +8 -0
- package/src/runtime/routes/__tests__/client-routes.test.ts +155 -0
- package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +0 -5
- package/src/runtime/routes/__tests__/heartbeat-routes.test.ts +1 -1
- package/src/runtime/routes/client-routes.ts +20 -2
- package/src/runtime/routes/contact-routes.ts +0 -25
- package/src/runtime/routes/conversation-routes.ts +35 -26
- package/src/runtime/routes/debug-bash-routes.ts +163 -0
- package/src/runtime/routes/disk-pressure-routes.ts +121 -0
- package/src/runtime/routes/document-pdf-renderer.ts +6 -2
- package/src/runtime/routes/documents-routes.ts +2 -75
- package/src/runtime/routes/events-routes.ts +41 -9
- package/src/runtime/routes/host-bash-routes.ts +23 -3
- package/src/runtime/routes/host-cu-routes.ts +33 -6
- package/src/runtime/routes/host-file-routes.ts +32 -6
- package/src/runtime/routes/host-transfer-routes.ts +79 -16
- package/src/runtime/routes/identity-routes.ts +7 -138
- package/src/runtime/routes/inbound-message-handler.ts +77 -12
- package/src/runtime/routes/inbound-stages/guardian-reply-intercept.ts +3 -0
- package/src/runtime/routes/index.ts +6 -0
- package/src/runtime/routes/memory-item-routes.test.ts +41 -15
- package/src/runtime/routes/memory-v2-routes.ts +33 -0
- package/src/runtime/routes/oauth-connect-routes.ts +153 -0
- package/src/runtime/verification-outbound-actions.ts +4 -4
- package/src/schedule/run-script.ts +37 -5
- package/src/schedule/scheduler.ts +20 -1
- package/src/security/encrypted-store.ts +2 -0
- package/src/security/secure-keys.ts +55 -0
- package/src/skills/remote-skill-policy.ts +4 -10
- package/src/subagent/index.ts +1 -7
- package/src/subagent/manager.ts +1 -15
- package/src/tasks/task-runner.ts +0 -1
- package/src/tasks/task-store.ts +0 -3
- package/src/tools/background-tool-registry.ts +17 -3
- package/src/tools/host-filesystem/edit.test.ts +151 -0
- package/src/tools/host-filesystem/edit.ts +43 -1
- package/src/tools/host-filesystem/read.test.ts +129 -0
- package/src/tools/host-filesystem/read.ts +43 -1
- package/src/tools/host-filesystem/transfer.test.ts +127 -2
- package/src/tools/host-filesystem/transfer.ts +56 -11
- package/src/tools/host-filesystem/write.test.ts +134 -0
- package/src/tools/host-filesystem/write.ts +43 -1
- package/src/tools/host-terminal/host-shell.ts +13 -6
- package/src/tools/mcp/mcp-tool-factory.ts +2 -1
- package/src/tools/memory/register.test.ts +12 -9
- package/src/tools/memory/register.ts +1 -2
- package/src/tools/provider-tool-name.ts +28 -0
- package/src/tools/registry.ts +30 -9
- package/src/tools/terminal/shell.ts +9 -1
- package/src/tools/tool-approval-handler.ts +31 -6
- package/src/tools/types.ts +24 -2
- package/src/tts/provider-catalog.ts +3 -5
- package/src/util/disk-usage.ts +138 -0
- package/src/util/platform.ts +21 -11
- package/src/util/process-liveness.ts +26 -0
- package/src/workspace/heartbeat-service.ts +19 -0
- package/src/workspace/migrations/065-bump-stale-heartbeat-interval.ts +60 -0
- package/src/workspace/migrations/066-seed-heartbeat-callsite-cost-default.ts +146 -0
- package/src/workspace/migrations/067-release-notes-safe-storage-limits.ts +72 -0
- package/src/workspace/migrations/068-release-notes-local-timezone.ts +65 -0
- package/src/workspace/migrations/registry.ts +8 -0
- package/src/__tests__/conversation-tool-setup-memory-scope.test.ts +0 -167
- package/src/memory/v2/__tests__/skill-qdrant.test.ts +0 -657
- package/src/memory/v2/skill-qdrant.ts +0 -404
- package/src/signals/bash.ts +0 -198
|
@@ -42,6 +42,20 @@ let mockPlatformFetchCallIndex = 0;
|
|
|
42
42
|
|
|
43
43
|
let mockIsManagedMode: (key: string) => boolean = () => false;
|
|
44
44
|
|
|
45
|
+
// Configurable logger mock: by default no-ops; individual tests can override
|
|
46
|
+
// mockLogInfo to write to process.stdout so the JSON-mode suppression guard is
|
|
47
|
+
// exercised (the real CLI logger writes log lines to stdout).
|
|
48
|
+
let mockLogInfo: (msg: string) => void = () => {};
|
|
49
|
+
|
|
50
|
+
let mockCliIpcCallFn: (
|
|
51
|
+
method: string,
|
|
52
|
+
params?: Record<string, unknown>,
|
|
53
|
+
opts?: { timeoutMs?: number },
|
|
54
|
+
) => Promise<{ ok: boolean; result?: unknown; error?: string; statusCode?: number }> = async () => ({
|
|
55
|
+
ok: false,
|
|
56
|
+
error: "IPC unavailable (default mock — forces fallback)",
|
|
57
|
+
});
|
|
58
|
+
|
|
45
59
|
// ---------------------------------------------------------------------------
|
|
46
60
|
// Mocks
|
|
47
61
|
// ---------------------------------------------------------------------------
|
|
@@ -101,7 +115,7 @@ mock.module("../../../../util/logger.js", () => ({
|
|
|
101
115
|
debug: () => {},
|
|
102
116
|
}),
|
|
103
117
|
getCliLogger: () => ({
|
|
104
|
-
info: () =>
|
|
118
|
+
info: (msg: string) => mockLogInfo(msg),
|
|
105
119
|
warn: () => {},
|
|
106
120
|
error: () => {},
|
|
107
121
|
debug: () => {},
|
|
@@ -127,6 +141,14 @@ mock.module("../../../../security/secure-keys.js", () => ({
|
|
|
127
141
|
_resetBackend: () => {},
|
|
128
142
|
}));
|
|
129
143
|
|
|
144
|
+
mock.module("../../../../ipc/cli-client.js", () => ({
|
|
145
|
+
cliIpcCall: (
|
|
146
|
+
method: string,
|
|
147
|
+
params?: Record<string, unknown>,
|
|
148
|
+
opts?: { timeoutMs?: number },
|
|
149
|
+
) => mockCliIpcCallFn(method, params, opts),
|
|
150
|
+
}));
|
|
151
|
+
|
|
130
152
|
mock.module("../../../lib/daemon-credential-client.js", () => ({
|
|
131
153
|
deleteSecureKeyViaDaemon: async () => "not-found" as const,
|
|
132
154
|
setSecureKeyViaDaemon: async () => false,
|
|
@@ -254,6 +276,8 @@ describe("assistant oauth connect", () => {
|
|
|
254
276
|
mockPlatformFetchResults = [];
|
|
255
277
|
mockPlatformFetchCallIndex = 0;
|
|
256
278
|
mockIsManagedMode = () => false;
|
|
279
|
+
mockCliIpcCallFn = async () => ({ ok: false, error: "IPC unavailable" });
|
|
280
|
+
mockLogInfo = () => {};
|
|
257
281
|
process.exitCode = 0;
|
|
258
282
|
});
|
|
259
283
|
|
|
@@ -724,6 +748,418 @@ describe("assistant oauth connect", () => {
|
|
|
724
748
|
expect(parsed.error).toContain("--field");
|
|
725
749
|
});
|
|
726
750
|
|
|
751
|
+
// -------------------------------------------------------------------------
|
|
752
|
+
// IPC-first path (daemon-orchestrated)
|
|
753
|
+
// -------------------------------------------------------------------------
|
|
754
|
+
|
|
755
|
+
describe("IPC-first path (BYO mode via daemon)", () => {
|
|
756
|
+
beforeEach(() => {
|
|
757
|
+
// Set up a valid BYO provider and app for all IPC tests
|
|
758
|
+
mockGetProvider = () => ({
|
|
759
|
+
provider: "google",
|
|
760
|
+
authorizeUrl: "https://accounts.google.com/o/oauth2/v2/auth",
|
|
761
|
+
tokenExchangeUrl: "https://oauth2.googleapis.com/token",
|
|
762
|
+
tokenExchangeBodyFormat: "form",
|
|
763
|
+
managedServiceConfigKey: null,
|
|
764
|
+
});
|
|
765
|
+
mockIsManagedMode = () => false;
|
|
766
|
+
mockGetMostRecentAppByProvider = () => ({
|
|
767
|
+
id: "app-1",
|
|
768
|
+
clientId: "ipc-client-id",
|
|
769
|
+
clientSecretCredentialPath: "oauth_app/app-1/client_secret",
|
|
770
|
+
provider: "google",
|
|
771
|
+
createdAt: 0,
|
|
772
|
+
updatedAt: 0,
|
|
773
|
+
});
|
|
774
|
+
});
|
|
775
|
+
|
|
776
|
+
test("IPC start succeeds + polling returns complete → exits 0 with success output", async () => {
|
|
777
|
+
let pollCallCount = 0;
|
|
778
|
+
mockCliIpcCallFn = async (method) => {
|
|
779
|
+
if (method === "internal_oauth_connect_start") {
|
|
780
|
+
return {
|
|
781
|
+
ok: true,
|
|
782
|
+
result: {
|
|
783
|
+
auth_url: "https://accounts.google.com/o/oauth2/auth?state=ipc-state",
|
|
784
|
+
state: "ipc-state",
|
|
785
|
+
},
|
|
786
|
+
};
|
|
787
|
+
}
|
|
788
|
+
if (method === "internal_oauth_connect_status") {
|
|
789
|
+
pollCallCount++;
|
|
790
|
+
return {
|
|
791
|
+
ok: true,
|
|
792
|
+
result: {
|
|
793
|
+
status: "complete",
|
|
794
|
+
service: "google",
|
|
795
|
+
account_info: "user@example.com",
|
|
796
|
+
},
|
|
797
|
+
};
|
|
798
|
+
}
|
|
799
|
+
return { ok: false, error: "unexpected method" };
|
|
800
|
+
};
|
|
801
|
+
|
|
802
|
+
const { exitCode, stdout } = await runCommand([
|
|
803
|
+
"connect",
|
|
804
|
+
"google",
|
|
805
|
+
"--json",
|
|
806
|
+
]);
|
|
807
|
+
expect(exitCode).toBe(0);
|
|
808
|
+
const parsed = JSON.parse(stdout);
|
|
809
|
+
expect(parsed.ok).toBe(true);
|
|
810
|
+
expect(parsed.accountInfo).toBe("user@example.com");
|
|
811
|
+
expect(mockOpenInBrowserCalls.length).toBe(1);
|
|
812
|
+
expect(mockOpenInBrowserCalls[0]).toBe(
|
|
813
|
+
"https://accounts.google.com/o/oauth2/auth?state=ipc-state",
|
|
814
|
+
);
|
|
815
|
+
expect(pollCallCount).toBeGreaterThanOrEqual(1);
|
|
816
|
+
});
|
|
817
|
+
|
|
818
|
+
test("IPC start succeeds + polling returns error → exits 1 with error message", async () => {
|
|
819
|
+
mockCliIpcCallFn = async (method) => {
|
|
820
|
+
if (method === "internal_oauth_connect_start") {
|
|
821
|
+
return {
|
|
822
|
+
ok: true,
|
|
823
|
+
result: {
|
|
824
|
+
auth_url: "https://accounts.google.com/o/oauth2/auth?state=ipc-state",
|
|
825
|
+
state: "ipc-state",
|
|
826
|
+
},
|
|
827
|
+
};
|
|
828
|
+
}
|
|
829
|
+
if (method === "internal_oauth_connect_status") {
|
|
830
|
+
return {
|
|
831
|
+
ok: true,
|
|
832
|
+
result: {
|
|
833
|
+
status: "error",
|
|
834
|
+
service: "google",
|
|
835
|
+
error: "exchange failed",
|
|
836
|
+
},
|
|
837
|
+
};
|
|
838
|
+
}
|
|
839
|
+
return { ok: false, error: "unexpected method" };
|
|
840
|
+
};
|
|
841
|
+
|
|
842
|
+
const { exitCode, stdout } = await runCommand([
|
|
843
|
+
"connect",
|
|
844
|
+
"google",
|
|
845
|
+
"--json",
|
|
846
|
+
]);
|
|
847
|
+
expect(exitCode).toBe(1);
|
|
848
|
+
const parsed = JSON.parse(stdout);
|
|
849
|
+
expect(parsed.ok).toBe(false);
|
|
850
|
+
expect(parsed.error).toBe("exchange failed");
|
|
851
|
+
});
|
|
852
|
+
|
|
853
|
+
test("IPC start + --no-browser + json → returns deferred JSON without polling status", async () => {
|
|
854
|
+
let statusCallCount = 0;
|
|
855
|
+
mockCliIpcCallFn = async (method) => {
|
|
856
|
+
if (method === "internal_oauth_connect_start") {
|
|
857
|
+
return {
|
|
858
|
+
ok: true,
|
|
859
|
+
result: {
|
|
860
|
+
auth_url: "https://accounts.google.com/o/oauth2/auth?state=ipc-state",
|
|
861
|
+
state: "ipc-state",
|
|
862
|
+
},
|
|
863
|
+
};
|
|
864
|
+
}
|
|
865
|
+
if (method === "internal_oauth_connect_status") {
|
|
866
|
+
statusCallCount++;
|
|
867
|
+
}
|
|
868
|
+
return { ok: false, error: "unexpected method" };
|
|
869
|
+
};
|
|
870
|
+
|
|
871
|
+
const { exitCode, stdout } = await runCommand([
|
|
872
|
+
"connect",
|
|
873
|
+
"google",
|
|
874
|
+
"--no-browser",
|
|
875
|
+
"--json",
|
|
876
|
+
]);
|
|
877
|
+
expect(exitCode).toBe(0);
|
|
878
|
+
const parsed = JSON.parse(stdout);
|
|
879
|
+
expect(parsed.ok).toBe(true);
|
|
880
|
+
expect(parsed.deferred).toBe(true);
|
|
881
|
+
expect(parsed.authUrl).toBe("https://accounts.google.com/o/oauth2/auth?state=ipc-state");
|
|
882
|
+
expect(parsed.state).toBe("ipc-state");
|
|
883
|
+
expect(parsed.service).toBe("google");
|
|
884
|
+
// Should NOT poll status when --no-browser is set
|
|
885
|
+
expect(statusCallCount).toBe(0);
|
|
886
|
+
// Should NOT open browser
|
|
887
|
+
expect(mockOpenInBrowserCalls.length).toBe(0);
|
|
888
|
+
});
|
|
889
|
+
|
|
890
|
+
test("IPC start + --no-browser without json → prints URL to stdout", async () => {
|
|
891
|
+
mockCliIpcCallFn = async (method) => {
|
|
892
|
+
if (method === "internal_oauth_connect_start") {
|
|
893
|
+
return {
|
|
894
|
+
ok: true,
|
|
895
|
+
result: {
|
|
896
|
+
auth_url: "https://accounts.google.com/o/oauth2/auth?state=ipc-state",
|
|
897
|
+
state: "ipc-state",
|
|
898
|
+
},
|
|
899
|
+
};
|
|
900
|
+
}
|
|
901
|
+
return { ok: false, error: "unexpected method" };
|
|
902
|
+
};
|
|
903
|
+
|
|
904
|
+
const { exitCode, stdout } = await runCommand([
|
|
905
|
+
"connect",
|
|
906
|
+
"google",
|
|
907
|
+
"--no-browser",
|
|
908
|
+
]);
|
|
909
|
+
expect(exitCode).toBe(0);
|
|
910
|
+
expect(stdout).toContain("https://accounts.google.com/o/oauth2/auth?state=ipc-state");
|
|
911
|
+
expect(mockOpenInBrowserCalls.length).toBe(0);
|
|
912
|
+
});
|
|
913
|
+
|
|
914
|
+
test("IPC returns ok:false → falls back to in-process orchestrateOAuthConnect", async () => {
|
|
915
|
+
// Default mockCliIpcCallFn already returns ok: false
|
|
916
|
+
let orchestratorCalled = false;
|
|
917
|
+
mockOrchestrateOAuthConnect = async () => {
|
|
918
|
+
orchestratorCalled = true;
|
|
919
|
+
return {
|
|
920
|
+
success: true,
|
|
921
|
+
deferred: false,
|
|
922
|
+
grantedScopes: ["email"],
|
|
923
|
+
accountInfo: "fallback@example.com",
|
|
924
|
+
};
|
|
925
|
+
};
|
|
926
|
+
|
|
927
|
+
const { exitCode, stdout } = await runCommand([
|
|
928
|
+
"connect",
|
|
929
|
+
"google",
|
|
930
|
+
"--json",
|
|
931
|
+
]);
|
|
932
|
+
expect(exitCode).toBe(0);
|
|
933
|
+
expect(orchestratorCalled).toBe(true);
|
|
934
|
+
const parsed = JSON.parse(stdout);
|
|
935
|
+
expect(parsed.ok).toBe(true);
|
|
936
|
+
expect(parsed.accountInfo).toBe("fallback@example.com");
|
|
937
|
+
});
|
|
938
|
+
|
|
939
|
+
test("IPC returns ok:false with statusCode → surfaces daemon error, does NOT fall back", async () => {
|
|
940
|
+
// Daemon was reachable but returned an error (e.g. 500)
|
|
941
|
+
mockCliIpcCallFn = async (method) => {
|
|
942
|
+
if (method === "internal_oauth_connect_start") {
|
|
943
|
+
return { ok: false, statusCode: 500, error: "internal server error" };
|
|
944
|
+
}
|
|
945
|
+
return { ok: false, error: "unexpected method" };
|
|
946
|
+
};
|
|
947
|
+
let orchestratorCalled = false;
|
|
948
|
+
mockOrchestrateOAuthConnect = async () => {
|
|
949
|
+
orchestratorCalled = true;
|
|
950
|
+
return { success: true, deferred: false, grantedScopes: [] };
|
|
951
|
+
};
|
|
952
|
+
|
|
953
|
+
const { exitCode, stdout } = await runCommand([
|
|
954
|
+
"connect",
|
|
955
|
+
"google",
|
|
956
|
+
"--json",
|
|
957
|
+
]);
|
|
958
|
+
expect(exitCode).toBe(1);
|
|
959
|
+
// Must NOT fall back to the in-process orchestrator
|
|
960
|
+
expect(orchestratorCalled).toBe(false);
|
|
961
|
+
const parsed = JSON.parse(stdout);
|
|
962
|
+
expect(parsed.ok).toBe(false);
|
|
963
|
+
expect(parsed.error).toBe("internal server error");
|
|
964
|
+
});
|
|
965
|
+
|
|
966
|
+
test("IPC poll returns ok:false with statusCode → breaks early with error, does NOT wait for timeout", async () => {
|
|
967
|
+
// Fix 1: daemon was reachable during status poll but errored — should surface the
|
|
968
|
+
// error immediately instead of waiting out the full 5-minute timeout.
|
|
969
|
+
mockCliIpcCallFn = async (method) => {
|
|
970
|
+
if (method === "internal_oauth_connect_start") {
|
|
971
|
+
return {
|
|
972
|
+
ok: true,
|
|
973
|
+
result: {
|
|
974
|
+
auth_url: "https://accounts.google.com/o/oauth2/auth?state=poll-err-state",
|
|
975
|
+
state: "poll-err-state",
|
|
976
|
+
},
|
|
977
|
+
};
|
|
978
|
+
}
|
|
979
|
+
if (method === "internal_oauth_connect_status") {
|
|
980
|
+
return { ok: false, statusCode: 500, error: "poll error" };
|
|
981
|
+
}
|
|
982
|
+
return { ok: false, error: "unexpected method" };
|
|
983
|
+
};
|
|
984
|
+
|
|
985
|
+
const { exitCode, stdout } = await runCommand([
|
|
986
|
+
"connect",
|
|
987
|
+
"google",
|
|
988
|
+
"--json",
|
|
989
|
+
]);
|
|
990
|
+
expect(exitCode).toBe(1);
|
|
991
|
+
const parsed = JSON.parse(stdout);
|
|
992
|
+
expect(parsed.ok).toBe(false);
|
|
993
|
+
// The daemon error should be surfaced, not a timeout sentinel
|
|
994
|
+
expect(parsed.error).toBe("poll error");
|
|
995
|
+
});
|
|
996
|
+
|
|
997
|
+
test("IPC start returns ok:true with no auth_url → surfaces error, does NOT call in-process orchestrator", async () => {
|
|
998
|
+
// Fix 2: daemon returns { ok: true } but without an auth_url — malformed response
|
|
999
|
+
// should be an error, not a silent fallback to in-process (which has heap-split bug).
|
|
1000
|
+
mockCliIpcCallFn = async (method) => {
|
|
1001
|
+
if (method === "internal_oauth_connect_start") {
|
|
1002
|
+
return { ok: true, result: {} };
|
|
1003
|
+
}
|
|
1004
|
+
return { ok: false, error: "unexpected method" };
|
|
1005
|
+
};
|
|
1006
|
+
let orchestratorCalled = false;
|
|
1007
|
+
mockOrchestrateOAuthConnect = async () => {
|
|
1008
|
+
orchestratorCalled = true;
|
|
1009
|
+
return { success: true, deferred: false, grantedScopes: [] };
|
|
1010
|
+
};
|
|
1011
|
+
|
|
1012
|
+
const { exitCode, stdout } = await runCommand([
|
|
1013
|
+
"connect",
|
|
1014
|
+
"google",
|
|
1015
|
+
"--json",
|
|
1016
|
+
]);
|
|
1017
|
+
expect(exitCode).toBe(1);
|
|
1018
|
+
// Must NOT fall back to the in-process orchestrator
|
|
1019
|
+
expect(orchestratorCalled).toBe(false);
|
|
1020
|
+
const parsed = JSON.parse(stdout);
|
|
1021
|
+
expect(parsed.ok).toBe(false);
|
|
1022
|
+
expect(parsed.error).toContain("assistant returned unexpected response");
|
|
1023
|
+
});
|
|
1024
|
+
|
|
1025
|
+
test("IPC poll: transient ok:false with no statusCode does not abort the flow (continues to next poll)", async () => {
|
|
1026
|
+
// Verifies intentional behavior: a single IPC status call returning { ok: false }
|
|
1027
|
+
// with NO statusCode (socket error / timeout) is treated as a transient failure and
|
|
1028
|
+
// silently retried. Only ok:false WITH a statusCode (i.e., the daemon was reachable
|
|
1029
|
+
// and returned an HTTP error) causes an early abort.
|
|
1030
|
+
let statusCallCount = 0;
|
|
1031
|
+
mockCliIpcCallFn = async (method) => {
|
|
1032
|
+
if (method === "internal_oauth_connect_start") {
|
|
1033
|
+
return {
|
|
1034
|
+
ok: true,
|
|
1035
|
+
result: {
|
|
1036
|
+
auth_url: "https://accounts.google.com/o/oauth2/auth?state=transient-state",
|
|
1037
|
+
state: "transient-state",
|
|
1038
|
+
},
|
|
1039
|
+
};
|
|
1040
|
+
}
|
|
1041
|
+
if (method === "internal_oauth_connect_status") {
|
|
1042
|
+
statusCallCount++;
|
|
1043
|
+
if (statusCallCount === 1) {
|
|
1044
|
+
// First poll: transient IPC failure (no statusCode — socket error/timeout)
|
|
1045
|
+
return { ok: false };
|
|
1046
|
+
}
|
|
1047
|
+
// Second poll: succeeds
|
|
1048
|
+
return {
|
|
1049
|
+
ok: true,
|
|
1050
|
+
result: {
|
|
1051
|
+
status: "complete",
|
|
1052
|
+
service: "google",
|
|
1053
|
+
account_info: "user@example.com",
|
|
1054
|
+
},
|
|
1055
|
+
};
|
|
1056
|
+
}
|
|
1057
|
+
return { ok: false, error: "unexpected method" };
|
|
1058
|
+
};
|
|
1059
|
+
|
|
1060
|
+
const { exitCode, stdout } = await runCommand([
|
|
1061
|
+
"connect",
|
|
1062
|
+
"google",
|
|
1063
|
+
"--json",
|
|
1064
|
+
]);
|
|
1065
|
+
expect(exitCode).toBe(0);
|
|
1066
|
+
const parsed = JSON.parse(stdout);
|
|
1067
|
+
expect(parsed.ok).toBe(true);
|
|
1068
|
+
expect(parsed.accountInfo).toBe("user@example.com");
|
|
1069
|
+
// Both poll calls were made — the transient failure did not abort the loop
|
|
1070
|
+
expect(statusCallCount).toBeGreaterThanOrEqual(2);
|
|
1071
|
+
});
|
|
1072
|
+
|
|
1073
|
+
test("IPC success path with --json: stdout does NOT contain 'Waiting for authorization' text", async () => {
|
|
1074
|
+
// Regression guard for P1: the browser-wait log.info must be suppressed in JSON mode
|
|
1075
|
+
// so that machine consumers parsing stdout as JSON don't see corrupted non-JSON output.
|
|
1076
|
+
//
|
|
1077
|
+
// We configure the logger mock to write to process.stdout (matching the real CLI logger's
|
|
1078
|
+
// behavior) so this test would FAIL if the `if (!jsonMode)` guard were removed from connect.ts.
|
|
1079
|
+
mockLogInfo = (msg: string) => {
|
|
1080
|
+
process.stdout.write(msg + "\n");
|
|
1081
|
+
};
|
|
1082
|
+
|
|
1083
|
+
mockCliIpcCallFn = async (method) => {
|
|
1084
|
+
if (method === "internal_oauth_connect_start") {
|
|
1085
|
+
return {
|
|
1086
|
+
ok: true,
|
|
1087
|
+
result: {
|
|
1088
|
+
auth_url: "https://accounts.google.com/o/oauth2/auth?state=json-mode-state",
|
|
1089
|
+
state: "json-mode-state",
|
|
1090
|
+
},
|
|
1091
|
+
};
|
|
1092
|
+
}
|
|
1093
|
+
if (method === "internal_oauth_connect_status") {
|
|
1094
|
+
return {
|
|
1095
|
+
ok: true,
|
|
1096
|
+
result: {
|
|
1097
|
+
status: "complete",
|
|
1098
|
+
service: "google",
|
|
1099
|
+
account_info: "user@example.com",
|
|
1100
|
+
},
|
|
1101
|
+
};
|
|
1102
|
+
}
|
|
1103
|
+
return { ok: false, error: "unexpected method" };
|
|
1104
|
+
};
|
|
1105
|
+
|
|
1106
|
+
const { exitCode, stdout } = await runCommand([
|
|
1107
|
+
"connect",
|
|
1108
|
+
"google",
|
|
1109
|
+
"--json",
|
|
1110
|
+
]);
|
|
1111
|
+
expect(exitCode).toBe(0);
|
|
1112
|
+
// The suppressed log line must not appear anywhere in stdout
|
|
1113
|
+
expect(stdout).not.toContain("Waiting for authorization");
|
|
1114
|
+
// stdout must be valid JSON — no plain-text lines mixed in
|
|
1115
|
+
expect(() => JSON.parse(stdout)).not.toThrow();
|
|
1116
|
+
const parsed = JSON.parse(stdout);
|
|
1117
|
+
expect(parsed.ok).toBe(true);
|
|
1118
|
+
});
|
|
1119
|
+
|
|
1120
|
+
test("IPC start with --callback-transport=gateway passes callbackTransport in body", async () => {
|
|
1121
|
+
let capturedParams: Record<string, unknown> | undefined;
|
|
1122
|
+
mockCliIpcCallFn = async (method, params) => {
|
|
1123
|
+
if (method === "internal_oauth_connect_start") {
|
|
1124
|
+
capturedParams = params;
|
|
1125
|
+
return {
|
|
1126
|
+
ok: true,
|
|
1127
|
+
result: {
|
|
1128
|
+
auth_url: "https://accounts.google.com/o/oauth2/auth?state=gw-state",
|
|
1129
|
+
state: "gw-state",
|
|
1130
|
+
},
|
|
1131
|
+
};
|
|
1132
|
+
}
|
|
1133
|
+
if (method === "internal_oauth_connect_status") {
|
|
1134
|
+
return {
|
|
1135
|
+
ok: true,
|
|
1136
|
+
result: {
|
|
1137
|
+
status: "complete",
|
|
1138
|
+
service: "google",
|
|
1139
|
+
account_info: "gw-user@example.com",
|
|
1140
|
+
},
|
|
1141
|
+
};
|
|
1142
|
+
}
|
|
1143
|
+
return { ok: false, error: "unexpected method" };
|
|
1144
|
+
};
|
|
1145
|
+
|
|
1146
|
+
const { exitCode, stdout } = await runCommand([
|
|
1147
|
+
"connect",
|
|
1148
|
+
"google",
|
|
1149
|
+
"--callback-transport",
|
|
1150
|
+
"gateway",
|
|
1151
|
+
"--json",
|
|
1152
|
+
]);
|
|
1153
|
+
expect(exitCode).toBe(0);
|
|
1154
|
+
// Verify callbackTransport was forwarded in the IPC body
|
|
1155
|
+
expect(capturedParams).toBeDefined();
|
|
1156
|
+
expect((capturedParams!.body as Record<string, unknown>).callbackTransport).toBe("gateway");
|
|
1157
|
+
const parsed = JSON.parse(stdout);
|
|
1158
|
+
expect(parsed.ok).toBe(true);
|
|
1159
|
+
expect(parsed.accountInfo).toBe("gw-user@example.com");
|
|
1160
|
+
});
|
|
1161
|
+
});
|
|
1162
|
+
|
|
727
1163
|
// -------------------------------------------------------------------------
|
|
728
1164
|
// Orchestrator error propagation
|
|
729
1165
|
// -------------------------------------------------------------------------
|
|
@@ -3,6 +3,7 @@ import { createServer, type Server } from "node:http";
|
|
|
3
3
|
import type { Command } from "commander";
|
|
4
4
|
|
|
5
5
|
import { getIsContainerized } from "../../../config/env-registry.js";
|
|
6
|
+
import { cliIpcCall } from "../../../ipc/cli-client.js";
|
|
6
7
|
import { orchestrateOAuthConnect } from "../../../oauth/connect-orchestrator.js";
|
|
7
8
|
import {
|
|
8
9
|
getAppByProviderAndClientId,
|
|
@@ -69,6 +70,39 @@ function startManagedRedirectServer(provider: string): Promise<{
|
|
|
69
70
|
});
|
|
70
71
|
}
|
|
71
72
|
|
|
73
|
+
// ---------------------------------------------------------------------------
|
|
74
|
+
// IPC polling helpers
|
|
75
|
+
// ---------------------------------------------------------------------------
|
|
76
|
+
|
|
77
|
+
type OAuthConnectStatusResponse =
|
|
78
|
+
| { status: "pending"; service: string }
|
|
79
|
+
| { status: "complete"; service: string; account_info?: string; granted_scopes?: string[] }
|
|
80
|
+
| { status: "error"; service: string; error?: string };
|
|
81
|
+
|
|
82
|
+
async function pollOAuthConnectStatus(
|
|
83
|
+
state: string,
|
|
84
|
+
opts: { intervalMs: number; timeoutMs: number },
|
|
85
|
+
): Promise<OAuthConnectStatusResponse> {
|
|
86
|
+
const deadline = Date.now() + opts.timeoutMs;
|
|
87
|
+
while (Date.now() < deadline) {
|
|
88
|
+
const r = await cliIpcCall<OAuthConnectStatusResponse>(
|
|
89
|
+
"internal_oauth_connect_status",
|
|
90
|
+
{ pathParams: { state } },
|
|
91
|
+
);
|
|
92
|
+
if (r.ok && r.result) {
|
|
93
|
+
const { status } = r.result;
|
|
94
|
+
if (status === "complete" || status === "error") {
|
|
95
|
+
return r.result;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
if (!r.ok && r.statusCode !== undefined) {
|
|
99
|
+
return { status: "error", service: "?", error: r.error ?? "assistant error during OAuth status poll" };
|
|
100
|
+
}
|
|
101
|
+
await new Promise<void>((res) => setTimeout(res, opts.intervalMs));
|
|
102
|
+
}
|
|
103
|
+
return { status: "error", service: "?", error: "Timed out waiting for OAuth callback" };
|
|
104
|
+
}
|
|
105
|
+
|
|
72
106
|
// ---------------------------------------------------------------------------
|
|
73
107
|
// Command registration
|
|
74
108
|
// ---------------------------------------------------------------------------
|
|
@@ -392,7 +426,99 @@ Examples:
|
|
|
392
426
|
}
|
|
393
427
|
}
|
|
394
428
|
|
|
395
|
-
// e.
|
|
429
|
+
// e. Try daemon-orchestrated path first (fixes heap-split for gateway transport).
|
|
430
|
+
const startResult = await cliIpcCall<{ auth_url: string; state: string }>(
|
|
431
|
+
"internal_oauth_connect_start",
|
|
432
|
+
{
|
|
433
|
+
body: {
|
|
434
|
+
service: provider,
|
|
435
|
+
clientId,
|
|
436
|
+
...(clientSecret !== undefined ? { clientSecret } : {}),
|
|
437
|
+
callbackTransport: opts.callbackTransport,
|
|
438
|
+
...(opts.scopes ? { requestedScopes: opts.scopes } : {}),
|
|
439
|
+
},
|
|
440
|
+
},
|
|
441
|
+
);
|
|
442
|
+
|
|
443
|
+
if (startResult.ok && startResult.result?.auth_url) {
|
|
444
|
+
const { auth_url, state } = startResult.result;
|
|
445
|
+
|
|
446
|
+
if (opts.browser !== false) {
|
|
447
|
+
await openInHostBrowser(auth_url);
|
|
448
|
+
|
|
449
|
+
if (!jsonMode) {
|
|
450
|
+
log.info("Waiting for authorization in browser... (press Ctrl+C to cancel)");
|
|
451
|
+
}
|
|
452
|
+
const final = await pollOAuthConnectStatus(state, {
|
|
453
|
+
intervalMs: 2000,
|
|
454
|
+
timeoutMs: 5 * 60 * 1000, // match LOOPBACK_TIMEOUT_MS in oauth2.ts (5 min)
|
|
455
|
+
});
|
|
456
|
+
|
|
457
|
+
if (final.status === "complete") {
|
|
458
|
+
if (jsonMode) {
|
|
459
|
+
writeOutput(cmd, {
|
|
460
|
+
ok: true,
|
|
461
|
+
grantedScopes: final.granted_scopes ?? [],
|
|
462
|
+
accountInfo: final.account_info,
|
|
463
|
+
});
|
|
464
|
+
} else {
|
|
465
|
+
process.stdout.write(
|
|
466
|
+
`Connected to ${provider}${final.account_info ? ` as ${final.account_info}` : ""}\n`,
|
|
467
|
+
);
|
|
468
|
+
}
|
|
469
|
+
return;
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
if (final.status === "error") {
|
|
473
|
+
// Includes the timeout sentinel emitted by pollOAuthConnectStatus.
|
|
474
|
+
writeError(final.error ?? "OAuth connect failed");
|
|
475
|
+
return;
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
// Defensive: pollOAuthConnectStatus should never return pending,
|
|
479
|
+
// but TS narrowing requires us to handle it.
|
|
480
|
+
writeError("OAuth connect ended in an unexpected pending state");
|
|
481
|
+
return;
|
|
482
|
+
} else {
|
|
483
|
+
// --no-browser: return the URL immediately, matching existing deferred behavior.
|
|
484
|
+
if (jsonMode) {
|
|
485
|
+
writeOutput(cmd, {
|
|
486
|
+
ok: true,
|
|
487
|
+
deferred: true,
|
|
488
|
+
authUrl: auth_url,
|
|
489
|
+
state,
|
|
490
|
+
service: provider,
|
|
491
|
+
});
|
|
492
|
+
} else {
|
|
493
|
+
process.stdout.write(
|
|
494
|
+
`\nAuthorize with ${provider}:\n\n${auth_url}\n\nThe connection will complete automatically once you authorize.\n`,
|
|
495
|
+
);
|
|
496
|
+
}
|
|
497
|
+
return;
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
// ok:true but no auth_url means a malformed daemon response — surface an error rather
|
|
502
|
+
// than falling back to in-process (which would re-introduce the heap-split bug for
|
|
503
|
+
// gateway transport).
|
|
504
|
+
if (startResult.ok && !startResult.result?.auth_url) {
|
|
505
|
+
writeError("assistant returned unexpected response for OAuth connect start");
|
|
506
|
+
return;
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
// If the daemon was reachable but returned an error, surface it rather than
|
|
510
|
+
// falling back to in-process (which would re-introduce the heap-split bug for
|
|
511
|
+
// gateway transport).
|
|
512
|
+
if (!startResult.ok && startResult.statusCode !== undefined) {
|
|
513
|
+
writeError(startResult.error ?? "OAuth connect failed (assistant error)");
|
|
514
|
+
return;
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
// IPC unavailable (daemon unreachable, older daemon without this route, socket missing).
|
|
518
|
+
// Fall through to the existing in-process flow. This still carries the heap-split bug
|
|
519
|
+
// for gateway transport, but if the daemon is unreachable we have a worse problem;
|
|
520
|
+
// the fallback preserves existing behavior as a regression guard.
|
|
521
|
+
// e. Call the orchestrator (in-process fallback)
|
|
396
522
|
const result = await orchestrateOAuthConnect({
|
|
397
523
|
service: provider,
|
|
398
524
|
clientId,
|
|
@@ -10,7 +10,6 @@ let mockResolvePlatformCallbackRegistrationContext: () => Promise<
|
|
|
10
10
|
isPlatform: false,
|
|
11
11
|
platformBaseUrl: "",
|
|
12
12
|
assistantId: "",
|
|
13
|
-
hasInternalApiKey: false,
|
|
14
13
|
hasAssistantApiKey: false,
|
|
15
14
|
authHeader: null,
|
|
16
15
|
enabled: false,
|
|
@@ -93,7 +92,6 @@ function connectedContext(
|
|
|
93
92
|
isPlatform: false,
|
|
94
93
|
platformBaseUrl: "https://dev-platform.vellum.ai",
|
|
95
94
|
assistantId: "019d6d4f-6dbd-779f-91d3-cb273b9429a5",
|
|
96
|
-
hasInternalApiKey: false,
|
|
97
95
|
hasAssistantApiKey: true,
|
|
98
96
|
authHeader: "Api-Key vak_test123",
|
|
99
97
|
enabled: false,
|
|
@@ -181,7 +179,6 @@ describe("assistant platform callback-routes list", () => {
|
|
|
181
179
|
isPlatform: false,
|
|
182
180
|
platformBaseUrl: "",
|
|
183
181
|
assistantId: "",
|
|
184
|
-
hasInternalApiKey: false,
|
|
185
182
|
hasAssistantApiKey: false,
|
|
186
183
|
authHeader: null,
|
|
187
184
|
enabled: false,
|
|
@@ -32,6 +32,13 @@ mock.module("../../../../security/secure-keys.js", () => ({
|
|
|
32
32
|
onCesClientChanged: () => ({ unsubscribe: () => {} }),
|
|
33
33
|
setCesReconnect: () => {},
|
|
34
34
|
getActiveBackendName: () => "file",
|
|
35
|
+
getActiveBackendInfoAsync: async () => ({
|
|
36
|
+
backend: "encrypted-store",
|
|
37
|
+
storePath: "/tmp/keys.enc",
|
|
38
|
+
storeKeyPath: "/tmp/store.key",
|
|
39
|
+
storeExists: false,
|
|
40
|
+
storeKeyExists: false,
|
|
41
|
+
}),
|
|
35
42
|
_resetBackend: () => {},
|
|
36
43
|
}));
|
|
37
44
|
|
|
@@ -45,7 +52,6 @@ mock.module("../../../../inbound/platform-callback-registration.js", () => ({
|
|
|
45
52
|
isPlatform: false,
|
|
46
53
|
platformBaseUrl: "",
|
|
47
54
|
assistantId: "",
|
|
48
|
-
hasInternalApiKey: false,
|
|
49
55
|
hasAssistantApiKey: false,
|
|
50
56
|
authHeader: null,
|
|
51
57
|
enabled: false,
|
|
@@ -39,7 +39,6 @@ mock.module("../../../../inbound/platform-callback-registration.js", () => ({
|
|
|
39
39
|
isPlatform: false,
|
|
40
40
|
platformBaseUrl: "",
|
|
41
41
|
assistantId: "",
|
|
42
|
-
hasInternalApiKey: false,
|
|
43
42
|
hasAssistantApiKey: false,
|
|
44
43
|
authHeader: null,
|
|
45
44
|
enabled: false,
|
|
@@ -64,6 +63,13 @@ mock.module("../../../../security/secure-keys.js", () => ({
|
|
|
64
63
|
onCesClientChanged: () => ({ unsubscribe: () => {} }),
|
|
65
64
|
setCesReconnect: () => {},
|
|
66
65
|
getActiveBackendName: () => "file",
|
|
66
|
+
getActiveBackendInfoAsync: async () => ({
|
|
67
|
+
backend: "encrypted-store",
|
|
68
|
+
storePath: "/tmp/keys.enc",
|
|
69
|
+
storeKeyPath: "/tmp/store.key",
|
|
70
|
+
storeExists: false,
|
|
71
|
+
storeKeyExists: false,
|
|
72
|
+
}),
|
|
67
73
|
_resetBackend: () => {},
|
|
68
74
|
}));
|
|
69
75
|
|