@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
|
@@ -216,6 +216,8 @@ export interface StaticCredentialPolicyInput {
|
|
|
216
216
|
*/
|
|
217
217
|
export class StaticCredentialMetadataStore {
|
|
218
218
|
private metadataPath: string;
|
|
219
|
+
/** When set, reads and writes stay in process and do not touch the file. */
|
|
220
|
+
private memory: MetadataFile | null = null;
|
|
219
221
|
|
|
220
222
|
constructor(metadataPath: string) {
|
|
221
223
|
this.metadataPath = metadataPath;
|
|
@@ -224,6 +226,17 @@ export class StaticCredentialMetadataStore {
|
|
|
224
226
|
/** Update the metadata file path (primarily for testing). */
|
|
225
227
|
setPath(path: string): void {
|
|
226
228
|
this.metadataPath = path;
|
|
229
|
+
this.memory = null;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* Switch to an in-memory backing filled with `records`.
|
|
234
|
+
*/
|
|
235
|
+
useMemory(records: StaticCredentialRecord[] = []): void {
|
|
236
|
+
this.memory = {
|
|
237
|
+
version: CURRENT_VERSION,
|
|
238
|
+
credentials: records.map((record) => ({ ...record })),
|
|
239
|
+
};
|
|
227
240
|
}
|
|
228
241
|
|
|
229
242
|
/** Get the current metadata file path. */
|
|
@@ -231,13 +244,28 @@ export class StaticCredentialMetadataStore {
|
|
|
231
244
|
return this.metadataPath;
|
|
232
245
|
}
|
|
233
246
|
|
|
247
|
+
private load(): LoadResult {
|
|
248
|
+
if (this.memory) {
|
|
249
|
+
return this.memory;
|
|
250
|
+
}
|
|
251
|
+
return loadFile(this.metadataPath, saveFile);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
private persist(data: MetadataFile): void {
|
|
255
|
+
if (this.memory) {
|
|
256
|
+
this.memory = data;
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
259
|
+
saveFile(data, this.metadataPath);
|
|
260
|
+
}
|
|
261
|
+
|
|
234
262
|
/**
|
|
235
263
|
* Throws if the metadata file has an unrecognized version.
|
|
236
264
|
* Call this before performing irreversible credential store operations
|
|
237
265
|
* so the operation fails cleanly before any side effects.
|
|
238
266
|
*/
|
|
239
267
|
assertWritable(): void {
|
|
240
|
-
const result =
|
|
268
|
+
const result = this.load();
|
|
241
269
|
if (isUnknownVersion(result)) {
|
|
242
270
|
throw new Error(
|
|
243
271
|
"Credential metadata file has an unrecognized version; refusing to mutate to avoid data loss"
|
|
@@ -254,7 +282,7 @@ export class StaticCredentialMetadataStore {
|
|
|
254
282
|
field: string,
|
|
255
283
|
policy?: StaticCredentialPolicyInput
|
|
256
284
|
): StaticCredentialRecord {
|
|
257
|
-
const result =
|
|
285
|
+
const result = this.load();
|
|
258
286
|
if (isUnknownVersion(result)) {
|
|
259
287
|
throw new Error(
|
|
260
288
|
"Credential metadata file has an unrecognized version; refusing to mutate to avoid data loss"
|
|
@@ -268,12 +296,15 @@ export class StaticCredentialMetadataStore {
|
|
|
268
296
|
);
|
|
269
297
|
|
|
270
298
|
if (existing) {
|
|
271
|
-
if (policy?.allowedTools !== undefined)
|
|
299
|
+
if (policy?.allowedTools !== undefined) {
|
|
272
300
|
existing.allowedTools = policy.allowedTools;
|
|
273
|
-
|
|
301
|
+
}
|
|
302
|
+
if (policy?.allowedDomains !== undefined) {
|
|
274
303
|
existing.allowedDomains = policy.allowedDomains;
|
|
275
|
-
|
|
304
|
+
}
|
|
305
|
+
if (policy?.usageDescription !== undefined) {
|
|
276
306
|
existing.usageDescription = policy.usageDescription;
|
|
307
|
+
}
|
|
277
308
|
if (policy?.alias !== undefined) {
|
|
278
309
|
if (policy.alias == null) {
|
|
279
310
|
delete existing.alias;
|
|
@@ -289,7 +320,7 @@ export class StaticCredentialMetadataStore {
|
|
|
289
320
|
}
|
|
290
321
|
}
|
|
291
322
|
existing.updatedAt = now;
|
|
292
|
-
|
|
323
|
+
this.persist(data);
|
|
293
324
|
return existing;
|
|
294
325
|
}
|
|
295
326
|
|
|
@@ -307,7 +338,7 @@ export class StaticCredentialMetadataStore {
|
|
|
307
338
|
};
|
|
308
339
|
|
|
309
340
|
data.credentials.push(record);
|
|
310
|
-
|
|
341
|
+
this.persist(data);
|
|
311
342
|
return record;
|
|
312
343
|
}
|
|
313
344
|
|
|
@@ -318,7 +349,7 @@ export class StaticCredentialMetadataStore {
|
|
|
318
349
|
service: string,
|
|
319
350
|
field: string
|
|
320
351
|
): StaticCredentialRecord | undefined {
|
|
321
|
-
const result =
|
|
352
|
+
const result = this.load();
|
|
322
353
|
if (isUnknownVersion(result)) return undefined;
|
|
323
354
|
return result.credentials.find(
|
|
324
355
|
(c) => c.service === service && c.field === field
|
|
@@ -329,7 +360,7 @@ export class StaticCredentialMetadataStore {
|
|
|
329
360
|
* Get metadata for a credential by its opaque ID.
|
|
330
361
|
*/
|
|
331
362
|
getById(credentialId: string): StaticCredentialRecord | undefined {
|
|
332
|
-
const result =
|
|
363
|
+
const result = this.load();
|
|
333
364
|
if (isUnknownVersion(result)) return undefined;
|
|
334
365
|
return result.credentials.find((c) => c.credentialId === credentialId);
|
|
335
366
|
}
|
|
@@ -338,7 +369,7 @@ export class StaticCredentialMetadataStore {
|
|
|
338
369
|
* List all credential metadata records.
|
|
339
370
|
*/
|
|
340
371
|
list(): StaticCredentialRecord[] {
|
|
341
|
-
const result =
|
|
372
|
+
const result = this.load();
|
|
342
373
|
if (isUnknownVersion(result)) return [];
|
|
343
374
|
return result.credentials;
|
|
344
375
|
}
|
|
@@ -347,7 +378,7 @@ export class StaticCredentialMetadataStore {
|
|
|
347
378
|
* Delete metadata for a credential.
|
|
348
379
|
*/
|
|
349
380
|
delete(service: string, field: string): boolean {
|
|
350
|
-
const result =
|
|
381
|
+
const result = this.load();
|
|
351
382
|
if (isUnknownVersion(result)) {
|
|
352
383
|
throw new Error(
|
|
353
384
|
"Credential metadata file has an unrecognized version; refusing to mutate to avoid data loss"
|
|
@@ -359,7 +390,7 @@ export class StaticCredentialMetadataStore {
|
|
|
359
390
|
);
|
|
360
391
|
if (idx === -1) return false;
|
|
361
392
|
data.credentials.splice(idx, 1);
|
|
362
|
-
|
|
393
|
+
this.persist(data);
|
|
363
394
|
return true;
|
|
364
395
|
}
|
|
365
396
|
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { mkdirSync, mkdtempSync, rmSync, symlinkSync } from "node:fs";
|
|
2
|
+
import { homedir, tmpdir } from "node:os";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { describe, expect, test } from "bun:test";
|
|
5
|
+
|
|
6
|
+
import {
|
|
7
|
+
assertTestPathIsEphemeral,
|
|
8
|
+
canonicalizePathThroughExistingParent,
|
|
9
|
+
isBunTestProcess,
|
|
10
|
+
} from "../test-path-guard.js";
|
|
11
|
+
|
|
12
|
+
const OPTIONS = {
|
|
13
|
+
allowEnvVar: "TEST_PATH_GUARD_ALLOW",
|
|
14
|
+
runHint: "Run tests from the package root.",
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
describe("isBunTestProcess", () => {
|
|
18
|
+
test("detects this bun test run", () => {
|
|
19
|
+
expect(isBunTestProcess()).toBe(true);
|
|
20
|
+
});
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
describe("canonicalizePathThroughExistingParent", () => {
|
|
24
|
+
test("resolves symlinks on the deepest existing ancestor", () => {
|
|
25
|
+
const root = mkdtempSync(join(tmpdir(), "test-path-guard-"));
|
|
26
|
+
try {
|
|
27
|
+
const target = join(root, "target");
|
|
28
|
+
mkdirSync(target);
|
|
29
|
+
const link = join(root, "link");
|
|
30
|
+
symlinkSync(target, link, "dir");
|
|
31
|
+
expect(
|
|
32
|
+
canonicalizePathThroughExistingParent(join(link, "missing", "leaf")),
|
|
33
|
+
).toBe(
|
|
34
|
+
join(canonicalizePathThroughExistingParent(target), "missing", "leaf"),
|
|
35
|
+
);
|
|
36
|
+
} finally {
|
|
37
|
+
rmSync(root, { recursive: true, force: true });
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
test("returns the input path when no ancestor exists", () => {
|
|
42
|
+
expect(canonicalizePathThroughExistingParent("/nonexistent-root-x/y")).toBe(
|
|
43
|
+
"/nonexistent-root-x/y",
|
|
44
|
+
);
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
describe("assertTestPathIsEphemeral", () => {
|
|
49
|
+
test("allows paths under tmpdir, existing or not", () => {
|
|
50
|
+
expect(() =>
|
|
51
|
+
assertTestPathIsEphemeral(join(tmpdir(), "guard-missing", "x"), OPTIONS),
|
|
52
|
+
).not.toThrow();
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
test("refuses paths outside tmpdir, naming the escape hatch and hint", () => {
|
|
56
|
+
delete process.env[OPTIONS.allowEnvVar];
|
|
57
|
+
const dir = join(homedir(), "test-path-guard-live");
|
|
58
|
+
expect(() => assertTestPathIsEphemeral(dir, OPTIONS)).toThrow(
|
|
59
|
+
/Refusing to use[\s\S]*package root[\s\S]*TEST_PATH_GUARD_ALLOW/,
|
|
60
|
+
);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
test("refuses a tmpdir symlink that resolves outside tmpdir", () => {
|
|
64
|
+
delete process.env[OPTIONS.allowEnvVar];
|
|
65
|
+
const link = join(tmpdir(), `test-path-guard-escape-${process.pid}`);
|
|
66
|
+
rmSync(link, { force: true });
|
|
67
|
+
symlinkSync(homedir(), link);
|
|
68
|
+
try {
|
|
69
|
+
expect(() => assertTestPathIsEphemeral(link, OPTIONS)).toThrow(
|
|
70
|
+
/Refusing to use/,
|
|
71
|
+
);
|
|
72
|
+
} finally {
|
|
73
|
+
rmSync(link, { force: true });
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
test("the allow env var bypasses the guard, per call", () => {
|
|
78
|
+
const dir = join(homedir(), "test-path-guard-optout");
|
|
79
|
+
process.env[OPTIONS.allowEnvVar] = "1";
|
|
80
|
+
try {
|
|
81
|
+
expect(() => assertTestPathIsEphemeral(dir, OPTIONS)).not.toThrow();
|
|
82
|
+
} finally {
|
|
83
|
+
delete process.env[OPTIONS.allowEnvVar];
|
|
84
|
+
}
|
|
85
|
+
expect(() => assertTestPathIsEphemeral(dir, OPTIONS)).toThrow(
|
|
86
|
+
/Refusing to use/,
|
|
87
|
+
);
|
|
88
|
+
});
|
|
89
|
+
});
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Live-path guard for test processes, shared by every service that resolves
|
|
3
|
+
* install-layout paths (the assistant daemon's workspace/root, the gateway's
|
|
4
|
+
* workspace and security dirs).
|
|
5
|
+
*
|
|
6
|
+
* A test process must never resolve one of those paths to a real, non-temp
|
|
7
|
+
* directory: production code exercised by a test would then read and
|
|
8
|
+
* destructively write live state. The tmpdir redirection normally comes from
|
|
9
|
+
* each package's bunfig.toml test preload, but bun only loads bunfig from
|
|
10
|
+
* the cwd, so `bun test` run from any other directory skips the preload and
|
|
11
|
+
* inherits the ambient env (or a ~/.vellum fallback). The containment
|
|
12
|
+
* assertion therefore lives in production code, where it fires no matter how
|
|
13
|
+
* the test process was launched.
|
|
14
|
+
*
|
|
15
|
+
* assistant/src/__tests__/assert-not-live-db.ts keeps its own containment
|
|
16
|
+
* check: test machinery must not import production dependencies.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import { realpathSync } from "node:fs";
|
|
20
|
+
import { tmpdir } from "node:os";
|
|
21
|
+
import { basename, dirname, resolve, sep } from "node:path";
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Resolve symlinks in the deepest existing ancestor of `path`, then
|
|
25
|
+
* re-append the not-yet-created tail. This keeps containment checks honest
|
|
26
|
+
* both for paths under a symlinked temp root (macOS /var/folders) and for
|
|
27
|
+
* symlinks that point outside it, whether or not the leaf exists yet.
|
|
28
|
+
*/
|
|
29
|
+
export function canonicalizePathThroughExistingParent(path: string): string {
|
|
30
|
+
const resolvedPath = resolve(path);
|
|
31
|
+
const pendingSegments: string[] = [];
|
|
32
|
+
let currentPath = resolvedPath;
|
|
33
|
+
|
|
34
|
+
while (true) {
|
|
35
|
+
try {
|
|
36
|
+
return resolve(realpathSync(currentPath), ...pendingSegments.reverse());
|
|
37
|
+
} catch {
|
|
38
|
+
const parentPath = dirname(currentPath);
|
|
39
|
+
if (parentPath === currentPath) {
|
|
40
|
+
return resolvedPath;
|
|
41
|
+
}
|
|
42
|
+
pendingSegments.push(basename(currentPath));
|
|
43
|
+
currentPath = parentPath;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** Lazily computed: is this process a `bun test` run? */
|
|
49
|
+
let isTestProcess: boolean | undefined;
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* True when this process is a `bun test` run. Computed once and cached for
|
|
53
|
+
* the process lifetime, so a runtime flip of NODE_ENV or BUN_TEST does not
|
|
54
|
+
* change the answer. Test-mode behavior toggles that must observe such
|
|
55
|
+
* flips read the env directly instead of calling this.
|
|
56
|
+
*/
|
|
57
|
+
export function isBunTestProcess(): boolean {
|
|
58
|
+
isTestProcess ??=
|
|
59
|
+
process.env.NODE_ENV === "test" ||
|
|
60
|
+
process.env.BUN_TEST === "1" ||
|
|
61
|
+
// `bun test` sets NODE_ENV=test only when unset; Bun.main being the test
|
|
62
|
+
// file itself is the backstop signal that survives a preset NODE_ENV.
|
|
63
|
+
(typeof Bun !== "undefined" &&
|
|
64
|
+
/\.(test|spec)\.[cm]?[jt]sx?$/.test(Bun.main));
|
|
65
|
+
return isTestProcess;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export interface EphemeralTestPathOptions {
|
|
69
|
+
/**
|
|
70
|
+
* Escape hatch for the rare intentional run against real state: setting
|
|
71
|
+
* this env var to "1" bypasses the guard. Callers reuse the variable their
|
|
72
|
+
* DB-open guard already honors. Deliberately not forwarded into
|
|
73
|
+
* agent-spawned shells (assistant/src/tools/terminal/safe-env.ts), so a
|
|
74
|
+
* daemon-level opt-out cannot disarm the guard for tests run from there.
|
|
75
|
+
*/
|
|
76
|
+
allowEnvVar: string;
|
|
77
|
+
/** Package-specific line telling the reader where to run tests from. */
|
|
78
|
+
runHint: string;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Throws when called in a bun-test process and `dir` does not resolve under
|
|
83
|
+
* `os.tmpdir()`. Inert outside test processes. Canonicalizes on every call
|
|
84
|
+
* (no memo): a previously validated path could later be replaced by a
|
|
85
|
+
* symlink, and the guard only runs in test processes anyway.
|
|
86
|
+
*/
|
|
87
|
+
export function assertTestPathIsEphemeral(
|
|
88
|
+
dir: string,
|
|
89
|
+
options: EphemeralTestPathOptions,
|
|
90
|
+
): void {
|
|
91
|
+
if (!isBunTestProcess()) {
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
if (process.env[options.allowEnvVar] === "1") {
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
const tmpRoot = canonicalizePathThroughExistingParent(tmpdir());
|
|
98
|
+
const resolved = canonicalizePathThroughExistingParent(dir);
|
|
99
|
+
if (resolved !== tmpRoot && !resolved.startsWith(tmpRoot + sep)) {
|
|
100
|
+
throw new Error(
|
|
101
|
+
[
|
|
102
|
+
`Refusing to use ${dir} (resolves to ${resolved}) in a test process: it is not under the temp directory (${tmpRoot}).`,
|
|
103
|
+
"",
|
|
104
|
+
"Tests must only touch ephemeral state; a real directory would expose",
|
|
105
|
+
"live install state to destructive test fixtures. This usually means",
|
|
106
|
+
"`bun test` ran from a cwd without the package's bunfig.toml, so the",
|
|
107
|
+
"test preload that redirects paths to a tmpdir never loaded.",
|
|
108
|
+
options.runHint,
|
|
109
|
+
`Set ${options.allowEnvVar}=1 to bypass deliberately.`,
|
|
110
|
+
].join("\n"),
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
}
|
package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/package.json
CHANGED
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
"./client-metadata": "./src/client-metadata.ts",
|
|
12
12
|
"./credential-rpc": "./src/credential-rpc.ts",
|
|
13
13
|
"./ingress": "./src/ingress.ts",
|
|
14
|
+
"./no-response": "./src/no-response.ts",
|
|
14
15
|
"./remote-web-pairing": "./src/remote-web-pairing.ts",
|
|
15
16
|
"./twilio-ingress": "./src/twilio-ingress.ts",
|
|
16
17
|
"./trust-rules": "./src/trust-rules.ts",
|
|
@@ -66,6 +66,13 @@ export function isChannelId(value: unknown): value is ChannelId {
|
|
|
66
66
|
* That irregularity is why this is stated rather than derived from the key,
|
|
67
67
|
* and it is stated here because this file already owns what a channel is.
|
|
68
68
|
*
|
|
69
|
+
* Both senses can hold a user token, which is the sharpest edge. The `slack`
|
|
70
|
+
* integration's persisted token *is* the installer's user token, held on its
|
|
71
|
+
* OAuth connection. `slack_channel` holds an optional `user_token` in the
|
|
72
|
+
* credential store, beside its bot and app tokens. Same words, different
|
|
73
|
+
* homes, and only the second is a credential-store key: a pasted token is
|
|
74
|
+
* always the channel's, because the integration's never leaves the exchange.
|
|
75
|
+
*
|
|
69
76
|
* Deliberately only the key. What fields each credential requires is declared
|
|
70
77
|
* once already, per service, in the gateway's credential specs; restating it
|
|
71
78
|
* here would be a second copy of a different fact.
|
package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/index.ts
CHANGED
|
@@ -27,6 +27,7 @@ export * from "./handles.js";
|
|
|
27
27
|
export * from "./rpc.js";
|
|
28
28
|
export * from "./trust-rules.js";
|
|
29
29
|
export * from "./ingress.js";
|
|
30
|
+
export * from "./no-response.js";
|
|
30
31
|
export * from "./remote-web-pairing.js";
|
|
31
32
|
export * from "./twilio-ingress.js";
|
|
32
33
|
export * from "./url-normalization.js";
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The `<no_response/>` sentinel: the cross-service convention for a turn
|
|
3
|
+
* that deliberately produces no user-visible reply.
|
|
4
|
+
*
|
|
5
|
+
* The model emits it, the daemon stamps and strips it, channel delivery
|
|
6
|
+
* suppresses it, and clients hold live-streamed prefixes of it back from
|
|
7
|
+
* display. One definition here keeps every consumer's parsing identical;
|
|
8
|
+
* a hand-rolled copy in any one of them is how the case-sensitivity of a
|
|
9
|
+
* regex drifts.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
/** Matches a message whose entire content is the sentinel. */
|
|
13
|
+
const NO_RESPONSE_ONLY_RE = /^\s*<no_response\s*\/?>\s*$/i;
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Whether `text` is nothing but the sentinel: the whole reply is a
|
|
17
|
+
* deliberate non-response, as opposed to real content with an inline
|
|
18
|
+
* sentinel mixed in.
|
|
19
|
+
*/
|
|
20
|
+
export function isNoResponseOnlyText(text: string): boolean {
|
|
21
|
+
return NO_RESPONSE_ONLY_RE.test(text);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** Matches every sentinel occurrence for stripping it out of mixed content. */
|
|
25
|
+
export const NO_RESPONSE_INLINE_RE = /<no_response\s*\/?>/gi;
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Detection variant without the `g` flag: a `g`-flagged regex is stateful
|
|
29
|
+
* under `.test()` (it resumes from `lastIndex`), so reusing
|
|
30
|
+
* {@link NO_RESPONSE_INLINE_RE} for detection would alternate between
|
|
31
|
+
* matches and misses across calls.
|
|
32
|
+
*/
|
|
33
|
+
const NO_RESPONSE_MARKER_RE = new RegExp(NO_RESPONSE_INLINE_RE.source, "i");
|
|
34
|
+
|
|
35
|
+
/** Whether `text` contains the sentinel anywhere. */
|
|
36
|
+
export function containsNoResponseMarker(text: string): boolean {
|
|
37
|
+
return NO_RESPONSE_MARKER_RE.test(text);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** Removes every sentinel occurrence, trimming the leftover whitespace. */
|
|
41
|
+
export function stripNoResponseMarkers(text: string): string {
|
|
42
|
+
return text.replace(NO_RESPONSE_INLINE_RE, "").trim();
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const NO_RESPONSE_SENTINEL_FORMS = [
|
|
46
|
+
"<no_response/>",
|
|
47
|
+
"<no_response />",
|
|
48
|
+
"<no_response>",
|
|
49
|
+
] as const;
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Whether `text` could still grow into the sentinel, i.e. it is a leading
|
|
53
|
+
* substring of one of its forms. Holding on these keeps a slowly-streamed
|
|
54
|
+
* `<no_response/>` from surfacing as visible partial content.
|
|
55
|
+
*/
|
|
56
|
+
export function isPotentialNoResponsePrefix(text: string): boolean {
|
|
57
|
+
const lower = text.trim().toLowerCase();
|
|
58
|
+
if (lower.length === 0) {
|
|
59
|
+
return false;
|
|
60
|
+
}
|
|
61
|
+
return NO_RESPONSE_SENTINEL_FORMS.some((sentinel) =>
|
|
62
|
+
sentinel.startsWith(lower),
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Whether `text` carries user-visible content worth acting on now. Returns
|
|
68
|
+
* `false` for empty text, the standalone sentinel, and any prefix that could
|
|
69
|
+
* still complete into one; returns `true` once real content remains after
|
|
70
|
+
* stripping inline sentinels.
|
|
71
|
+
*/
|
|
72
|
+
export function hasDeliverableAssistantText(text: string): boolean {
|
|
73
|
+
const trimmed = text.trim();
|
|
74
|
+
if (trimmed.length === 0) {
|
|
75
|
+
return false;
|
|
76
|
+
}
|
|
77
|
+
if (NO_RESPONSE_ONLY_RE.test(trimmed)) {
|
|
78
|
+
return false;
|
|
79
|
+
}
|
|
80
|
+
if (isPotentialNoResponsePrefix(trimmed)) {
|
|
81
|
+
return false;
|
|
82
|
+
}
|
|
83
|
+
return stripNoResponseMarkers(trimmed).length > 0;
|
|
84
|
+
}
|
package/node_modules/@vellumai/gateway-client/src/__tests__/guardian-request-contract.test.ts
CHANGED
|
@@ -31,8 +31,7 @@ import {
|
|
|
31
31
|
ListGuardianRequestsIpcParamsSchema,
|
|
32
32
|
ListPendingGuardianRequestsByDestinationIpcParamsSchema,
|
|
33
33
|
ListPendingGuardianRequestsByScopeIpcParamsSchema,
|
|
34
|
-
|
|
35
|
-
SweepExpiredGuardianRequestsIpcResponseSchema,
|
|
34
|
+
ListExpiredPendingGuardianRequestsIpcParamsSchema,
|
|
36
35
|
UpdateGuardianRequestIpcParamsSchema,
|
|
37
36
|
type GuardianRequestWire,
|
|
38
37
|
} from "../guardian-request-contract.js";
|
|
@@ -439,29 +438,20 @@ describe("decide IPC schemas", () => {
|
|
|
439
438
|
});
|
|
440
439
|
|
|
441
440
|
describe("expiry IPC schemas", () => {
|
|
442
|
-
test("expire and
|
|
441
|
+
test("expire and expired-pending list round-trip", () => {
|
|
443
442
|
expect(ExpireGuardianRequestIpcParamsSchema.parse({ id: "req-1" })).toEqual(
|
|
444
443
|
{ id: "req-1" },
|
|
445
444
|
);
|
|
446
445
|
|
|
447
446
|
expect(
|
|
448
|
-
|
|
447
|
+
ListExpiredPendingGuardianRequestsIpcParamsSchema.parse({
|
|
449
448
|
now: 1_700_000_000_000,
|
|
449
|
+
limit: 50,
|
|
450
450
|
}),
|
|
451
|
-
).toEqual({ now: 1_700_000_000_000 });
|
|
452
|
-
expect(
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
expect(
|
|
456
|
-
SweepExpiredGuardianRequestsIpcResponseSchema.parse({
|
|
457
|
-
expired: [expiredRow],
|
|
458
|
-
}),
|
|
459
|
-
).toEqual({ expired: [expiredRow] });
|
|
460
|
-
expect(() =>
|
|
461
|
-
SweepExpiredGuardianRequestsIpcResponseSchema.parse({
|
|
462
|
-
expired: ["req-1"],
|
|
463
|
-
}),
|
|
464
|
-
).toThrow();
|
|
451
|
+
).toEqual({ now: 1_700_000_000_000, limit: 50 });
|
|
452
|
+
expect(ListExpiredPendingGuardianRequestsIpcParamsSchema.parse({})).toEqual(
|
|
453
|
+
{},
|
|
454
|
+
);
|
|
465
455
|
|
|
466
456
|
expect(
|
|
467
457
|
ExpireInteractionBoundIpcResponseSchema.parse({ expired: 3 }),
|
|
@@ -9,6 +9,7 @@ import { describe, expect, test } from "bun:test";
|
|
|
9
9
|
import {
|
|
10
10
|
inboundEventRefersToAnotherMessage,
|
|
11
11
|
resolveInboundEventKind,
|
|
12
|
+
resolveInboundReactionPayload,
|
|
12
13
|
} from "../inbound-event-kind.js";
|
|
13
14
|
|
|
14
15
|
describe("resolveInboundEventKind", () => {
|
|
@@ -80,3 +81,48 @@ describe("inboundEventRefersToAnotherMessage", () => {
|
|
|
80
81
|
expect(inboundEventRefersToAnotherMessage("button")).toBe(true);
|
|
81
82
|
});
|
|
82
83
|
});
|
|
84
|
+
|
|
85
|
+
describe("resolveInboundReactionPayload", () => {
|
|
86
|
+
test("a structured payload wins outright", () => {
|
|
87
|
+
expect(
|
|
88
|
+
resolveInboundReactionPayload({
|
|
89
|
+
eventKind: "reaction",
|
|
90
|
+
reaction: { op: "removed", emoji: "+1", targetMessageId: "11.22" },
|
|
91
|
+
callbackData: "reaction:eyes",
|
|
92
|
+
}),
|
|
93
|
+
).toEqual({ op: "removed", emoji: "+1", targetMessageId: "11.22" });
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
test("a replayed payload parses its sentinel and wire target", () => {
|
|
97
|
+
expect(
|
|
98
|
+
resolveInboundReactionPayload({
|
|
99
|
+
callbackData: "reaction:thumbsup",
|
|
100
|
+
sourceMetadata: { messageId: "33.44" },
|
|
101
|
+
}),
|
|
102
|
+
).toEqual({ op: "added", emoji: "thumbsup", targetMessageId: "33.44" });
|
|
103
|
+
expect(
|
|
104
|
+
resolveInboundReactionPayload({
|
|
105
|
+
callbackData: "reaction_removed:thumbsup",
|
|
106
|
+
sourceMetadata: { messageId: "33.44" },
|
|
107
|
+
}),
|
|
108
|
+
).toEqual({ op: "removed", emoji: "thumbsup", targetMessageId: "33.44" });
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
test("no emoji or no target is not a reaction payload", () => {
|
|
112
|
+
expect(
|
|
113
|
+
resolveInboundReactionPayload({
|
|
114
|
+
callbackData: "reaction:",
|
|
115
|
+
sourceMetadata: { messageId: "33.44" },
|
|
116
|
+
}),
|
|
117
|
+
).toBeNull();
|
|
118
|
+
expect(
|
|
119
|
+
resolveInboundReactionPayload({ callbackData: "reaction:thumbsup" }),
|
|
120
|
+
).toBeNull();
|
|
121
|
+
expect(
|
|
122
|
+
resolveInboundReactionPayload({
|
|
123
|
+
callbackData: "apr:req-1:approve_once",
|
|
124
|
+
sourceMetadata: { messageId: "33.44" },
|
|
125
|
+
}),
|
|
126
|
+
).toBeNull();
|
|
127
|
+
});
|
|
128
|
+
});
|
|
@@ -94,6 +94,7 @@ describe("isValidE164", () => {
|
|
|
94
94
|
describe("invite-code channel gating", () => {
|
|
95
95
|
test("allows exactly the redemption-enabled channels", () => {
|
|
96
96
|
expect([...INVITE_CODE_REDEMPTION_CHANNELS].sort()).toEqual([
|
|
97
|
+
"discord",
|
|
97
98
|
"email",
|
|
98
99
|
"slack",
|
|
99
100
|
"telegram",
|
|
@@ -355,6 +355,52 @@ export type GetGuardianContactIpcResponse = z.infer<
|
|
|
355
355
|
typeof GetGuardianContactIpcResponseSchema
|
|
356
356
|
>;
|
|
357
357
|
|
|
358
|
+
// ── contacts_identity_snapshot ───────────────────────────────────────────────
|
|
359
|
+
// Lean identity projection of every gateway contact + channel, for the
|
|
360
|
+
// daemon's mirror reconciler: enough to converge the assistant identity
|
|
361
|
+
// mirror onto gateway truth (ids, ownership, addresses), nothing more. No
|
|
362
|
+
// ACL columns (the mirror never carries them) and no assistant-info join
|
|
363
|
+
// (the daemon IS the assistant), so the read is uncapped and cheap.
|
|
364
|
+
|
|
365
|
+
export const ContactsIdentitySnapshotIpcParamsSchema = z
|
|
366
|
+
.object({})
|
|
367
|
+
.strict()
|
|
368
|
+
.default({});
|
|
369
|
+
|
|
370
|
+
export type ContactsIdentitySnapshotIpcParams = z.infer<
|
|
371
|
+
typeof ContactsIdentitySnapshotIpcParamsSchema
|
|
372
|
+
>;
|
|
373
|
+
|
|
374
|
+
export const ContactIdentityChannelSchema = z.object({
|
|
375
|
+
id: z.string(),
|
|
376
|
+
contactId: z.string(),
|
|
377
|
+
type: z.string(),
|
|
378
|
+
address: z.string(),
|
|
379
|
+
externalChatId: z.string().nullable(),
|
|
380
|
+
isPrimary: z.boolean(),
|
|
381
|
+
});
|
|
382
|
+
|
|
383
|
+
export type ContactIdentityChannel = z.infer<
|
|
384
|
+
typeof ContactIdentityChannelSchema
|
|
385
|
+
>;
|
|
386
|
+
|
|
387
|
+
export const ContactIdentitySchema = z.object({
|
|
388
|
+
id: z.string(),
|
|
389
|
+
displayName: z.string(),
|
|
390
|
+
channels: z.array(ContactIdentityChannelSchema),
|
|
391
|
+
});
|
|
392
|
+
|
|
393
|
+
export type ContactIdentity = z.infer<typeof ContactIdentitySchema>;
|
|
394
|
+
|
|
395
|
+
export const ContactsIdentitySnapshotIpcResponseSchema = z.object({
|
|
396
|
+
ok: z.boolean(),
|
|
397
|
+
contacts: z.array(ContactIdentitySchema),
|
|
398
|
+
});
|
|
399
|
+
|
|
400
|
+
export type ContactsIdentitySnapshotIpcResponse = z.infer<
|
|
401
|
+
typeof ContactsIdentitySnapshotIpcResponseSchema
|
|
402
|
+
>;
|
|
403
|
+
|
|
358
404
|
// ── classify_risk ────────────────────────────────────────────────────────────
|
|
359
405
|
// Risk classification is gateway-owned; the assistant sends one request per
|
|
360
406
|
// tool invocation and reads the whole answer back. The gateway validates the
|