@vellumai/assistant 0.11.3 → 0.11.4-staging.1
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 +11 -6
- package/docs/architecture/memory.md +11 -0
- package/docs/architecture/turn-actor.md +70 -0
- package/docs/flux-turn-detection-spike.md +243 -0
- package/docs/stt-provider-onboarding.md +3 -1
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/channels.ts +11 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/channels.ts +11 -0
- package/node_modules/@vellumai/gateway-client/src/admission-policy-contract.ts +34 -0
- package/node_modules/@vellumai/gateway-client/src/index.ts +2 -0
- package/node_modules/@vellumai/service-contracts/src/channels.ts +11 -0
- package/openapi.yaml +140 -38
- package/package.json +1 -1
- package/scripts/voice-ttft-spike.ts +3 -3
- package/src/__tests__/app-compiler.test.ts +38 -3
- package/src/__tests__/attachments-store.test.ts +21 -12
- package/src/__tests__/byok-default-profile-ensure.test.ts +2 -0
- package/src/__tests__/call-setup-flow-name-capture.test.ts +0 -1
- package/src/__tests__/call-site-routing-provider.test.ts +1 -1
- package/src/__tests__/channel-availability-routes.test.ts +14 -1
- package/src/__tests__/channel-capabilities-dedupe.test.ts +214 -0
- package/src/__tests__/channel-delivery-store.test.ts +14 -14
- package/src/__tests__/config-loader-backfill.test.ts +3 -3
- package/src/__tests__/config-schema.test.ts +25 -10
- package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +8 -11
- package/src/__tests__/conversation-agent-loop-overflow.test.ts +8 -11
- package/src/__tests__/conversation-agent-loop.test.ts +28 -20
- package/src/__tests__/conversation-attention-store.test.ts +63 -0
- package/src/__tests__/conversation-delete-schedule-cleanup.test.ts +0 -4
- package/src/__tests__/conversation-fork-crud.test.ts +69 -0
- package/src/__tests__/conversation-fork-referential.test.ts +67 -0
- package/src/__tests__/conversation-fork-retrospective.test.ts +24 -0
- package/src/__tests__/conversation-notifiers-provenance.test.ts +59 -0
- package/src/__tests__/conversation-queue.test.ts +55 -4
- package/src/__tests__/conversation-runtime-assembly.test.ts +134 -102
- package/src/__tests__/conversation-runtime-workspace.test.ts +14 -10
- package/src/__tests__/credential-prompt-route.test.ts +7 -10
- package/src/__tests__/custom-profile-ensure.test.ts +5 -1
- package/src/__tests__/discord-access-request-privacy.test.ts +5 -1
- package/src/__tests__/discord-requester-notice-privacy.test.ts +3 -3
- package/src/__tests__/document-append-idempotency.test.ts +233 -0
- package/src/__tests__/edit-propagation.test.ts +0 -7
- package/src/__tests__/helpers/mock-actor-context.ts +49 -0
- package/src/__tests__/helpers/mock-conversation.ts +13 -1
- package/src/__tests__/injector-chain.test.ts +63 -41
- package/src/__tests__/injector-disk-pressure.test.ts +11 -23
- package/src/__tests__/llm-context-resolution.test.ts +73 -1
- package/src/__tests__/llm-schema.test.ts +5 -2
- package/src/__tests__/mcp-list-plugin-servers.test.ts +250 -0
- package/src/__tests__/memory-retrieval-hook.test.ts +6 -5
- package/src/__tests__/messages-read-boundary-guard.test.ts +134 -0
- package/src/__tests__/mtime-cache.test.ts +1 -1
- package/src/__tests__/non-member-access-request.test.ts +0 -20
- package/src/__tests__/outbound-slack-persistence.test.ts +40 -1
- package/src/__tests__/plugin-import-boundary-guard.test.ts +5 -0
- package/src/__tests__/plugin-secret-pattern-contribution.test.ts +1 -1
- package/src/__tests__/post-compaction-reinjection-idempotency.test.ts +14 -7
- package/src/__tests__/provider-commit-message-generator.test.ts +20 -0
- package/src/__tests__/run-conversation-turn-persistence.test.ts +434 -105
- package/src/__tests__/scoped-approval-grants.test.ts +11 -6
- package/src/__tests__/secret-ingress-channel.test.ts +0 -1
- package/src/__tests__/skills.test.ts +32 -0
- package/src/__tests__/slack-edit-ordering-characterization.test.ts +0 -1
- package/src/__tests__/subagent-call-site-routing.test.ts +31 -19
- package/src/__tests__/subagent-spawn-and-await.test.ts +14 -10
- package/src/__tests__/turn-events-store.test.ts +43 -0
- package/src/__tests__/user-plugin-loader.test.ts +1 -1
- package/src/__tests__/visible-app-context.test.ts +16 -9
- package/src/__tests__/worker-entrypoint-guards.test.ts +54 -0
- package/src/__tests__/worker-plugin-surface.test.ts +77 -0
- package/src/__tests__/workspace-migration-142-consolidate-voice-front-door.test.ts +158 -0
- package/src/__tests__/workspace-migration-143-repair-deprecated-codex-model-id.test.ts +133 -0
- package/src/__tests__/workspace-migration-144-convert-stranded-subscription-openai-profiles.test.ts +316 -0
- package/src/__tests__/workspace-migration-145-collapse-profile-bindings-to-entries.test.ts +325 -0
- package/src/acp/__tests__/acp-claude-oauth.test.ts +10 -2
- package/src/acp/__tests__/auth-required.test.ts +161 -0
- package/src/acp/acp-claude-oauth.ts +19 -2
- package/src/acp/agent-process.test.ts +100 -0
- package/src/acp/agent-process.ts +29 -26
- package/src/acp/auth-required.ts +102 -0
- package/src/acp/session-manager.test.ts +119 -0
- package/src/acp/session-manager.ts +68 -2
- package/src/api/events/acp-auth-required.ts +55 -0
- package/src/api/index.ts +7 -0
- package/src/apps/app-store.ts +3 -0
- package/src/bundler/package-resolver.ts +2 -30
- package/src/calls/__tests__/voice-session-bridge.test.ts +173 -1
- package/src/calls/__tests__/voice-triage-escalate.test.ts +94 -0
- package/src/calls/call-controller.ts +9 -2
- package/src/calls/call-setup-flow.ts +0 -1
- package/src/calls/media-stream-stt-session.ts +15 -0
- package/src/calls/voice-session-bridge.ts +71 -16
- package/src/calls/voice-triage-escalate.ts +104 -2
- package/src/channels/__tests__/plugin-channel-declarations.test.ts +161 -0
- package/src/channels/config.ts +13 -0
- package/src/channels/plugin-channel-declarations.ts +108 -0
- package/src/channels/types.ts +30 -0
- package/src/cli/AGENTS.md +5 -2
- package/src/cli/commands/credentials.help.ts +2 -2
- package/src/cli/commands/inference-providers.ts +1 -1
- package/src/cli/commands/mcp.help.ts +13 -4
- package/src/cli/commands/mcp.ts +9 -0
- package/src/cli/commands/memory/__tests__/memory-v3.test.ts +128 -5
- package/src/cli/commands/memory/index.help.ts +43 -1
- package/src/cli/commands/memory/memory-v3.ts +64 -0
- package/src/cli/commands/stt.help.ts +27 -2
- package/src/cli/lib/__tests__/upgrade-plugin.test.ts +39 -0
- package/src/cli/lib/bundled-marketplace.json +13 -0
- package/src/cli/lib/upgrade-plugin.ts +42 -0
- package/src/config/__tests__/default-profile-catalog.test.ts +34 -2
- package/src/config/__tests__/default-provider.test.ts +6 -1
- package/src/config/__tests__/profile-materialization.test.ts +75 -19
- package/src/config/bundled-skills/acp/SKILL.md +6 -7
- package/src/config/bundled-skills/document-editor/SKILL.md +2 -2
- package/src/config/bundled-skills/document-editor/TOOLS.json +2 -2
- package/src/config/bundled-skills/media-processing/services/preprocess.ts +14 -4
- package/src/config/bundled-skills/settings/TOOLS.json +3 -3
- package/src/config/bundled-skills/transcribe/tools/transcribe-media.test.ts +22 -1
- package/src/config/bundled-skills/transcribe/tools/transcribe-media.ts +9 -2
- package/src/config/call-site-defaults.ts +4 -5
- package/src/config/default-profile-catalog.ts +82 -11
- package/src/config/default-profile-names.ts +4 -1
- package/src/config/default-provider-resolution.ts +4 -0
- package/src/config/llm-context-resolution.ts +11 -3
- package/src/config/llm-resolver.ts +28 -1
- package/src/config/profile-materialization.ts +70 -22
- package/src/config/schemas/__tests__/live-voice.test.ts +107 -4
- package/src/config/schemas/call-site-catalog.ts +4 -4
- package/src/config/schemas/live-voice.ts +57 -23
- package/src/config/schemas/llm.ts +59 -32
- package/src/config/schemas/mcp.ts +23 -0
- package/src/config/schemas/plugin-updates.ts +6 -2
- package/src/config/schemas/stt.ts +1 -0
- package/src/context/outbound-sanitize.ts +96 -1
- package/src/daemon/__tests__/plugin-mcp-reconcile.test.ts +82 -0
- package/src/daemon/conversation-agent-loop-handlers.ts +15 -10
- package/src/daemon/conversation-agent-loop.ts +17 -6
- package/src/daemon/conversation-messaging.ts +5 -1
- package/src/daemon/conversation-notifiers.ts +9 -1
- package/src/daemon/conversation-process.ts +9 -6
- package/src/daemon/conversation-runtime-assembly.ts +3 -4
- package/src/daemon/conversation-tool-setup.ts +1 -2
- package/src/daemon/conversation.ts +48 -0
- package/src/daemon/mcp-reload-service.ts +36 -6
- package/src/daemon/process-message.ts +13 -3
- package/src/daemon/providers-setup.ts +6 -3
- package/src/daemon/trust-context-types.ts +29 -0
- package/src/daemon/wake-conversation-ops.ts +3 -2
- package/src/documents/document-store.ts +138 -5
- package/src/hooks/hook-loader.ts +3 -3
- package/src/hooks/registry.ts +50 -6
- package/src/inbound/__tests__/oauth-callback-url.test.ts +83 -0
- package/src/inbound/oauth-callback-url.ts +61 -0
- package/src/live-voice/__tests__/live-voice-agent-turn.test.ts +1 -104
- package/src/live-voice/__tests__/live-voice-events.test.ts +7 -8
- package/src/live-voice/__tests__/live-voice-flux-turn-end.test.ts +932 -0
- package/src/live-voice/__tests__/live-voice-metrics.test.ts +115 -8
- package/src/live-voice/__tests__/live-voice-photo.test.ts +100 -0
- package/src/live-voice/__tests__/live-voice-progress.test.ts +60 -194
- package/src/live-voice/__tests__/live-voice-stt.test.ts +14 -0
- package/src/live-voice/__tests__/live-voice-triage-escalate.test.ts +29 -0
- package/src/live-voice/__tests__/live-voice-tts-session.test.ts +0 -483
- package/src/live-voice/__tests__/live-voice-vad.test.ts +0 -16
- package/src/live-voice/__tests__/progress-narration.test.ts +214 -0
- package/src/live-voice/live-voice-archive.ts +2 -0
- package/src/live-voice/live-voice-metrics.ts +57 -32
- package/src/live-voice/live-voice-photo.ts +1 -2
- package/src/live-voice/live-voice-session.ts +535 -314
- package/src/live-voice/progress-narration.ts +277 -0
- package/src/live-voice/protocol.ts +21 -1
- package/src/mcp/__tests__/effective-config.test.ts +238 -0
- package/src/mcp/__tests__/mcp-auth-orchestrator.test.ts +0 -1
- package/src/mcp/__tests__/mcp-oauth-client-registration.test.ts +200 -0
- package/src/mcp/__tests__/mcp-oauth-provider.test.ts +9 -9
- package/src/mcp/__tests__/plugin-server-credential-isolation.test.ts +95 -0
- package/src/mcp/client.ts +16 -11
- package/src/mcp/effective-config.ts +113 -0
- package/src/mcp/manager.ts +11 -6
- package/src/mcp/mcp-auth-orchestrator.ts +13 -22
- package/src/mcp/mcp-oauth-provider.ts +205 -240
- package/src/monitoring/__tests__/plugin-auto-update.test.ts +166 -3
- package/src/monitoring/plugin-auto-update.ts +128 -24
- package/src/notifications/signal.ts +1 -0
- package/src/permissions/confirmation-guardian-request.test.ts +15 -11
- package/src/permissions/confirmation-guardian-request.ts +2 -2
- package/src/permissions/question-guardian-request.test.ts +14 -6
- package/src/permissions/question-guardian-request.ts +1 -2
- package/src/persistence/attachments-store.ts +8 -1
- package/src/persistence/bookmark-crud.ts +3 -7
- package/src/persistence/conversation-attention-store.ts +16 -45
- package/src/persistence/conversation-crud.ts +33 -4
- package/src/persistence/conversation-lineage.ts +9 -0
- package/src/persistence/conversation-queries.ts +108 -41
- package/src/persistence/delivery-crud.ts +38 -29
- package/src/persistence/external-conversation-store.ts +32 -4
- package/src/persistence/llm-request-log-store.ts +4 -10
- package/src/persistence/llm-usage-store.ts +8 -3
- package/src/persistence/message-reads.test.ts +197 -0
- package/src/persistence/message-reads.ts +211 -0
- package/src/persistence/migrations/366-chatgpt-subscription-row-identity.test.ts +120 -0
- package/src/persistence/migrations/366-chatgpt-subscription-row-identity.ts +62 -0
- package/src/persistence/real-user-turn-filter.ts +27 -3
- package/src/persistence/steps.ts +9 -0
- package/src/plugin-api/__tests__/oauth-callback-url-export.test.ts +29 -0
- package/src/plugin-api/conversation-turn.ts +168 -5
- package/src/plugin-api/index.ts +21 -5
- package/src/plugins/__tests__/mcp-servers.test.ts +371 -0
- package/src/plugins/defaults/memory/AGENTS.md +4 -0
- package/src/plugins/defaults/memory/__tests__/buffer-format.test.ts +204 -0
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-accounting.test.ts +72 -0
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-job.test.ts +4 -1
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-provider-path.test.ts +4 -1
- package/src/plugins/defaults/memory/buffer-format.ts +165 -0
- package/src/plugins/defaults/memory/context-search/sources/conversations.ts +6 -0
- package/src/plugins/defaults/memory/graph/image-ref-utils.ts +3 -0
- package/src/plugins/defaults/memory/graph/tool-handlers.ts +1 -30
- package/src/plugins/defaults/memory/graph-topology/pending-buffer.test.ts +34 -0
- package/src/plugins/defaults/memory/graph-topology/pending-buffer.ts +8 -12
- package/src/plugins/defaults/memory/hooks/post-compact.ts +1 -4
- package/src/plugins/defaults/memory/indexer.ts +3 -1
- package/src/plugins/defaults/memory/memory-retrospective-accounting.ts +19 -7
- package/src/plugins/defaults/memory/src/__tests__/memory-v3-gate-stats.test.ts +281 -0
- package/src/plugins/defaults/memory/src/memory-v3-routes.ts +207 -0
- package/src/plugins/defaults/memory/substrate/__tests__/consolidation-job.test.ts +33 -0
- package/src/plugins/defaults/memory/substrate/__tests__/static-context.test.ts +199 -2
- package/src/plugins/defaults/memory/substrate/consolidation-job.ts +25 -18
- package/src/plugins/defaults/memory/substrate/skill-content.ts +8 -1
- package/src/plugins/defaults/memory/substrate/static-context.ts +160 -4
- package/src/plugins/defaults/memory/substrate/sweep-job.ts +2 -4
- package/src/plugins/defaults/memory/v1/graph/extraction.ts +3 -1
- package/src/plugins/defaults/memory/v3/prune.ts +2 -0
- package/src/plugins/defaults/memory/v3/selection-log-store.ts +2 -0
- package/src/plugins/defaults/memory/worker.ts +6 -3
- package/src/plugins/external-plugin-loader.ts +47 -0
- package/src/plugins/mcp-servers.ts +361 -0
- package/src/plugins/mtime-cache.ts +23 -49
- package/src/plugins/worker-plugin-surface.ts +33 -0
- package/src/providers/__tests__/connection-model-compat.test.ts +1 -1
- package/src/providers/__tests__/dispatch-connection-routing.test.ts +214 -2
- package/src/providers/__tests__/preflight-resolved-config.test.ts +57 -0
- package/src/providers/__tests__/retry-callsite.test.ts +5 -2
- package/src/providers/call-site-routing.ts +30 -3
- package/src/providers/connection-resolution.ts +194 -11
- package/src/providers/inference/auth.ts +6 -0
- package/src/providers/inference/connection-availability.ts +24 -2
- package/src/providers/inference/connections.ts +2 -0
- package/src/providers/model-intents.ts +26 -6
- package/src/providers/openai/codex-models.ts +2 -1
- package/src/providers/provider-send-message.ts +32 -3
- package/src/providers/speech-to-text/__tests__/deepgram-flux-frames.test.ts +433 -0
- package/src/providers/speech-to-text/__tests__/deepgram-flux-realtime.test.ts +620 -0
- package/src/providers/speech-to-text/__tests__/provider-catalog.test.ts +34 -0
- package/src/providers/speech-to-text/__tests__/resolve.test.ts +285 -6
- package/src/providers/speech-to-text/deepgram-flux-frames.ts +395 -0
- package/src/providers/speech-to-text/deepgram-flux-realtime.ts +719 -0
- package/src/providers/speech-to-text/provider-catalog.ts +99 -8
- package/src/providers/speech-to-text/resolve.ts +25 -2
- package/src/routes/worker.ts +17 -5
- package/src/runtime/access-request-helper.ts +9 -12
- package/src/runtime/agent-wake.ts +3 -3
- package/src/runtime/pre-first-message-gate.ts +4 -0
- package/src/runtime/routes/__tests__/acp-claude-auth-routes.test.ts +12 -4
- package/src/runtime/routes/__tests__/conversation-list-routes.test.ts +219 -1
- package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +52 -0
- package/src/runtime/routes/__tests__/default-provider-routes.test.ts +61 -0
- package/src/runtime/routes/__tests__/inference-profiles-routes.test.ts +44 -0
- package/src/runtime/routes/__tests__/inference-provider-connection-routes.test.ts +102 -1
- package/src/runtime/routes/__tests__/plugins-routes.test.ts +44 -0
- package/src/runtime/routes/__tests__/stt-routes.test.ts +25 -0
- package/src/runtime/routes/__tests__/user-route-dispatcher.test.ts +62 -1
- package/src/runtime/routes/channel-availability-routes.ts +32 -14
- package/src/runtime/routes/channel-route-shared.ts +0 -6
- package/src/runtime/routes/chatgpt-subscription-auth-routes.ts +6 -6
- package/src/runtime/routes/conversation-list-routes.ts +112 -1
- package/src/runtime/routes/conversation-query-routes.ts +40 -27
- package/src/runtime/routes/credential-prompt-routes.ts +4 -7
- package/src/runtime/routes/default-provider-routes.ts +15 -0
- package/src/runtime/routes/inbound-message-handler.ts +17 -41
- package/src/runtime/routes/inbound-stages/acl-enforcement.test.ts +0 -1
- package/src/runtime/routes/inbound-stages/acl-enforcement.ts +0 -9
- package/src/runtime/routes/inbound-stages/admission-policy.ts +1 -17
- package/src/runtime/routes/inbound-stages/bootstrap-intercept.test.ts +0 -1
- package/src/runtime/routes/inbound-stages/bootstrap-intercept.ts +2 -3
- package/src/runtime/routes/inbound-stages/edit-intercept.ts +1 -3
- package/src/runtime/routes/inbound-stages/guardian-reply-intercept.test.ts +0 -1
- package/src/runtime/routes/inbound-stages/guardian-reply-intercept.ts +3 -4
- package/src/runtime/routes/inbound-stages/reaction-intercept.test.ts +0 -1
- package/src/runtime/routes/inbound-stages/reaction-intercept.ts +11 -20
- package/src/runtime/routes/inbound-stages/secret-ingress-check.ts +2 -3
- package/src/runtime/routes/inference-profiles-routes.ts +20 -11
- package/src/runtime/routes/inference-provider-connection-routes.ts +77 -15
- package/src/runtime/routes/log-export-routes.ts +3 -0
- package/src/runtime/routes/mcp-auth-routes.ts +148 -57
- package/src/runtime/routes/plugins-routes.ts +21 -3
- package/src/runtime/routes/stt-routes.ts +31 -25
- package/src/runtime/routes/surface-conversation-resolver.ts +3 -0
- package/src/runtime/routes/user-route-dispatcher.ts +39 -14
- package/src/runtime/routes/user-route-import.ts +108 -0
- package/src/schedule/worker.ts +6 -0
- package/src/security/oauth2.ts +6 -22
- package/src/stt/__tests__/daemon-batch-transcriber.test.ts +22 -0
- package/src/stt/__tests__/types.test.ts +94 -0
- package/src/stt/daemon-batch-transcriber.ts +10 -0
- package/src/stt/stt-stream-session.ts +8 -4
- package/src/stt/types.ts +103 -0
- package/src/subagent/manager.ts +1 -3
- package/src/subagent/types.ts +7 -6
- package/src/tools/acp/spawn.test.ts +97 -0
- package/src/tools/acp/spawn.ts +32 -0
- package/src/tools/document/document-tool.ts +12 -3
- package/src/tools/registry.ts +2 -1
- package/src/tools/workflows/run-workflow.ts +1 -2
- package/src/tts/__tests__/reasoning-tag-filter.test.ts +63 -0
- package/src/tts/reasoning-tag-filter.ts +110 -0
- package/src/workspace/byok-default-profile-ensure.ts +61 -18
- package/src/workspace/custom-profile-ensure.ts +4 -24
- package/src/workspace/migrations/142-consolidate-voice-front-door.ts +70 -0
- package/src/workspace/migrations/143-repair-deprecated-codex-model-id.ts +134 -0
- package/src/workspace/migrations/144-convert-stranded-subscription-openai-profiles.ts +265 -0
- package/src/workspace/migrations/145-collapse-profile-bindings-to-entries.ts +328 -0
- package/src/workspace/migrations/__tests__/141-stt-english-default-to-multilingual.test.ts +0 -10
- package/src/workspace/migrations/registry.ts +8 -0
- package/src/workspace/provider-commit-message-generator.ts +7 -5
- package/src/live-voice/__tests__/front-decision.test.ts +0 -645
- package/src/live-voice/front-decision.ts +0 -476
package/src/acp/agent-process.ts
CHANGED
|
@@ -20,16 +20,11 @@ import type {
|
|
|
20
20
|
import * as acp from "@agentclientprotocol/sdk";
|
|
21
21
|
|
|
22
22
|
import { getLogger } from "../util/logger.js";
|
|
23
|
+
import { AcpAuthRequiredError, isAcpAuthRequired } from "./auth-required.js";
|
|
23
24
|
import type { AcpAgentConfig } from "./types.js";
|
|
24
25
|
|
|
25
26
|
const log = getLogger("acp");
|
|
26
27
|
|
|
27
|
-
/**
|
|
28
|
-
* JSON-RPC error code agents use to signal that authentication is required
|
|
29
|
-
* (matches the SDK's RequestError.authRequired()).
|
|
30
|
-
*/
|
|
31
|
-
const AUTH_REQUIRED_CODE = -32000;
|
|
32
|
-
|
|
33
28
|
/**
|
|
34
29
|
* Rough byte cap for retained stderr. Oldest lines are evicted once the sum of
|
|
35
30
|
* retained line lengths exceeds this, so the stderr ring (read via stderrSince)
|
|
@@ -37,19 +32,6 @@ const AUTH_REQUIRED_CODE = -32000;
|
|
|
37
32
|
*/
|
|
38
33
|
const STDERR_RETENTION_BYTES = 4096;
|
|
39
34
|
|
|
40
|
-
/**
|
|
41
|
-
* Detects the ACP auth-required error. Checks the `code` property rather than
|
|
42
|
-
* `instanceof acp.RequestError` so plain JSON-RPC error objects are also
|
|
43
|
-
* recognized.
|
|
44
|
-
*/
|
|
45
|
-
function isAuthRequiredError(err: unknown): boolean {
|
|
46
|
-
return (
|
|
47
|
-
typeof err === "object" &&
|
|
48
|
-
err !== null &&
|
|
49
|
-
(err as { code?: unknown }).code === AUTH_REQUIRED_CODE
|
|
50
|
-
);
|
|
51
|
-
}
|
|
52
|
-
|
|
53
35
|
function isEnvVarMethod(
|
|
54
36
|
method: AuthMethod,
|
|
55
37
|
): method is AuthMethodEnvVar & { type: "env_var" } {
|
|
@@ -296,7 +278,7 @@ export class AcpAgentProcess {
|
|
|
296
278
|
try {
|
|
297
279
|
return await op();
|
|
298
280
|
} catch (err) {
|
|
299
|
-
if (!
|
|
281
|
+
if (!isAcpAuthRequired(err)) {
|
|
300
282
|
throw err;
|
|
301
283
|
}
|
|
302
284
|
|
|
@@ -306,12 +288,23 @@ export class AcpAgentProcess {
|
|
|
306
288
|
|
|
307
289
|
const method = this.selectEnvVarAuthMethod();
|
|
308
290
|
if (!method) {
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
291
|
+
// Typed so the session manager can tell an auth failure from a crash
|
|
292
|
+
// and surface a re-authentication path. The remediation text is split
|
|
293
|
+
// by what the agent advertises: env-var and `credentials prompt`
|
|
294
|
+
// advice only helps when an env_var method exists to satisfy, and for
|
|
295
|
+
// terminal-login-only adapters it sends the user chasing a CLI
|
|
296
|
+
// workaround for a credential the app repairs on its own.
|
|
297
|
+
throw new AcpAuthRequiredError(
|
|
298
|
+
this.agentId,
|
|
299
|
+
this.hasEnvVarAuthMethod()
|
|
300
|
+
? `ACP agent "${this.agentId}" requires authentication. ` +
|
|
301
|
+
`Advertised methods: ${this.describeAuthMethods()}. ` +
|
|
302
|
+
"Set the required env var under acp.agents.<id>.env in config.json, " +
|
|
303
|
+
"or collect it securely via 'assistant credentials prompt --service acp --field <field> --label \"<label>\"'."
|
|
304
|
+
: `ACP agent "${this.agentId}" requires authentication and its ` +
|
|
305
|
+
"stored credential was not accepted. Advertised methods: " +
|
|
306
|
+
`${this.describeAuthMethods()}. None can be completed headlessly, ` +
|
|
307
|
+
"so the credential has to be reconnected.",
|
|
315
308
|
);
|
|
316
309
|
}
|
|
317
310
|
|
|
@@ -325,6 +318,16 @@ export class AcpAgentProcess {
|
|
|
325
318
|
}
|
|
326
319
|
}
|
|
327
320
|
|
|
321
|
+
/**
|
|
322
|
+
* Whether the agent advertises any `env_var` auth method at all, satisfiable
|
|
323
|
+
* or not. Distinguishes "we could not satisfy the env var it wants" from
|
|
324
|
+
* "env vars are not how this agent authenticates", which are different
|
|
325
|
+
* problems with different fixes.
|
|
326
|
+
*/
|
|
327
|
+
private hasEnvVarAuthMethod(): boolean {
|
|
328
|
+
return this.authMethods.some(isEnvVarMethod);
|
|
329
|
+
}
|
|
330
|
+
|
|
328
331
|
/**
|
|
329
332
|
* Renders the agent's advertised auth methods for error messages, e.g.
|
|
330
333
|
* `"Login with ChatGPT" (chatgpt), "Use OPENAI_API_KEY" (env var OPENAI_API_KEY)`.
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Detection of Claude authentication failures on ACP runs.
|
|
3
|
+
*
|
|
4
|
+
* These failures reach the daemon in two shapes, and both are needed:
|
|
5
|
+
*
|
|
6
|
+
* 1. No credentials at all: the adapter raises a structured ACP
|
|
7
|
+
* `auth_required` rejection (JSON-RPC -32000). {@link isAcpAuthRequired}.
|
|
8
|
+
* 2. Credentials present but rejected (the expired/revoked case): the CLI
|
|
9
|
+
* surfaces the API 401 as an error message it authors, and the adapter
|
|
10
|
+
* relays it as a generic -32603 internal error, NOT as `auth_required`.
|
|
11
|
+
* {@link isClaudeAuthFailureMessage}.
|
|
12
|
+
*
|
|
13
|
+
* A positive detection becomes {@link ACP_CLAUDE_AUTH_REQUIRED_CODE} on the
|
|
14
|
+
* `acp_auth_required` event, which the client turns into the inline
|
|
15
|
+
* "Connect Claude Code" card.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
export { ACP_CLAUDE_AUTH_REQUIRED_CODE } from "../api/events/acp-auth-required.js";
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* JSON-RPC error code agents use to signal that authentication is required
|
|
22
|
+
* (matches the ACP SDK's `RequestError.authRequired()`).
|
|
23
|
+
*/
|
|
24
|
+
export const AUTH_REQUIRED_CODE = -32000;
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* An agent rejected an operation with `auth_required` and the daemon has no
|
|
28
|
+
* way to satisfy it. A distinct type so the classification survives to the
|
|
29
|
+
* session manager, where the failure becomes an event the UI can act on.
|
|
30
|
+
*/
|
|
31
|
+
export class AcpAuthRequiredError extends Error {
|
|
32
|
+
constructor(
|
|
33
|
+
readonly agentId: string,
|
|
34
|
+
message: string,
|
|
35
|
+
) {
|
|
36
|
+
super(message);
|
|
37
|
+
this.name = "AcpAuthRequiredError";
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Detects the structured auth-required signal in either shape it travels: the
|
|
43
|
+
* raw JSON-RPC rejection from the agent, or our own
|
|
44
|
+
* {@link AcpAuthRequiredError}. Checks the `code` property rather than an
|
|
45
|
+
* `instanceof` so plain JSON-RPC error objects are recognized too.
|
|
46
|
+
*/
|
|
47
|
+
export function isAcpAuthRequired(err: unknown): boolean {
|
|
48
|
+
if (err instanceof AcpAuthRequiredError) {
|
|
49
|
+
return true;
|
|
50
|
+
}
|
|
51
|
+
return (
|
|
52
|
+
typeof err === "object" &&
|
|
53
|
+
err !== null &&
|
|
54
|
+
(err as { code?: unknown }).code === AUTH_REQUIRED_CODE
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* The Claude CLI's own auth-failure phrasings. Unanchored (no ^/$) because
|
|
60
|
+
* the messages arrive with adapter framing ("Internal error: ...") or as a
|
|
61
|
+
* line `deriveFailureError` picked out of stderr.
|
|
62
|
+
*/
|
|
63
|
+
const CLAUDE_AUTH_FAILURE_PATTERNS: readonly RegExp[] = [
|
|
64
|
+
/Failed to authenticate\b/,
|
|
65
|
+
/Please run \/login/,
|
|
66
|
+
/\bNot logged in\b/,
|
|
67
|
+
];
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Whether a failure message is a Claude authentication failure. Callers gate
|
|
71
|
+
* on the adapter (`claude-agent-acp`) BEFORE consulting this: the patterns
|
|
72
|
+
* are Claude-specific, and the marker built on them promises a repair only
|
|
73
|
+
* the Connect Claude flow can perform.
|
|
74
|
+
*/
|
|
75
|
+
export function isClaudeAuthFailureMessage(
|
|
76
|
+
message: string | undefined,
|
|
77
|
+
): boolean {
|
|
78
|
+
return (
|
|
79
|
+
message != null &&
|
|
80
|
+
CLAUDE_AUTH_FAILURE_PATTERNS.some((pattern) => pattern.test(message))
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** The adapter whose auth failures the Connect Claude flow can repair. */
|
|
85
|
+
export const CLAUDE_ACP_COMMAND = "claude-agent-acp";
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Model-facing text accompanying a Claude auth failure wherever the inline
|
|
89
|
+
* Connect card is raised (the tool result for a pre-spawn rejection, the
|
|
90
|
+
* parent notification for a post-spawn one), so the model points at the card
|
|
91
|
+
* instead of inventing a remedy (CLI commands, pasted tokens, or a card that
|
|
92
|
+
* is not there). Bans placement words because card position is a UI detail
|
|
93
|
+
* the model cannot see.
|
|
94
|
+
*/
|
|
95
|
+
export const ACP_AUTH_RECOVERY_GUIDANCE =
|
|
96
|
+
"The Claude Code connection needs to be re-authorized. The app shows the " +
|
|
97
|
+
'user an inline "Connect Claude Code" card. Reply with ONE short sentence: ' +
|
|
98
|
+
"ask them to click Connect to sign in again, and tell them you'll continue " +
|
|
99
|
+
"automatically once they are connected. Do NOT say where the card is; never " +
|
|
100
|
+
'say "below", "above", "at the bottom", or "here". Do NOT tell them to run ' +
|
|
101
|
+
"`claude setup-token`, paste a token in chat, run credential CLI commands, " +
|
|
102
|
+
"or re-run the agent yourself; the card and auto-continue handle it.";
|
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
deleteConversation,
|
|
6
6
|
setConversation,
|
|
7
7
|
} from "../daemon/conversation-registry.js";
|
|
8
|
+
import { hasAcpConnectCardRaised } from "./acp-connect-card-state.js";
|
|
8
9
|
import { VellumAcpClientHandler } from "./client-handler.js";
|
|
9
10
|
import { AcpSessionManager } from "./session-manager.js";
|
|
10
11
|
|
|
@@ -310,3 +311,121 @@ describe("AcpSessionManager parent notification", () => {
|
|
|
310
311
|
expect((manager.getStatus() as unknown[]).length).toBe(1);
|
|
311
312
|
});
|
|
312
313
|
});
|
|
314
|
+
|
|
315
|
+
// ---------------------------------------------------------------------------
|
|
316
|
+
// Auth-required recovery surface
|
|
317
|
+
// ---------------------------------------------------------------------------
|
|
318
|
+
|
|
319
|
+
describe("AcpSessionManager auth-required recovery surface", () => {
|
|
320
|
+
/** The live-captured shape of a rejected-credential failure. */
|
|
321
|
+
const authFailure = () =>
|
|
322
|
+
Promise.reject(
|
|
323
|
+
new Error(
|
|
324
|
+
"Internal error: Failed to authenticate. API Error: 401 OAuth access token has been revoked.",
|
|
325
|
+
),
|
|
326
|
+
);
|
|
327
|
+
|
|
328
|
+
/** Drives an auth-shaped prompt failure through firePromptInBackground. */
|
|
329
|
+
async function driveAuthFailure(opts: {
|
|
330
|
+
id: string;
|
|
331
|
+
command: string;
|
|
332
|
+
parentToolUseId?: string;
|
|
333
|
+
cancelled?: boolean;
|
|
334
|
+
}) {
|
|
335
|
+
const manager = new AcpSessionManager(1);
|
|
336
|
+
const parentId = `parent-${opts.id}`;
|
|
337
|
+
const { conversation, persistUserMessage, loopRan } = mockConversation();
|
|
338
|
+
setConversation(parentId, conversation);
|
|
339
|
+
registered.push(parentId);
|
|
340
|
+
|
|
341
|
+
const entry = injectSession(
|
|
342
|
+
manager,
|
|
343
|
+
opts.id,
|
|
344
|
+
parentId,
|
|
345
|
+
fakeProcess(authFailure),
|
|
346
|
+
);
|
|
347
|
+
entry.command = opts.command;
|
|
348
|
+
(entry as { parentToolUseId?: string }).parentToolUseId =
|
|
349
|
+
opts.parentToolUseId;
|
|
350
|
+
if (opts.cancelled) {
|
|
351
|
+
entry.state.status = "cancelled";
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
await fire(manager, opts.id, entry).catch(() => {});
|
|
355
|
+
if (!opts.cancelled) {
|
|
356
|
+
await loopRan;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
const events = (
|
|
360
|
+
entry.sendToVellum as ReturnType<typeof mock>
|
|
361
|
+
).mock.calls.map((c) => c[0] as { type: string } & Record<string, unknown>);
|
|
362
|
+
const firstPersist = persistUserMessage.mock.calls[0] as unknown as
|
|
363
|
+
| [{ content: string }]
|
|
364
|
+
| undefined;
|
|
365
|
+
return {
|
|
366
|
+
parentId,
|
|
367
|
+
authEvent: events.find((e) => e.type === "acp_auth_required"),
|
|
368
|
+
persistedContent: firstPersist?.[0].content,
|
|
369
|
+
};
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
test("claude failure with an anchor raises the full surface: event, registry mark, guidance", async () => {
|
|
373
|
+
const r = await driveAuthFailure({
|
|
374
|
+
id: "sess-auth-anchor",
|
|
375
|
+
command: "claude-agent-acp",
|
|
376
|
+
parentToolUseId: "tool-anchor-1",
|
|
377
|
+
});
|
|
378
|
+
|
|
379
|
+
expect(r.authEvent).toMatchObject({
|
|
380
|
+
acpSessionId: "sess-auth-anchor",
|
|
381
|
+
authCode: "acp_claude_auth_required",
|
|
382
|
+
agent: "claude",
|
|
383
|
+
parentToolUseId: "tool-anchor-1",
|
|
384
|
+
});
|
|
385
|
+
// The credential-prompt route consults this registry to redirect a
|
|
386
|
+
// redundant secure prompt at the card instead of stacking a second one.
|
|
387
|
+
expect(hasAcpConnectCardRaised(r.parentId)).toBe(true);
|
|
388
|
+
expect(r.persistedContent).toContain("Connect Claude Code");
|
|
389
|
+
});
|
|
390
|
+
|
|
391
|
+
test("claude failure without an anchor keeps the plain failure: no event, no mark, no guidance", async () => {
|
|
392
|
+
// No spawning tool call means no transcript row to render the card under;
|
|
393
|
+
// guidance would point the model at a card that cannot appear.
|
|
394
|
+
const r = await driveAuthFailure({
|
|
395
|
+
id: "sess-auth-noanchor",
|
|
396
|
+
command: "claude-agent-acp",
|
|
397
|
+
});
|
|
398
|
+
|
|
399
|
+
expect(r.authEvent).toBeUndefined();
|
|
400
|
+
expect(hasAcpConnectCardRaised(r.parentId)).toBe(false);
|
|
401
|
+
expect(r.persistedContent).not.toContain("Connect Claude Code");
|
|
402
|
+
});
|
|
403
|
+
|
|
404
|
+
test("a run the user already stopped raises nothing: no event, no registry mark, no parent turn", async () => {
|
|
405
|
+
// The client never renders a card for a cancelled run, and the
|
|
406
|
+
// prompt-dedup registry is never cleared, so marking it here would
|
|
407
|
+
// suppress the secure token prompt for the daemon's lifetime against a
|
|
408
|
+
// card that does not exist.
|
|
409
|
+
const r = await driveAuthFailure({
|
|
410
|
+
id: "sess-auth-cancelled",
|
|
411
|
+
command: "claude-agent-acp",
|
|
412
|
+
parentToolUseId: "tool-anchor-3",
|
|
413
|
+
cancelled: true,
|
|
414
|
+
});
|
|
415
|
+
|
|
416
|
+
expect(r.authEvent).toBeUndefined();
|
|
417
|
+
expect(hasAcpConnectCardRaised(r.parentId)).toBe(false);
|
|
418
|
+
expect(r.persistedContent).toBeUndefined();
|
|
419
|
+
});
|
|
420
|
+
|
|
421
|
+
test("a non-claude adapter never raises the surface, even on an auth-shaped failure", async () => {
|
|
422
|
+
const r = await driveAuthFailure({
|
|
423
|
+
id: "sess-auth-codex",
|
|
424
|
+
command: "codex-acp",
|
|
425
|
+
parentToolUseId: "tool-anchor-2",
|
|
426
|
+
});
|
|
427
|
+
|
|
428
|
+
expect(r.authEvent).toBeUndefined();
|
|
429
|
+
expect(hasAcpConnectCardRaised(r.parentId)).toBe(false);
|
|
430
|
+
});
|
|
431
|
+
});
|
|
@@ -15,7 +15,15 @@ import { getDb } from "../persistence/db-connection.js";
|
|
|
15
15
|
import { acpSessionHistory } from "../persistence/schema/index.js";
|
|
16
16
|
import * as pendingInteractions from "../runtime/pending-interactions.js";
|
|
17
17
|
import { getLogger } from "../util/logger.js";
|
|
18
|
+
import { markAcpConnectCardRaised } from "./acp-connect-card-state.js";
|
|
18
19
|
import { AcpAgentProcess } from "./agent-process.js";
|
|
20
|
+
import {
|
|
21
|
+
ACP_AUTH_RECOVERY_GUIDANCE,
|
|
22
|
+
ACP_CLAUDE_AUTH_REQUIRED_CODE,
|
|
23
|
+
CLAUDE_ACP_COMMAND,
|
|
24
|
+
isAcpAuthRequired,
|
|
25
|
+
isClaudeAuthFailureMessage,
|
|
26
|
+
} from "./auth-required.js";
|
|
19
27
|
import { resolveAgentWithAutoInstall } from "./auto-install.js";
|
|
20
28
|
import { VellumAcpClientHandler } from "./client-handler.js";
|
|
21
29
|
import { deriveFailureError } from "./failure-error.js";
|
|
@@ -26,6 +34,30 @@ import type { AcpAgentConfig, AcpSessionState } from "./types.js";
|
|
|
26
34
|
|
|
27
35
|
const log = getLogger("acp:session-manager");
|
|
28
36
|
|
|
37
|
+
/**
|
|
38
|
+
* The `authCode` for a run that failed on its Claude credential, or undefined
|
|
39
|
+
* for any other failure. Adapter-gated first: the signals are Claude-specific,
|
|
40
|
+
* and the marker promises a repair only the Connect Claude flow can perform.
|
|
41
|
+
* Checks both auth-failure shapes (see `auth-required.ts`) against both the
|
|
42
|
+
* raw rejection and the derived failure message, since `deriveFailureError`
|
|
43
|
+
* may replace one with the other.
|
|
44
|
+
*/
|
|
45
|
+
function claudeAuthRequiredCode(
|
|
46
|
+
err: unknown,
|
|
47
|
+
failureMessage: string,
|
|
48
|
+
entry: { command: string },
|
|
49
|
+
): string | undefined {
|
|
50
|
+
if (entry.command !== CLAUDE_ACP_COMMAND) {
|
|
51
|
+
return undefined;
|
|
52
|
+
}
|
|
53
|
+
const rawMessage = err instanceof Error ? err.message : String(err);
|
|
54
|
+
return isAcpAuthRequired(err) ||
|
|
55
|
+
isClaudeAuthFailureMessage(rawMessage) ||
|
|
56
|
+
isClaudeAuthFailureMessage(failureMessage)
|
|
57
|
+
? ACP_CLAUDE_AUTH_REQUIRED_CODE
|
|
58
|
+
: undefined;
|
|
59
|
+
}
|
|
60
|
+
|
|
29
61
|
/**
|
|
30
62
|
* The manager's "unknown session id" error. Thrown whenever an operation
|
|
31
63
|
* references an acpSessionId with no in-memory entry (and, for resume, no
|
|
@@ -1093,6 +1125,11 @@ export class AcpSessionManager {
|
|
|
1093
1125
|
err.message,
|
|
1094
1126
|
current.process.stderrSince(stderrMark),
|
|
1095
1127
|
);
|
|
1128
|
+
const errorCode = claudeAuthRequiredCode(
|
|
1129
|
+
err,
|
|
1130
|
+
failureMessage,
|
|
1131
|
+
current,
|
|
1132
|
+
);
|
|
1096
1133
|
if (current.state.status !== "cancelled") {
|
|
1097
1134
|
current.state.status = "failed";
|
|
1098
1135
|
current.state.completedAt = Date.now();
|
|
@@ -1100,14 +1137,40 @@ export class AcpSessionManager {
|
|
|
1100
1137
|
}
|
|
1101
1138
|
current.currentPrompt = null;
|
|
1102
1139
|
log.error(
|
|
1103
|
-
{ acpSessionId, error: err.message, failureMessage },
|
|
1140
|
+
{ acpSessionId, error: err.message, failureMessage, errorCode },
|
|
1104
1141
|
"ACP prompt failed",
|
|
1105
1142
|
);
|
|
1143
|
+
// `acp_session_error` keeps its pre-existing shape so older packaged
|
|
1144
|
+
// clients still parse it; the recovery signal rides its own event
|
|
1145
|
+
// below (see `api/events/acp-auth-required.ts`).
|
|
1106
1146
|
current.sendToVellum({
|
|
1107
1147
|
type: "acp_session_error",
|
|
1108
1148
|
acpSessionId,
|
|
1109
1149
|
error: failureMessage,
|
|
1110
1150
|
});
|
|
1151
|
+
// The recovery surface (event, model guidance, prompt-dedup marker)
|
|
1152
|
+
// is raised as a unit, and only when a spawning tool call exists to
|
|
1153
|
+
// anchor the card AND the user did not already stop the run. A
|
|
1154
|
+
// cancelled run renders no card (the client guards it), so marking
|
|
1155
|
+
// the never-cleared prompt-dedup registry here would suppress the
|
|
1156
|
+
// secure token prompt against a card that never appears.
|
|
1157
|
+
const recoveryAnchor =
|
|
1158
|
+
errorCode !== undefined && current.state.status !== "cancelled"
|
|
1159
|
+
? current.parentToolUseId
|
|
1160
|
+
: undefined;
|
|
1161
|
+
if (errorCode !== undefined && recoveryAnchor !== undefined) {
|
|
1162
|
+
current.sendToVellum({
|
|
1163
|
+
type: "acp_auth_required",
|
|
1164
|
+
acpSessionId,
|
|
1165
|
+
authCode: errorCode,
|
|
1166
|
+
agent: current.state.agentId,
|
|
1167
|
+
parentToolUseId: recoveryAnchor,
|
|
1168
|
+
});
|
|
1169
|
+
// Same registry as the missing-token spawn path, so the
|
|
1170
|
+
// credential-prompt route redirects a redundant secure prompt at
|
|
1171
|
+
// the card instead of stacking a second one.
|
|
1172
|
+
markAcpConnectCardRaised(current.parentConversationId);
|
|
1173
|
+
}
|
|
1111
1174
|
|
|
1112
1175
|
// Persist the terminal row before teardown clears the buffer.
|
|
1113
1176
|
this.persistTerminal(acpSessionId, current);
|
|
@@ -1123,7 +1186,10 @@ export class AcpSessionManager {
|
|
|
1123
1186
|
if (current.state.status !== "cancelled") {
|
|
1124
1187
|
this.notifyParent(
|
|
1125
1188
|
current,
|
|
1126
|
-
`[ACP agent "${current.state.agentId}" failed]\n\n${failureMessage}
|
|
1189
|
+
`[ACP agent "${current.state.agentId}" failed]\n\n${failureMessage}` +
|
|
1190
|
+
(recoveryAnchor !== undefined
|
|
1191
|
+
? `\n\n${ACP_AUTH_RECOVERY_GUIDANCE}`
|
|
1192
|
+
: ""),
|
|
1127
1193
|
);
|
|
1128
1194
|
}
|
|
1129
1195
|
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `acp_auth_required` SSE event.
|
|
3
|
+
*
|
|
4
|
+
* Server to client notification that an ACP run failed because the agent
|
|
5
|
+
* rejected its stored credential and the client can offer a re-authentication
|
|
6
|
+
* affordance. Always emitted immediately AFTER the `acp_session_error` that
|
|
7
|
+
* carries the human-readable failure.
|
|
8
|
+
*
|
|
9
|
+
* It is a separate event rather than a field on `acp_session_error` because
|
|
10
|
+
* every event schema here is `.strict()` and clients fall back to an inert
|
|
11
|
+
* `unknown` event when parsing fails: a new FIELD makes an older packaged
|
|
12
|
+
* client (iOS/macOS bundle the web app) reject the whole error event and
|
|
13
|
+
* render the run as still active, while an unknown event TYPE is simply
|
|
14
|
+
* ignored. Additive signals on existing events need their own event type for
|
|
15
|
+
* the same reason.
|
|
16
|
+
*
|
|
17
|
+
* Canonical wire-contract source. Re-exported to external consumers via
|
|
18
|
+
* `@vellumai/assistant-api` (the `api/index.ts` barrel).
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
import { z } from "zod";
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* The code for a `claude-agent-acp` run whose Claude credential needs to be
|
|
25
|
+
* reconnected: the `authCode` on this event for post-spawn failures, and the
|
|
26
|
+
* `errorCode` on the failed `acp_spawn` tool result for pre-spawn ones.
|
|
27
|
+
* Single source for both sides of the wire: the daemon re-exports it via
|
|
28
|
+
* `acp/auth-required.ts`, the web client via
|
|
29
|
+
* `domains/chat/utils/acp-connect.ts`.
|
|
30
|
+
*/
|
|
31
|
+
export const ACP_CLAUDE_AUTH_REQUIRED_CODE = "acp_claude_auth_required";
|
|
32
|
+
|
|
33
|
+
export const AcpAuthRequiredEventSchema = z
|
|
34
|
+
.object({
|
|
35
|
+
type: z.literal("acp_auth_required"),
|
|
36
|
+
acpSessionId: z.string(),
|
|
37
|
+
/**
|
|
38
|
+
* Machine-readable classification of what kind of re-authentication is
|
|
39
|
+
* needed, so clients branch on a stable value rather than on the failure
|
|
40
|
+
* text (which varies by adapter and failure mode). Currently only
|
|
41
|
+
* {@link ACP_CLAUDE_AUTH_REQUIRED_CODE}.
|
|
42
|
+
*/
|
|
43
|
+
authCode: z.string(),
|
|
44
|
+
/** Agent id the run was spawned with, for display. */
|
|
45
|
+
agent: z.string(),
|
|
46
|
+
/**
|
|
47
|
+
* Tool-use id of the `acp_spawn` call that started this run, so the
|
|
48
|
+
* client anchors the inline affordance to the right transcript row.
|
|
49
|
+
* Absent when the run was not started by a tool call.
|
|
50
|
+
*/
|
|
51
|
+
parentToolUseId: z.string().optional(),
|
|
52
|
+
})
|
|
53
|
+
.strict();
|
|
54
|
+
|
|
55
|
+
export type AcpAuthRequiredEvent = z.infer<typeof AcpAuthRequiredEventSchema>;
|
package/src/api/index.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
|
|
3
|
+
import { AcpAuthRequiredEventSchema } from "./events/acp-auth-required.js";
|
|
3
4
|
import { AcpSessionCompletedEventSchema } from "./events/acp-session-completed.js";
|
|
4
5
|
import { AcpSessionErrorEventSchema } from "./events/acp-session-error.js";
|
|
5
6
|
import { AcpSessionSpawnedEventSchema } from "./events/acp-session-spawned.js";
|
|
@@ -149,6 +150,11 @@ export {
|
|
|
149
150
|
SSE_REPLAY_RING_COUNT_LIMIT,
|
|
150
151
|
} from "./constants/sse-replay.js";
|
|
151
152
|
export { DEFAULT_TOOL_EXECUTION_TIMEOUT_SEC } from "./constants/tool-execution.js";
|
|
153
|
+
export {
|
|
154
|
+
ACP_CLAUDE_AUTH_REQUIRED_CODE,
|
|
155
|
+
type AcpAuthRequiredEvent,
|
|
156
|
+
AcpAuthRequiredEventSchema,
|
|
157
|
+
} from "./events/acp-auth-required.js";
|
|
152
158
|
export {
|
|
153
159
|
type AcpSessionCompletedEvent,
|
|
154
160
|
AcpSessionCompletedEventSchema,
|
|
@@ -895,6 +901,7 @@ export {
|
|
|
895
901
|
* migration recipe.
|
|
896
902
|
*/
|
|
897
903
|
export const AssistantEventSchema = z.discriminatedUnion("type", [
|
|
904
|
+
AcpAuthRequiredEventSchema,
|
|
898
905
|
AcpSessionCompletedEventSchema,
|
|
899
906
|
AcpSessionErrorEventSchema,
|
|
900
907
|
AcpSessionSpawnedEventSchema,
|
package/src/apps/app-store.ts
CHANGED
|
@@ -1422,6 +1422,9 @@ export function backfillAppConversationIds(): void {
|
|
|
1422
1422
|
}
|
|
1423
1423
|
|
|
1424
1424
|
try {
|
|
1425
|
+
// Unfinalized rows cannot match: their content is a { ref } pointer,
|
|
1426
|
+
// never inline surface JSON, so this scan sees finalized content only by
|
|
1427
|
+
// shape. No completeness predicate needed.
|
|
1425
1428
|
const rows = rawAll<{ conversation_id: string; content: string }>(
|
|
1426
1429
|
"apps:backfillConversationIds",
|
|
1427
1430
|
`SELECT conversation_id, content FROM messages WHERE content LIKE '%"type":"ui_surface"%'`,
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import { existsSync } from "node:fs";
|
|
9
|
-
import { mkdir
|
|
9
|
+
import { mkdir } from "node:fs/promises";
|
|
10
10
|
import { join } from "node:path";
|
|
11
11
|
|
|
12
12
|
import { ensureBun } from "../util/bun-runtime.js";
|
|
@@ -24,8 +24,8 @@ export const ALLOWED_PACKAGES: readonly string[] = [
|
|
|
24
24
|
"clsx",
|
|
25
25
|
] as const;
|
|
26
26
|
|
|
27
|
+
/** Bounds a runaway install; the allowlist above bounds what may be installed. */
|
|
27
28
|
const INSTALL_TIMEOUT_MS = 10_000;
|
|
28
|
-
const MAX_PACKAGE_SIZE_BYTES = 5 * 1024 * 1024; // 5 MB
|
|
29
29
|
|
|
30
30
|
/** In-flight install promises keyed by package name, to deduplicate concurrent requests. */
|
|
31
31
|
const inflight = new Map<string, Promise<string | null>>();
|
|
@@ -148,37 +148,9 @@ async function installPackage(
|
|
|
148
148
|
return null;
|
|
149
149
|
}
|
|
150
150
|
|
|
151
|
-
// Enforce max size
|
|
152
|
-
if (existsSync(pkgDir)) {
|
|
153
|
-
const size = await dirSize(pkgDir);
|
|
154
|
-
if (size > MAX_PACKAGE_SIZE_BYTES) {
|
|
155
|
-
log.warn({ pkg, size }, "Package exceeds size limit, removing");
|
|
156
|
-
const { rm } = await import("node:fs/promises");
|
|
157
|
-
await rm(pkgDir, { recursive: true, force: true });
|
|
158
|
-
return null;
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
|
|
162
151
|
return existsSync(pkgDir) ? nodeModulesDir : null;
|
|
163
152
|
} catch (err) {
|
|
164
153
|
log.warn({ pkg, err }, "Package resolution failed");
|
|
165
154
|
return null;
|
|
166
155
|
}
|
|
167
156
|
}
|
|
168
|
-
|
|
169
|
-
/** Recursively sum file sizes under a directory. */
|
|
170
|
-
async function dirSize(dir: string): Promise<number> {
|
|
171
|
-
const { readdir } = await import("node:fs/promises");
|
|
172
|
-
const entries = await readdir(dir, { withFileTypes: true });
|
|
173
|
-
let total = 0;
|
|
174
|
-
for (const entry of entries) {
|
|
175
|
-
const full = join(dir, entry.name);
|
|
176
|
-
if (entry.isDirectory()) {
|
|
177
|
-
total += await dirSize(full);
|
|
178
|
-
} else {
|
|
179
|
-
const s = await stat(full);
|
|
180
|
-
total += s.size;
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
return total;
|
|
184
|
-
}
|