@vellumai/assistant 0.11.4 → 0.11.5-dev.202608220019.a778cf0
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/AGENTS.md +6 -2
- package/ARCHITECTURE.md +29 -4
- package/README.md +1 -1
- package/docs/architecture/memory.md +17 -5
- package/docs/architecture/security.md +96 -152
- package/docs/guardian-request-flow.md +13 -2
- 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/__tests__/ingress.test.ts +118 -0
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/__tests__/url-normalization.test.ts +135 -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/ingress.ts +103 -0
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/url-normalization.ts +107 -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/__tests__/ingress.test.ts +118 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/__tests__/url-normalization.test.ts +135 -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/ingress.ts +103 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/url-normalization.ts +107 -0
- package/node_modules/@vellumai/gateway-client/src/gateway-ipc-contracts.ts +166 -1
- package/node_modules/@vellumai/gateway-client/src/http-delivery.ts +7 -14
- package/node_modules/@vellumai/gateway-client/src/index.ts +2 -0
- package/node_modules/@vellumai/gateway-client/src/outbound-contract.ts +30 -32
- package/node_modules/@vellumai/service-contracts/package.json +1 -0
- package/node_modules/@vellumai/service-contracts/src/__tests__/ingress.test.ts +118 -0
- package/node_modules/@vellumai/service-contracts/src/__tests__/url-normalization.test.ts +135 -0
- package/node_modules/@vellumai/service-contracts/src/index.ts +1 -0
- package/node_modules/@vellumai/service-contracts/src/ingress.ts +103 -0
- package/node_modules/@vellumai/service-contracts/src/url-normalization.ts +107 -0
- package/openapi.yaml +487 -11
- package/package.json +1 -1
- package/scripts/sync-web-search-catalog.ts +6 -0
- package/src/__tests__/anthropic-provider.test.ts +10 -3
- package/src/__tests__/approval-interception-trust-gates.test.ts +40 -0
- package/src/__tests__/background-workers-disk-pressure.test.ts +1 -1
- package/src/__tests__/channel-approval-routes.test.ts +4 -0
- package/src/__tests__/channel-inbound-disk-pressure.test.ts +5 -6
- package/src/__tests__/channel-readiness-service.test.ts +126 -0
- package/src/__tests__/channel-reply-delivery.test.ts +30 -17
- package/src/__tests__/checker.test.ts +20 -314
- package/src/__tests__/cli-memory-v2-reembed-skills.test.ts +6 -2
- package/src/__tests__/client-os-metadata-persistence.test.ts +16 -0
- package/src/__tests__/compactor-retained-image-validation.test.ts +142 -0
- package/src/__tests__/config-loader-backfill.test.ts +19 -10
- package/src/__tests__/container-cpu-sampler.test.ts +153 -0
- package/src/__tests__/context-search-memory-v2-source.test.ts +0 -1
- package/src/__tests__/conversation-attachments.test.ts +0 -1
- package/src/__tests__/conversation-delete-watch-timeline.test.ts +231 -0
- package/src/__tests__/conversation-error.test.ts +17 -0
- package/src/__tests__/conversation-lifecycle.test.ts +20 -26
- package/src/__tests__/conversation-pairing.test.ts +186 -0
- package/src/__tests__/conversation-runtime-assembly.test.ts +21 -2
- package/src/__tests__/credential-broker-server-use.test.ts +24 -5
- package/src/__tests__/credential-routes.test.ts +22 -3
- package/src/__tests__/credential-security-invariants.test.ts +2 -1
- package/src/__tests__/daemon-credential-client.test.ts +88 -0
- package/src/__tests__/default-plugin-names-guard.test.ts +12 -1
- package/src/__tests__/dm-backfill.test.ts +63 -0
- package/src/__tests__/dynamic-skill-background-guard.test.ts +0 -2
- package/src/__tests__/edit-propagation.test.ts +107 -4
- package/src/__tests__/events-client-registration.test.ts +28 -0
- package/src/__tests__/file-write-tool.test.ts +4 -2
- package/src/__tests__/guardian-prompt-notice-privacy.test.ts +133 -0
- package/src/__tests__/guardian-verify-setup-skill-regression.test.ts +143 -97
- package/src/__tests__/helpers/gateway-classify-mock.ts +27 -6
- package/src/__tests__/host-proxy-interface.test.ts +11 -1
- package/src/__tests__/host-shell-tool.test.ts +23 -4
- package/src/__tests__/image-conversion.test.ts +143 -1
- package/src/__tests__/inline-skill-load-permissions.test.ts +47 -36
- package/src/__tests__/input-repairs.test.ts +207 -0
- package/src/__tests__/live-workspace-guard.test.ts +75 -0
- package/src/__tests__/managed-profile-guard.test.ts +4 -2
- package/src/__tests__/mcp-abort-signal.test.ts +1 -1
- package/src/__tests__/mcp-client-auth.test.ts +1 -1
- package/src/__tests__/mcp-tool-annotations-risk.test.ts +1 -1
- package/src/__tests__/media-resolve-image-validation.test.ts +310 -0
- package/src/__tests__/mtime-cache.test.ts +2 -0
- package/src/__tests__/notification-vellum-adapter.test.ts +124 -1
- package/src/__tests__/openai-provider.test.ts +40 -0
- package/src/__tests__/openai-responses-provider.test.ts +18 -0
- package/src/__tests__/personal-memory-auth-bypass.test.ts +22 -6
- package/src/__tests__/platform-bash-auto-approve.test.ts +0 -4
- package/src/__tests__/platform.test.ts +16 -1
- package/src/__tests__/plugin-api-resolve-credential.test.ts +22 -13
- package/src/__tests__/plugin-api-shim.test.ts +5 -0
- package/src/__tests__/plugin-api-store-credential.test.ts +268 -0
- package/src/__tests__/plugin-config-data-migration.test.ts +8 -1
- package/src/__tests__/plugin-disabled-state.test.ts +2 -0
- package/src/__tests__/plugin-effective-enabled-set.test.ts +55 -3
- package/src/__tests__/plugin-execution-context.test.ts +73 -0
- package/src/__tests__/plugin-import-boundary-guard.test.ts +0 -1
- package/src/__tests__/plugin-import-boundary-reverse-guard.test.ts +0 -1
- package/src/__tests__/reaction-persistence.test.ts +200 -7
- package/src/__tests__/require-fresh-approval.test.ts +0 -4
- package/src/__tests__/resource-pressure-guard.test.ts +428 -0
- package/src/__tests__/resource-pressure-routes.test.ts +113 -0
- package/src/__tests__/risk-classification-boundary-guard.test.ts +115 -0
- package/src/__tests__/run-conversation-turn-persistence.test.ts +27 -1
- package/src/__tests__/sanitize-config-for-transfer.test.ts +28 -0
- package/src/__tests__/secret-routes-platform-proxy.test.ts +7 -0
- package/src/__tests__/settings-routes.test.ts +85 -3
- package/src/__tests__/skill-tool-factory.test.ts +161 -0
- package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +1 -1
- package/src/__tests__/tool-executor-lifecycle-events.test.ts +132 -5
- package/src/__tests__/tool-executor.test.ts +63 -38
- package/src/__tests__/tool-policy.test.ts +97 -1
- package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +7 -4
- package/src/__tests__/user-plugin-loader.test.ts +2 -0
- package/src/__tests__/validate-input.test.ts +243 -7
- package/src/__tests__/verification-control-plane-policy.test.ts +0 -2
- package/src/__tests__/voice-scoped-grant-consumer.test.ts +2 -0
- package/src/__tests__/voice-session-bridge.test.ts +42 -0
- package/src/__tests__/web-search-catalog-parity.test.ts +8 -0
- package/src/acp/__tests__/acp-claude-oauth.test.ts +97 -29
- package/src/acp/__tests__/prepare-agent-env.test.ts +376 -153
- package/src/acp/acp-claude-oauth.ts +38 -21
- package/src/acp/prepare-agent-env.ts +146 -55
- package/src/agent/loop-tool-dedup.test.ts +161 -0
- package/src/agent/loop.ts +68 -22
- package/src/api/constants/app-tools.ts +34 -0
- package/src/api/constants/profile-config-validation.ts +60 -0
- package/src/api/events/resource-pressure-status-changed.ts +53 -0
- package/src/api/events/tool-result.ts +6 -1
- package/src/api/events/watch-retro-completed.ts +52 -0
- package/src/api/index.ts +30 -0
- package/src/api/responses/resource-pressure-status.ts +25 -0
- package/src/approvals/guardian-channel-delivery.ts +70 -6
- package/src/approvals/guardian-request-resolvers.ts +32 -66
- package/src/calls/__tests__/voice-session-bridge.test.ts +114 -0
- package/src/calls/voice-session-bridge.ts +60 -6
- package/src/channels/__tests__/message-audience.test.ts +49 -0
- package/src/channels/__tests__/types.test.ts +17 -3
- package/src/channels/message-audience.ts +40 -0
- package/src/channels/types.ts +16 -12
- package/src/cli/__tests__/catalog-search-help.test.ts +50 -0
- package/src/cli/commands/__tests__/channel-verification-sessions.test.ts +31 -0
- package/src/cli/commands/__tests__/conversations-search.test.ts +289 -0
- package/src/cli/commands/__tests__/conversations-slack.test.ts +2 -1
- package/src/cli/commands/__tests__/inference-profiles.test.ts +16 -4
- package/src/cli/commands/__tests__/inference-providers.test.ts +135 -2
- package/src/cli/commands/__tests__/keys.test.ts +89 -8
- package/src/cli/commands/__tests__/plugins.test.ts +57 -1
- package/src/cli/commands/channel-verification-sessions.help.ts +11 -9
- package/src/cli/commands/channel-verification-sessions.ts +11 -21
- package/src/cli/commands/conversations.help.ts +34 -0
- package/src/cli/commands/conversations.ts +125 -0
- package/src/cli/commands/inference-profiles.ts +56 -3
- package/src/cli/commands/inference-providers.ts +37 -6
- package/src/cli/commands/inference.help.ts +9 -4
- package/src/cli/commands/keys.help.ts +13 -1
- package/src/cli/commands/keys.ts +26 -15
- package/src/cli/commands/memory/__tests__/memory-v2.test.ts +57 -7
- package/src/cli/commands/memory/index.help.ts +41 -27
- package/src/cli/commands/memory/index.ts +2 -0
- package/src/cli/commands/memory/memory-v2.ts +58 -54
- package/src/cli/commands/memory/memory-validate.ts +18 -0
- package/src/cli/commands/monitoring.ts +1 -0
- package/src/cli/commands/oauth/request.test.ts +93 -0
- package/src/cli/commands/oauth/request.ts +23 -9
- package/src/cli/commands/plugins.help.ts +28 -31
- package/src/cli/commands/plugins.ts +10 -0
- package/src/cli/commands/skills.help.ts +13 -16
- package/src/cli/commands/trust.ts +3 -14
- package/src/cli/lib/__tests__/install-from-github.test.ts +38 -0
- package/src/cli/lib/__tests__/merge-plugin-tree.test.ts +34 -0
- package/src/cli/lib/__tests__/plugin-surfaces.test.ts +32 -1
- package/src/cli/lib/__tests__/toggle-plugin.test.ts +2 -1
- package/src/cli/lib/__tests__/upgrade-plugin.test.ts +64 -0
- package/src/cli/lib/bundled-marketplace.json +26 -0
- package/src/cli/lib/daemon-credential-client.ts +25 -3
- package/src/cli/lib/install-from-github.ts +35 -24
- package/src/cli/lib/merge-plugin-tree.ts +22 -4
- package/src/cli/lib/open-browser.test.ts +67 -0
- package/src/cli/lib/open-browser.ts +24 -5
- package/src/cli/lib/plugin-surfaces.ts +27 -0
- package/src/config/__tests__/balanced-model-experiment.test.ts +7 -7
- package/src/config/__tests__/default-profile-catalog.test.ts +3 -3
- package/src/config/__tests__/profile-materialization.test.ts +26 -0
- package/src/config/default-profile-catalog.ts +5 -6
- package/src/config/feature-flag-registry.json +36 -28
- package/src/config/profile-materialization.ts +29 -0
- package/src/config/sanitize-for-transfer.ts +16 -0
- package/src/config/schemas/llm.ts +7 -0
- package/src/config/schemas/services.ts +6 -0
- package/src/config/webhook-routing.ts +8 -0
- package/src/context/compactor.ts +31 -2
- package/src/context/outbound-sanitize.ts +6 -0
- package/src/daemon/__tests__/lifecycle-watch-timeline-sweep.test.ts +98 -0
- package/src/daemon/__tests__/provider-rejection-log-fields.test.ts +272 -0
- package/src/daemon/conversation-agent-loop-handlers.ts +4 -1
- package/src/daemon/conversation-error.ts +19 -2
- package/src/daemon/conversation-messaging.ts +22 -2
- package/src/daemon/conversation-process.ts +11 -0
- package/src/daemon/conversation-store.ts +10 -0
- package/src/daemon/conversation-surfaces.ts +21 -8
- package/src/daemon/conversation.ts +12 -10
- package/src/daemon/daemon-control.ts +1 -0
- package/src/daemon/handlers/__tests__/config-ingress-tunnel-records.test.ts +208 -0
- package/src/daemon/handlers/config-ingress.ts +115 -5
- package/src/daemon/lifecycle.ts +30 -2
- package/src/daemon/message-types/conversations.ts +5 -7
- package/src/daemon/message-types/web-activity.ts +3 -2
- package/src/daemon/provider-rejection-log-fields.ts +124 -0
- package/src/daemon/resource-pressure-guard-lifecycle.ts +66 -0
- package/src/daemon/resource-pressure-guard.ts +387 -0
- package/src/daemon/shutdown-handlers.ts +2 -0
- package/src/daemon/startup-error.ts +16 -0
- package/src/daemon/trust-context.ts +14 -10
- package/src/daemon/unsendable-image-notice.ts +76 -0
- package/src/hooks/registry.ts +65 -51
- package/src/inbound/__tests__/tunnel-probe.test.ts +448 -0
- package/src/inbound/tunnel-probe.ts +261 -0
- package/src/ipc/__tests__/socket-path.test.ts +15 -7
- package/src/ipc/gateway-client.test.ts +1 -1
- package/src/ipc/gateway-client.ts +14 -21
- package/src/ipc/socket-cleanup.ts +2 -11
- package/src/mcp/__tests__/mcp-auth-orchestrator.test.ts +1 -1
- package/src/messaging/provider-message-metadata.ts +128 -0
- package/src/messaging/providers/__tests__/transport-dispatch.test.ts +138 -73
- package/src/messaging/providers/channel-transport.ts +100 -16
- package/src/messaging/providers/discord/send.ts +16 -0
- package/src/messaging/providers/discord/transport.ts +23 -1
- package/src/messaging/providers/index.ts +84 -16
- package/src/messaging/providers/slack/message-metadata.ts +45 -0
- package/src/messaging/providers/slack/render-transcript.ts +1 -4
- package/src/messaging/providers/slack/send.test.ts +88 -34
- package/src/messaging/providers/slack/send.ts +99 -50
- package/src/messaging/providers/slack/transport.ts +35 -37
- package/src/messaging/providers/telegram-bot/send.test.ts +88 -18
- package/src/messaging/providers/telegram-bot/send.ts +36 -0
- package/src/messaging/providers/telegram-bot/transport.ts +22 -8
- package/src/messaging/read-provider-metadata.test.ts +157 -0
- package/src/messaging/read-provider-metadata.ts +51 -0
- package/src/notifications/__tests__/assistant-reply-producer.test.ts +94 -0
- package/src/notifications/__tests__/edit-notification.test.ts +174 -2
- package/src/notifications/__tests__/home-feed-side-effect.test.ts +536 -4
- package/src/notifications/adapters/macos.ts +55 -0
- package/src/notifications/adapters/slack.ts +10 -5
- package/src/notifications/assistant-reply-producer.ts +37 -4
- package/src/notifications/conversation-pairing.ts +111 -21
- package/src/notifications/edit-notification.ts +38 -8
- package/src/notifications/emit-signal.ts +12 -14
- package/src/notifications/home-feed-side-effect.ts +218 -15
- package/src/notifications/types.ts +5 -0
- package/src/permissions/AGENTS.md +16 -0
- package/src/permissions/checker.test.ts +75 -122
- package/src/permissions/checker.ts +44 -560
- package/src/permissions/confirmation-guardian-request.test.ts +28 -3
- package/src/permissions/confirmation-guardian-request.ts +5 -1
- package/src/persistence/conversation-crud.ts +69 -20
- package/src/persistence/conversation-queries.ts +14 -1
- package/src/persistence/conversation-types.ts +22 -0
- package/src/persistence/db-async-query.ts +2 -1
- package/src/persistence/db-maintenance.ts +15 -0
- package/src/persistence/delivery-crud.ts +122 -15
- package/src/persistence/embeddings/qdrant-manager.ts +1 -0
- package/src/persistence/migrations/367-create-watch-timeline-entries.ts +46 -0
- package/src/persistence/migrations/368-watch-timeline-screenshot-blob.ts +33 -0
- package/src/persistence/migrations/__tests__/367-create-watch-timeline-entries.test.ts +98 -0
- package/src/persistence/migrations/__tests__/368-watch-timeline-screenshot-blob.test.ts +98 -0
- package/src/persistence/schema/index.ts +1 -0
- package/src/persistence/schema/watch.ts +29 -0
- package/src/persistence/steps.ts +4 -0
- package/src/plugin-api/__tests__/import-graph-partial-mock.test.ts +67 -0
- package/src/plugin-api/conversation-turn.ts +27 -0
- package/src/plugin-api/credential-scope.test.ts +15 -0
- package/src/plugin-api/credential-scope.ts +14 -0
- package/src/plugin-api/index.ts +19 -1
- package/src/plugin-api/resolve-credential.ts +15 -10
- package/src/plugin-api/store-credential.ts +148 -0
- package/src/plugin-api/system-card.ts +36 -0
- package/src/plugin-api/vision-support.test.ts +82 -0
- package/src/plugin-api/vision-support.ts +14 -2
- package/src/plugins/defaults/image-fallback/__tests__/image-fallback.test.ts +361 -110
- package/src/plugins/defaults/image-fallback/__tests__/vision-recovery.test.ts +4 -0
- package/src/plugins/defaults/image-fallback/hooks/user-prompt-submit.ts +58 -0
- package/src/plugins/defaults/image-fallback/src/caption-blocks.ts +64 -23
- package/src/plugins/defaults/image-recovery/detect.ts +24 -1
- package/src/plugins/defaults/main.ts +15 -0
- package/src/plugins/defaults/memory/AGENTS.md +11 -3
- package/src/plugins/defaults/memory/__tests__/memory-tier-boundary-guard.test.ts +0 -1
- package/src/plugins/defaults/memory/graph-topology/pending-buffer.ts +9 -12
- package/src/plugins/defaults/memory/memory-retrospective-prompt.ts +1 -1
- package/src/plugins/defaults/memory/src/memory-v2-routes.ts +12 -10
- package/src/plugins/defaults/memory/substrate/__tests__/consolidation-job.test.ts +114 -0
- package/src/plugins/defaults/memory/substrate/__tests__/consolidation-prompt-flag-gating-guard.test.ts +8 -0
- package/src/plugins/defaults/memory/substrate/__tests__/edge-index.test.ts +0 -30
- package/src/plugins/defaults/memory/substrate/__tests__/ingest.test.ts +73 -1
- package/src/plugins/defaults/memory/substrate/__tests__/page-index.test.ts +37 -0
- package/src/plugins/defaults/memory/substrate/__tests__/page-links.test.ts +208 -0
- package/src/plugins/defaults/memory/substrate/__tests__/prompts-consolidation.test.ts +158 -1
- package/src/plugins/defaults/memory/substrate/__tests__/static-context.test.ts +1 -61
- package/src/plugins/defaults/memory/substrate/consolidation-job.ts +68 -6
- package/src/plugins/defaults/memory/substrate/edge-index.ts +0 -31
- package/src/plugins/defaults/memory/substrate/ingest.ts +59 -0
- package/src/plugins/defaults/memory/substrate/page-index.ts +35 -8
- package/src/plugins/defaults/memory/substrate/page-links.ts +133 -0
- package/src/plugins/defaults/memory/substrate/page-store.ts +10 -0
- package/src/plugins/defaults/memory/substrate/prompts/consolidation.ts +124 -34
- package/src/plugins/defaults/memory/substrate/static-context.ts +0 -29
- package/src/plugins/defaults/memory/v3/__tests__/shadow-plugin.test.ts +3 -1
- package/src/plugins/defaults/memory/v3/card.ts +9 -9
- package/src/plugins/defaults/memory/v3/core-set.test.ts +7 -0
- package/src/plugins/defaults/memory/v3/core-set.ts +5 -1
- package/src/plugins/defaults/memory/v3/edge.ts +13 -57
- package/src/plugins/mtime-cache.ts +1 -1
- package/src/plugins/pipeline.ts +14 -7
- package/src/plugins/plugin-execution-context.ts +35 -11
- package/src/plugins/plugin-tree-walk.ts +63 -4
- package/src/providers/__tests__/retry-network-error.test.ts +84 -0
- package/src/providers/anthropic/__tests__/pause-turn-continuation.test.ts +170 -0
- package/src/providers/anthropic/client.ts +332 -241
- package/src/providers/connection-resolution.ts +25 -2
- package/src/providers/content-blocks.ts +22 -0
- package/src/providers/fetch-provider-catalog.ts +19 -0
- package/src/providers/gemini/client.ts +5 -2
- package/src/providers/inference/__tests__/adapter-factory-litellm.test.ts +47 -1
- package/src/providers/inference/__tests__/adapter-factory-ollama.test.ts +83 -0
- package/src/providers/inference/__tests__/adapter-factory-openai-compatible.test.ts +98 -1
- package/src/providers/inference/__tests__/base-url-route-validation.test.ts +38 -0
- package/src/providers/inference/__tests__/base-url-security.test.ts +10 -0
- package/src/providers/inference/__tests__/connections-ollama.test.ts +90 -0
- package/src/providers/inference/__tests__/endpoint-probe.test.ts +92 -0
- package/src/providers/inference/__tests__/missing-credential-guard.test.ts +117 -0
- package/src/providers/inference/__tests__/profile-config-validation.test.ts +39 -0
- package/src/providers/inference/__tests__/profile-probe-classify.test.ts +66 -0
- package/src/providers/inference/adapter-factory.ts +33 -2
- package/src/providers/inference/auth.ts +13 -2
- package/src/providers/inference/credential-usage.ts +37 -0
- package/src/providers/inference/endpoint-probe.ts +115 -0
- package/src/providers/inference/missing-credential-guard.ts +110 -0
- package/src/providers/inference/profile-probe.ts +256 -0
- package/src/providers/inference/resolve-auth.ts +7 -7
- package/src/providers/media-resolve.ts +176 -15
- package/src/providers/model-catalog.ts +10 -0
- package/src/providers/openai/__tests__/api-error-normalization.test.ts +17 -1
- package/src/providers/openai/__tests__/chat-completions-provider-reasoning.test.ts +576 -46
- package/src/providers/openai/__tests__/connection-error-wrap.test.ts +44 -0
- package/src/providers/openai/__tests__/tool-choice-mapping.test.ts +125 -2
- package/src/providers/openai/api-error-normalization.ts +16 -2
- package/src/providers/openai/chat-completions-provider.ts +326 -44
- package/src/providers/openai/responses-provider.ts +5 -2
- package/src/providers/provider-send-message.ts +11 -0
- package/src/providers/retry.ts +6 -0
- package/src/providers/search-provider-catalog.ts +20 -0
- package/src/routes/route-host-protocol.ts +7 -0
- package/src/routes/worker.ts +31 -10
- package/src/runtime/AGENTS.md +36 -0
- package/src/runtime/__tests__/host-observe.test.ts +302 -0
- package/src/runtime/__tests__/runtime-http-port-collision.test.ts +131 -0
- package/src/runtime/__tests__/web-presence.test.ts +234 -0
- package/src/runtime/assistant-event-hub.ts +38 -0
- package/src/runtime/channel-readiness-service.ts +39 -13
- package/src/runtime/channel-readiness-types.ts +49 -2
- package/src/runtime/channel-reply-delivery.ts +55 -32
- package/src/runtime/channel-retry-sweep.ts +6 -4
- package/src/runtime/effective-capabilities.test.ts +19 -0
- package/src/runtime/effective-capabilities.ts +25 -0
- package/src/runtime/guardian-reply-router.ts +6 -2
- package/src/runtime/host-observe.ts +246 -0
- package/src/runtime/http-errors.ts +1 -0
- package/src/runtime/http-server.ts +185 -6
- package/src/runtime/routes/__tests__/client-routes.test.ts +150 -0
- package/src/runtime/routes/__tests__/conversation-list-routes.test.ts +133 -0
- package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +80 -0
- package/src/runtime/routes/__tests__/credential-delete-in-use.test.ts +155 -0
- package/src/runtime/routes/__tests__/inference-profiles-routes.test.ts +118 -0
- package/src/runtime/routes/__tests__/inference-provider-connection-routes.test.ts +72 -1
- package/src/runtime/routes/__tests__/ingress-status-routes.test.ts +508 -0
- package/src/runtime/routes/__tests__/monitoring-routes.test.ts +1 -0
- package/src/runtime/routes/__tests__/pending-interactions-route.test.ts +175 -0
- package/src/runtime/routes/__tests__/plugins-routes.test.ts +103 -71
- package/src/runtime/routes/__tests__/surface-action-routes.test.ts +55 -6
- package/src/runtime/routes/__tests__/system-card-turn-termination.test.ts +107 -0
- package/src/runtime/routes/__tests__/user-route-dispatcher-host.test.ts +52 -1
- package/src/runtime/routes/__tests__/user-route-dispatcher.test.ts +75 -4
- package/src/runtime/routes/__tests__/watch-routes-guardian-cache.test.ts +139 -0
- package/src/runtime/routes/__tests__/watch-routes.test.ts +598 -0
- package/src/runtime/routes/approval-routes.ts +37 -4
- package/src/runtime/routes/approval-strategies/guardian-text-engine-strategy.ts +16 -22
- package/src/runtime/routes/canned-message-complete.ts +68 -29
- package/src/runtime/routes/channel-readiness-routes.ts +14 -2
- package/src/runtime/routes/client-routes.ts +113 -0
- package/src/runtime/routes/conversation-list-routes.ts +22 -7
- package/src/runtime/routes/conversation-management-routes.ts +15 -9
- package/src/runtime/routes/conversation-query-routes.ts +10 -0
- package/src/runtime/routes/conversation-routes.ts +36 -22
- package/src/runtime/routes/credential-in-use.ts +85 -0
- package/src/runtime/routes/credential-routes.ts +55 -85
- package/src/runtime/routes/guardian-approval-interception.ts +44 -53
- package/src/runtime/routes/guardian-approval-reply-helpers.ts +14 -12
- package/src/runtime/routes/host-cu-routes.ts +18 -0
- package/src/runtime/routes/identity-routes.ts +5 -121
- package/src/runtime/routes/inbound-message-handler.ts +74 -30
- package/src/runtime/routes/inbound-stages/acl-enforcement.ts +16 -17
- package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +182 -297
- package/src/runtime/routes/inbound-stages/background-dispatch.ts +173 -386
- package/src/runtime/routes/inbound-stages/edit-intercept.ts +101 -77
- package/src/runtime/routes/inbound-stages/guardian-reply-intercept.ts +17 -8
- package/src/runtime/routes/inbound-stages/reaction-intercept.test.ts +91 -7
- package/src/runtime/routes/inbound-stages/reaction-intercept.ts +72 -70
- package/src/runtime/routes/index.ts +4 -0
- package/src/runtime/routes/inference-profiles-routes.ts +232 -31
- package/src/runtime/routes/inference-provider-connection-routes.ts +33 -11
- package/src/runtime/routes/ingress-status-routes.ts +180 -0
- package/src/runtime/routes/monitoring-routes.ts +9 -1
- package/src/runtime/routes/oauth-commands-routes.ts +1 -1
- package/src/runtime/routes/plugins-routes.ts +28 -32
- package/src/runtime/routes/resource-pressure-routes.ts +22 -0
- package/src/runtime/routes/secret-routes.ts +39 -8
- package/src/runtime/routes/settings-routes.ts +15 -12
- package/src/runtime/routes/surface-action-routes.ts +20 -19
- package/src/runtime/routes/user-route-dispatcher.ts +31 -2
- package/src/runtime/routes/user-route-resolution.ts +18 -1
- package/src/runtime/routes/watch-routes.ts +807 -0
- package/src/runtime/slack-reply-session.test.ts +23 -13
- package/src/runtime/slack-reply-session.ts +38 -55
- package/src/runtime/web-presence.ts +90 -0
- package/src/security/__tests__/untrusted-content.test.ts +42 -0
- package/src/security/untrusted-content.ts +28 -9
- package/src/skills/validate-input.ts +177 -40
- package/src/subagent/__tests__/consult-context-gating.test.ts +6 -10
- package/src/subagent/consult-context.ts +7 -23
- package/src/tools/credentials/broker.ts +24 -79
- package/src/tools/credentials/ref-parse.ts +35 -0
- package/src/tools/credentials/resolve.ts +4 -10
- package/src/tools/credentials/store.ts +168 -0
- package/src/tools/credentials/tool-policy.ts +67 -0
- package/src/tools/executor.ts +53 -15
- package/src/tools/network/__tests__/firecrawl-compat.test.ts +77 -0
- package/src/tools/network/__tests__/web-fetch-fastcrw.test.ts +169 -0
- package/src/tools/network/__tests__/web-search.test.ts +138 -3
- package/src/tools/network/firecrawl-compat.ts +90 -0
- package/src/tools/network/url-safety.ts +9 -16
- package/src/tools/network/web-fetch.ts +142 -62
- package/src/tools/network/web-search.ts +162 -58
- package/src/tools/permission-checker.ts +43 -52
- package/src/tools/schema-transforms.ts +40 -5
- package/src/tools/shared/input-repairs.ts +160 -0
- package/src/tools/skills/skill-tool-factory.ts +18 -4
- package/src/tools/subagent/spawn.ts +0 -1
- package/src/tools/tool-approval-handler.ts +16 -10
- package/src/tools/tool-types.ts +26 -13
- package/src/tools/types.ts +6 -6
- package/src/util/__tests__/cgroup-memory.test.ts +3 -0
- package/src/util/cgroup-memory.ts +3 -0
- package/src/util/container-cpu-sampler.ts +250 -0
- package/src/util/image-conversion.ts +178 -14
- package/src/util/platform.ts +93 -10
- package/src/util/worker-process.ts +1 -0
- package/src/watch/__tests__/watch-retro.test.ts +665 -0
- package/src/watch/__tests__/watch-session-manager.test.ts +566 -0
- package/src/watch/__tests__/watch-timeline.test.ts +670 -0
- package/src/watch/watch-retro.ts +480 -0
- package/src/watch/watch-session-manager.ts +575 -0
- package/src/watch/watch-timeline.ts +848 -0
- package/src/permissions/ipc-risk-types.ts +0 -143
- package/src/permissions/risk-types.ts +0 -76
|
@@ -49,6 +49,7 @@ afterAll(() => {
|
|
|
49
49
|
});
|
|
50
50
|
|
|
51
51
|
import {
|
|
52
|
+
CODE_DEFAULT_PROFILE_ENTRIES,
|
|
52
53
|
getEffectiveProfile,
|
|
53
54
|
getEffectiveProfiles,
|
|
54
55
|
getEffectiveProfilesForProvider,
|
|
@@ -164,6 +165,14 @@ const LEGACY_HATCH_PROFILE_NAMES = [
|
|
|
164
165
|
"custom-cost-optimized",
|
|
165
166
|
] as const;
|
|
166
167
|
|
|
168
|
+
/**
|
|
169
|
+
* The managed Balanced model. These tests assert that resolution serves the
|
|
170
|
+
* code catalog rather than a workspace body, so they follow the catalog
|
|
171
|
+
* instead of restating its pin.
|
|
172
|
+
*/
|
|
173
|
+
const MANAGED_BALANCED_MODEL = CODE_DEFAULT_PROFILE_ENTRIES.balanced
|
|
174
|
+
.model as string;
|
|
175
|
+
|
|
167
176
|
function createProviderConnectionsDb(): DrizzleDb {
|
|
168
177
|
const sqlite = new Database(":memory:");
|
|
169
178
|
sqlite.exec("PRAGMA journal_mode=WAL");
|
|
@@ -669,7 +678,7 @@ describe("loadConfig startup behavior", () => {
|
|
|
669
678
|
expect(raw.llm.profiles).toEqual({});
|
|
670
679
|
// Default content resolves from the code catalog via the effective view.
|
|
671
680
|
const effectiveBalanced = getEffectiveProfile(raw.llm.profiles, "balanced");
|
|
672
|
-
expect(effectiveBalanced?.model).toBe(
|
|
681
|
+
expect(effectiveBalanced?.model).toBe(MANAGED_BALANCED_MODEL);
|
|
673
682
|
expect(effectiveBalanced?.provider).toBe("vellum");
|
|
674
683
|
expect(effectiveBalanced?.provider_connection).toBeUndefined();
|
|
675
684
|
});
|
|
@@ -997,7 +1006,7 @@ describe("loadConfig startup behavior", () => {
|
|
|
997
1006
|
// Resolution ignores the drifted body: a managed-source entry contributes
|
|
998
1007
|
// only label/status/topP, everything else comes from the catalog.
|
|
999
1008
|
const effectiveBalanced = getEffectiveProfile(raw.llm.profiles, "balanced");
|
|
1000
|
-
expect(effectiveBalanced?.model).toBe(
|
|
1009
|
+
expect(effectiveBalanced?.model).toBe(MANAGED_BALANCED_MODEL);
|
|
1001
1010
|
expect(effectiveBalanced?.provider_connection).toBeUndefined();
|
|
1002
1011
|
});
|
|
1003
1012
|
|
|
@@ -1077,7 +1086,7 @@ describe("loadConfig startup behavior", () => {
|
|
|
1077
1086
|
expect(raw.llm.profiles.balanced).toEqual(drifted);
|
|
1078
1087
|
expect(raw.llm.activeProfile).toBe("balanced");
|
|
1079
1088
|
const effectiveBalanced = getEffectiveProfile(raw.llm.profiles, "balanced");
|
|
1080
|
-
expect(effectiveBalanced?.model).toBe(
|
|
1089
|
+
expect(effectiveBalanced?.model).toBe(MANAGED_BALANCED_MODEL);
|
|
1081
1090
|
expect(effectiveBalanced?.maxTokens).toBe(32000);
|
|
1082
1091
|
expect(effectiveBalanced?.provider_connection).toBeUndefined();
|
|
1083
1092
|
// The catalog body carries no topP and the entry has none, so the
|
|
@@ -1109,7 +1118,7 @@ describe("loadConfig startup behavior", () => {
|
|
|
1109
1118
|
expect(raw.llm.profiles.balanced).toEqual(edited);
|
|
1110
1119
|
const effectiveBalanced = getEffectiveProfile(raw.llm.profiles, "balanced");
|
|
1111
1120
|
// Content is served from the catalog...
|
|
1112
|
-
expect(effectiveBalanced?.model).toBe(
|
|
1121
|
+
expect(effectiveBalanced?.model).toBe(MANAGED_BALANCED_MODEL);
|
|
1113
1122
|
// ...with the user's label and status overlaid.
|
|
1114
1123
|
expect(effectiveBalanced?.label).toBe("My Default");
|
|
1115
1124
|
expect(effectiveBalanced?.status).toBe("disabled");
|
|
@@ -1134,7 +1143,7 @@ describe("loadConfig startup behavior", () => {
|
|
|
1134
1143
|
expect(raw.llm.profiles.balanced).toEqual(stub);
|
|
1135
1144
|
const effectiveBalanced = getEffectiveProfile(raw.llm.profiles, "balanced");
|
|
1136
1145
|
expect(effectiveBalanced?.label).toBe("My Default");
|
|
1137
|
-
expect(effectiveBalanced?.model).toBe(
|
|
1146
|
+
expect(effectiveBalanced?.model).toBe(MANAGED_BALANCED_MODEL);
|
|
1138
1147
|
});
|
|
1139
1148
|
|
|
1140
1149
|
test("off-platform boot preserves user-toggled status on a managed stub", () => {
|
|
@@ -1156,7 +1165,7 @@ describe("loadConfig startup behavior", () => {
|
|
|
1156
1165
|
expect(effectiveBalanced?.status).toBe("disabled");
|
|
1157
1166
|
// Content still comes from the catalog — only label/status/topP are
|
|
1158
1167
|
// workspace-owned.
|
|
1159
|
-
expect(effectiveBalanced?.model).toBe(
|
|
1168
|
+
expect(effectiveBalanced?.model).toBe(MANAGED_BALANCED_MODEL);
|
|
1160
1169
|
});
|
|
1161
1170
|
|
|
1162
1171
|
test("boot preserves a user-edited topP override on a managed stub", () => {
|
|
@@ -1180,7 +1189,7 @@ describe("loadConfig startup behavior", () => {
|
|
|
1180
1189
|
expect(effectiveBalanced?.topP).toBe(0.5);
|
|
1181
1190
|
// Content still comes from the catalog — topP is workspace-owned, the
|
|
1182
1191
|
// rest is code-owned.
|
|
1183
|
-
expect(effectiveBalanced?.model).toBe(
|
|
1192
|
+
expect(effectiveBalanced?.model).toBe(MANAGED_BALANCED_MODEL);
|
|
1184
1193
|
});
|
|
1185
1194
|
|
|
1186
1195
|
test("effective balanced profile carries no topP override by default", () => {
|
|
@@ -1235,7 +1244,7 @@ describe("loadConfig startup behavior", () => {
|
|
|
1235
1244
|
expect(raw.llm.profiles.balanced).toBeUndefined();
|
|
1236
1245
|
const effectiveBalanced = getEffectiveProfile(raw.llm.profiles, "balanced");
|
|
1237
1246
|
expect(effectiveBalanced?.label).toBe("Balanced");
|
|
1238
|
-
expect(effectiveBalanced?.model).toBe(
|
|
1247
|
+
expect(effectiveBalanced?.model).toBe(MANAGED_BALANCED_MODEL);
|
|
1239
1248
|
// Status is unset — the default resolves active.
|
|
1240
1249
|
expect("status" in (effectiveBalanced ?? {})).toBe(false);
|
|
1241
1250
|
});
|
|
@@ -1314,7 +1323,7 @@ describe("loadConfig startup behavior", () => {
|
|
|
1314
1323
|
// overlay boot. The overlay-set label is what shows through the
|
|
1315
1324
|
// effective view.
|
|
1316
1325
|
expect(mainAgentConfig.provider).toBe("vellum");
|
|
1317
|
-
expect(mainAgentConfig.model).toBe(
|
|
1326
|
+
expect(mainAgentConfig.model).toBe(MANAGED_BALANCED_MODEL);
|
|
1318
1327
|
|
|
1319
1328
|
const raw = JSON.parse(readFileSync(CONFIG_PATH, "utf-8"));
|
|
1320
1329
|
expect(raw.llm.profiles.balanced).toEqual({
|
|
@@ -1345,7 +1354,7 @@ describe("loadConfig startup behavior", () => {
|
|
|
1345
1354
|
);
|
|
1346
1355
|
expect(effectiveBalanced?.provider).toBe("vellum");
|
|
1347
1356
|
expect(effectiveBalanced?.provider_connection).toBeUndefined();
|
|
1348
|
-
expect(effectiveBalanced?.model).toBe(
|
|
1357
|
+
expect(effectiveBalanced?.model).toBe(MANAGED_BALANCED_MODEL);
|
|
1349
1358
|
expect(effectiveBalanced?.maxTokens).toBe(32000);
|
|
1350
1359
|
expect(effectiveBalanced?.thinking).toEqual({
|
|
1351
1360
|
enabled: true,
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
__resetContainerCpuSamplerForTests,
|
|
5
|
+
__runContainerCpuSamplerTickForTests,
|
|
6
|
+
computeCpuPercent,
|
|
7
|
+
computeDurationWeightedMeanPercent,
|
|
8
|
+
getAverageContainerCpuPercentOrNull,
|
|
9
|
+
getCachedContainerCpuPercent,
|
|
10
|
+
getCachedContainerCpuPercentOrNull,
|
|
11
|
+
} from "../util/container-cpu-sampler.js";
|
|
12
|
+
|
|
13
|
+
describe("computeCpuPercent", () => {
|
|
14
|
+
test("computes percent of the full allocation from a CPU-time delta", () => {
|
|
15
|
+
// 2.5s of CPU over a 5s window on 1 core is 50% of the allocation.
|
|
16
|
+
expect(computeCpuPercent(2_500_000, 5_000, 1)).toBe(50);
|
|
17
|
+
// The same delta on 2 cores is half the utilization.
|
|
18
|
+
expect(computeCpuPercent(2_500_000, 5_000, 2)).toBe(25);
|
|
19
|
+
// Fully saturating 4 cores for the whole window is 100%.
|
|
20
|
+
expect(computeCpuPercent(20_000_000, 5_000, 4)).toBe(100);
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
test("rounds to 2 decimal places", () => {
|
|
24
|
+
expect(computeCpuPercent(123_456, 5_000, 2)).toBe(1.23);
|
|
25
|
+
expect(computeCpuPercent(123_999, 5_000, 2)).toBe(1.24);
|
|
26
|
+
expect(computeCpuPercent(0, 5_000, 1)).toBe(0);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
test("yields a non-finite result for zero cores, which callers must guard", () => {
|
|
30
|
+
expect(Number.isFinite(computeCpuPercent(1_000_000, 5_000, 0))).toBe(false);
|
|
31
|
+
expect(Number.isFinite(computeCpuPercent(0, 5_000, 0))).toBe(false);
|
|
32
|
+
});
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
describe("getCachedContainerCpuPercent", () => {
|
|
36
|
+
test("returns a finite number", () => {
|
|
37
|
+
expect(Number.isFinite(getCachedContainerCpuPercent())).toBe(true);
|
|
38
|
+
});
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
describe("getCachedContainerCpuPercentOrNull", () => {
|
|
42
|
+
test("returns null until the sampler computes its first delta", () => {
|
|
43
|
+
__resetContainerCpuSamplerForTests();
|
|
44
|
+
|
|
45
|
+
expect(getCachedContainerCpuPercentOrNull()).toBeNull();
|
|
46
|
+
// The plain accessor keeps its 0 placeholder for /v1/health.
|
|
47
|
+
expect(getCachedContainerCpuPercent()).toBe(0);
|
|
48
|
+
|
|
49
|
+
// Drive one sampler tick a full interval ahead of the reset baseline so
|
|
50
|
+
// it computes a real delta from process.cpuUsage().
|
|
51
|
+
__runContainerCpuSamplerTickForTests(Date.now() + 5_000);
|
|
52
|
+
|
|
53
|
+
const sampled = getCachedContainerCpuPercentOrNull();
|
|
54
|
+
expect(sampled).not.toBeNull();
|
|
55
|
+
expect(Number.isFinite(sampled!)).toBe(true);
|
|
56
|
+
expect(sampled).toBe(getCachedContainerCpuPercent());
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
describe("computeDurationWeightedMeanPercent", () => {
|
|
61
|
+
test("weights each percent by the wall time its delta spanned", () => {
|
|
62
|
+
// 25s at 80% then 5s at 100% is 83.33% of the half minute, not the 90%
|
|
63
|
+
// an equal-weight mean would report.
|
|
64
|
+
expect(
|
|
65
|
+
computeDurationWeightedMeanPercent(
|
|
66
|
+
[
|
|
67
|
+
{ at: 25_000, percent: 80, elapsedMs: 25_000 },
|
|
68
|
+
{ at: 30_000, percent: 100, elapsedMs: 5_000 },
|
|
69
|
+
],
|
|
70
|
+
0,
|
|
71
|
+
),
|
|
72
|
+
).toBe(83.33);
|
|
73
|
+
// Equal durations degrade to the plain mean.
|
|
74
|
+
expect(
|
|
75
|
+
computeDurationWeightedMeanPercent(
|
|
76
|
+
[
|
|
77
|
+
{ at: 5_000, percent: 40, elapsedMs: 5_000 },
|
|
78
|
+
{ at: 10_000, percent: 60, elapsedMs: 5_000 },
|
|
79
|
+
],
|
|
80
|
+
0,
|
|
81
|
+
),
|
|
82
|
+
).toBe(50);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
test("clips a delayed tick's contribution to its overlap with the window", () => {
|
|
86
|
+
// A 25s CPU-bound stall at 100% ends 1s into the window; only that 1s
|
|
87
|
+
// may count. With 29s of idle at 10% after it, the window reads 13%,
|
|
88
|
+
// not the 52.8% an unclipped duration weighting would report.
|
|
89
|
+
expect(
|
|
90
|
+
computeDurationWeightedMeanPercent(
|
|
91
|
+
[
|
|
92
|
+
{ at: 1_000, percent: 100, elapsedMs: 25_000 },
|
|
93
|
+
{ at: 30_000, percent: 10, elapsedMs: 29_000 },
|
|
94
|
+
],
|
|
95
|
+
0,
|
|
96
|
+
),
|
|
97
|
+
).toBe(13);
|
|
98
|
+
// A tick entirely before the window contributes nothing.
|
|
99
|
+
expect(
|
|
100
|
+
computeDurationWeightedMeanPercent(
|
|
101
|
+
[
|
|
102
|
+
{ at: -1_000, percent: 100, elapsedMs: 5_000 },
|
|
103
|
+
{ at: 10_000, percent: 20, elapsedMs: 10_000 },
|
|
104
|
+
],
|
|
105
|
+
0,
|
|
106
|
+
),
|
|
107
|
+
).toBe(20);
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
test("returns null without any overlapping samples", () => {
|
|
111
|
+
expect(computeDurationWeightedMeanPercent([], 0)).toBeNull();
|
|
112
|
+
expect(
|
|
113
|
+
computeDurationWeightedMeanPercent(
|
|
114
|
+
[{ at: 5_000, percent: 50, elapsedMs: 0 }],
|
|
115
|
+
0,
|
|
116
|
+
),
|
|
117
|
+
).toBeNull();
|
|
118
|
+
expect(
|
|
119
|
+
computeDurationWeightedMeanPercent(
|
|
120
|
+
[{ at: -1_000, percent: 50, elapsedMs: 5_000 }],
|
|
121
|
+
0,
|
|
122
|
+
),
|
|
123
|
+
).toBeNull();
|
|
124
|
+
});
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
describe("getAverageContainerCpuPercentOrNull", () => {
|
|
128
|
+
test("returns null until a tick lands inside the window", () => {
|
|
129
|
+
__resetContainerCpuSamplerForTests();
|
|
130
|
+
|
|
131
|
+
expect(getAverageContainerCpuPercentOrNull(60_000)).toBeNull();
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
test("averages the ticks recorded within the trailing window", () => {
|
|
135
|
+
__resetContainerCpuSamplerForTests();
|
|
136
|
+
|
|
137
|
+
// A single recorded tick makes the average equal the last cached value.
|
|
138
|
+
__runContainerCpuSamplerTickForTests(Date.now() + 5_000);
|
|
139
|
+
const single = getAverageContainerCpuPercentOrNull(60_000);
|
|
140
|
+
expect(single).not.toBeNull();
|
|
141
|
+
expect(single).toBe(getCachedContainerCpuPercentOrNull());
|
|
142
|
+
|
|
143
|
+
// More ticks keep the average finite and within the recorded range; the
|
|
144
|
+
// exact value depends on real process CPU deltas, so only the invariant
|
|
145
|
+
// is asserted.
|
|
146
|
+
__runContainerCpuSamplerTickForTests(Date.now() + 10_000);
|
|
147
|
+
__runContainerCpuSamplerTickForTests(Date.now() + 15_000);
|
|
148
|
+
const averaged = getAverageContainerCpuPercentOrNull(60_000);
|
|
149
|
+
expect(averaged).not.toBeNull();
|
|
150
|
+
expect(Number.isFinite(averaged!)).toBe(true);
|
|
151
|
+
expect(averaged!).toBeGreaterThanOrEqual(0);
|
|
152
|
+
});
|
|
153
|
+
});
|
|
@@ -71,7 +71,6 @@ mock.module("../plugins/defaults/memory/substrate/edge-index.js", () => ({
|
|
|
71
71
|
getEdgeIndex: async (): Promise<EdgeIndex> => edgeIndex,
|
|
72
72
|
invalidateEdgeIndex: () => {},
|
|
73
73
|
getReachable: () => new Set<string>(),
|
|
74
|
-
validateEdgeTargets: () => ({ ok: true, missing: [] }),
|
|
75
74
|
totalEdgeCount: () => 0,
|
|
76
75
|
}));
|
|
77
76
|
|
|
@@ -15,7 +15,6 @@ mock.module("../permissions/checker.js", () => ({
|
|
|
15
15
|
isDynamicSkillLoadInvocation: () => false,
|
|
16
16
|
check: checkSpy,
|
|
17
17
|
classifyRisk: () => Promise.resolve({ level: "low" }),
|
|
18
|
-
generateAllowlistOptions: () => Promise.resolve([]),
|
|
19
18
|
generateScopeOptions: () => [],
|
|
20
19
|
}));
|
|
21
20
|
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A watch session's timeline is the most sensitive thing a conversation owns:
|
|
3
|
+
* what the user narrated, and frames of their screen while they narrated it.
|
|
4
|
+
* Both live in the timeline row, so every path that removes a conversation has
|
|
5
|
+
* to take those rows with it.
|
|
6
|
+
*
|
|
7
|
+
* `clearAll` is the sharpest case. Its bulk deletes run in a sqlite3
|
|
8
|
+
* subprocess, so the daemon's event loop is free between them and an append
|
|
9
|
+
* can land mid-wipe. The timeline purge is the wipe's last statement, after
|
|
10
|
+
* `conversations` is emptied: an append that beat it is a row it deletes, and
|
|
11
|
+
* one that follows it has no conversation to key itself to.
|
|
12
|
+
*/
|
|
13
|
+
import { describe, expect, mock, test } from "bun:test";
|
|
14
|
+
|
|
15
|
+
// Keep the rest of the module real; only the Qdrant collection drop is
|
|
16
|
+
// replaced, so `clearAll` does not reach for a lexical index that is not
|
|
17
|
+
// running here.
|
|
18
|
+
const actualLexical =
|
|
19
|
+
await import("../persistence/job-handlers/message-lexical.js");
|
|
20
|
+
mock.module("../persistence/job-handlers/message-lexical.js", () => ({
|
|
21
|
+
...actualLexical,
|
|
22
|
+
clearMessagesLexicalIndex: async () => {},
|
|
23
|
+
}));
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* An append to run from inside the wipe, on the statement it is keyed to.
|
|
27
|
+
*
|
|
28
|
+
* Which interleaving a race test runs is otherwise up to whichever subprocess
|
|
29
|
+
* happens to be slow, and the interleaving that matters is a narrow one: the
|
|
30
|
+
* append has to write its row while `conversations` is still populated, so its
|
|
31
|
+
* own existence check has nothing to catch and only the wipe's ordering keeps
|
|
32
|
+
* the row from outliving the wipe.
|
|
33
|
+
*/
|
|
34
|
+
let appendDuringWipe: { onStatement: string; run: () => void } | null = null;
|
|
35
|
+
|
|
36
|
+
// Every bulk delete in `clearAll` goes through `runAsyncSqlite`, which spawns
|
|
37
|
+
// a sqlite3 subprocess and yields the event loop. The wrapper runs the real
|
|
38
|
+
// statement and stands in for whatever else the daemon would have done while
|
|
39
|
+
// it was in flight.
|
|
40
|
+
const actualAsyncQuery = await import("../persistence/db-async-query.js");
|
|
41
|
+
const realRunAsyncSqlite = actualAsyncQuery.runAsyncSqlite;
|
|
42
|
+
mock.module("../persistence/db-async-query.js", () => ({
|
|
43
|
+
...actualAsyncQuery,
|
|
44
|
+
runAsyncSqlite: async (
|
|
45
|
+
sql: string,
|
|
46
|
+
label: string,
|
|
47
|
+
options?: Parameters<typeof actualAsyncQuery.runAsyncSqlite>[2],
|
|
48
|
+
) => {
|
|
49
|
+
const result = await realRunAsyncSqlite(sql, label, options);
|
|
50
|
+
if (appendDuringWipe && appendDuringWipe.onStatement === sql) {
|
|
51
|
+
const pending = appendDuringWipe;
|
|
52
|
+
appendDuringWipe = null;
|
|
53
|
+
pending.run();
|
|
54
|
+
}
|
|
55
|
+
return result;
|
|
56
|
+
},
|
|
57
|
+
}));
|
|
58
|
+
|
|
59
|
+
import {
|
|
60
|
+
clearAll,
|
|
61
|
+
createConversation,
|
|
62
|
+
deleteConversation,
|
|
63
|
+
deleteConversationGently,
|
|
64
|
+
} from "../persistence/conversation-crud.js";
|
|
65
|
+
import { getDb } from "../persistence/db-connection.js";
|
|
66
|
+
import { initializeDb } from "../persistence/db-init.js";
|
|
67
|
+
import { attachments } from "../persistence/schema/conversations.js";
|
|
68
|
+
import type { WatchAppendResult } from "../watch/watch-timeline.js";
|
|
69
|
+
import {
|
|
70
|
+
appendNarration,
|
|
71
|
+
appendObservation,
|
|
72
|
+
readWatchScreenshot,
|
|
73
|
+
renderWatchTimeline,
|
|
74
|
+
} from "../watch/watch-timeline.js";
|
|
75
|
+
|
|
76
|
+
await initializeDb();
|
|
77
|
+
|
|
78
|
+
/** A 1x1 JPEG, the shape the host hands over on every observe. */
|
|
79
|
+
const SCREENSHOT_BASE64 =
|
|
80
|
+
"/9j/4AAQSkZJRgABAQEAYABgAAD/2wBDAAgGBgcGBQgHBwcJCQgKDBQNDAsLDBkSEw8UHRofHh0aHBwgJC4nICIsIxwcKDcpLDAxNDQ0Hyc5PTgyPC4zNDL/2wBDAQkJCQwLDBgNDRgyIRwhMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjL/wAARCAABAAEDASIAAhEBAxEB/8QAHwAAAQUBAQEBAQEAAAAAAAAAAAECAwQFBgcICQoL/8QAtRAAAgEDAwIEAwUFBAQAAAF9AQIDAAQRBRIhMUEGE1FhByJxFDKBkaEII0KxwRVS0fAkM2JyggkKFhcYGRolJicoKSo0NTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uHi4+Tl5ufo6erx8vP09fb3+Pn6/8QAHwEAAwEBAQEBAQEBAQAAAAAAAAECAwQFBgcICQoL/8QAtREAAgECBAQDBAcFBAQAAQJ3AAECAxEEBSExBhJBUQdhcRMiMoEIFEKRobHBCSMzUvAVYnLRChYkNOEl8RcYGRomJygpKjU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6goOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3uLm6wsPExcbHyMnK0tPU1dbX2Nna4uPk5ebn6Onq8vP09fb3+Pn6/9oADAMBAAIRAxEAPwD3+iiigD//2Q==";
|
|
81
|
+
|
|
82
|
+
interface WatchedConversation {
|
|
83
|
+
readonly conversationId: string;
|
|
84
|
+
readonly sessionId: string;
|
|
85
|
+
readonly entryId: string;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/** A conversation with one narration and one screenshot-carrying observation. */
|
|
89
|
+
function seedWatchedConversation(title: string): WatchedConversation {
|
|
90
|
+
const conversation = createConversation(title);
|
|
91
|
+
const sessionId = `watch-${conversation.id}`;
|
|
92
|
+
|
|
93
|
+
appendObservation(sessionId, {
|
|
94
|
+
conversationId: conversation.id,
|
|
95
|
+
atMs: 1_000,
|
|
96
|
+
observation: { axTree: "window Mail", screenshot: SCREENSHOT_BASE64 },
|
|
97
|
+
attachScreenshot: true,
|
|
98
|
+
});
|
|
99
|
+
appendNarration(sessionId, {
|
|
100
|
+
conversationId: conversation.id,
|
|
101
|
+
atMs: 2_000,
|
|
102
|
+
text: "this is how I file it",
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
const rendered = renderWatchTimeline(sessionId);
|
|
106
|
+
expect(rendered.totalEntries).toBe(2);
|
|
107
|
+
const entryId = rendered.screenshotEntryIds[0];
|
|
108
|
+
expect(entryId).toBeDefined();
|
|
109
|
+
expect(readWatchScreenshot(entryId as string)).not.toBeNull();
|
|
110
|
+
|
|
111
|
+
return { conversationId: conversation.id, sessionId, entryId: entryId ?? "" };
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function expectPurged(watched: WatchedConversation): void {
|
|
115
|
+
expect(renderWatchTimeline(watched.sessionId).totalEntries).toBe(0);
|
|
116
|
+
expect(readWatchScreenshot(watched.entryId)).toBeNull();
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/** Every attachment row in the store. */
|
|
120
|
+
function attachmentCount(): number {
|
|
121
|
+
return getDb().select({ id: attachments.id }).from(attachments).all().length;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
describe("deleteConversation purges the watch timeline", () => {
|
|
125
|
+
test("takes the entries and the frames they carry", () => {
|
|
126
|
+
const watched = seedWatchedConversation("watched-sync");
|
|
127
|
+
const other = seedWatchedConversation("watched-sync-other");
|
|
128
|
+
|
|
129
|
+
// A timeline keeps its screenshots in its own rows, so watching a session
|
|
130
|
+
// stages nothing in the attachment store for a delete to chase.
|
|
131
|
+
expect(attachmentCount()).toBe(0);
|
|
132
|
+
|
|
133
|
+
deleteConversation(watched.conversationId);
|
|
134
|
+
|
|
135
|
+
expectPurged(watched);
|
|
136
|
+
// Scoped to the conversation being deleted, not the whole store.
|
|
137
|
+
expect(renderWatchTimeline(other.sessionId).totalEntries).toBe(2);
|
|
138
|
+
expect(readWatchScreenshot(other.entryId)).not.toBeNull();
|
|
139
|
+
});
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
describe("deleteConversationGently purges the watch timeline", () => {
|
|
143
|
+
test("the off-loop path clears the same state as the synchronous one", async () => {
|
|
144
|
+
const watched = seedWatchedConversation("watched-gentle");
|
|
145
|
+
|
|
146
|
+
await deleteConversationGently(watched.conversationId);
|
|
147
|
+
|
|
148
|
+
expectPurged(watched);
|
|
149
|
+
});
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
describe("an append that lands after the delete", () => {
|
|
153
|
+
test("is refused and leaves no row", () => {
|
|
154
|
+
const watched = seedWatchedConversation("watched-late-append");
|
|
155
|
+
|
|
156
|
+
deleteConversation(watched.conversationId);
|
|
157
|
+
const result = appendObservation(watched.sessionId, {
|
|
158
|
+
conversationId: watched.conversationId,
|
|
159
|
+
atMs: 3_000,
|
|
160
|
+
observation: { axTree: "window Mail", screenshot: SCREENSHOT_BASE64 },
|
|
161
|
+
attachScreenshot: true,
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
expect(result).toEqual({ ok: false, reason: "conversation_missing" });
|
|
165
|
+
expect(renderWatchTimeline(watched.sessionId).totalEntries).toBe(0);
|
|
166
|
+
});
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
describe("clearAll purges every watch timeline", () => {
|
|
170
|
+
test("wipes the entries and the frames they carry", async () => {
|
|
171
|
+
const first = seedWatchedConversation("watched-clear-all-a");
|
|
172
|
+
const second = seedWatchedConversation("watched-clear-all-b");
|
|
173
|
+
|
|
174
|
+
await clearAll();
|
|
175
|
+
|
|
176
|
+
expectPurged(first);
|
|
177
|
+
expectPurged(second);
|
|
178
|
+
});
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
describe("an append that lands inside a clear-all", () => {
|
|
182
|
+
test("does not outlive the wipe that overtook it", async () => {
|
|
183
|
+
const watched = seedWatchedConversation("watched-clear-all-race");
|
|
184
|
+
|
|
185
|
+
// The append runs where the wipe is most exposed: mid-sequence, with
|
|
186
|
+
// `conversations` still populated so its existence check has nothing to
|
|
187
|
+
// catch. Only the timeline purge coming after `conversations` keeps the
|
|
188
|
+
// row from surviving.
|
|
189
|
+
let landed: WatchAppendResult | null = null;
|
|
190
|
+
appendDuringWipe = {
|
|
191
|
+
onStatement: "DELETE FROM messages",
|
|
192
|
+
run: () => {
|
|
193
|
+
landed = appendObservation(watched.sessionId, {
|
|
194
|
+
conversationId: watched.conversationId,
|
|
195
|
+
atMs: 3_000,
|
|
196
|
+
observation: {
|
|
197
|
+
axTree: "window Mail",
|
|
198
|
+
screenshot: SCREENSHOT_BASE64,
|
|
199
|
+
},
|
|
200
|
+
attachScreenshot: true,
|
|
201
|
+
});
|
|
202
|
+
},
|
|
203
|
+
};
|
|
204
|
+
|
|
205
|
+
await clearAll();
|
|
206
|
+
|
|
207
|
+
// The row really did land, so the wipe had something to sweep.
|
|
208
|
+
const result = landed as WatchAppendResult | null;
|
|
209
|
+
expect(result?.ok).toBe(true);
|
|
210
|
+
expect(renderWatchTimeline(watched.sessionId).totalEntries).toBe(0);
|
|
211
|
+
expect(
|
|
212
|
+
readWatchScreenshot(result?.ok === true ? result.entryId : ""),
|
|
213
|
+
).toBeNull();
|
|
214
|
+
});
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
describe("an append that arrives once the clear-all is done", () => {
|
|
218
|
+
test("is refused, because there is no conversation left to key it to", async () => {
|
|
219
|
+
const watched = seedWatchedConversation("watched-clear-all-after");
|
|
220
|
+
|
|
221
|
+
await clearAll();
|
|
222
|
+
const result = appendNarration(watched.sessionId, {
|
|
223
|
+
conversationId: watched.conversationId,
|
|
224
|
+
atMs: 3_000,
|
|
225
|
+
text: "too late",
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
expect(result).toEqual({ ok: false, reason: "conversation_missing" });
|
|
229
|
+
expect(renderWatchTimeline(watched.sessionId).totalEntries).toBe(0);
|
|
230
|
+
});
|
|
231
|
+
});
|
|
@@ -521,6 +521,23 @@ describe("classifyConversationError", () => {
|
|
|
521
521
|
expect(result.userMessage.toLowerCase()).toContain("image");
|
|
522
522
|
});
|
|
523
523
|
|
|
524
|
+
it("classifies an OpenAI-compatible 400 for an unreadable image as image_unsupported_format (non-retryable)", () => {
|
|
525
|
+
// The rejection an OpenAI-compatible endpoint returns when any image in
|
|
526
|
+
// the request is not one of the formats it decodes (a HEIC photo renamed
|
|
527
|
+
// to .png, say). It names no index, so the whole turn dies unless it is
|
|
528
|
+
// classified for the image-recovery path with actionable copy.
|
|
529
|
+
const err = new ProviderError(
|
|
530
|
+
"The image data you provided does not represent a valid image. Please check your input and try again with one of the supported image formats: [image/jpeg, image/png, image/gif, image/webp]",
|
|
531
|
+
"openai",
|
|
532
|
+
400,
|
|
533
|
+
);
|
|
534
|
+
const result = classifyConversationError(err, baseCtx);
|
|
535
|
+
expect(result.code).toBe("IMAGE_TOO_LARGE");
|
|
536
|
+
expect(result.errorCategory).toBe("image_unsupported_format");
|
|
537
|
+
expect(result.retryable).toBe(false);
|
|
538
|
+
expect(result.userMessage.toLowerCase()).toContain("image");
|
|
539
|
+
});
|
|
540
|
+
|
|
524
541
|
it("does not steal generic 400s that happen to mention 'image'", () => {
|
|
525
542
|
const err = new ProviderError(
|
|
526
543
|
"invalid request: image source is missing",
|
|
@@ -652,14 +652,12 @@ describe("loadFromDb metadata injection rehydration", () => {
|
|
|
652
652
|
expect(messages[2].content).toEqual([{ type: "text", text: "Tail" }]);
|
|
653
653
|
});
|
|
654
654
|
|
|
655
|
-
test("internal-channel trusted_contact view
|
|
656
|
-
// Rehydration keys on
|
|
657
|
-
// `
|
|
658
|
-
//
|
|
659
|
-
//
|
|
660
|
-
//
|
|
661
|
-
// injection so a daemon-restart reload of the same conversation produces
|
|
662
|
-
// an identical prefix.
|
|
655
|
+
test("internal-channel trusted_contact view does not rehydrate memoryV2StaticBlock", async () => {
|
|
656
|
+
// Rehydration keys on the actor, not the surface: a trusted_contact is
|
|
657
|
+
// denied personal memory on the internal `"vellum"` channel exactly as it
|
|
658
|
+
// is on a remote one. Injection and rehydration share
|
|
659
|
+
// `canSeePersonalMemory`, so a daemon-restart reload of the same
|
|
660
|
+
// conversation produces an identical prefix.
|
|
663
661
|
mockConversation = defaultConv();
|
|
664
662
|
mockDbMessages = [
|
|
665
663
|
{
|
|
@@ -697,13 +695,8 @@ describe("loadFromDb metadata injection rehydration", () => {
|
|
|
697
695
|
const messages = conversation.getMessages();
|
|
698
696
|
|
|
699
697
|
expect(messages).toHaveLength(3);
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
type: "text",
|
|
703
|
-
text: "<info>\n## Essentials\n\nAlice prefers VS Code.\n</info>",
|
|
704
|
-
},
|
|
705
|
-
{ type: "text", text: "First" },
|
|
706
|
-
]);
|
|
698
|
+
// The persisted block stays out: the console is a surface, not an actor.
|
|
699
|
+
expect(messages[0].content).toEqual([{ type: "text", text: "First" }]);
|
|
707
700
|
});
|
|
708
701
|
|
|
709
702
|
test("rehydration order matches injection-time order for the full personal-memory set", async () => {
|
|
@@ -1047,12 +1040,14 @@ describe("loadFromDb metadata injection rehydration", () => {
|
|
|
1047
1040
|
expect(messages[2].content).toEqual([{ type: "text", text: "Tail" }]);
|
|
1048
1041
|
});
|
|
1049
1042
|
|
|
1050
|
-
test("ensureActorScopedHistory reloads when
|
|
1051
|
-
//
|
|
1052
|
-
// `loadFromDb
|
|
1053
|
-
//
|
|
1054
|
-
//
|
|
1055
|
-
//
|
|
1043
|
+
test("ensureActorScopedHistory reloads when the actor's memory visibility changes", async () => {
|
|
1044
|
+
// Reuse of a resident conversation by a different actor must re-run
|
|
1045
|
+
// `loadFromDb`, or the incoming actor inherits the previous actor's
|
|
1046
|
+
// transcript with its personal-memory blocks still spliced in.
|
|
1047
|
+
//
|
|
1048
|
+
// Driven by a trust-class change rather than a channel change: personal
|
|
1049
|
+
// memory is keyed on the actor, so two channels at the same class are the
|
|
1050
|
+
// same view and reloading between them would prove nothing.
|
|
1056
1051
|
mockConversation = defaultConv();
|
|
1057
1052
|
mockDbMessages = [
|
|
1058
1053
|
{
|
|
@@ -1080,10 +1075,9 @@ describe("loadFromDb metadata injection rehydration", () => {
|
|
|
1080
1075
|
];
|
|
1081
1076
|
|
|
1082
1077
|
const conversation = makeConversation();
|
|
1083
|
-
// First load:
|
|
1084
|
-
// exposed via `shouldExposePersonalMemory({sourceChannel: "vellum", ...})`.
|
|
1078
|
+
// First load: guardian actor → personal memory exposed.
|
|
1085
1079
|
conversation.setTrustContext({
|
|
1086
|
-
trustClass: "
|
|
1080
|
+
trustClass: "guardian",
|
|
1087
1081
|
sourceChannel: "vellum",
|
|
1088
1082
|
});
|
|
1089
1083
|
await conversation.ensureActorScopedHistory();
|
|
@@ -1095,8 +1089,8 @@ describe("loadFromDb metadata injection rehydration", () => {
|
|
|
1095
1089
|
{ type: "text", text: "First" },
|
|
1096
1090
|
]);
|
|
1097
1091
|
|
|
1098
|
-
// Reuse
|
|
1099
|
-
//
|
|
1092
|
+
// Reuse by a contact. The cache must invalidate and trigger a reload that
|
|
1093
|
+
// strips the personal-memory block.
|
|
1100
1094
|
conversation.setTrustContext({
|
|
1101
1095
|
trustClass: "trusted_contact",
|
|
1102
1096
|
sourceChannel: "telegram",
|