@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
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
import { randomUUID } from "node:crypto";
|
|
24
24
|
|
|
25
25
|
import {
|
|
26
|
+
DELIVERY_STATUS,
|
|
26
27
|
deriveGuardianRequestSourceType,
|
|
27
28
|
isGuardianRequestExpired,
|
|
28
29
|
} from "@vellumai/gateway-client";
|
|
@@ -177,7 +178,7 @@ export function createGuardianGatewaySim() {
|
|
|
177
178
|
destinationConversationId: params.destinationConversationId ?? null,
|
|
178
179
|
destinationChatId: params.destinationChatId ?? null,
|
|
179
180
|
destinationMessageId: params.destinationMessageId ?? null,
|
|
180
|
-
status: params.status ??
|
|
181
|
+
status: params.status ?? DELIVERY_STATUS.pending,
|
|
181
182
|
createdAt: params.createdAt ?? now,
|
|
182
183
|
updatedAt: params.updatedAt ?? now,
|
|
183
184
|
};
|
|
@@ -290,6 +291,11 @@ export function createGuardianGatewaySim() {
|
|
|
290
291
|
if (!row || row.status !== params.expectedStatus) {
|
|
291
292
|
return { applied: false, reason: "status_conflict" };
|
|
292
293
|
}
|
|
294
|
+
// Mirrors the gateway decide CAS: the deadline is part of the
|
|
295
|
+
// arbitration, so a decision arriving past expiresAt loses atomically.
|
|
296
|
+
if (isGuardianRequestExpired(row, Date.now())) {
|
|
297
|
+
return { applied: false, reason: "status_conflict" };
|
|
298
|
+
}
|
|
293
299
|
if (state.outcomeError && params.aclOutcome) {
|
|
294
300
|
// Gateway transaction rollback: the CAS never lands.
|
|
295
301
|
throw state.outcomeError;
|
|
@@ -344,14 +350,22 @@ export function createGuardianGatewaySim() {
|
|
|
344
350
|
}
|
|
345
351
|
|
|
346
352
|
async function expireGuardianRequest(id: string): Promise<void> {
|
|
353
|
+
// Mirrors the gateway: deliveries expire only when the request CAS
|
|
354
|
+
// applies, so a decided request's rows are never restamped.
|
|
347
355
|
const row = requests.get(id);
|
|
348
|
-
if (row?.status
|
|
349
|
-
|
|
350
|
-
row.updatedAt = Date.now();
|
|
356
|
+
if (row?.status !== "pending") {
|
|
357
|
+
return;
|
|
351
358
|
}
|
|
359
|
+
row.status = "expired";
|
|
360
|
+
row.updatedAt = Date.now();
|
|
352
361
|
for (const delivery of deliveries) {
|
|
353
|
-
|
|
354
|
-
|
|
362
|
+
// A withdrawn row is the daemon's per-surface receipt; the gateway
|
|
363
|
+
// preserves it through the bulk flip.
|
|
364
|
+
if (
|
|
365
|
+
delivery.requestId === id &&
|
|
366
|
+
delivery.status !== DELIVERY_STATUS.withdrawn
|
|
367
|
+
) {
|
|
368
|
+
delivery.status = DELIVERY_STATUS.expired;
|
|
355
369
|
delivery.updatedAt = Date.now();
|
|
356
370
|
}
|
|
357
371
|
}
|
|
@@ -364,9 +378,13 @@ export function createGuardianGatewaySim() {
|
|
|
364
378
|
if (row.status !== "pending") {
|
|
365
379
|
continue;
|
|
366
380
|
}
|
|
381
|
+
// Mirrors the gateway contract: only the kinds that die with the
|
|
382
|
+
// daemon's in-memory pendingInteractions map expire at boot.
|
|
383
|
+
// Persistent kinds stay pending, whatever their deadline; their
|
|
384
|
+
// expiry belongs to the sweep, which owns the fan-out.
|
|
367
385
|
const interactionBound =
|
|
368
386
|
row.kind === "tool_approval" || row.kind === "pending_question";
|
|
369
|
-
if (interactionBound
|
|
387
|
+
if (interactionBound) {
|
|
370
388
|
row.status = "expired";
|
|
371
389
|
row.updatedAt = now;
|
|
372
390
|
expired += 1;
|
|
@@ -375,19 +393,18 @@ export function createGuardianGatewaySim() {
|
|
|
375
393
|
return expired;
|
|
376
394
|
}
|
|
377
395
|
|
|
378
|
-
async function
|
|
379
|
-
|
|
396
|
+
async function listExpiredPendingGuardianRequests(
|
|
397
|
+
limit = 50,
|
|
380
398
|
): Promise<SimGuardianRequest[]> {
|
|
381
|
-
const cutoff =
|
|
382
|
-
const
|
|
399
|
+
const cutoff = Date.now();
|
|
400
|
+
const stale: SimGuardianRequest[] = [];
|
|
383
401
|
for (const row of requests.values()) {
|
|
384
402
|
if (row.status === "pending" && isGuardianRequestExpired(row, cutoff)) {
|
|
385
|
-
|
|
386
|
-
row.updatedAt = cutoff;
|
|
387
|
-
expired.push({ ...row });
|
|
403
|
+
stale.push({ ...row });
|
|
388
404
|
}
|
|
389
405
|
}
|
|
390
|
-
|
|
406
|
+
stale.sort((a, b) => (a.expiresAt ?? 0) - (b.expiresAt ?? 0));
|
|
407
|
+
return stale.slice(0, limit);
|
|
391
408
|
}
|
|
392
409
|
|
|
393
410
|
async function createGuardianRequestDelivery(
|
|
@@ -406,7 +423,13 @@ export function createGuardianGatewaySim() {
|
|
|
406
423
|
if (!row) {
|
|
407
424
|
throw new Error(`sim: delivery ${id} not found`);
|
|
408
425
|
}
|
|
409
|
-
|
|
426
|
+
// Mirrors the gateway store: `withdrawn` is a terminal per-surface
|
|
427
|
+
// receipt a later status patch never overwrites.
|
|
428
|
+
const effectivePatch =
|
|
429
|
+
row.status === "withdrawn" && patch.status !== undefined
|
|
430
|
+
? { ...patch, status: row.status }
|
|
431
|
+
: patch;
|
|
432
|
+
Object.assign(row, effectivePatch, { updatedAt: Date.now() });
|
|
410
433
|
}
|
|
411
434
|
|
|
412
435
|
async function listGuardianRequestDeliveries(
|
|
@@ -418,6 +441,19 @@ export function createGuardianGatewaySim() {
|
|
|
418
441
|
.map((d) => ({ ...d }));
|
|
419
442
|
}
|
|
420
443
|
|
|
444
|
+
async function listGuardianRequestDeliveriesByChat(
|
|
445
|
+
channel: string,
|
|
446
|
+
chatId: string,
|
|
447
|
+
): Promise<SimGuardianDelivery[]> {
|
|
448
|
+
throwIfReadError();
|
|
449
|
+
return deliveries
|
|
450
|
+
.filter(
|
|
451
|
+
(d) =>
|
|
452
|
+
d.destinationChannel === channel && d.destinationChatId === chatId,
|
|
453
|
+
)
|
|
454
|
+
.map((d) => ({ ...d }));
|
|
455
|
+
}
|
|
456
|
+
|
|
421
457
|
async function getPendingRequestByDestinationMessage(
|
|
422
458
|
channel: string,
|
|
423
459
|
chatId: string,
|
|
@@ -504,7 +540,6 @@ export function createGuardianGatewaySim() {
|
|
|
504
540
|
async function isGuardianRequestInScope(
|
|
505
541
|
requestId: string,
|
|
506
542
|
conversationId: string,
|
|
507
|
-
channel?: string,
|
|
508
543
|
): Promise<boolean> {
|
|
509
544
|
throwIfReadError();
|
|
510
545
|
const request = requests.get(requestId);
|
|
@@ -517,8 +552,7 @@ export function createGuardianGatewaySim() {
|
|
|
517
552
|
return deliveries.some(
|
|
518
553
|
(d) =>
|
|
519
554
|
d.requestId === requestId &&
|
|
520
|
-
d.destinationConversationId === conversationId
|
|
521
|
-
(!channel || d.destinationChannel === channel),
|
|
555
|
+
d.destinationConversationId === conversationId,
|
|
522
556
|
);
|
|
523
557
|
}
|
|
524
558
|
|
|
@@ -575,15 +609,17 @@ export function createGuardianGatewaySim() {
|
|
|
575
609
|
getGuardianRequest,
|
|
576
610
|
getGuardianRequestOrNull: degrade(getGuardianRequest, null),
|
|
577
611
|
getGuardianRequestByCodeOrNull: degrade(getGuardianRequestByCode, null),
|
|
612
|
+
listGuardianRequests,
|
|
578
613
|
listGuardianRequestsOrEmpty: degrade(listGuardianRequests, []),
|
|
579
614
|
updateGuardianRequest,
|
|
580
615
|
decideGuardianRequest,
|
|
581
616
|
expireGuardianRequest,
|
|
582
617
|
expireInteractionBoundGuardianRequests,
|
|
583
|
-
|
|
618
|
+
listExpiredPendingGuardianRequests,
|
|
584
619
|
createGuardianRequestDelivery,
|
|
585
620
|
updateGuardianRequestDelivery,
|
|
586
621
|
listGuardianRequestDeliveries,
|
|
622
|
+
listGuardianRequestDeliveriesByChat,
|
|
587
623
|
listGuardianRequestDeliveriesOrEmpty: degrade(
|
|
588
624
|
listGuardianRequestDeliveries,
|
|
589
625
|
[],
|
|
@@ -34,7 +34,13 @@ mock.module("../approvals/guardian-decision-primitive.js", () => ({
|
|
|
34
34
|
applyGuardianDecisionMock(params),
|
|
35
35
|
}));
|
|
36
36
|
|
|
37
|
+
// Spread the actual module so transitive importers of names this
|
|
38
|
+
// factory does not stub keep resolving (a partial factory breaks at
|
|
39
|
+
// import time when the graph gains a new named import).
|
|
40
|
+
const actualGatewayGuardianRequests =
|
|
41
|
+
await import("../channels/gateway-guardian-requests.js");
|
|
37
42
|
mock.module("../channels/gateway-guardian-requests.js", () => ({
|
|
43
|
+
...actualGatewayGuardianRequests,
|
|
38
44
|
getGuardianRequestOrNull: (id: string) =>
|
|
39
45
|
Promise.resolve(requestsById.get(id) ?? null),
|
|
40
46
|
getGuardianRequestByCodeOrNull: () => Promise.resolve(null),
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, mock, test } from "bun:test";
|
|
2
|
+
|
|
3
|
+
const completeSurfaceAndNotify = mock(() => true);
|
|
4
|
+
const markSurfaceCompleted = mock(() => true);
|
|
5
|
+
mock.module("../daemon/conversation-surfaces.js", () => ({
|
|
6
|
+
completeSurfaceAndNotify,
|
|
7
|
+
markSurfaceCompleted,
|
|
8
|
+
}));
|
|
9
|
+
|
|
10
|
+
const withdrawSlackApprovalCard = mock(
|
|
11
|
+
async (_params: Record<string, unknown>) => {},
|
|
12
|
+
);
|
|
13
|
+
mock.module("../messaging/providers/slack/withdraw.js", () => ({
|
|
14
|
+
withdrawSlackApprovalCard,
|
|
15
|
+
}));
|
|
16
|
+
|
|
17
|
+
const withdrawDiscordApprovalCard = mock(async (_params: unknown) => undefined);
|
|
18
|
+
mock.module("../messaging/providers/discord/withdraw.js", () => ({
|
|
19
|
+
withdrawDiscordApprovalCard,
|
|
20
|
+
}));
|
|
21
|
+
|
|
22
|
+
const withdrawTelegramApprovalCard = mock(
|
|
23
|
+
async (_params: Record<string, unknown>) => ({ complete: true }),
|
|
24
|
+
);
|
|
25
|
+
mock.module("../messaging/providers/telegram-bot/withdraw.js", () => ({
|
|
26
|
+
withdrawTelegramApprovalCard,
|
|
27
|
+
}));
|
|
28
|
+
|
|
29
|
+
import {
|
|
30
|
+
bridgeState,
|
|
31
|
+
gatewayGuardianRequestsStoreBridge,
|
|
32
|
+
} from "./helpers/gateway-guardian-requests-store-bridge.js";
|
|
33
|
+
|
|
34
|
+
mock.module(
|
|
35
|
+
"../channels/gateway-guardian-requests.js",
|
|
36
|
+
() => gatewayGuardianRequestsStoreBridge,
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
import { syncTerminalGuardianRequestStatus } from "../approvals/guardian-request-status-sync.js";
|
|
40
|
+
import { initializeDb } from "../persistence/db-init.js";
|
|
41
|
+
|
|
42
|
+
await initializeDb();
|
|
43
|
+
|
|
44
|
+
const PRINCIPAL_ID = "status-sync-test-principal";
|
|
45
|
+
|
|
46
|
+
function makeRequest(overrides: Record<string, unknown> = {}) {
|
|
47
|
+
return bridgeState.seedRequest({
|
|
48
|
+
kind: "tool_approval",
|
|
49
|
+
sourceType: "channel",
|
|
50
|
+
sourceChannel: "slack",
|
|
51
|
+
toolName: "shell",
|
|
52
|
+
guardianPrincipalId: PRINCIPAL_ID,
|
|
53
|
+
...overrides,
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function deliveriesFor(requestId: string) {
|
|
58
|
+
return bridgeState.deliveries.filter((d) => d.requestId === requestId);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
describe("syncTerminalGuardianRequestStatus", () => {
|
|
62
|
+
beforeEach(() => {
|
|
63
|
+
bridgeState.reset();
|
|
64
|
+
completeSurfaceAndNotify.mockClear();
|
|
65
|
+
markSurfaceCompleted.mockClear();
|
|
66
|
+
withdrawSlackApprovalCard.mockClear();
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
test("a landed CAS withdraws every delivered projection", async () => {
|
|
70
|
+
const req = makeRequest();
|
|
71
|
+
const vellum = bridgeState.seedDelivery({
|
|
72
|
+
requestId: req.id,
|
|
73
|
+
destinationChannel: "vellum",
|
|
74
|
+
destinationConversationId: "conv-thread",
|
|
75
|
+
});
|
|
76
|
+
const slack = bridgeState.seedDelivery({
|
|
77
|
+
requestId: req.id,
|
|
78
|
+
destinationChannel: "slack",
|
|
79
|
+
destinationChatId: "C1",
|
|
80
|
+
destinationMessageId: "1.0",
|
|
81
|
+
destinationConversationId: "conv-dm",
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
await syncTerminalGuardianRequestStatus({
|
|
85
|
+
requestId: req.id,
|
|
86
|
+
status: "approved",
|
|
87
|
+
syncContext: "test",
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
expect(bridgeState.requests.get(req.id)?.status).toBe("approved");
|
|
91
|
+
// Both in-app projections converge live (no origin channel: the acting
|
|
92
|
+
// surface was the confirmation prompt, never the card).
|
|
93
|
+
expect(completeSurfaceAndNotify).toHaveBeenCalledWith(
|
|
94
|
+
"conv-thread",
|
|
95
|
+
`tool-approval-${req.id}`,
|
|
96
|
+
"Approved",
|
|
97
|
+
);
|
|
98
|
+
expect(completeSurfaceAndNotify).toHaveBeenCalledWith(
|
|
99
|
+
"conv-dm",
|
|
100
|
+
`tool-approval-${req.id}`,
|
|
101
|
+
"Approved",
|
|
102
|
+
);
|
|
103
|
+
expect(withdrawSlackApprovalCard).toHaveBeenCalledTimes(1);
|
|
104
|
+
const byId = new Map(deliveriesFor(req.id).map((d) => [d.id, d.status]));
|
|
105
|
+
expect(byId.get(vellum.id)).toBe("withdrawn");
|
|
106
|
+
expect(byId.get(slack.id)).toBe("withdrawn");
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
test("a denial renders Denied on the withdrawn cards", async () => {
|
|
110
|
+
const req = makeRequest();
|
|
111
|
+
bridgeState.seedDelivery({
|
|
112
|
+
requestId: req.id,
|
|
113
|
+
destinationChannel: "vellum",
|
|
114
|
+
destinationConversationId: "conv-thread",
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
await syncTerminalGuardianRequestStatus({
|
|
118
|
+
requestId: req.id,
|
|
119
|
+
status: "denied",
|
|
120
|
+
syncContext: "test",
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
expect(bridgeState.requests.get(req.id)?.status).toBe("denied");
|
|
124
|
+
expect(completeSurfaceAndNotify).toHaveBeenCalledWith(
|
|
125
|
+
"conv-thread",
|
|
126
|
+
`tool-approval-${req.id}`,
|
|
127
|
+
"Denied",
|
|
128
|
+
);
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
test("a CAS miss leaves withdrawal to the path that resolved the request", async () => {
|
|
132
|
+
const req = makeRequest({ status: "approved" });
|
|
133
|
+
bridgeState.seedDelivery({
|
|
134
|
+
requestId: req.id,
|
|
135
|
+
destinationChannel: "vellum",
|
|
136
|
+
destinationConversationId: "conv-thread",
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
await syncTerminalGuardianRequestStatus({
|
|
140
|
+
requestId: req.id,
|
|
141
|
+
status: "approved",
|
|
142
|
+
syncContext: "test",
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
expect(completeSurfaceAndNotify).not.toHaveBeenCalled();
|
|
146
|
+
expect(withdrawSlackApprovalCard).not.toHaveBeenCalled();
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
test("never throws when the gateway is unreachable", async () => {
|
|
150
|
+
const req = makeRequest();
|
|
151
|
+
bridgeState.state.decideError = new Error("gateway down");
|
|
152
|
+
await expect(
|
|
153
|
+
syncTerminalGuardianRequestStatus({
|
|
154
|
+
requestId: req.id,
|
|
155
|
+
status: "approved",
|
|
156
|
+
syncContext: "test",
|
|
157
|
+
}),
|
|
158
|
+
).resolves.toBeUndefined();
|
|
159
|
+
bridgeState.state.decideError = null;
|
|
160
|
+
expect(completeSurfaceAndNotify).not.toHaveBeenCalled();
|
|
161
|
+
});
|
|
162
|
+
});
|
|
@@ -37,7 +37,13 @@ mock.module("../util/logger.js", () => ({
|
|
|
37
37
|
|
|
38
38
|
// Gateway guardian-request client — in-memory map driven by tests.
|
|
39
39
|
const guardianRequests = new Map<string, GuardianRequestWire>();
|
|
40
|
+
// Spread the actual module so transitive importers of names this
|
|
41
|
+
// factory does not stub keep resolving (a partial factory breaks at
|
|
42
|
+
// import time when the graph gains a new named import).
|
|
43
|
+
const actualGatewayGuardianRequests =
|
|
44
|
+
await import("../channels/gateway-guardian-requests.js");
|
|
40
45
|
mock.module("../channels/gateway-guardian-requests.js", () => ({
|
|
46
|
+
...actualGatewayGuardianRequests,
|
|
41
47
|
getGuardianRequestOrNull: async (id: string) =>
|
|
42
48
|
guardianRequests.get(id) ?? null,
|
|
43
49
|
}));
|
|
@@ -148,6 +148,7 @@ import {
|
|
|
148
148
|
formatModeSelectionFailure,
|
|
149
149
|
parseBrowserMode,
|
|
150
150
|
} from "../tools/browser/browser-execution.js";
|
|
151
|
+
import { CHROME_WEB_STORE_INSTALL_URL } from "../tools/browser/browser-status-constants.js";
|
|
151
152
|
import type { ToolContext } from "../tools/types.js";
|
|
152
153
|
|
|
153
154
|
// Restore all module mocks after this file completes so they don't
|
|
@@ -305,6 +306,7 @@ describe("formatModeSelectionFailure", () => {
|
|
|
305
306
|
);
|
|
306
307
|
expect(formatted).toContain("Remediation:");
|
|
307
308
|
expect(formatted).toContain("extension is installed and enabled");
|
|
309
|
+
expect(formatted).toContain(CHROME_WEB_STORE_INSTALL_URL);
|
|
308
310
|
});
|
|
309
311
|
|
|
310
312
|
test("renders cdp-inspect transport_error with remediation", () => {
|
|
@@ -331,6 +333,29 @@ describe("formatModeSelectionFailure", () => {
|
|
|
331
333
|
expect(formatted).toContain("Remediation:");
|
|
332
334
|
expect(formatted).toContain("--remote-debugging-port");
|
|
333
335
|
});
|
|
336
|
+
|
|
337
|
+
test("includes the Chrome Web Store install URL for host-bridge failures", () => {
|
|
338
|
+
const diagnostics: AttemptDiagnostic[] = [
|
|
339
|
+
{
|
|
340
|
+
candidateKind: "host-bridge",
|
|
341
|
+
inclusionReason: "auto candidate",
|
|
342
|
+
stage: "send",
|
|
343
|
+
errorCode: "transport_error",
|
|
344
|
+
errorMessage: "desktop client could not reach Chrome",
|
|
345
|
+
},
|
|
346
|
+
];
|
|
347
|
+
|
|
348
|
+
const error = new CdpError(
|
|
349
|
+
"transport_error",
|
|
350
|
+
"Host bridge unreachable",
|
|
351
|
+
{ attemptDiagnostics: diagnostics },
|
|
352
|
+
);
|
|
353
|
+
|
|
354
|
+
const formatted = formatModeSelectionFailure("auto", error);
|
|
355
|
+
|
|
356
|
+
expect(formatted).toContain("Remediation:");
|
|
357
|
+
expect(formatted).toContain(CHROME_WEB_STORE_INSTALL_URL);
|
|
358
|
+
});
|
|
334
359
|
});
|
|
335
360
|
|
|
336
361
|
describe("browser_mode wiring through tool execution", () => {
|
|
@@ -398,6 +423,7 @@ describe("browser_mode wiring through tool execution", () => {
|
|
|
398
423
|
);
|
|
399
424
|
expect(result.content).toContain("Remediation:");
|
|
400
425
|
expect(result.content).toContain("extension is installed and enabled");
|
|
426
|
+
expect(result.content).toContain(CHROME_WEB_STORE_INSTALL_URL);
|
|
401
427
|
// Factory should not have been called for CDP
|
|
402
428
|
expect(cdpSendCalls).toEqual([]);
|
|
403
429
|
});
|
|
@@ -585,6 +611,7 @@ describe("browser_mode wiring through tool execution", () => {
|
|
|
585
611
|
expect(result.content).toContain("Reason: Host browser not reachable");
|
|
586
612
|
expect(result.content).toContain("Remediation:");
|
|
587
613
|
expect(result.content).toContain("extension is installed and enabled");
|
|
614
|
+
expect(result.content).toContain(CHROME_WEB_STORE_INSTALL_URL);
|
|
588
615
|
});
|
|
589
616
|
|
|
590
617
|
test("pinned extension with timeout transport error surfaces failover diagnostics in snapshot", async () => {
|
|
@@ -36,6 +36,8 @@ export function asConversation<TExtra extends object = object>(
|
|
|
36
36
|
getTrustContext: () => merged.trustContext,
|
|
37
37
|
getTurnOrRestingTrust: () =>
|
|
38
38
|
merged.currentTurnTrustContext ?? merged.trustContext,
|
|
39
|
+
isStale: () => false,
|
|
40
|
+
hasInFlightWork: () => false,
|
|
39
41
|
...mock,
|
|
40
42
|
};
|
|
41
43
|
return merged as unknown as Conversation & TExtra;
|
|
@@ -19,6 +19,7 @@ import { ConversationMessageSchema } from "../api/responses/conversation-message
|
|
|
19
19
|
import {
|
|
20
20
|
addMessage,
|
|
21
21
|
createConversation,
|
|
22
|
+
NO_RESPONSE_MESSAGE_KIND,
|
|
22
23
|
SYSTEM_CARD_MESSAGE_KIND,
|
|
23
24
|
} from "../persistence/conversation-crud.js";
|
|
24
25
|
import { getDb } from "../persistence/db-connection.js";
|
|
@@ -39,6 +40,7 @@ interface ProjectedMessage {
|
|
|
39
40
|
id: string;
|
|
40
41
|
role: string;
|
|
41
42
|
systemCard?: boolean;
|
|
43
|
+
noResponse?: boolean;
|
|
42
44
|
}
|
|
43
45
|
|
|
44
46
|
describe("handleListMessages system-card projection", () => {
|
|
@@ -74,6 +76,74 @@ describe("handleListMessages system-card projection", () => {
|
|
|
74
76
|
expect(cardRow?.systemCard).toBe(true);
|
|
75
77
|
});
|
|
76
78
|
|
|
79
|
+
test("projects noResponse from the deliberate-silence marker", async () => {
|
|
80
|
+
const conv = createConversation();
|
|
81
|
+
await addMessage(
|
|
82
|
+
conv.id,
|
|
83
|
+
"user",
|
|
84
|
+
JSON.stringify([{ type: "text", text: "thanks!" }]),
|
|
85
|
+
);
|
|
86
|
+
// Mirror the agent loop's turn-boundary stamp: the row keeps the raw
|
|
87
|
+
// sentinel as content and carries the marker in metadata.
|
|
88
|
+
const silent = await addMessage(
|
|
89
|
+
conv.id,
|
|
90
|
+
"assistant",
|
|
91
|
+
JSON.stringify([{ type: "text", text: "<no_response/>" }]),
|
|
92
|
+
{ metadata: { messageKind: NO_RESPONSE_MESSAGE_KIND } },
|
|
93
|
+
);
|
|
94
|
+
|
|
95
|
+
const response = (await handleListMessages({
|
|
96
|
+
queryParams: { conversationId: conv.id },
|
|
97
|
+
})) as { messages: ProjectedMessage[] };
|
|
98
|
+
|
|
99
|
+
for (const message of response.messages) {
|
|
100
|
+
expect(() => ConversationMessageSchema.parse(message)).not.toThrow();
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const silentRow = response.messages.find((m) => m.id === silent.id);
|
|
104
|
+
expect(silentRow?.noResponse).toBe(true);
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
test("projects the reaction fact from a reaction row's envelope", async () => {
|
|
108
|
+
const conv = createConversation();
|
|
109
|
+
const row = await addMessage(
|
|
110
|
+
conv.id,
|
|
111
|
+
"assistant",
|
|
112
|
+
JSON.stringify([{ type: "text", text: "[reaction]" }]),
|
|
113
|
+
{
|
|
114
|
+
metadata: {
|
|
115
|
+
messageKind: "reaction",
|
|
116
|
+
providerMeta: JSON.stringify({
|
|
117
|
+
source: "discord",
|
|
118
|
+
conversationExternalId: "chan-1",
|
|
119
|
+
eventKind: "reaction",
|
|
120
|
+
reaction: {
|
|
121
|
+
targetMessageId: "555.1",
|
|
122
|
+
emoji: "🎉",
|
|
123
|
+
op: "added",
|
|
124
|
+
},
|
|
125
|
+
}),
|
|
126
|
+
},
|
|
127
|
+
},
|
|
128
|
+
);
|
|
129
|
+
|
|
130
|
+
const response = (await handleListMessages({
|
|
131
|
+
queryParams: { conversationId: conv.id },
|
|
132
|
+
})) as {
|
|
133
|
+
messages: Array<
|
|
134
|
+
ProjectedMessage & {
|
|
135
|
+
reaction?: { emoji: string; selfAuthored?: boolean };
|
|
136
|
+
}
|
|
137
|
+
>;
|
|
138
|
+
};
|
|
139
|
+
for (const message of response.messages) {
|
|
140
|
+
expect(() => ConversationMessageSchema.parse(message)).not.toThrow();
|
|
141
|
+
}
|
|
142
|
+
const projected = response.messages.find((m) => m.id === row.id);
|
|
143
|
+
expect(projected?.reaction?.emoji).toBe("🎉");
|
|
144
|
+
expect(projected?.reaction?.selfAuthored).toBe(true);
|
|
145
|
+
});
|
|
146
|
+
|
|
77
147
|
test("omits systemCard on ordinary user and assistant rows", async () => {
|
|
78
148
|
const conv = createConversation();
|
|
79
149
|
await addMessage(
|
|
@@ -51,6 +51,7 @@ interface ClientCatalogModel {
|
|
|
51
51
|
longContextMode?: "native-model" | "provider-request-option" | "unsupported";
|
|
52
52
|
supportsThinking?: boolean;
|
|
53
53
|
adaptiveThinkingOnly?: boolean;
|
|
54
|
+
thinkingFloor?: "minimal" | "low";
|
|
54
55
|
supportsCaching?: boolean;
|
|
55
56
|
supportsVision?: boolean;
|
|
56
57
|
supportsToolUse?: boolean;
|
|
@@ -198,6 +199,7 @@ describe("LLM catalog parity: daemon vs client", () => {
|
|
|
198
199
|
expect(clientModel.adaptiveThinkingOnly).toBe(
|
|
199
200
|
daemonModel.adaptiveThinkingOnly,
|
|
200
201
|
);
|
|
202
|
+
expect(clientModel.thinkingFloor).toBe(daemonModel.thinkingFloor);
|
|
201
203
|
expect(clientModel.supportsCaching).toBe(daemonModel.supportsCaching);
|
|
202
204
|
expect(clientModel.supportsVision).toBe(daemonModel.supportsVision);
|
|
203
205
|
expect(clientModel.supportsToolUse).toBe(daemonModel.supportsToolUse);
|
|
@@ -1118,7 +1118,7 @@ describe("code-owned default profiles — wire view and write normalization", ()
|
|
|
1118
1118
|
});
|
|
1119
1119
|
const response = (await getRoute.handler({})) as Record<string, any>;
|
|
1120
1120
|
const wireBalanced = response.llm.profiles.balanced;
|
|
1121
|
-
expect(wireBalanced.model).toBe("accounts/fireworks/models/glm-
|
|
1121
|
+
expect(wireBalanced.model).toBe("accounts/fireworks/models/glm-5p3-flash");
|
|
1122
1122
|
expect(wireBalanced.provider).toBe("vellum");
|
|
1123
1123
|
expect(wireBalanced.provider_connection).toBeUndefined();
|
|
1124
1124
|
expect(wireBalanced.status).toBe("disabled");
|
|
@@ -1366,7 +1366,7 @@ describe("code-owned default profiles — echoes over stale on-disk bodies", ()
|
|
|
1366
1366
|
const response = (await getRoute.handler({})) as Record<string, any>;
|
|
1367
1367
|
// The wire view serves catalog content, not the stale body.
|
|
1368
1368
|
expect(response.llm.profiles.balanced.model).toBe(
|
|
1369
|
-
"accounts/fireworks/models/glm-
|
|
1369
|
+
"accounts/fireworks/models/glm-5p3-flash",
|
|
1370
1370
|
);
|
|
1371
1371
|
const result = await patchRoute.handler({
|
|
1372
1372
|
body: { llm: { profiles: response.llm.profiles } },
|
|
@@ -13,6 +13,7 @@ import { getDb } from "../persistence/db-connection.js";
|
|
|
13
13
|
import { initializeDb } from "../persistence/db-init.js";
|
|
14
14
|
import {
|
|
15
15
|
isUnsendableImageSource,
|
|
16
|
+
mediaSourceDescriptor,
|
|
16
17
|
resolveMediaReferences,
|
|
17
18
|
UNSENDABLE_IMAGE_FORMAT_NOTE,
|
|
18
19
|
} from "../providers/media-resolve.js";
|
|
@@ -221,6 +222,111 @@ describe("resolveMediaReferences image validation", () => {
|
|
|
221
222
|
});
|
|
222
223
|
});
|
|
223
224
|
|
|
225
|
+
describe("resolveMediaReferences attachment id", () => {
|
|
226
|
+
beforeEach(resetTables);
|
|
227
|
+
|
|
228
|
+
test("carries a file block's attachment id across reference resolution", async () => {
|
|
229
|
+
// Resolving a reference rebuilds the block around freshly read bytes. The
|
|
230
|
+
// id is the block's only remaining link to its attachment row, so it has to
|
|
231
|
+
// survive the rebuild.
|
|
232
|
+
const conv = createConversation();
|
|
233
|
+
const stored = await createInlineAttachment(
|
|
234
|
+
conv.id,
|
|
235
|
+
conv.createdAt,
|
|
236
|
+
"notes.txt",
|
|
237
|
+
"text/plain",
|
|
238
|
+
Buffer.from("hello").toString("base64"),
|
|
239
|
+
);
|
|
240
|
+
const message = userMessage([
|
|
241
|
+
{
|
|
242
|
+
type: "file",
|
|
243
|
+
source: {
|
|
244
|
+
type: "workspace_ref",
|
|
245
|
+
media_type: "text/plain",
|
|
246
|
+
attachmentId: stored.id,
|
|
247
|
+
sizeBytes: stored.sizeBytes,
|
|
248
|
+
filename: "notes.txt",
|
|
249
|
+
},
|
|
250
|
+
_attachmentId: stored.id,
|
|
251
|
+
},
|
|
252
|
+
]);
|
|
253
|
+
|
|
254
|
+
const [resolved] = await resolveMediaReferences([message]);
|
|
255
|
+
|
|
256
|
+
const block = resolved.content[0];
|
|
257
|
+
expect(block.type).toBe("file");
|
|
258
|
+
expect(block).toMatchObject({
|
|
259
|
+
source: { type: "base64" },
|
|
260
|
+
_attachmentId: stored.id,
|
|
261
|
+
});
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
test("omits the field when the source block carries no id", async () => {
|
|
265
|
+
const conv = createConversation();
|
|
266
|
+
const stored = await createInlineAttachment(
|
|
267
|
+
conv.id,
|
|
268
|
+
conv.createdAt,
|
|
269
|
+
"plain.txt",
|
|
270
|
+
"text/plain",
|
|
271
|
+
Buffer.from("hi").toString("base64"),
|
|
272
|
+
);
|
|
273
|
+
const message = userMessage([
|
|
274
|
+
{
|
|
275
|
+
type: "file",
|
|
276
|
+
source: {
|
|
277
|
+
type: "workspace_ref",
|
|
278
|
+
media_type: "text/plain",
|
|
279
|
+
attachmentId: stored.id,
|
|
280
|
+
sizeBytes: stored.sizeBytes,
|
|
281
|
+
filename: "plain.txt",
|
|
282
|
+
},
|
|
283
|
+
},
|
|
284
|
+
]);
|
|
285
|
+
|
|
286
|
+
const [resolved] = await resolveMediaReferences([message]);
|
|
287
|
+
|
|
288
|
+
expect("_attachmentId" in resolved.content[0]).toBe(false);
|
|
289
|
+
});
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
describe("mediaSourceDescriptor", () => {
|
|
293
|
+
// The fragment every media stub embeds. Both the retry path's stubs and the
|
|
294
|
+
// camera-frame stub read it from here, so this pins what they all report.
|
|
295
|
+
test("describes a reference from its persisted size hint", () => {
|
|
296
|
+
expect(
|
|
297
|
+
mediaSourceDescriptor({
|
|
298
|
+
type: "workspace_ref",
|
|
299
|
+
media_type: "image/jpeg",
|
|
300
|
+
attachmentId: "att-1",
|
|
301
|
+
sizeBytes: 1024,
|
|
302
|
+
}),
|
|
303
|
+
).toBe("image/jpeg, 1024 bytes");
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
test("describes an inline block from its base64 length", () => {
|
|
307
|
+
// 8 base64 chars decode to 6 bytes.
|
|
308
|
+
expect(
|
|
309
|
+
mediaSourceDescriptor({
|
|
310
|
+
type: "base64",
|
|
311
|
+
media_type: "image/png",
|
|
312
|
+
data: "AAAAAAAA",
|
|
313
|
+
}),
|
|
314
|
+
).toBe("image/png, 6 bytes");
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
test("covers non-image media too", () => {
|
|
318
|
+
expect(
|
|
319
|
+
mediaSourceDescriptor({
|
|
320
|
+
type: "workspace_ref",
|
|
321
|
+
media_type: "application/pdf",
|
|
322
|
+
attachmentId: "att-2",
|
|
323
|
+
sizeBytes: 42,
|
|
324
|
+
filename: "notes.pdf",
|
|
325
|
+
}),
|
|
326
|
+
).toBe("application/pdf, 42 bytes");
|
|
327
|
+
});
|
|
328
|
+
});
|
|
329
|
+
|
|
224
330
|
describe("isUnsendableImageSource", () => {
|
|
225
331
|
beforeEach(resetTables);
|
|
226
332
|
|