@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
|
@@ -1,77 +1,97 @@
|
|
|
1
1
|
import { describe, expect, test } from "bun:test";
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import { reactionDecisionForEmoji } from "../runtime/routes/channel-route-shared.js";
|
|
4
4
|
|
|
5
5
|
// =============================================================================
|
|
6
|
-
//
|
|
6
|
+
// reactionDecisionForEmoji
|
|
7
7
|
// =============================================================================
|
|
8
8
|
|
|
9
|
-
describe("
|
|
9
|
+
describe("reactionDecisionForEmoji", () => {
|
|
10
10
|
test("maps +1 emoji to approve_once", () => {
|
|
11
|
-
const result =
|
|
11
|
+
const result = reactionDecisionForEmoji("+1");
|
|
12
12
|
expect(result).toEqual({
|
|
13
13
|
action: "approve_once",
|
|
14
|
-
source: "
|
|
14
|
+
source: "reaction",
|
|
15
15
|
});
|
|
16
16
|
});
|
|
17
17
|
|
|
18
18
|
test("maps thumbsup emoji to approve_once", () => {
|
|
19
|
-
const result =
|
|
19
|
+
const result = reactionDecisionForEmoji("thumbsup");
|
|
20
20
|
expect(result).toEqual({
|
|
21
21
|
action: "approve_once",
|
|
22
|
-
source: "
|
|
22
|
+
source: "reaction",
|
|
23
23
|
});
|
|
24
24
|
});
|
|
25
25
|
|
|
26
26
|
test("maps -1 emoji to reject", () => {
|
|
27
|
-
const result =
|
|
27
|
+
const result = reactionDecisionForEmoji("-1");
|
|
28
28
|
expect(result).toEqual({
|
|
29
29
|
action: "reject",
|
|
30
|
-
source: "
|
|
30
|
+
source: "reaction",
|
|
31
31
|
});
|
|
32
32
|
});
|
|
33
33
|
|
|
34
34
|
test("maps thumbsdown emoji to reject", () => {
|
|
35
|
-
const result =
|
|
35
|
+
const result = reactionDecisionForEmoji("thumbsdown");
|
|
36
36
|
expect(result).toEqual({
|
|
37
37
|
action: "reject",
|
|
38
|
-
source: "
|
|
38
|
+
source: "reaction",
|
|
39
39
|
});
|
|
40
40
|
});
|
|
41
41
|
|
|
42
42
|
test("alarm_clock emoji maps to approve_once (legacy compat)", () => {
|
|
43
|
-
const result =
|
|
43
|
+
const result = reactionDecisionForEmoji("alarm_clock");
|
|
44
44
|
expect(result).toEqual({
|
|
45
45
|
action: "approve_once",
|
|
46
|
-
source: "
|
|
46
|
+
source: "reaction",
|
|
47
47
|
});
|
|
48
48
|
});
|
|
49
49
|
|
|
50
50
|
test("white_check_mark emoji maps to approve_once (legacy compat)", () => {
|
|
51
|
-
const result =
|
|
51
|
+
const result = reactionDecisionForEmoji("white_check_mark");
|
|
52
52
|
expect(result).toEqual({
|
|
53
53
|
action: "approve_once",
|
|
54
|
-
source: "
|
|
54
|
+
source: "reaction",
|
|
55
55
|
});
|
|
56
56
|
});
|
|
57
57
|
|
|
58
|
-
test("
|
|
59
|
-
const
|
|
60
|
-
|
|
58
|
+
test("unicode vocabulary: thumbs up, check mark, and alarm clock approve", () => {
|
|
59
|
+
for (const emoji of ["\u{1F44D}", "\u2705", "\u23F0"]) {
|
|
60
|
+
expect(reactionDecisionForEmoji(emoji)).toEqual({
|
|
61
|
+
action: "approve_once",
|
|
62
|
+
source: "reaction",
|
|
63
|
+
});
|
|
64
|
+
}
|
|
61
65
|
});
|
|
62
66
|
|
|
63
|
-
test("
|
|
64
|
-
|
|
67
|
+
test("unicode vocabulary: thumbs down rejects", () => {
|
|
68
|
+
expect(reactionDecisionForEmoji("\u{1F44E}")).toEqual({
|
|
69
|
+
action: "reject",
|
|
70
|
+
source: "reaction",
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
test("a Discord custom emoji in mention form never resolves a decision", () => {
|
|
75
|
+
// A guild custom emoji can be named after a Slack decision word; the
|
|
76
|
+
// gateway forwards it as its mention form, which must stay unmapped.
|
|
77
|
+
expect(
|
|
78
|
+
reactionDecisionForEmoji("<:white_check_mark:999888777>"),
|
|
79
|
+
).toBeNull();
|
|
80
|
+
expect(reactionDecisionForEmoji("<:thumbsup:999888777>")).toBeNull();
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
test("returns null for unknown emoji", () => {
|
|
84
|
+
const result = reactionDecisionForEmoji("tada");
|
|
65
85
|
expect(result).toBeNull();
|
|
66
86
|
});
|
|
67
87
|
|
|
68
|
-
test("returns null for
|
|
69
|
-
const result =
|
|
88
|
+
test("returns null for an unmapped emoji", () => {
|
|
89
|
+
const result = reactionDecisionForEmoji("eyes");
|
|
70
90
|
expect(result).toBeNull();
|
|
71
91
|
});
|
|
72
92
|
|
|
73
|
-
test("returns null for
|
|
74
|
-
const result =
|
|
93
|
+
test("returns null for an empty emoji name", () => {
|
|
94
|
+
const result = reactionDecisionForEmoji("");
|
|
75
95
|
expect(result).toBeNull();
|
|
76
96
|
});
|
|
77
97
|
});
|
|
@@ -117,7 +117,11 @@ function reaction(opts: {
|
|
|
117
117
|
guardianPrincipalId: opts.principal ?? PRINCIPAL,
|
|
118
118
|
},
|
|
119
119
|
conversationId: "guardian-conv",
|
|
120
|
-
|
|
120
|
+
reaction: {
|
|
121
|
+
op: "added" as const,
|
|
122
|
+
emoji: opts.emoji,
|
|
123
|
+
targetMessageId: opts.reactedMessageTs ?? "",
|
|
124
|
+
},
|
|
121
125
|
reactedMessageTs: opts.reactedMessageTs,
|
|
122
126
|
channelDeliveryContext: {
|
|
123
127
|
replyCallbackUrl: "https://gateway.test/deliver",
|
|
@@ -162,6 +166,30 @@ describe("routeGuardianReply / reactions", () => {
|
|
|
162
166
|
expect(hcr.mock.calls[0]?.[1]).toBe("deny");
|
|
163
167
|
});
|
|
164
168
|
|
|
169
|
+
test("a person-addressed delivery resolves through the reactor's own id", async () => {
|
|
170
|
+
// Person-addressed adapters (Discord) record the guardian's user id as
|
|
171
|
+
// the destination, while the reaction arrives addressed by its chat, so
|
|
172
|
+
// the chat probe misses and the actor-id probe must find the card.
|
|
173
|
+
const hcr = seedApproval({
|
|
174
|
+
requestId: "req-person",
|
|
175
|
+
ts: "300.3",
|
|
176
|
+
chatId: GUARDIAN_USER,
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
const result = await routeGuardianReply(
|
|
180
|
+
reaction({
|
|
181
|
+
emoji: "white_check_mark",
|
|
182
|
+
reactedMessageTs: "300.3",
|
|
183
|
+
chatId: "DM_CHANNEL_9",
|
|
184
|
+
}),
|
|
185
|
+
);
|
|
186
|
+
|
|
187
|
+
expect(result.consumed).toBe(true);
|
|
188
|
+
expect(result.decisionApplied).toBe(true);
|
|
189
|
+
expect(sim.getRequest("req-person")?.status).toBe("approved");
|
|
190
|
+
expect(hcr).toHaveBeenCalledTimes(1);
|
|
191
|
+
});
|
|
192
|
+
|
|
165
193
|
test("reacting on a specific card resolves only that card (disambiguates N>1)", async () => {
|
|
166
194
|
const hcrA = seedApproval({ requestId: "req-A", ts: "100.1" });
|
|
167
195
|
const hcrB = seedApproval({ requestId: "req-B", ts: "200.2" });
|
|
@@ -13,6 +13,8 @@ const capturedNotifications: {
|
|
|
13
13
|
|
|
14
14
|
mock.module("../daemon/conversation-registry.js", () => ({
|
|
15
15
|
findConversation: (id: string) => ({
|
|
16
|
+
isStale: () => false,
|
|
17
|
+
hasInFlightWork: () => false,
|
|
16
18
|
enqueueMessage: (options: { content: string }) => {
|
|
17
19
|
capturedNotifications.push({
|
|
18
20
|
parentConversationId: id,
|
|
@@ -13,6 +13,8 @@ const capturedNotifications: {
|
|
|
13
13
|
|
|
14
14
|
mock.module("../daemon/conversation-registry.js", () => ({
|
|
15
15
|
findConversation: (id: string) => ({
|
|
16
|
+
isStale: () => false,
|
|
17
|
+
hasInFlightWork: () => false,
|
|
16
18
|
enqueueMessage: (options: { content: string }) => {
|
|
17
19
|
capturedNotifications.push({
|
|
18
20
|
parentConversationId: id,
|
|
@@ -56,6 +56,8 @@ mock.module("../daemon/conversation-registry.js", () => ({
|
|
|
56
56
|
findConversation: (id: string) => {
|
|
57
57
|
capturedParentIds.push(id);
|
|
58
58
|
return {
|
|
59
|
+
isStale: () => false,
|
|
60
|
+
hasInFlightWork: () => false,
|
|
59
61
|
enqueueMessage: (options: { content: string }) => {
|
|
60
62
|
capturedMessages.push(options.content);
|
|
61
63
|
return { queued: true };
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Terminal (and other) parent injections rebuild a stale idle parent before
|
|
3
|
+
* the notification turn, so a reload or credential rotation is visible to
|
|
4
|
+
* that turn. A parent that still has in-flight work keeps its current
|
|
5
|
+
* instance so children are not aborted.
|
|
6
|
+
*/
|
|
7
|
+
import { beforeEach, describe, expect, mock, test } from "bun:test";
|
|
8
|
+
|
|
9
|
+
const parentState = { stale: false, inFlight: false };
|
|
10
|
+
const rebuilt: string[] = [];
|
|
11
|
+
const delivered: string[] = [];
|
|
12
|
+
|
|
13
|
+
mock.module("../daemon/conversation-registry.js", () => ({
|
|
14
|
+
findConversation: (id: string) => {
|
|
15
|
+
if (id === "missing-parent") {
|
|
16
|
+
return undefined;
|
|
17
|
+
}
|
|
18
|
+
return {
|
|
19
|
+
isStale: () => parentState.stale,
|
|
20
|
+
hasInFlightWork: () => parentState.inFlight,
|
|
21
|
+
enqueueMessage: (options: { content: string }) => {
|
|
22
|
+
delivered.push(options.content);
|
|
23
|
+
return { queued: true };
|
|
24
|
+
},
|
|
25
|
+
persistUserMessage: async () => ({ id: "mock-msg" }),
|
|
26
|
+
runAgentLoop: async () => {},
|
|
27
|
+
};
|
|
28
|
+
},
|
|
29
|
+
}));
|
|
30
|
+
|
|
31
|
+
mock.module("../daemon/conversation-store.js", () => ({
|
|
32
|
+
getOrCreateConversation: async (id: string) => {
|
|
33
|
+
rebuilt.push(id);
|
|
34
|
+
return {
|
|
35
|
+
enqueueMessage: (options: { content: string }) => {
|
|
36
|
+
delivered.push(`rebuilt:${options.content}`);
|
|
37
|
+
return { queued: true };
|
|
38
|
+
},
|
|
39
|
+
persistUserMessage: async () => ({ id: "rebuilt-msg" }),
|
|
40
|
+
runAgentLoop: async () => {},
|
|
41
|
+
};
|
|
42
|
+
},
|
|
43
|
+
}));
|
|
44
|
+
|
|
45
|
+
import { injectMessageIntoParent } from "../subagent/notify.js";
|
|
46
|
+
|
|
47
|
+
describe("injectMessageIntoParent stale rebuild", () => {
|
|
48
|
+
beforeEach(() => {
|
|
49
|
+
parentState.stale = false;
|
|
50
|
+
parentState.inFlight = false;
|
|
51
|
+
rebuilt.length = 0;
|
|
52
|
+
delivered.length = 0;
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
test("delivers on the live instance when the parent is not stale", () => {
|
|
56
|
+
injectMessageIntoParent("parent-1", "child done");
|
|
57
|
+
|
|
58
|
+
expect(rebuilt).toEqual([]);
|
|
59
|
+
expect(delivered).toEqual(["child done"]);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
test("keeps the live instance while the parent still has in-flight work", () => {
|
|
63
|
+
parentState.stale = true;
|
|
64
|
+
parentState.inFlight = true;
|
|
65
|
+
|
|
66
|
+
injectMessageIntoParent("parent-1", "child still running");
|
|
67
|
+
|
|
68
|
+
expect(rebuilt).toEqual([]);
|
|
69
|
+
expect(delivered).toEqual(["child still running"]);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
test("rebuilds a stale idle parent before the notification turn", async () => {
|
|
73
|
+
parentState.stale = true;
|
|
74
|
+
parentState.inFlight = false;
|
|
75
|
+
|
|
76
|
+
injectMessageIntoParent("parent-1", "child done");
|
|
77
|
+
const start = Date.now();
|
|
78
|
+
while (delivered.length === 0 && Date.now() - start < 1000) {
|
|
79
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
expect(rebuilt).toEqual(["parent-1"]);
|
|
83
|
+
expect(delivered).toEqual(["rebuilt:child done"]);
|
|
84
|
+
});
|
|
85
|
+
});
|
|
@@ -271,10 +271,10 @@ describe("createResolveToolsCallback — toolContextPin", () => {
|
|
|
271
271
|
const names = resolve(EMPTY_HISTORY)
|
|
272
272
|
.map((t) => t.name)
|
|
273
273
|
.sort();
|
|
274
|
-
// request_system_permission stays out: it keys on
|
|
275
|
-
//
|
|
276
|
-
//
|
|
277
|
-
//
|
|
274
|
+
// request_system_permission stays out: it keys on the pinned clientOs,
|
|
275
|
+
// which this pin leaves unset (a pin never falls back to the transport).
|
|
276
|
+
// ask_question stays IN: its macOS-specific hide keys on
|
|
277
|
+
// channelCapabilities.clientOS, which the pin also leaves unset.
|
|
278
278
|
expect(names).toEqual(["ask_question", "host_bash", "remember", "ui_show"]);
|
|
279
279
|
});
|
|
280
280
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
2
|
import { afterEach, describe, expect, mock, test } from "bun:test";
|
|
3
3
|
|
|
4
4
|
// ── Mock modules ────────────────────────────────────────────────────────────
|
|
@@ -40,7 +40,11 @@ mock.module("../tools/network/script-proxy/index.js", () => ({
|
|
|
40
40
|
|
|
41
41
|
// ── Imports (after mocks) ───────────────────────────────────────────────────
|
|
42
42
|
|
|
43
|
-
import {
|
|
43
|
+
import {
|
|
44
|
+
formatShellOutput,
|
|
45
|
+
MAX_OUTPUT_LENGTH,
|
|
46
|
+
OUTPUT_TRUNCATED_TAG,
|
|
47
|
+
} from "../tools/shared/shell-output.js";
|
|
44
48
|
import {
|
|
45
49
|
ALWAYS_INJECTED_ENV_VARS,
|
|
46
50
|
buildSanitizedEnv,
|
|
@@ -360,12 +364,8 @@ describe("formatShellOutput", () => {
|
|
|
360
364
|
test("truncates very long output", () => {
|
|
361
365
|
const longOutput = "x".repeat(30_000);
|
|
362
366
|
const result = formatShellOutput(longOutput, "", 0, false, 120);
|
|
363
|
-
expect(result.content).toContain(
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
expect(fileMatch).not.toBeNull();
|
|
367
|
-
const filePath = fileMatch![1];
|
|
368
|
-
expect(existsSync(filePath)).toBe(true);
|
|
369
|
-
expect(readFileSync(filePath, "utf-8")).toBe(longOutput);
|
|
367
|
+
expect(result.content).toContain(OUTPUT_TRUNCATED_TAG);
|
|
368
|
+
expect(result.content).not.toContain("file=");
|
|
369
|
+
expect(result.content.length).toBeLessThan(MAX_OUTPUT_LENGTH + 80);
|
|
370
370
|
});
|
|
371
371
|
});
|
|
@@ -140,11 +140,12 @@ describe("spoolAndStubOversizedToolResults", () => {
|
|
|
140
140
|
expect(existsSync(join(convDir, TOOL_RESULT_DIR))).toBe(false);
|
|
141
141
|
});
|
|
142
142
|
|
|
143
|
-
test("
|
|
143
|
+
test("leaves an oversized file read of an ordinary path inline (explicit self-sized read)", () => {
|
|
144
144
|
const blocks = [
|
|
145
145
|
makeToolResult(LONG, "tu_read"),
|
|
146
146
|
makeToolResult(LONG, "tu_host_read"),
|
|
147
147
|
];
|
|
148
|
+
const originals = blocks.map((b) => b);
|
|
148
149
|
|
|
149
150
|
const count = spoolAndStubOversizedToolResults(blocks, {
|
|
150
151
|
conversationDir: convDir,
|
|
@@ -154,13 +155,9 @@ describe("spoolAndStubOversizedToolResults", () => {
|
|
|
154
155
|
: { name: "host_file_read", input: { path: "/Users/me/notes.md" } },
|
|
155
156
|
});
|
|
156
157
|
|
|
157
|
-
expect(count).toBe(
|
|
158
|
-
expect(
|
|
159
|
-
|
|
160
|
-
);
|
|
161
|
-
expect((blocks[1] as { content: string }).content).toContain(
|
|
162
|
-
TRUNCATION_MARKER,
|
|
163
|
-
);
|
|
158
|
+
expect(count).toBe(0);
|
|
159
|
+
expect(blocks).toEqual(originals);
|
|
160
|
+
expect(existsSync(join(convDir, TOOL_RESULT_DIR))).toBe(false);
|
|
164
161
|
});
|
|
165
162
|
|
|
166
163
|
test("leaves a file read of a spooled .tool-results path inline", () => {
|
|
@@ -295,6 +295,32 @@ describe("resolveUsageAttribution — single-winner semantics", () => {
|
|
|
295
295
|
});
|
|
296
296
|
});
|
|
297
297
|
|
|
298
|
+
test("compaction observability site still resolves the profile through mainAgent", () => {
|
|
299
|
+
setLlmConfig({
|
|
300
|
+
profiles: { mine: completeProfile },
|
|
301
|
+
activeProfile: "mine",
|
|
302
|
+
callSites: { compactionAgent: { profile: "cost-optimized" } },
|
|
303
|
+
defaultProvider: { provider: "anthropic" },
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
const snapshot = resolveUsageAttribution({
|
|
307
|
+
callSite: "compactionAgent",
|
|
308
|
+
profileResolutionCallSite: "mainAgent",
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
expect(snapshot.callSite).toBe("compactionAgent");
|
|
312
|
+
expect(snapshot.appliedProfile).toBe("mine");
|
|
313
|
+
expect(snapshot.profileSource).toBe("active");
|
|
314
|
+
expect(snapshot.resolvedModel).toBe("gpt-5.5");
|
|
315
|
+
expect(
|
|
316
|
+
resolveUsageAttribution({ callSite: "compactionAgent" }),
|
|
317
|
+
).toMatchObject({
|
|
318
|
+
callSite: "compactionAgent",
|
|
319
|
+
appliedProfile: "cost-optimized",
|
|
320
|
+
profileSource: "call_site",
|
|
321
|
+
});
|
|
322
|
+
});
|
|
323
|
+
|
|
298
324
|
test("attribution provider/model agree with the resolver", () => {
|
|
299
325
|
setLlmConfig({
|
|
300
326
|
profiles: { mine: completeProfile },
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A credential-failure marker answers for itself whether it is still worth
|
|
3
|
+
* showing a Connect card for.
|
|
4
|
+
*
|
|
5
|
+
* The question is always the same: is the credential this failure names still
|
|
6
|
+
* the one a spawn would resolve? Nothing is remembered between the failure and
|
|
7
|
+
* the read, and nothing has to run at the right moment for a repaired failure
|
|
8
|
+
* to stop rendering, which is what a retirement sweep needed and what a daemon
|
|
9
|
+
* restart used to lose.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { beforeEach, describe, expect, test } from "bun:test";
|
|
13
|
+
|
|
14
|
+
import { getSqlite } from "../../persistence/db-connection.js";
|
|
15
|
+
import { initializeDb } from "../../persistence/db-init.js";
|
|
16
|
+
import {
|
|
17
|
+
acpAuthMarkerStillCurrent,
|
|
18
|
+
claudeTokenDigest,
|
|
19
|
+
claudeTokenRefusedByClaude,
|
|
20
|
+
conversationHasCurrentAcpMarker,
|
|
21
|
+
noteClaudeTokenRefused,
|
|
22
|
+
} from "../acp-auth-marker-store.js";
|
|
23
|
+
import { clearHistory, insertHistoryRow } from "./helpers/acp-history-db.js";
|
|
24
|
+
|
|
25
|
+
await initializeDb();
|
|
26
|
+
|
|
27
|
+
describe("acpAuthMarkerStillCurrent", () => {
|
|
28
|
+
const REFUSED = claudeTokenDigest("sk-ant-oat-refused");
|
|
29
|
+
const REPLACEMENT = claudeTokenDigest("sk-ant-oat-replacement");
|
|
30
|
+
|
|
31
|
+
test("a marker naming the credential still in use is served", () => {
|
|
32
|
+
expect(acpAuthMarkerStillCurrent(REFUSED, REFUSED)).toBe(true);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
test("a marker naming a credential since replaced is withheld", () => {
|
|
36
|
+
// The user completed Connect. The card exists to send them there, so it
|
|
37
|
+
// has nothing left to say.
|
|
38
|
+
expect(acpAuthMarkerStillCurrent(REFUSED, REPLACEMENT)).toBe(false);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
test("a marker with no credential named is served", () => {
|
|
42
|
+
// Predates the column, or came from a path that had no token to name. An
|
|
43
|
+
// unknown credential is no evidence the failure was repaired, and this
|
|
44
|
+
// path fails toward leaving the user a route back to auth.
|
|
45
|
+
expect(acpAuthMarkerStillCurrent(null, REPLACEMENT)).toBe(true);
|
|
46
|
+
expect(acpAuthMarkerStillCurrent(undefined, REPLACEMENT)).toBe(true);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
test("an empty vault serves the marker rather than hiding it", () => {
|
|
50
|
+
// Nothing replaced the refused token, so the failure it names still
|
|
51
|
+
// stands.
|
|
52
|
+
expect(acpAuthMarkerStillCurrent(REFUSED, undefined)).toBe(true);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
test("the same answer however many times it is asked", () => {
|
|
56
|
+
// No state advances on a read, so a second client asking gets what the
|
|
57
|
+
// first did. The sweep this replaced could only be run once.
|
|
58
|
+
expect(acpAuthMarkerStillCurrent(REFUSED, REPLACEMENT)).toBe(false);
|
|
59
|
+
expect(acpAuthMarkerStillCurrent(REFUSED, REPLACEMENT)).toBe(false);
|
|
60
|
+
expect(acpAuthMarkerStillCurrent(REFUSED, REFUSED)).toBe(true);
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
describe("claudeTokenDigest", () => {
|
|
65
|
+
test("is stable and does not carry the token", () => {
|
|
66
|
+
const token = "sk-ant-oat-example";
|
|
67
|
+
const digest = claudeTokenDigest(token);
|
|
68
|
+
|
|
69
|
+
expect(digest).toBe(claudeTokenDigest(token));
|
|
70
|
+
expect(digest).not.toContain(token);
|
|
71
|
+
expect(digest).toHaveLength(32);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
test("separates one alias's token from another's", () => {
|
|
75
|
+
expect(claudeTokenDigest("sk-ant-oat-a")).not.toBe(
|
|
76
|
+
claudeTokenDigest("sk-ant-oat-b"),
|
|
77
|
+
);
|
|
78
|
+
});
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
describe("claudeTokenRefusedByClaude", () => {
|
|
82
|
+
const CONFIG_TOKEN = "sk-ant-oat-from-config";
|
|
83
|
+
const DIGEST = claudeTokenDigest(CONFIG_TOKEN);
|
|
84
|
+
|
|
85
|
+
beforeEach(() => {
|
|
86
|
+
clearHistory();
|
|
87
|
+
getSqlite().run("DELETE FROM acp_refused_credentials");
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
test("reports a token Claude refused", () => {
|
|
91
|
+
noteClaudeTokenRefused(DIGEST, 1000);
|
|
92
|
+
|
|
93
|
+
expect(claudeTokenRefusedByClaude(CONFIG_TOKEN)).toBe(true);
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
test("reports nothing for a token never refused", () => {
|
|
97
|
+
noteClaudeTokenRefused(DIGEST, 1000);
|
|
98
|
+
|
|
99
|
+
expect(claudeTokenRefusedByClaude("sk-ant-oat-never-tried")).toBe(false);
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
test("one alias's refused token says nothing about another's", () => {
|
|
103
|
+
// A global one-shot would let whichever alias prepared first consume the
|
|
104
|
+
// record, discarding its own good token and re-trusting the bad one.
|
|
105
|
+
noteClaudeTokenRefused(DIGEST, 1000);
|
|
106
|
+
|
|
107
|
+
expect(claudeTokenRefusedByClaude("sk-ant-oat-other-alias")).toBe(false);
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
test("survives clearing ACP session history", () => {
|
|
111
|
+
// Deleting a run must not change which credential the next spawn picks.
|
|
112
|
+
// Held on the run row, a bulk clear (which sweeps failed rows too) would
|
|
113
|
+
// put the revoked configured token back in play against the vault
|
|
114
|
+
// replacement the user just connected.
|
|
115
|
+
noteClaudeTokenRefused(DIGEST, 1000);
|
|
116
|
+
insertHistoryRow({
|
|
117
|
+
id: "run-refused",
|
|
118
|
+
status: "failed",
|
|
119
|
+
authErrorCode: "acp_claude_auth_required",
|
|
120
|
+
authErrorCredential: DIGEST,
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
clearHistory();
|
|
124
|
+
|
|
125
|
+
expect(claudeTokenRefusedByClaude(CONFIG_TOKEN)).toBe(true);
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
test("recording the same refusal twice is not an error", () => {
|
|
129
|
+
// Every retry of a revoked configured token refuses the same digest.
|
|
130
|
+
noteClaudeTokenRefused(DIGEST, 1000);
|
|
131
|
+
noteClaudeTokenRefused(DIGEST, 2000);
|
|
132
|
+
|
|
133
|
+
expect(claudeTokenRefusedByClaude(CONFIG_TOKEN)).toBe(true);
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
test("a run that carried no credential records nothing", () => {
|
|
137
|
+
noteClaudeTokenRefused(undefined, 1000);
|
|
138
|
+
|
|
139
|
+
expect(claudeTokenRefusedByClaude(CONFIG_TOKEN)).toBe(false);
|
|
140
|
+
});
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
describe("conversationHasCurrentAcpMarker", () => {
|
|
144
|
+
const REFUSED = claudeTokenDigest("sk-ant-oat-refused");
|
|
145
|
+
|
|
146
|
+
beforeEach(() => {
|
|
147
|
+
clearHistory();
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
test("false for a conversation that has never failed", async () => {
|
|
151
|
+
expect(await conversationHasCurrentAcpMarker("conv-clean")).toBe(false);
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
test("true while a marker names the credential a spawn would resolve", async () => {
|
|
155
|
+
// No vault token in this suite, so the resolver reports none and the
|
|
156
|
+
// comparison treats an unknown as no evidence of repair.
|
|
157
|
+
insertHistoryRow({
|
|
158
|
+
id: "run-marked",
|
|
159
|
+
parentConversationId: "conv-marked",
|
|
160
|
+
status: "failed",
|
|
161
|
+
authErrorCode: "acp_claude_auth_required",
|
|
162
|
+
authErrorCredential: REFUSED,
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
expect(await conversationHasCurrentAcpMarker("conv-marked")).toBe(true);
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
test("false for a conversation whose rows carry no marker", async () => {
|
|
169
|
+
insertHistoryRow({
|
|
170
|
+
id: "run-plain",
|
|
171
|
+
parentConversationId: "conv-plain",
|
|
172
|
+
status: "completed",
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
expect(await conversationHasCurrentAcpMarker("conv-plain")).toBe(false);
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
test("scoped to its own conversation", async () => {
|
|
179
|
+
insertHistoryRow({
|
|
180
|
+
id: "run-elsewhere",
|
|
181
|
+
parentConversationId: "conv-other",
|
|
182
|
+
status: "failed",
|
|
183
|
+
authErrorCode: "acp_claude_auth_required",
|
|
184
|
+
authErrorCredential: REFUSED,
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
expect(await conversationHasCurrentAcpMarker("conv-asked-about")).toBe(
|
|
188
|
+
false,
|
|
189
|
+
);
|
|
190
|
+
});
|
|
191
|
+
});
|