@vellumai/assistant 0.11.7 → 0.11.8-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 +10 -10
- package/Dockerfile +1 -0
- package/docs/architecture/security.md +1 -1
- package/docs/error-handling.md +1 -2
- package/docs/guardian-request-flow.md +51 -5
- package/docs/trusted-contact-access.md +13 -12
- package/knip.json +1 -0
- package/node_modules/@vellumai/avatar-catalog/package.json +19 -0
- package/{src/avatar/character-components.ts → node_modules/@vellumai/avatar-catalog/src/catalog.ts} +12 -44
- package/node_modules/@vellumai/avatar-catalog/src/colors.ts +19 -0
- package/node_modules/@vellumai/avatar-catalog/src/index.ts +25 -0
- package/node_modules/@vellumai/avatar-catalog/src/types.ts +47 -0
- package/node_modules/@vellumai/avatar-catalog/tsconfig.json +20 -0
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/package.json +1 -0
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/channels.ts +7 -0
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/index.ts +1 -0
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/no-response.ts +84 -0
- package/node_modules/@vellumai/credential-storage/src/static-credentials.ts +43 -12
- package/node_modules/@vellumai/environments/package.json +2 -1
- package/node_modules/@vellumai/environments/src/__tests__/test-path-guard.test.ts +89 -0
- package/node_modules/@vellumai/environments/src/test-path-guard.ts +113 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/package.json +1 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/channels.ts +7 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/index.ts +1 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/no-response.ts +84 -0
- package/node_modules/@vellumai/gateway-client/src/__tests__/guardian-request-contract.test.ts +8 -18
- package/node_modules/@vellumai/gateway-client/src/__tests__/inbound-event-kind.test.ts +46 -0
- package/node_modules/@vellumai/gateway-client/src/__tests__/invite-contract.test.ts +1 -0
- package/node_modules/@vellumai/gateway-client/src/gateway-ipc-contracts.ts +46 -0
- package/node_modules/@vellumai/gateway-client/src/guardian-request-contract.ts +62 -21
- package/node_modules/@vellumai/gateway-client/src/inbound-contract.ts +14 -0
- package/node_modules/@vellumai/gateway-client/src/inbound-event-kind.ts +53 -0
- package/node_modules/@vellumai/gateway-client/src/index.ts +11 -5
- package/node_modules/@vellumai/gateway-client/src/invite-contract.ts +1 -0
- package/node_modules/@vellumai/service-contracts/package.json +1 -0
- package/node_modules/@vellumai/service-contracts/src/channels.ts +7 -0
- package/node_modules/@vellumai/service-contracts/src/index.ts +1 -0
- package/node_modules/@vellumai/service-contracts/src/no-response.ts +84 -0
- package/openapi.yaml +481 -9
- package/package.json +3 -1
- package/scripts/sync-llm-catalog.ts +3 -0
- package/src/__tests__/agent-loop-callsite-precedence.test.ts +16 -0
- package/src/__tests__/agent-loop-compacted-history-transform.test.ts +304 -0
- package/src/__tests__/agent-wake-disk-pressure-callsite.test.ts +3 -0
- package/src/__tests__/agent-wake-override-profile.test.ts +3 -0
- package/src/__tests__/anthropic-provider.test.ts +56 -0
- package/src/__tests__/approval-cascade.test.ts +0 -13
- package/src/__tests__/attachments-store.test.ts +117 -6
- package/src/__tests__/attachments.test.ts +122 -0
- package/src/__tests__/bundled-asset.test.ts +22 -1
- package/src/__tests__/channel-approval.test.ts +14 -7
- package/src/__tests__/channel-approvals.test.ts +3 -3
- package/src/__tests__/channel-delivery-store.test.ts +58 -0
- package/src/__tests__/channel-inbound-disk-pressure.test.ts +1 -0
- package/src/__tests__/channel-invite-transport.test.ts +1 -0
- package/src/__tests__/channel-readiness-slack-remote.test.ts +3 -3
- package/src/__tests__/compaction-events.test.ts +0 -12
- package/src/__tests__/compactor-call-site-logging.test.ts +7 -1
- package/src/__tests__/compactor-retained-image-validation.test.ts +15 -0
- package/src/__tests__/config-schema.test.ts +35 -0
- package/src/__tests__/contacts-write.test.ts +141 -3
- package/src/__tests__/conversation-abort-tool-results.test.ts +0 -13
- package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +0 -12
- package/src/__tests__/conversation-agent-loop-overflow.test.ts +0 -13
- package/src/__tests__/conversation-agent-loop.test.ts +95 -13
- package/src/__tests__/conversation-confirmation-signals.test.ts +0 -13
- package/src/__tests__/conversation-load-history-repair.test.ts +418 -0
- package/src/__tests__/conversation-media-retry.test.ts +33 -2
- package/src/__tests__/conversation-pairing.test.ts +54 -0
- package/src/__tests__/conversation-process-callsite.test.ts +0 -12
- package/src/__tests__/conversation-provider-retry-repair.test.ts +0 -13
- package/src/__tests__/conversation-queue.test.ts +4 -13
- package/src/__tests__/conversation-retry-route.test.ts +5 -0
- package/src/__tests__/conversation-routes-disk-view.test.ts +1 -1
- package/src/__tests__/conversation-routes-enabled-plugins.test.ts +1 -1
- package/src/__tests__/conversation-routes-hidden-queue.test.ts +1 -1
- package/src/__tests__/conversation-routes-slash-commands.test.ts +1 -1
- package/src/__tests__/conversation-sight-frames.test.ts +615 -0
- package/src/__tests__/conversation-slash-queue.test.ts +0 -13
- package/src/__tests__/conversation-slash-unknown.test.ts +0 -13
- package/src/__tests__/conversation-speed-override.test.ts +0 -12
- package/src/__tests__/conversation-store-ephemeral.test.ts +0 -12
- package/src/__tests__/conversation-summarize-route.test.ts +5 -0
- package/src/__tests__/conversation-summarize-up-to.test.ts +102 -0
- package/src/__tests__/conversation-wait-for-idle.test.ts +0 -12
- package/src/__tests__/conversation-workspace-cache-state.test.ts +0 -5
- package/src/__tests__/conversation-workspace-injection.test.ts +0 -17
- package/src/__tests__/conversation-workspace-tool-tracking.test.ts +0 -17
- package/src/__tests__/credential-health-service.test.ts +45 -0
- package/src/__tests__/credential-record-retire.test.ts +256 -0
- package/src/__tests__/credential-record-write-through.test.ts +8 -4
- package/src/__tests__/db-conversation-fork-lineage-migration.test.ts +0 -4
- package/src/__tests__/db-conversation-inference-profile-migration.test.ts +0 -4
- package/src/__tests__/db-llm-request-log-provider-migration.test.ts +0 -8
- package/src/__tests__/delete-propagation.test.ts +121 -3
- package/src/__tests__/evict-conversations-for-reload.test.ts +46 -6
- package/src/__tests__/file-ops-service.test.ts +8 -3
- package/src/__tests__/gemini-provider.test.ts +64 -0
- package/src/__tests__/guardian-action-service-scope.test.ts +118 -0
- package/src/__tests__/guardian-card-withdrawal.test.ts +299 -5
- package/src/__tests__/guardian-expiry-notifier.test.ts +117 -13
- package/src/__tests__/guardian-gateway-sim.test.ts +119 -0
- package/src/__tests__/guardian-gateway-sim.ts +56 -20
- package/src/__tests__/guardian-reply-router-answer-mode.test.ts +6 -0
- package/src/__tests__/guardian-request-status-sync.test.ts +162 -0
- package/src/__tests__/guardian-wait-controller.test.ts +6 -0
- package/src/__tests__/headless-browser-mode.test.ts +27 -0
- package/src/__tests__/helpers/mock-conversation.ts +2 -0
- package/src/__tests__/list-messages-system-card.test.ts +70 -0
- package/src/__tests__/llm-catalog-parity.test.ts +2 -0
- package/src/__tests__/managed-profile-guard.test.ts +2 -2
- package/src/__tests__/media-resolve-image-validation.test.ts +106 -0
- package/src/__tests__/media-stream-server-integration.test.ts +6 -0
- package/src/__tests__/non-member-access-request.test.ts +12 -0
- package/src/__tests__/notification-discord-adapter.test.ts +76 -4
- package/src/__tests__/oauth-commands-routes.test.ts +21 -0
- package/src/__tests__/openai-provider.test.ts +63 -0
- package/src/__tests__/outbound-slack-persistence.test.ts +114 -2
- package/src/__tests__/persist-unsendable-image-downscale.test.ts +63 -1
- package/src/__tests__/persist-unsendable-image.test.ts +19 -0
- package/src/__tests__/plugin-app-serve-routes.test.ts +2 -2
- package/src/__tests__/plugin-import-boundary-guard.test.ts +1 -0
- package/src/__tests__/process-message-background-slack.test.ts +1 -1
- package/src/__tests__/process-message-display-content.test.ts +1 -1
- package/src/__tests__/processing-flag-persist-failure.test.ts +0 -5
- package/src/__tests__/prompt-cache-cross-turn-stability.test.ts +0 -17
- package/src/__tests__/qdrant-manager.test.ts +6 -0
- package/src/__tests__/reaction-persistence.test.ts +58 -62
- package/src/__tests__/retry-thinking-adaptive-only.test.ts +67 -0
- package/src/__tests__/server-history-render.test.ts +22 -0
- package/src/__tests__/sight-frame-retention.test.ts +362 -0
- package/src/__tests__/slack-reaction-approvals.test.ts +44 -24
- package/src/__tests__/slack-reaction-guardian-approval.test.ts +29 -1
- package/src/__tests__/subagent-fork-notifications.test.ts +2 -0
- package/src/__tests__/subagent-manager-notify.test.ts +2 -0
- package/src/__tests__/subagent-notify-parent.test.ts +2 -0
- package/src/__tests__/subagent-notify-stale-rebuild.test.ts +85 -0
- package/src/__tests__/subagent-tool-gate-mode.test.ts +4 -4
- package/src/__tests__/terminal-tools.test.ts +9 -9
- package/src/__tests__/tool-result-spool.test.ts +5 -8
- package/src/__tests__/usage-attribution.test.ts +26 -0
- package/src/acp/__tests__/acp-auth-marker-store.test.ts +191 -0
- package/src/acp/__tests__/acp-claude-oauth.test.ts +262 -0
- package/src/acp/__tests__/acp-connect-card-state.test.ts +17 -0
- package/src/acp/__tests__/helpers/acp-history-db.ts +10 -3
- package/src/acp/acp-auth-marker-store.ts +196 -0
- package/src/acp/acp-claude-oauth.ts +154 -4
- package/src/acp/acp-connect-card-state.ts +49 -8
- package/src/acp/agent-process.ts +1 -0
- package/src/acp/auto-install.ts +1 -0
- package/src/acp/client-handler.ts +1 -0
- package/src/acp/prepare-agent-env.ts +79 -1
- package/src/acp/resolve-agent.ts +12 -0
- package/src/acp/session-manager.test.ts +183 -0
- package/src/acp/session-manager.ts +110 -6
- package/src/acp/types.ts +17 -0
- package/src/agent/attachments.ts +7 -2
- package/src/agent/loop.ts +59 -1
- package/src/api/events/contact-form-closed.ts +32 -0
- package/src/api/events/contact-record-request.ts +63 -0
- package/src/api/events/contact-request.ts +10 -3
- package/src/api/index.ts +20 -0
- package/src/api/responses/conversation-message.ts +20 -0
- package/src/api/responses/home.ts +101 -0
- package/src/approvals/AGENTS.md +1 -1
- package/src/approvals/guardian-card-withdrawal.ts +127 -30
- package/src/approvals/guardian-decision-primitive.ts +9 -0
- package/src/approvals/guardian-expiry-notifier.ts +22 -16
- package/src/approvals/guardian-request-status-sync.ts +98 -0
- package/src/avatar/svg-compositor.ts +1 -1
- package/src/avatar/traits-png-sync.ts +1 -1
- package/src/bundler/app-compiler.ts +1 -0
- package/src/bundler/compiler-tools.ts +1 -0
- package/src/bundler/package-resolver.ts +1 -0
- package/src/calls/__tests__/voice-session-bridge.test.ts +183 -3
- package/src/calls/guardian-action-sweep.ts +6 -1
- package/src/calls/voice-session-bridge.ts +46 -1
- package/src/channels/__tests__/gateway-guardian-requests.test.ts +13 -18
- package/src/channels/gateway-guardian-requests.ts +37 -18
- package/src/cli/__tests__/contacts-record-prompt-cli.test.ts +329 -0
- package/src/cli/commands/__tests__/browser.test.ts +65 -1
- package/src/cli/commands/browser.ts +30 -10
- package/src/cli/commands/contacts.help.ts +173 -11
- package/src/cli/commands/contacts.ts +343 -4
- package/src/cli/commands/db/refresh.ts +1 -0
- package/src/cli/commands/oauth/index.help.ts +6 -3
- package/src/cli/commands/oauth/request.test.ts +37 -0
- package/src/cli/commands/oauth/request.ts +7 -6
- package/src/cli/lib/bundled-marketplace.json +1 -1
- package/src/cli/lib/install-from-github.ts +20 -19
- package/src/cli/lib/install-plugin-dependencies.ts +3 -5
- package/src/cli/lib/merge-plugin-tree.ts +1 -0
- package/src/cli/lib/publish-plugin.ts +4 -1
- package/src/config/__tests__/balanced-model-experiment.test.ts +63 -31
- package/src/config/__tests__/default-profile-catalog.test.ts +2 -2
- package/src/config/balanced-model-experiment.ts +15 -9
- package/src/config/bundled-skills/contacts/SKILL.md +61 -18
- package/src/config/default-profile-catalog.ts +12 -11
- package/src/config/feature-flag-registry.json +22 -13
- package/src/config/schema.ts +2 -0
- package/src/config/schemas/__tests__/live-voice.test.ts +13 -0
- package/src/config/schemas/live-voice.ts +7 -0
- package/src/config/schemas/sight.ts +45 -0
- package/src/config/skills.ts +2 -1
- package/src/contacts/__tests__/mirror-reconciler.test.ts +352 -0
- package/src/contacts/contact-store.ts +47 -4
- package/src/contacts/contacts-write.ts +5 -2
- package/src/contacts/mirror-reconciler.ts +210 -0
- package/src/contacts/mirror-tombstones.ts +42 -0
- package/src/contacts/notify-contacts-changed.ts +11 -2
- package/src/context/compactor.ts +47 -19
- package/src/context/tool-result-spool.ts +21 -14
- package/src/conversations/message-consolidation.ts +2 -2
- package/src/credential-health/credential-health-service.ts +37 -6
- package/src/daemon/conversation-agent-loop-handlers.ts +19 -2
- package/src/daemon/conversation-agent-loop.ts +100 -1
- package/src/daemon/conversation-media-retry.ts +11 -6
- package/src/daemon/conversation-messaging.ts +293 -30
- package/src/daemon/conversation-runtime-assembly.ts +67 -0
- package/src/daemon/conversation-sight-frames.ts +187 -0
- package/src/daemon/conversation-store.ts +18 -28
- package/src/daemon/conversation-surfaces.ts +12 -2
- package/src/daemon/conversation-tool-setup.ts +39 -10
- package/src/daemon/conversation.ts +159 -16
- package/src/daemon/daemon-control.ts +6 -17
- package/src/daemon/handlers/config-model.test.ts +1 -0
- package/src/daemon/handlers/conversations.ts +10 -11
- package/src/daemon/install-assistant-command.ts +1 -0
- package/src/daemon/lifecycle.ts +10 -13
- package/src/daemon/message-types/sync.ts +4 -0
- package/src/daemon/process-message.ts +2 -17
- package/src/daemon/reaction-history-render.test.ts +162 -0
- package/src/daemon/reaction-history-render.ts +115 -0
- package/src/daemon/reaction-record.test.ts +162 -0
- package/src/daemon/reaction-record.ts +83 -0
- package/src/daemon/shutdown-handlers.ts +0 -2
- package/src/daemon/summarize-boundary.ts +12 -0
- package/src/daemon/video-thumbnail.ts +1 -1
- package/src/home/feed-types.ts +4 -0
- package/src/home/feed-writer.ts +25 -0
- package/src/live-voice/__tests__/live-voice-attach-frame.test.ts +679 -0
- package/src/live-voice/__tests__/live-voice-attach-image.test.ts +8 -5
- package/src/live-voice/__tests__/live-voice-photo.test.ts +637 -1
- package/src/live-voice/__tests__/live-voice-sight-frame-inline.test.ts +787 -0
- package/src/live-voice/__tests__/live-voice-sight-frame.test.ts +347 -0
- package/src/live-voice/__tests__/live-voice-vad.test.ts +109 -0
- package/src/live-voice/live-voice-photo.ts +420 -65
- package/src/live-voice/live-voice-session.ts +311 -27
- package/src/live-voice/protocol.ts +147 -11
- package/src/messaging/provider-message-metadata.ts +7 -0
- package/src/messaging/providers/__tests__/transport-dispatch.test.ts +113 -1
- package/src/messaging/providers/channel-transport.ts +37 -0
- package/src/messaging/providers/discord/api.ts +4 -4
- package/src/messaging/providers/discord/send.test.ts +177 -2
- package/src/messaging/providers/discord/send.ts +197 -10
- package/src/messaging/providers/discord/transport.ts +10 -0
- package/src/messaging/providers/index.ts +42 -0
- package/src/messaging/providers/slack/__tests__/adapter-mention-rendering.test.ts +129 -3
- package/src/messaging/providers/slack/__tests__/auth.test.ts +35 -0
- package/src/messaging/providers/slack/__tests__/message-content.test.ts +152 -0
- package/src/messaging/providers/slack/adapter.ts +112 -27
- package/src/messaging/providers/slack/api.test.ts +10 -0
- package/src/messaging/providers/slack/auth.ts +32 -6
- package/src/messaging/providers/slack/client.ts +0 -15
- package/src/messaging/providers/slack/message-content.ts +137 -0
- package/src/messaging/providers/slack/send.test.ts +43 -2
- package/src/messaging/providers/slack/send.ts +18 -5
- package/src/messaging/providers/slack/transport.ts +10 -0
- package/src/messaging/providers/slack/types.ts +21 -2
- package/src/messaging/providers/telegram-bot/adapter.ts +5 -1
- package/src/messaging/providers/telegram-bot/send.test.ts +49 -2
- package/src/messaging/providers/telegram-bot/send.ts +41 -1
- package/src/messaging/providers/telegram-bot/transport.ts +10 -0
- package/src/messaging/providers/telegram-bot/withdraw.ts +13 -5
- package/src/messaging/reaction-envelopes.ts +79 -0
- package/src/monitoring/control.ts +2 -2
- package/src/monitoring/page-cache.ts +5 -1
- package/src/notifications/AGENTS.md +1 -1
- package/src/notifications/__tests__/guardian-feed-projection.test.ts +360 -0
- package/src/notifications/__tests__/home-feed-side-effect.test.ts +8 -0
- package/src/notifications/adapters/discord.ts +62 -9
- package/src/notifications/conversation-pairing.ts +31 -4
- package/src/notifications/emit-signal.ts +8 -5
- package/src/notifications/guardian-delivery-recorder.ts +47 -6
- package/src/notifications/guardian-feed-projection.ts +492 -0
- package/src/notifications/home-feed-side-effect.ts +59 -11
- package/src/oauth/byo-connection.test.ts +22 -0
- package/src/oauth/byo-connection.ts +11 -5
- package/src/oauth/connection.ts +3 -2
- package/src/oauth/platform-connection.test.ts +35 -0
- package/src/oauth/platform-connection.ts +18 -13
- package/src/oauth/scope-utils.ts +48 -0
- package/src/oauth/seed-providers.ts +12 -0
- package/src/outbound-proxy/certs.ts +5 -4
- package/src/permissions/question-guardian-request.test.ts +7 -1
- package/src/persistence/attachments-store.ts +134 -31
- package/src/persistence/conversation-crud.ts +137 -4
- package/src/persistence/conversation-types.ts +109 -0
- package/src/persistence/db-connection.ts +3 -21
- package/src/persistence/delivery-crud.ts +42 -12
- package/src/persistence/embeddings/__tests__/embedding-local-lifecycle.test.ts +15 -2
- package/src/persistence/embeddings/embedding-local.ts +7 -112
- package/src/persistence/embeddings/qdrant-manager.ts +4 -1
- package/src/persistence/llm-request-log-store.ts +1 -1
- package/src/persistence/migrations/370-acp-session-history-auth-error-code.ts +36 -0
- package/src/persistence/migrations/371-acp-session-history-auth-error-credential.ts +40 -0
- package/src/persistence/migrations/372-create-acp-refused-credentials.ts +34 -0
- package/src/persistence/migrations/373-acp-auth-marker-index.ts +27 -0
- package/src/persistence/migrations/__tests__/370-acp-session-history-auth-error-code.test.ts +65 -0
- package/src/persistence/migrations/__tests__/371-acp-session-history-auth-error-credential.test.ts +68 -0
- package/src/persistence/migrations/__tests__/372-create-acp-refused-credentials.test.ts +64 -0
- package/src/persistence/migrations/__tests__/373-acp-auth-marker-index.test.ts +82 -0
- package/src/persistence/schema/acp.ts +25 -0
- package/src/persistence/steps.ts +8 -0
- package/src/platform/platform-patch-queue.test.ts +43 -41
- package/src/plugin-api/index.ts +12 -0
- package/src/plugins/defaults/compaction/window-manager.ts +2 -0
- package/src/plugins/defaults/image-recovery/recover.ts +12 -1
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-accounting.test.ts +166 -1
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-provider-path.test.ts +3 -0
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-wake-chain.test.ts +3 -0
- package/src/plugins/defaults/memory/jobs-worker.ts +1 -2
- package/src/plugins/defaults/memory/memory-retrospective-accounting.ts +53 -9
- package/src/plugins/defaults/memory/src/__tests__/memory-worker-routes.test.ts +4 -7
- package/src/plugins/defaults/memory/src/memory-worker-routes.ts +1 -1
- package/src/plugins/defaults/memory/v1/job-handlers/index-maintenance.ts +15 -1
- package/src/plugins/defaults/memory/worker-control.ts +1 -2
- package/src/providers/content-block-schema.ts +1 -0
- package/src/providers/fireworks/client.ts +9 -1
- package/src/providers/gemini/client.ts +22 -15
- package/src/providers/media-resolve.ts +24 -10
- package/src/providers/model-catalog.ts +91 -0
- package/src/providers/openai/chat-completions-provider.ts +44 -5
- package/src/providers/retry.ts +16 -11
- package/src/providers/types.ts +56 -0
- package/src/routes/__tests__/control.test.ts +4 -3
- package/src/routes/control.ts +1 -1
- package/src/routes/route-host-client.ts +1 -1
- package/src/runtime/AGENTS.md +30 -12
- package/src/runtime/__tests__/agent-wake.test.ts +71 -6
- package/src/runtime/__tests__/guardian-form-registry.test.ts +242 -0
- package/src/runtime/access-request-helper.ts +15 -7
- package/src/runtime/agent-wake.ts +16 -4
- package/src/runtime/channel-approval-types.ts +8 -5
- package/src/runtime/channel-invite-transport.ts +2 -0
- package/src/runtime/channel-invite-transports/discord.ts +27 -0
- package/src/runtime/channel-readiness-service.ts +4 -4
- package/src/runtime/channel-reply-delivery.ts +72 -28
- package/src/runtime/guardian-action-service.ts +9 -5
- package/src/runtime/guardian-form-registry.ts +256 -0
- package/src/runtime/guardian-reply-router.ts +33 -14
- package/src/runtime/invite-instruction-generator.ts +14 -2
- package/src/runtime/migrations/vbundle-metadata-merge.ts +4 -7
- package/src/runtime/no-response.ts +13 -74
- package/src/runtime/routes/__tests__/acp-routes.test.ts +527 -0
- package/src/runtime/routes/__tests__/app-management-shared-apps.test.ts +74 -0
- package/src/runtime/routes/__tests__/contact-form-kinds.test.ts +22 -0
- package/src/runtime/routes/__tests__/contact-record-prompt-route.test.ts +303 -0
- package/src/runtime/routes/__tests__/monitoring-routes.test.ts +3 -4
- package/src/runtime/routes/__tests__/pending-interactions-route.test.ts +6 -0
- package/src/runtime/routes/__tests__/route-host-worker-routes.test.ts +3 -4
- package/src/runtime/routes/__tests__/schedule-worker-routes.test.ts +5 -6
- package/src/runtime/routes/acp-routes-list.test.ts +58 -0
- package/src/runtime/routes/acp-routes.ts +253 -47
- package/src/runtime/routes/app-management-routes.ts +80 -47
- package/src/runtime/routes/approval-routes.ts +9 -0
- package/src/runtime/routes/archive-utils.ts +6 -1
- package/src/runtime/routes/avatar-routes.ts +1 -1
- package/src/runtime/routes/channel-route-shared.ts +18 -20
- package/src/runtime/routes/contact-prompt-routes.ts +271 -105
- package/src/runtime/routes/conversation-routes.ts +41 -32
- package/src/runtime/routes/credential-prompt-routes.ts +10 -1
- package/src/runtime/routes/debug-bash-routes.ts +1 -0
- package/src/runtime/routes/guardian-expiry-sweep.ts +89 -31
- package/src/runtime/routes/guardian-form-routes.ts +115 -0
- package/src/runtime/routes/inbound-message-handler.ts +82 -29
- package/src/runtime/routes/inbound-stages/acl-enforcement.ts +16 -3
- package/src/runtime/routes/inbound-stages/edit-intercept.ts +6 -0
- package/src/runtime/routes/inbound-stages/guardian-reply-intercept.test.ts +6 -0
- package/src/runtime/routes/inbound-stages/guardian-reply-intercept.ts +6 -1
- package/src/runtime/routes/inbound-stages/reaction-intercept.test.ts +50 -17
- package/src/runtime/routes/inbound-stages/reaction-intercept.ts +97 -94
- package/src/runtime/routes/index.ts +2 -0
- package/src/runtime/routes/integrations/slack/channel.ts +39 -18
- package/src/runtime/routes/migration-routes.ts +1 -3
- package/src/runtime/routes/monitoring-routes.ts +0 -3
- package/src/runtime/routes/oauth-commands-routes.ts +18 -5
- package/src/runtime/routes/route-host-worker-routes.ts +1 -1
- package/src/runtime/routes/schedule-worker-routes.ts +1 -1
- package/src/runtime/tool-grant-request-helper.ts +4 -0
- package/src/schedule/run-script.ts +1 -0
- package/src/schedule/scheduler.ts +1 -1
- package/src/schedule/worker-control.ts +2 -2
- package/src/security/oauth2.ts +2 -11
- package/src/security/secure-keys.ts +55 -8
- package/src/skills/catalog-install.ts +7 -4
- package/src/skills/clawhub.ts +1 -0
- package/src/skills/inline-command-runner.ts +1 -0
- package/src/stt/__tests__/room-noise-floor.test.ts +108 -0
- package/src/stt/room-noise-floor.ts +140 -0
- package/src/subagent/notify.ts +49 -3
- package/src/tools/__tests__/tool-input-schemas.test.ts +2 -0
- package/src/tools/acp/spawn.ts +2 -2
- package/src/tools/browser/__tests__/browser-status.test.ts +9 -0
- package/src/tools/browser/browser-execution.ts +10 -43
- package/src/tools/browser/browser-manager.ts +1 -0
- package/src/tools/browser/browser-status-constants.ts +6 -5
- package/src/tools/browser/runtime-check.ts +2 -0
- package/src/tools/channel/react-to-message.test.ts +193 -0
- package/src/tools/channel/react-to-message.ts +159 -0
- package/src/tools/credentials/metadata-store.ts +115 -19
- package/src/tools/credentials/store.ts +0 -2
- package/src/tools/filesystem/read.ts +2 -2
- package/src/tools/host-filesystem/read.ts +2 -2
- package/src/tools/host-terminal/host-shell.test.ts +24 -0
- package/src/tools/host-terminal/host-shell.ts +11 -38
- package/src/tools/shared/filesystem/file-ops-service.ts +10 -7
- package/src/tools/shared/shell-output.test.ts +104 -0
- package/src/tools/shared/shell-output.ts +112 -39
- package/src/tools/skills/sandbox-runner.ts +1 -0
- package/src/tools/system/request-permission.test.ts +34 -0
- package/src/tools/system/request-permission.ts +29 -5
- package/src/tools/terminal/shell.test.ts +63 -0
- package/src/tools/terminal/shell.ts +12 -44
- package/src/tools/tool-input-schemas.ts +2 -0
- package/src/tools/tool-manifest.ts +2 -0
- package/src/usage/attribution.ts +12 -4
- package/src/util/__tests__/worker-ownership.test.ts +77 -0
- package/src/util/__tests__/worker-process-command.test.ts +246 -1
- package/src/util/bun-runtime.ts +44 -13
- package/src/util/bundled-asset.ts +9 -1
- package/src/util/disk-usage.ts +1 -0
- package/src/util/file-use.ts +4 -1
- package/src/util/guardian-form-timeouts.ts +50 -0
- package/src/util/image-conversion.ts +1 -0
- package/src/util/oauth-request-body.test.ts +59 -0
- package/src/util/oauth-request-body.ts +56 -5
- package/src/util/platform.ts +69 -73
- package/src/util/process-table.ts +16 -5
- package/src/util/read-stdin.ts +5 -0
- package/src/util/spawn.ts +1 -0
- package/src/util/sqlite3-runtime.ts +25 -5
- package/src/util/worker-ownership.ts +193 -0
- package/src/util/worker-process.ts +375 -37
- package/src/workspace/git-service.ts +4 -12
- package/src/workspace/migrations/010-app-dir-rename.ts +1 -0
|
@@ -28,7 +28,13 @@ const setSecureKeyAsync = mock(
|
|
|
28
28
|
);
|
|
29
29
|
const getSecureKeyAsync = mock(async (_account: string) => getReturn);
|
|
30
30
|
|
|
31
|
+
// Spread the real module rather than listing two exports. These cases reach
|
|
32
|
+
// the marker tables, which pulls persistence into the graph, and anything in
|
|
33
|
+
// there that imports another secure-keys export would fail to resolve against
|
|
34
|
+
// a partial factory.
|
|
35
|
+
const realSecureKeys = await import("../../security/secure-keys.js");
|
|
31
36
|
mock.module("../../security/secure-keys.js", () => ({
|
|
37
|
+
...realSecureKeys,
|
|
32
38
|
setSecureKeyAsync,
|
|
33
39
|
getSecureKeyAsync,
|
|
34
40
|
}));
|
|
@@ -39,7 +45,24 @@ const { _setMetadataPath, getCredentialMetadata, upsertCredentialMetadata } =
|
|
|
39
45
|
const { acpSpawnCredentialDenialReason } =
|
|
40
46
|
await import("../prepare-agent-env.js");
|
|
41
47
|
|
|
48
|
+
const { hasAcpConnectCardRaised, markAcpConnectCardRaised } =
|
|
49
|
+
await import("../acp-connect-card-state.js");
|
|
50
|
+
const { installAcpConfigStub } = await import("./helpers/acp-config-stub.js");
|
|
51
|
+
const acpConfig = await installAcpConfigStub();
|
|
52
|
+
|
|
53
|
+
// The registry is retired per conversation by asking whether that conversation
|
|
54
|
+
// still has a marker worth showing, so these cases need a real (empty)
|
|
55
|
+
// database rather than an error path standing in for one.
|
|
56
|
+
const { initializeDb } = await import("../../persistence/db-init.js");
|
|
57
|
+
await initializeDb();
|
|
58
|
+
|
|
59
|
+
const { claudeTokenDigest, noteClaudeTokenRefused } =
|
|
60
|
+
await import("../acp-auth-marker-store.js");
|
|
61
|
+
const { clearHistory, insertHistoryRow } =
|
|
62
|
+
await import("./helpers/acp-history-db.js");
|
|
63
|
+
|
|
42
64
|
const {
|
|
65
|
+
acpConnectCardStillWarranted,
|
|
43
66
|
CLAUDE_OAUTH_CONFIG,
|
|
44
67
|
CLAUDE_MANUAL_REDIRECT_URI,
|
|
45
68
|
buildClaudeAuthorizeUrl,
|
|
@@ -48,6 +71,13 @@ const {
|
|
|
48
71
|
hasAcpClaudeToken,
|
|
49
72
|
} = await import("../acp-claude-oauth.js");
|
|
50
73
|
|
|
74
|
+
/**
|
|
75
|
+
* The card notification is detached from the store so it cannot delay a
|
|
76
|
+
* sign-in, so a test asserting on its effects has to let it run.
|
|
77
|
+
*/
|
|
78
|
+
const settleNotification = () =>
|
|
79
|
+
new Promise((resolve) => setTimeout(resolve, 10));
|
|
80
|
+
|
|
51
81
|
const ACP_SERVICE = "acp";
|
|
52
82
|
const OAUTH_FIELD = "claude_oauth_token";
|
|
53
83
|
const ACP_SPAWN_TOOL = "acp_spawn";
|
|
@@ -280,3 +310,235 @@ describe("hasAcpClaudeToken", () => {
|
|
|
280
310
|
expect(oauthMetadata()).toBeUndefined();
|
|
281
311
|
});
|
|
282
312
|
});
|
|
313
|
+
|
|
314
|
+
describe("storeAcpClaudeToken: retiring the card registry", () => {
|
|
315
|
+
test("clears the registry after the policy repair, not before it", () => {
|
|
316
|
+
// The write happens first and the policy repair second, so at the moment
|
|
317
|
+
// the credential seam asks, a token whose `acp_spawn` read is denied is
|
|
318
|
+
// not usable and nothing is retired. The repair on the next line is what
|
|
319
|
+
// makes it usable, so the notification has to run again after it.
|
|
320
|
+
upsertCredentialMetadata(ACP_SERVICE, OAUTH_FIELD, {
|
|
321
|
+
allowedTools: ["some_other_tool"],
|
|
322
|
+
allowedDomains: [],
|
|
323
|
+
});
|
|
324
|
+
expect(acpSpawnCredentialDenialReason(OAUTH_FIELD)).toBeDefined();
|
|
325
|
+
markAcpConnectCardRaised("conv-with-card");
|
|
326
|
+
getReturn = "sk-ant-oat-token";
|
|
327
|
+
|
|
328
|
+
return storeAcpClaudeToken("sk-ant-oat-token")
|
|
329
|
+
.then(settleNotification)
|
|
330
|
+
.then(() => {
|
|
331
|
+
expect(acpSpawnCredentialDenialReason(OAUTH_FIELD)).toBeUndefined();
|
|
332
|
+
expect(hasAcpConnectCardRaised("conv-with-card")).toBe(false);
|
|
333
|
+
});
|
|
334
|
+
});
|
|
335
|
+
|
|
336
|
+
test("leaves the registry alone when the stored value is unusable", () => {
|
|
337
|
+
// A bulk restore can land an api-key-shaped value. The marker comparison
|
|
338
|
+
// keeps the card up for that reason, so forgetting the registry would open
|
|
339
|
+
// the second prompt it exists to suppress.
|
|
340
|
+
markAcpConnectCardRaised("conv-keeps-card");
|
|
341
|
+
getReturn = "sk-ant-api-key-shaped";
|
|
342
|
+
|
|
343
|
+
return storeAcpClaudeToken("sk-ant-api-key-shaped")
|
|
344
|
+
.then(settleNotification)
|
|
345
|
+
.then(() => {
|
|
346
|
+
expect(hasAcpConnectCardRaised("conv-keeps-card")).toBe(true);
|
|
347
|
+
});
|
|
348
|
+
});
|
|
349
|
+
});
|
|
350
|
+
|
|
351
|
+
describe("storeAcpClaudeToken: a re-written rejected token retires nothing", () => {
|
|
352
|
+
test("keeps the registry entry while the marker still stands", () => {
|
|
353
|
+
// A bulk restore can store the very token Claude rejected. It passes the
|
|
354
|
+
// shape and policy checks, so usability says yes, but nothing about the
|
|
355
|
+
// failure changed: the snapshot goes on serving that marker and the card
|
|
356
|
+
// stays up. Forgetting the entry would open a second prompt beside it.
|
|
357
|
+
clearHistory();
|
|
358
|
+
const token = "sk-ant-oat-still-rejected";
|
|
359
|
+
getReturn = token;
|
|
360
|
+
insertHistoryRow({
|
|
361
|
+
id: "run-still-rejected",
|
|
362
|
+
parentConversationId: "conv-still-broken",
|
|
363
|
+
status: "failed",
|
|
364
|
+
authErrorCode: "acp_claude_auth_required",
|
|
365
|
+
authErrorCredential: claudeTokenDigest(token),
|
|
366
|
+
});
|
|
367
|
+
markAcpConnectCardRaised("conv-still-broken");
|
|
368
|
+
|
|
369
|
+
return storeAcpClaudeToken(token)
|
|
370
|
+
.then(settleNotification)
|
|
371
|
+
.then(() => {
|
|
372
|
+
expect(hasAcpConnectCardRaised("conv-still-broken")).toBe(true);
|
|
373
|
+
});
|
|
374
|
+
});
|
|
375
|
+
|
|
376
|
+
test("drops it once a different token makes the marker stale", () => {
|
|
377
|
+
clearHistory();
|
|
378
|
+
getReturn = "sk-ant-oat-replacement";
|
|
379
|
+
insertHistoryRow({
|
|
380
|
+
id: "run-repaired",
|
|
381
|
+
parentConversationId: "conv-repaired",
|
|
382
|
+
status: "failed",
|
|
383
|
+
authErrorCode: "acp_claude_auth_required",
|
|
384
|
+
authErrorCredential: claudeTokenDigest("sk-ant-oat-old-rejected"),
|
|
385
|
+
});
|
|
386
|
+
markAcpConnectCardRaised("conv-repaired");
|
|
387
|
+
|
|
388
|
+
return storeAcpClaudeToken("sk-ant-oat-replacement")
|
|
389
|
+
.then(settleNotification)
|
|
390
|
+
.then(() => {
|
|
391
|
+
expect(hasAcpConnectCardRaised("conv-repaired")).toBe(false);
|
|
392
|
+
});
|
|
393
|
+
});
|
|
394
|
+
});
|
|
395
|
+
|
|
396
|
+
describe("acpConnectCardStillWarranted", () => {
|
|
397
|
+
test("a mid-run card stands while its marker names the credential in use", () => {
|
|
398
|
+
clearHistory();
|
|
399
|
+
const token = "sk-ant-oat-in-use";
|
|
400
|
+
getReturn = token;
|
|
401
|
+
insertHistoryRow({
|
|
402
|
+
id: "run-warranted",
|
|
403
|
+
parentConversationId: "conv-warranted",
|
|
404
|
+
status: "failed",
|
|
405
|
+
authErrorCode: "acp_claude_auth_required",
|
|
406
|
+
authErrorCredential: claudeTokenDigest(token),
|
|
407
|
+
});
|
|
408
|
+
|
|
409
|
+
return acpConnectCardStillWarranted("conv-warranted").then((warranted) => {
|
|
410
|
+
expect(warranted).toBe(true);
|
|
411
|
+
});
|
|
412
|
+
});
|
|
413
|
+
|
|
414
|
+
test("a mid-run card falls once a different token is stored", () => {
|
|
415
|
+
// The repair need not be a credential write: editing the agent's
|
|
416
|
+
// configured token moves this answer too, which is why it is asked rather
|
|
417
|
+
// than remembered.
|
|
418
|
+
clearHistory();
|
|
419
|
+
getReturn = "sk-ant-oat-replacement";
|
|
420
|
+
insertHistoryRow({
|
|
421
|
+
id: "run-repaired",
|
|
422
|
+
parentConversationId: "conv-repaired-warrant",
|
|
423
|
+
status: "failed",
|
|
424
|
+
authErrorCode: "acp_claude_auth_required",
|
|
425
|
+
authErrorCredential: claudeTokenDigest("sk-ant-oat-old"),
|
|
426
|
+
});
|
|
427
|
+
|
|
428
|
+
return acpConnectCardStillWarranted("conv-repaired-warrant").then(
|
|
429
|
+
(warranted) => {
|
|
430
|
+
expect(warranted).toBe(false);
|
|
431
|
+
},
|
|
432
|
+
);
|
|
433
|
+
});
|
|
434
|
+
|
|
435
|
+
test("a pre-spawn card stands while there is no usable token", () => {
|
|
436
|
+
// The missing-token path has no session to record a marker on, so the
|
|
437
|
+
// absence of one says nothing; what keeps its card meaningful is that a
|
|
438
|
+
// spawn still has nothing to authenticate with.
|
|
439
|
+
clearHistory();
|
|
440
|
+
getReturn = undefined;
|
|
441
|
+
|
|
442
|
+
return acpConnectCardStillWarranted("conv-no-token").then((warranted) => {
|
|
443
|
+
expect(warranted).toBe(true);
|
|
444
|
+
});
|
|
445
|
+
});
|
|
446
|
+
|
|
447
|
+
test("a pre-spawn card falls once a usable token exists", () => {
|
|
448
|
+
clearHistory();
|
|
449
|
+
getReturn = "sk-ant-oat-now-present";
|
|
450
|
+
|
|
451
|
+
return acpConnectCardStillWarranted("conv-no-marker").then((warranted) => {
|
|
452
|
+
expect(warranted).toBe(false);
|
|
453
|
+
});
|
|
454
|
+
});
|
|
455
|
+
});
|
|
456
|
+
|
|
457
|
+
describe("acpConnectCardStillWarranted: a pre-spawn card and configured tokens", () => {
|
|
458
|
+
test("falls once the agent's configured token supplies a credential", () => {
|
|
459
|
+
// Repairing by setting `acp.agents.<id>.env.CLAUDE_CODE_OAUTH_TOKEN` never
|
|
460
|
+
// touches the vault, so asking the vault would call this card warranted
|
|
461
|
+
// forever even though the next spawn authenticates fine.
|
|
462
|
+
clearHistory();
|
|
463
|
+
getReturn = undefined;
|
|
464
|
+
acpConfig.setConfig({
|
|
465
|
+
agents: {
|
|
466
|
+
claude: {
|
|
467
|
+
command: "claude-agent-acp",
|
|
468
|
+
args: [],
|
|
469
|
+
env: { CLAUDE_CODE_OAUTH_TOKEN: "sk-ant-oat-from-config" },
|
|
470
|
+
},
|
|
471
|
+
},
|
|
472
|
+
});
|
|
473
|
+
markAcpConnectCardRaised("conv-config-repair", "claude");
|
|
474
|
+
|
|
475
|
+
return acpConnectCardStillWarranted("conv-config-repair").then(
|
|
476
|
+
(warranted) => {
|
|
477
|
+
expect(warranted).toBe(false);
|
|
478
|
+
},
|
|
479
|
+
);
|
|
480
|
+
});
|
|
481
|
+
|
|
482
|
+
test("stands while neither config nor the vault offers one", () => {
|
|
483
|
+
clearHistory();
|
|
484
|
+
getReturn = undefined;
|
|
485
|
+
acpConfig.setConfig({ agents: {} });
|
|
486
|
+
markAcpConnectCardRaised("conv-still-nothing", "claude");
|
|
487
|
+
|
|
488
|
+
return acpConnectCardStillWarranted("conv-still-nothing").then(
|
|
489
|
+
(warranted) => {
|
|
490
|
+
expect(warranted).toBe(true);
|
|
491
|
+
},
|
|
492
|
+
);
|
|
493
|
+
});
|
|
494
|
+
});
|
|
495
|
+
|
|
496
|
+
describe("acpConnectCardStillWarranted: a resolved credential Claude already refused", () => {
|
|
497
|
+
test("stands while the only credential a spawn would pick is the refused one", () => {
|
|
498
|
+
// Standing down needs a replacement to stand down *to*. With none stored,
|
|
499
|
+
// the resolver still reports the refused configured token, and the next
|
|
500
|
+
// spawn rejects it exactly as this one did. Reading that as repaired drops
|
|
501
|
+
// the card and lets a second prompt open beside it.
|
|
502
|
+
clearHistory();
|
|
503
|
+
const token = "sk-ant-oat-config-refused";
|
|
504
|
+
getReturn = undefined;
|
|
505
|
+
acpConfig.setConfig({
|
|
506
|
+
agents: {
|
|
507
|
+
claude: {
|
|
508
|
+
command: "claude-agent-acp",
|
|
509
|
+
args: [],
|
|
510
|
+
env: { CLAUDE_CODE_OAUTH_TOKEN: token },
|
|
511
|
+
},
|
|
512
|
+
},
|
|
513
|
+
});
|
|
514
|
+
noteClaudeTokenRefused(claudeTokenDigest(token), 1000);
|
|
515
|
+
markAcpConnectCardRaised("conv-refused-config", "claude");
|
|
516
|
+
|
|
517
|
+
return acpConnectCardStillWarranted("conv-refused-config").then(
|
|
518
|
+
(warranted) => {
|
|
519
|
+
expect(warranted).toBe(true);
|
|
520
|
+
},
|
|
521
|
+
);
|
|
522
|
+
});
|
|
523
|
+
|
|
524
|
+
test("falls once the resolved credential is one Claude has not refused", () => {
|
|
525
|
+
clearHistory();
|
|
526
|
+
getReturn = undefined;
|
|
527
|
+
acpConfig.setConfig({
|
|
528
|
+
agents: {
|
|
529
|
+
claude: {
|
|
530
|
+
command: "claude-agent-acp",
|
|
531
|
+
args: [],
|
|
532
|
+
env: { CLAUDE_CODE_OAUTH_TOKEN: "sk-ant-oat-config-fresh" },
|
|
533
|
+
},
|
|
534
|
+
},
|
|
535
|
+
});
|
|
536
|
+
markAcpConnectCardRaised("conv-fresh-config", "claude");
|
|
537
|
+
|
|
538
|
+
return acpConnectCardStillWarranted("conv-fresh-config").then(
|
|
539
|
+
(warranted) => {
|
|
540
|
+
expect(warranted).toBe(false);
|
|
541
|
+
},
|
|
542
|
+
);
|
|
543
|
+
});
|
|
544
|
+
});
|
|
@@ -3,6 +3,7 @@ import { describe, expect, test } from "bun:test";
|
|
|
3
3
|
import {
|
|
4
4
|
hasAcpConnectCardRaised,
|
|
5
5
|
markAcpConnectCardRaised,
|
|
6
|
+
takeConversationsWithAcpConnectCard,
|
|
6
7
|
} from "../acp-connect-card-state.js";
|
|
7
8
|
|
|
8
9
|
describe("acp-connect-card-state", () => {
|
|
@@ -20,4 +21,20 @@ describe("acp-connect-card-state", () => {
|
|
|
20
21
|
markAcpConnectCardRaised("");
|
|
21
22
|
expect(hasAcpConnectCardRaised("")).toBe(false);
|
|
22
23
|
});
|
|
24
|
+
|
|
25
|
+
test("take hands back every raised conversation and forgets them", () => {
|
|
26
|
+
// A new token retires these conversations' persisted markers, so an entry
|
|
27
|
+
// left behind would keep redirecting the fallback at a card nothing can
|
|
28
|
+
// re-raise.
|
|
29
|
+
markAcpConnectCardRaised("conv-take-a");
|
|
30
|
+
markAcpConnectCardRaised("conv-take-b");
|
|
31
|
+
|
|
32
|
+
const taken = takeConversationsWithAcpConnectCard();
|
|
33
|
+
|
|
34
|
+
expect(taken).toContain("conv-take-a");
|
|
35
|
+
expect(taken).toContain("conv-take-b");
|
|
36
|
+
expect(hasAcpConnectCardRaised("conv-take-a")).toBe(false);
|
|
37
|
+
expect(hasAcpConnectCardRaised("conv-take-b")).toBe(false);
|
|
38
|
+
expect(takeConversationsWithAcpConnectCard()).toEqual([]);
|
|
39
|
+
});
|
|
23
40
|
});
|
|
@@ -30,6 +30,8 @@ export interface HistoryRow {
|
|
|
30
30
|
cost_currency: string | null;
|
|
31
31
|
input_tokens: number | null;
|
|
32
32
|
output_tokens: number | null;
|
|
33
|
+
auth_error_code: string | null;
|
|
34
|
+
auth_error_credential: string | null;
|
|
33
35
|
}
|
|
34
36
|
|
|
35
37
|
export function clearHistory(): void {
|
|
@@ -62,6 +64,8 @@ export function insertHistoryRow(row: {
|
|
|
62
64
|
costCurrency?: string | null;
|
|
63
65
|
inputTokens?: number | null;
|
|
64
66
|
outputTokens?: number | null;
|
|
67
|
+
authErrorCode?: string | null;
|
|
68
|
+
authErrorCredential?: string | null;
|
|
65
69
|
}): void {
|
|
66
70
|
getSqlite()
|
|
67
71
|
.query(
|
|
@@ -70,8 +74,8 @@ export function insertHistoryRow(row: {
|
|
|
70
74
|
started_at, completed_at, status, stop_reason, error,
|
|
71
75
|
event_log_json, cwd, task, parent_tool_use_id,
|
|
72
76
|
used_tokens, context_size, cost_amount, cost_currency,
|
|
73
|
-
input_tokens, output_tokens
|
|
74
|
-
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
77
|
+
input_tokens, output_tokens, auth_error_code, auth_error_credential
|
|
78
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
75
79
|
)
|
|
76
80
|
.run(
|
|
77
81
|
row.id,
|
|
@@ -93,6 +97,8 @@ export function insertHistoryRow(row: {
|
|
|
93
97
|
row.costCurrency ?? null,
|
|
94
98
|
row.inputTokens ?? null,
|
|
95
99
|
row.outputTokens ?? null,
|
|
100
|
+
row.authErrorCode ?? null,
|
|
101
|
+
row.authErrorCredential ?? null,
|
|
96
102
|
);
|
|
97
103
|
}
|
|
98
104
|
|
|
@@ -103,7 +109,8 @@ export function readHistoryRow(id: string): HistoryRow | null {
|
|
|
103
109
|
started_at, completed_at, status, stop_reason, error,
|
|
104
110
|
event_log_json, cwd, task, parent_tool_use_id,
|
|
105
111
|
used_tokens, context_size, cost_amount, cost_currency,
|
|
106
|
-
input_tokens, output_tokens
|
|
112
|
+
input_tokens, output_tokens, auth_error_code,
|
|
113
|
+
auth_error_credential
|
|
107
114
|
FROM acp_session_history WHERE id = ?`,
|
|
108
115
|
)
|
|
109
116
|
.get(id) as HistoryRow | null;
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
|
|
3
|
+
import { and, desc, eq, isNotNull } from "drizzle-orm";
|
|
4
|
+
|
|
5
|
+
import { getDb } from "../persistence/db-connection.js";
|
|
6
|
+
import {
|
|
7
|
+
acpRefusedCredentials,
|
|
8
|
+
acpSessionHistory,
|
|
9
|
+
} from "../persistence/schema/index.js";
|
|
10
|
+
import { getLogger } from "../util/logger.js";
|
|
11
|
+
|
|
12
|
+
const log = getLogger("acp-auth-marker-store");
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* How many of a conversation's newest markers are examined before giving up on
|
|
16
|
+
* finding a current one.
|
|
17
|
+
*
|
|
18
|
+
* A conversation keeps every marker it has ever had, so this bounds the read
|
|
19
|
+
* to a fixed cost. Beyond the newest few the answer is nearly always the same,
|
|
20
|
+
* and erring toward "no current marker" only forgets bookkeeping that the next
|
|
21
|
+
* failure recreates.
|
|
22
|
+
*/
|
|
23
|
+
export const MARKER_SCAN_LIMIT = 20;
|
|
24
|
+
|
|
25
|
+
/** Digest a Claude token for identity comparison. Never stores the token. */
|
|
26
|
+
export function claudeTokenDigest(token: string): string {
|
|
27
|
+
return createHash("sha256").update(token).digest("hex").slice(0, 32);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Whether a credential-failure marker is still worth showing a card for.
|
|
32
|
+
*
|
|
33
|
+
* The marker names the credential the run was refused on. It stops being worth
|
|
34
|
+
* showing the moment a spawn would resolve a different one, because that is
|
|
35
|
+
* the user having repaired the auth the card exists to send them to.
|
|
36
|
+
*
|
|
37
|
+
* Answered by comparing, not by remembering. Nothing has to run at the right
|
|
38
|
+
* moment for a repaired marker to stop rendering, which is the difference
|
|
39
|
+
* between this and a retirement sweep: a sweep has to be ordered against every
|
|
40
|
+
* in-flight write and in-flight read, and a daemon restart loses whatever it
|
|
41
|
+
* was tracking. A comparison has no ordering to get wrong and no lifetime.
|
|
42
|
+
*
|
|
43
|
+
* Fails toward showing the card. A marker with no credential named predates
|
|
44
|
+
* this column or was written by a path that had none, and an unknown
|
|
45
|
+
* credential is no evidence the failure was repaired.
|
|
46
|
+
*/
|
|
47
|
+
export function acpAuthMarkerStillCurrent(
|
|
48
|
+
markerCredential: string | null | undefined,
|
|
49
|
+
resolvedCredential: string | undefined,
|
|
50
|
+
): boolean {
|
|
51
|
+
if (markerCredential == null || resolvedCredential === undefined) {
|
|
52
|
+
return true;
|
|
53
|
+
}
|
|
54
|
+
return markerCredential === resolvedCredential;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Record that Claude refused this token, so no later spawn resolves it again.
|
|
59
|
+
*
|
|
60
|
+
* Kept in its own table rather than read back off the marker rows. A marker is
|
|
61
|
+
* about showing a card for one run and the user may delete it with that run,
|
|
62
|
+
* while this decides which credential a spawn selects, and clearing session
|
|
63
|
+
* history must not change that. A configured
|
|
64
|
+
* `acp.agents.<id>.env.CLAUDE_CODE_OAUTH_TOKEN` lives in config, so forgetting
|
|
65
|
+
* a refusal lets the revoked value win over the vault replacement again.
|
|
66
|
+
*
|
|
67
|
+
* Never throws: a run has already failed by the time this is called, and
|
|
68
|
+
* losing the record costs one more failed spawn rather than the failure
|
|
69
|
+
* handling itself.
|
|
70
|
+
*/
|
|
71
|
+
export function noteClaudeTokenRefused(
|
|
72
|
+
digest: string | undefined,
|
|
73
|
+
refusedAt: number,
|
|
74
|
+
): void {
|
|
75
|
+
if (digest === undefined) {
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
try {
|
|
79
|
+
getDb()
|
|
80
|
+
.insert(acpRefusedCredentials)
|
|
81
|
+
.values({ digest, refusedAt })
|
|
82
|
+
.onConflictDoNothing()
|
|
83
|
+
.run();
|
|
84
|
+
} catch (err) {
|
|
85
|
+
log.error(
|
|
86
|
+
{ err },
|
|
87
|
+
"recording a refused Claude credential failed; it may be resolved again",
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Whether Claude has refused this token before.
|
|
94
|
+
*
|
|
95
|
+
* Never throws: this only narrows which token a spawn prefers, and a read
|
|
96
|
+
* failure must not take the spawn with it. Failing open resolves the
|
|
97
|
+
* configured token, which is what would have happened before it was ever
|
|
98
|
+
* refused.
|
|
99
|
+
*/
|
|
100
|
+
export function claudeTokenRefusedByClaude(token: string): boolean {
|
|
101
|
+
return claudeCredentialRefused(claudeTokenDigest(token));
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Whether Claude has refused the credential with this digest.
|
|
106
|
+
*
|
|
107
|
+
* The same question as {@link claudeTokenRefusedByClaude}, for callers that
|
|
108
|
+
* hold an identity rather than the token itself: a resolver reports which
|
|
109
|
+
* credential a spawn would pick, and "would pick it" is not the same as
|
|
110
|
+
* "it works".
|
|
111
|
+
*/
|
|
112
|
+
export function claudeCredentialRefused(digest: string): boolean {
|
|
113
|
+
try {
|
|
114
|
+
const row = getDb()
|
|
115
|
+
.select({ digest: acpRefusedCredentials.digest })
|
|
116
|
+
.from(acpRefusedCredentials)
|
|
117
|
+
.where(eq(acpRefusedCredentials.digest, digest))
|
|
118
|
+
.limit(1)
|
|
119
|
+
.get();
|
|
120
|
+
return row !== undefined;
|
|
121
|
+
} catch (err) {
|
|
122
|
+
log.error(
|
|
123
|
+
{ err },
|
|
124
|
+
"reading refused Claude credentials failed; treating the token as untried",
|
|
125
|
+
);
|
|
126
|
+
return false;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Whether this conversation still has a credential failure worth showing a
|
|
132
|
+
* card for.
|
|
133
|
+
*
|
|
134
|
+
* The same question the session list answers per row, asked for one
|
|
135
|
+
* conversation. Callers that hold their own idea of which conversations have a
|
|
136
|
+
* card up use this to decide whether that idea is still true, rather than
|
|
137
|
+
* guessing from whether a token was written: a writer can store the very token
|
|
138
|
+
* Claude rejected, which changes nothing about the failure and must not be
|
|
139
|
+
* mistaken for repairing it.
|
|
140
|
+
*
|
|
141
|
+
* Never throws, and answers `false` when it cannot tell. The caller uses this
|
|
142
|
+
* to decide whether to forget that a conversation has a card up, and the two
|
|
143
|
+
* mistakes are not equal: forgetting wrongly costs a redundant secure prompt
|
|
144
|
+
* beside a card, while remembering wrongly suppresses that prompt and points
|
|
145
|
+
* the user at a card that may no longer be there.
|
|
146
|
+
*/
|
|
147
|
+
export async function conversationHasCurrentAcpMarker(
|
|
148
|
+
conversationId: string,
|
|
149
|
+
): Promise<boolean> {
|
|
150
|
+
try {
|
|
151
|
+
const rows = getDb()
|
|
152
|
+
.select({
|
|
153
|
+
agentId: acpSessionHistory.agentId,
|
|
154
|
+
credential: acpSessionHistory.authErrorCredential,
|
|
155
|
+
})
|
|
156
|
+
.from(acpSessionHistory)
|
|
157
|
+
.where(
|
|
158
|
+
and(
|
|
159
|
+
eq(acpSessionHistory.parentConversationId, conversationId),
|
|
160
|
+
isNotNull(acpSessionHistory.authErrorCode),
|
|
161
|
+
),
|
|
162
|
+
)
|
|
163
|
+
.orderBy(desc(acpSessionHistory.startedAt))
|
|
164
|
+
.limit(MARKER_SCAN_LIMIT)
|
|
165
|
+
.all();
|
|
166
|
+
if (rows.length === 0) {
|
|
167
|
+
return false;
|
|
168
|
+
}
|
|
169
|
+
// Imported at call time: `prepare-agent-env` owns the precedence a spawn
|
|
170
|
+
// applies, and reaching it from here at load would put the whole spawn
|
|
171
|
+
// graph behind this module.
|
|
172
|
+
const { resolvedClaudeCredentialDigest } =
|
|
173
|
+
await import("./prepare-agent-env.js");
|
|
174
|
+
const resolved = new Map<string, string | undefined>();
|
|
175
|
+
for (const row of rows) {
|
|
176
|
+
if (!resolved.has(row.agentId)) {
|
|
177
|
+
resolved.set(
|
|
178
|
+
row.agentId,
|
|
179
|
+
await resolvedClaudeCredentialDigest(row.agentId),
|
|
180
|
+
);
|
|
181
|
+
}
|
|
182
|
+
if (
|
|
183
|
+
acpAuthMarkerStillCurrent(row.credential, resolved.get(row.agentId))
|
|
184
|
+
) {
|
|
185
|
+
return true;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
return false;
|
|
189
|
+
} catch (err) {
|
|
190
|
+
log.error(
|
|
191
|
+
{ err },
|
|
192
|
+
"reading ACP auth markers failed; treating the card as no longer raised",
|
|
193
|
+
);
|
|
194
|
+
return false;
|
|
195
|
+
}
|
|
196
|
+
}
|