@vellumai/assistant 0.7.2 → 0.8.0
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 +45 -29
- package/Dockerfile +1 -0
- package/__tests__/permissions/gateway-threshold-reader.test.ts +236 -9
- package/bun.lock +3 -0
- package/docs/architecture/memory.md +5 -2
- package/knip.json +1 -0
- package/node_modules/@vellumai/gateway-client/src/ipc-client.ts +13 -4
- package/node_modules/@vellumai/ipc-server-utils/bun.lock +24 -0
- package/node_modules/@vellumai/ipc-server-utils/package.json +18 -0
- package/node_modules/@vellumai/ipc-server-utils/src/index.ts +6 -0
- package/node_modules/@vellumai/ipc-server-utils/src/socket-watchdog.test.ts +430 -0
- package/node_modules/@vellumai/ipc-server-utils/src/socket-watchdog.ts +221 -0
- package/node_modules/@vellumai/ipc-server-utils/tsconfig.json +20 -0
- package/node_modules/@vellumai/skill-host-contracts/src/assistant-event.ts +0 -9
- package/node_modules/@vellumai/slack-text/src/index.test.ts +18 -35
- package/node_modules/@vellumai/slack-text/src/index.ts +2 -48
- package/openapi.yaml +470 -25
- package/package.json +3 -1
- package/src/__tests__/annotate-risk-options.test.ts +291 -0
- package/src/__tests__/app-control-flow.test.ts +21 -11
- package/src/__tests__/approval-cascade.test.ts +8 -16
- package/src/__tests__/approval-routes-http.test.ts +6 -0
- package/src/__tests__/assistant-event-hub.test.ts +48 -0
- package/src/__tests__/assistant-event.test.ts +0 -10
- package/src/__tests__/assistant-events-sse-hardening.test.ts +2 -7
- package/src/__tests__/assistant-feature-flags-integration.test.ts +18 -0
- package/src/__tests__/auto-analysis-end-to-end.test.ts +48 -0
- package/src/__tests__/background-workers-disk-pressure.test.ts +268 -0
- package/src/__tests__/call-constants.test.ts +10 -1
- package/src/__tests__/call-controller.test.ts +127 -0
- package/src/__tests__/call-conversation-messages.test.ts +8 -2
- package/src/__tests__/channel-inbound-disk-pressure.test.ts +537 -0
- package/src/__tests__/channel-readiness-service.test.ts +4 -2
- package/src/__tests__/cli-memory-v2-reembed-skills.test.ts +58 -28
- package/src/__tests__/config-loader-backfill.test.ts +379 -0
- package/src/__tests__/config-loader-platform-defaults.test.ts +284 -1
- package/src/__tests__/config-schema.test.ts +1 -0
- package/src/__tests__/config-watcher-cleanup-throttle.test.ts +18 -9
- package/src/__tests__/config-watcher.test.ts +140 -69
- package/src/__tests__/context-search-agent-runner.test.ts +61 -3
- package/src/__tests__/context-search-conversations-source.test.ts +0 -24
- package/src/__tests__/context-search-fanout.test.ts +0 -1
- package/src/__tests__/context-search-memory-source.test.ts +6 -33
- package/src/__tests__/context-search-memory-v2-source.test.ts +0 -2
- package/src/__tests__/context-search-pkb-source.test.ts +12 -7
- package/src/__tests__/context-search-workspace-source.test.ts +0 -1
- package/src/__tests__/conversation-abort-tool-results.test.ts +1 -0
- package/src/__tests__/conversation-agent-loop-disk-pressure.test.ts +223 -0
- package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +1 -1
- package/src/__tests__/conversation-agent-loop-overflow.test.ts +1 -1
- package/src/__tests__/conversation-agent-loop.test.ts +457 -8
- package/src/__tests__/conversation-confirmation-signals.test.ts +5 -13
- package/src/__tests__/conversation-error.test.ts +150 -3
- package/src/__tests__/conversation-init.benchmark.test.ts +1 -1
- package/src/__tests__/conversation-process-callsite.test.ts +38 -0
- package/src/__tests__/conversation-provider-retry-repair.test.ts +1 -0
- package/src/__tests__/conversation-runtime-assembly.test.ts +74 -0
- package/src/__tests__/conversation-slash-unknown.test.ts +1 -0
- package/src/__tests__/conversation-speed-override.test.ts +0 -3
- package/src/__tests__/conversation-store.test.ts +0 -18
- package/src/__tests__/conversation-surfaces-action-delivery.test.ts +170 -9
- package/src/__tests__/conversation-surfaces-app-control.test.ts +15 -4
- package/src/__tests__/conversation-surfaces-data-persist.test.ts +476 -0
- package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +61 -5
- package/src/__tests__/conversation-workspace-injection.test.ts +1 -1
- package/src/__tests__/conversation-workspace-tool-tracking.test.ts +1 -1
- package/src/__tests__/credentials-cli.test.ts +7 -0
- package/src/__tests__/cu-unified-flow.test.ts +176 -10
- package/src/__tests__/date-context.test.ts +164 -2
- package/src/__tests__/disk-pressure-guard.test.ts +262 -0
- package/src/__tests__/disk-pressure-lifecycle.test.ts +168 -0
- package/src/__tests__/disk-pressure-policy.test.ts +241 -0
- package/src/__tests__/disk-pressure-routes.test.ts +379 -0
- package/src/__tests__/disk-pressure-tools.test.ts +277 -0
- package/src/__tests__/disk-usage.test.ts +150 -0
- package/src/__tests__/events-client-registration.test.ts +52 -0
- package/src/__tests__/events-dev-bypass-actor.test.ts +162 -0
- package/src/__tests__/file-write-tool.test.ts +4 -10
- package/src/__tests__/filing-service.test.ts +2 -20
- package/src/__tests__/handlers-skills-memory-v2-reseed.test.ts +10 -26
- package/src/__tests__/heartbeat-disk-pressure.test.ts +183 -0
- package/src/__tests__/heartbeat-service.test.ts +260 -11
- package/src/__tests__/host-app-control-proxy.test.ts +195 -25
- package/src/__tests__/host-bash-proxy.test.ts +227 -34
- package/src/__tests__/host-bash-routes.test.ts +178 -13
- package/src/__tests__/host-cu-proxy.test.ts +210 -3
- package/src/__tests__/host-cu-routes-targeted.test.ts +141 -12
- package/src/__tests__/host-file-proxy-targeted.test.ts +48 -9
- package/src/__tests__/host-file-proxy.test.ts +268 -6
- package/src/__tests__/host-file-routes-targeted.test.ts +175 -17
- package/src/__tests__/host-transfer-proxy-targeted.test.ts +408 -59
- package/src/__tests__/host-transfer-routes-targeted.test.ts +232 -17
- package/src/__tests__/http-user-message-parity.test.ts +107 -1
- package/src/__tests__/injector-chain.test.ts +36 -16
- package/src/__tests__/injector-disk-pressure.test.ts +224 -0
- package/src/__tests__/injector-pkb-v2-silenced.test.ts +10 -7
- package/src/__tests__/lifecycle-memory-v2-seed.test.ts +154 -67
- package/src/__tests__/managed-profile-guard.test.ts +18 -0
- package/src/__tests__/mcp-abort-signal.test.ts +130 -0
- package/src/__tests__/memory-admin-recall.test.ts +3 -11
- package/src/__tests__/memory-retrieval-pipeline.test.ts +22 -1
- package/src/__tests__/normalize-onboarding.test.ts +180 -0
- package/src/__tests__/notification-decision-fallback.test.ts +91 -0
- package/src/__tests__/notification-decision-strategy.test.ts +22 -0
- package/src/__tests__/oauth-cli.test.ts +121 -0
- package/src/__tests__/oauth-connect-routes.test.ts +316 -0
- package/src/__tests__/oauth-provider-seed-logos.test.ts +24 -2
- package/src/__tests__/onboarding-persona-write.test.ts +308 -0
- package/src/__tests__/openai-provider.test.ts +45 -8
- package/src/__tests__/persist-onboarding-artifacts.test.ts +44 -64
- package/src/__tests__/platform-callback-registration.test.ts +21 -4
- package/src/__tests__/platform.test.ts +2 -1
- package/src/__tests__/playbook-execution.test.ts +0 -43
- package/src/__tests__/plugin-tool-contribution.test.ts +47 -0
- package/src/__tests__/prechat-onboarding-contract.test.ts +214 -27
- package/src/__tests__/provider-tool-name.test.ts +23 -0
- package/src/__tests__/relay-server.test.ts +60 -5
- package/src/__tests__/runtime-events-sse.test.ts +4 -8
- package/src/__tests__/scheduler-disk-pressure.test.ts +148 -0
- package/src/__tests__/secret-ingress-http.test.ts +0 -1
- package/src/__tests__/secret-prompt-log-hygiene.test.ts +7 -5
- package/src/__tests__/secret-prompter-channel-fallback.test.ts +7 -5
- package/src/__tests__/secret-response-routing.test.ts +7 -5
- package/src/__tests__/server-history-render.test.ts +82 -0
- package/src/__tests__/skill-include-graph.test.ts +31 -0
- package/src/__tests__/skill-load-tool.test.ts +44 -16
- package/src/__tests__/skills.test.ts +39 -0
- package/src/__tests__/suggestion-routes.test.ts +46 -0
- package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +0 -42
- package/src/__tests__/tool-executor.test.ts +155 -0
- package/src/__tests__/twilio-validation.test.ts +2 -2
- package/src/__tests__/voice-session-bridge.test.ts +3 -0
- package/src/__tests__/workspace-migration-065-bump-stale-heartbeat-interval.test.ts +122 -0
- package/src/__tests__/workspace-migration-066-seed-heartbeat-callsite-cost-default.test.ts +285 -0
- package/src/__tests__/workspace-migration-068-release-notes-local-timezone.test.ts +90 -0
- package/src/__tests__/workspace-migration-069-seed-onboarding-threads.test.ts +120 -0
- package/src/__tests__/workspace-migration-071-remove-safe-storage-release-note.test.ts +206 -0
- package/src/__tests__/workspace-migration-safe-storage-limits-release.test.ts +78 -0
- package/src/agent/loop.ts +11 -0
- package/src/approvals/guardian-request-resolvers.ts +3 -32
- package/src/backup/snapshot-lock.ts +2 -27
- package/src/bundler/compiler-tools.ts +3 -2
- package/src/calls/call-constants.ts +5 -8
- package/src/calls/call-controller.ts +130 -67
- package/src/calls/call-conversation-messages.ts +46 -10
- package/src/calls/relay-server.ts +7 -1
- package/src/calls/voice-session-bridge.ts +1 -1
- package/src/cli/commands/__tests__/webhooks.test.ts +0 -4
- package/src/cli/commands/bash.ts +35 -108
- package/src/cli/commands/contacts.ts +64 -25
- package/src/cli/commands/credentials.ts +56 -0
- package/src/cli/commands/memory-v2.ts +11 -10
- package/src/cli/commands/oauth/__tests__/connect.test.ts +401 -219
- package/src/cli/commands/oauth/connect.ts +124 -40
- package/src/cli/commands/platform/__tests__/callback-routes-list.test.ts +0 -3
- package/src/cli/commands/platform/__tests__/connect.test.ts +7 -1
- package/src/cli/commands/platform/__tests__/disconnect.test.ts +7 -1
- package/src/cli/commands/platform/__tests__/status.test.ts +103 -6
- package/src/cli/commands/platform/index.ts +16 -7
- package/src/cli/commands/status.ts +57 -0
- package/src/cli/program.ts +4 -2
- package/src/config/assistant-feature-flags.ts +13 -3
- package/src/config/bundled-skills/app-builder/SKILL.md +1 -3
- package/src/config/bundled-skills/messaging/tools/messaging-analyze-style.ts +4 -3
- package/src/config/bundled-skills/phone-calls/references/TROUBLESHOOTING.md +13 -7
- package/src/config/bundled-skills/playbooks/tools/playbook-create.ts +2 -2
- package/src/config/bundled-skills/playbooks/tools/playbook-delete.ts +2 -2
- package/src/config/bundled-skills/playbooks/tools/playbook-list.ts +2 -2
- package/src/config/bundled-skills/playbooks/tools/playbook-update.ts +2 -2
- package/src/config/env.ts +0 -8
- package/src/config/feature-flag-registry.json +13 -5
- package/src/config/loader.ts +199 -27
- package/src/config/schemas/__tests__/memory-v2.test.ts +10 -5
- package/src/config/schemas/call-site-catalog.ts +14 -0
- package/src/config/schemas/channels.ts +0 -5
- package/src/config/schemas/heartbeat.ts +1 -1
- package/src/config/schemas/llm.ts +2 -0
- package/src/config/schemas/memory-lifecycle.ts +13 -0
- package/src/config/schemas/memory-v2.ts +76 -12
- package/src/config/schemas/platform.ts +43 -3
- package/src/config/schemas/services.ts +28 -0
- package/src/config/seed-inference-profiles.ts +230 -33
- package/src/contacts/contact-store.ts +0 -25
- package/src/daemon/__tests__/conversation-lifecycle-auto-analyze.test.ts +32 -0
- package/src/daemon/__tests__/conversation-tool-setup.test.ts +86 -25
- package/src/daemon/assistant-attachments.ts +4 -4
- package/src/daemon/config-watcher.ts +85 -57
- package/src/daemon/conversation-agent-loop-handlers.ts +38 -0
- package/src/daemon/conversation-agent-loop.ts +183 -43
- package/src/daemon/conversation-error.ts +87 -15
- package/src/daemon/conversation-lifecycle.ts +22 -10
- package/src/daemon/conversation-process.ts +8 -0
- package/src/daemon/conversation-runtime-assembly.ts +26 -0
- package/src/daemon/conversation-store.ts +2 -2
- package/src/daemon/conversation-surfaces.ts +211 -29
- package/src/daemon/conversation-tool-setup.ts +66 -19
- package/src/daemon/conversation.ts +18 -23
- package/src/daemon/date-context.ts +71 -22
- package/src/daemon/disk-pressure-background-gate.ts +73 -0
- package/src/daemon/disk-pressure-guard.ts +343 -0
- package/src/daemon/disk-pressure-policy.ts +163 -0
- package/src/daemon/handlers/shared.ts +26 -1
- package/src/daemon/handlers/skills.ts +3 -4
- package/src/daemon/host-app-control-proxy.ts +137 -41
- package/src/daemon/host-bash-proxy.ts +47 -22
- package/src/daemon/host-browser-proxy.ts +1 -1
- package/src/daemon/host-cu-proxy.ts +50 -4
- package/src/daemon/host-file-proxy.ts +44 -8
- package/src/daemon/host-transfer-proxy.ts +97 -6
- package/src/daemon/lifecycle.ts +167 -101
- package/src/daemon/meet-host-supervisor.ts +4 -4
- package/src/daemon/meet-manifest-loader.ts +0 -1
- package/src/daemon/memory-v2-startup.ts +66 -15
- package/src/daemon/message-protocol.ts +3 -0
- package/src/daemon/message-types/conversations.ts +4 -0
- package/src/daemon/message-types/disk-pressure.ts +9 -0
- package/src/daemon/message-types/messages.ts +22 -1
- package/src/daemon/profiler-run-store.ts +5 -5
- package/src/daemon/tool-setup-types.ts +2 -2
- package/src/documents/document-store.ts +119 -0
- package/src/filing/filing-service.ts +29 -5
- package/src/heartbeat/__tests__/heartbeat-feed-event.test.ts +9 -16
- package/src/heartbeat/__tests__/heartbeat-run-store.test.ts +36 -0
- package/src/heartbeat/heartbeat-run-store.ts +13 -0
- package/src/heartbeat/heartbeat-service.ts +205 -31
- package/src/home/feed-scheduler.ts +18 -0
- package/src/inbound/platform-callback-registration.ts +8 -15
- package/src/ipc/__tests__/clients-list-ipc.test.ts +169 -0
- package/src/ipc/assistant-server.ts +149 -38
- package/src/ipc/gateway-client.ts +37 -3
- package/src/ipc/skill-server.ts +99 -42
- package/src/live-voice/live-voice-archive.ts +4 -4
- package/src/live-voice/protocol.ts +5 -7
- package/src/media/image-service.ts +1 -7
- package/src/memory/__tests__/fixtures/memory-v2-activation-fixtures.ts +21 -13
- package/src/memory/__tests__/jobs-worker-v2-schedule.test.ts +34 -51
- package/src/memory/__tests__/memory-v2-activation-log-store.test.ts +0 -6
- package/src/memory/__tests__/memory-v2-concept-frequency.test.ts +272 -0
- package/src/memory/admin.ts +5 -9
- package/src/memory/context-search/agent-runner.ts +19 -2
- package/src/memory/context-search/sources/conversations.ts +2 -11
- package/src/memory/context-search/sources/memory-v2.ts +1 -16
- package/src/memory/context-search/sources/memory.ts +2 -3
- package/src/memory/context-search/sources/pkb.ts +2 -3
- package/src/memory/context-search/types.ts +0 -1
- package/src/memory/conversation-crud.ts +4 -12
- package/src/memory/db-init.ts +2 -0
- package/src/memory/embedding-runtime-manager.ts +119 -5
- package/src/memory/graph/__tests__/conversation-graph-memory-v2-routing.test.ts +136 -82
- package/src/memory/graph/__tests__/handle-remember-v2.test.ts +11 -26
- package/src/memory/graph/conversation-graph-memory.ts +72 -61
- package/src/memory/graph/extraction.ts +1 -3
- package/src/memory/graph/graph-search.test.ts +11 -67
- package/src/memory/graph/graph-search.ts +4 -24
- package/src/memory/graph/retriever.test.ts +12 -1
- package/src/memory/graph/retriever.ts +10 -15
- package/src/memory/graph/tool-handlers.ts +3 -4
- package/src/memory/graph/tools.ts +4 -4
- package/src/memory/indexer.ts +53 -45
- package/src/memory/job-handlers/backfill.ts +2 -11
- package/src/memory/job-handlers/cleanup.ts +43 -0
- package/src/memory/job-handlers/embedding.ts +6 -8
- package/src/memory/job-handlers/summarization.ts +2 -7
- package/src/memory/jobs/__tests__/embed-concept-page.test.ts +116 -0
- package/src/memory/jobs/embed-concept-page.ts +223 -87
- package/src/memory/jobs-store.ts +48 -0
- package/src/memory/jobs-worker.ts +85 -43
- package/src/memory/memory-v2-activation-log-store.ts +32 -14
- package/src/memory/memory-v2-concept-frequency.ts +169 -0
- package/src/memory/migrations/239-trace-events-created-at-index.ts +18 -0
- package/src/memory/migrations/index.ts +1 -0
- package/src/memory/pkb/pkb-search.test.ts +7 -0
- package/src/memory/pkb/pkb-search.ts +4 -5
- package/src/memory/qdrant-client.ts +3 -13
- package/src/memory/rerank-local.ts +374 -0
- package/src/memory/search/semantic.ts +10 -72
- package/src/memory/trace-event-store.ts +1 -17
- package/src/memory/v2/__tests__/activation.test.ts +346 -255
- package/src/memory/v2/__tests__/consolidation-job.test.ts +61 -40
- package/src/memory/v2/__tests__/injection.test.ts +297 -190
- package/src/memory/v2/__tests__/prompts-consolidation.test.ts +61 -2
- package/src/memory/v2/__tests__/qdrant.test.ts +326 -9
- package/src/memory/v2/__tests__/reranker.test.ts +338 -0
- package/src/memory/v2/__tests__/sim.test.ts +113 -196
- package/src/memory/v2/__tests__/skill-store.test.ts +71 -65
- package/src/memory/v2/__tests__/static-context.test.ts +77 -14
- package/src/memory/v2/__tests__/sweep-job.test.ts +19 -33
- package/src/memory/v2/activation.ts +149 -156
- package/src/memory/v2/consolidation-job.ts +69 -20
- package/src/memory/v2/injection.ts +75 -68
- package/src/memory/v2/page-store.ts +39 -0
- package/src/memory/v2/prompts/consolidation.ts +41 -1
- package/src/memory/v2/qdrant.ts +306 -46
- package/src/memory/v2/reranker.ts +177 -0
- package/src/memory/v2/sim.ts +77 -110
- package/src/memory/v2/skill-content.ts +4 -3
- package/src/memory/v2/skill-store.ts +82 -59
- package/src/memory/v2/static-context.ts +26 -8
- package/src/memory/v2/sweep-job.ts +5 -6
- package/src/memory/v2/types.ts +17 -10
- package/src/notifications/copy-composer.ts +47 -0
- package/src/notifications/decision-engine.ts +46 -0
- package/src/notifications/signal.ts +4 -0
- package/src/oauth/AGENTS.md +3 -1
- package/src/oauth/__tests__/oauth-connect-state.test.ts +137 -0
- package/src/oauth/connect-orchestrator.ts +2 -0
- package/src/oauth/connection-resolver.test.ts +66 -1
- package/src/oauth/connection-resolver.ts +55 -1
- package/src/oauth/oauth-connect-state.ts +77 -0
- package/src/oauth/seed-providers.ts +58 -1
- package/src/permissions/gateway-threshold-reader.ts +116 -8
- package/src/permissions/prompter.ts +86 -96
- package/src/permissions/secret-prompter.ts +31 -31
- package/src/plugins/defaults/injectors.ts +36 -4
- package/src/plugins/defaults/memory-retrieval.ts +5 -6
- package/src/plugins/types.ts +7 -0
- package/src/proactive-artifact/aux-message-injector.ts +74 -0
- package/src/proactive-artifact/decision.test.ts +226 -0
- package/src/proactive-artifact/decision.ts +165 -0
- package/src/proactive-artifact/index.ts +7 -0
- package/src/proactive-artifact/job.test.ts +914 -0
- package/src/proactive-artifact/job.ts +366 -0
- package/src/proactive-artifact/message-copy.ts +58 -0
- package/src/proactive-artifact/trigger-state.test.ts +277 -0
- package/src/proactive-artifact/trigger-state.ts +119 -0
- package/src/prompts/normalize-onboarding.ts +80 -0
- package/src/prompts/persona-resolver.ts +101 -9
- package/src/prompts/system-prompt.ts +21 -7
- package/src/prompts/templates/BOOTSTRAP.md +13 -5
- package/src/prompts/templates/SOUL.md +13 -28
- package/src/providers/__tests__/retry-callsite.test.ts +222 -1
- package/src/providers/model-intents.ts +7 -0
- package/src/providers/openrouter/client.ts +8 -0
- package/src/providers/retry.ts +50 -0
- package/src/providers/types.ts +1 -0
- package/src/runtime/__tests__/agent-wake.test.ts +456 -3
- package/src/runtime/agent-wake.ts +238 -100
- package/src/runtime/assistant-event-hub.ts +36 -6
- package/src/runtime/assistant-event.ts +0 -1
- package/src/runtime/auth/__tests__/route-policy.test.ts +64 -0
- package/src/runtime/auth/route-policy.ts +15 -1
- package/src/runtime/auth/same-actor.ts +216 -0
- package/src/runtime/channel-approvals.ts +3 -2
- package/src/runtime/channel-retry-sweep.ts +65 -1
- package/src/runtime/local-actor-identity.ts +52 -11
- package/src/runtime/pending-interactions.ts +27 -15
- package/src/runtime/routes/__tests__/client-routes.test.ts +155 -0
- package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +0 -5
- package/src/runtime/routes/__tests__/heartbeat-routes.test.ts +1 -1
- package/src/runtime/routes/__tests__/memory-v2-routes.test.ts +147 -0
- package/src/runtime/routes/approval-routes.ts +7 -3
- package/src/runtime/routes/client-routes.ts +20 -2
- package/src/runtime/routes/consolidation-routes.ts +8 -9
- package/src/runtime/routes/contact-routes.ts +0 -25
- package/src/runtime/routes/conversation-query-routes.ts +44 -1
- package/src/runtime/routes/conversation-routes.ts +35 -26
- package/src/runtime/routes/debug-bash-routes.ts +165 -0
- package/src/runtime/routes/disk-pressure-routes.ts +121 -0
- package/src/runtime/routes/document-pdf-renderer.ts +6 -2
- package/src/runtime/routes/documents-routes.ts +2 -75
- package/src/runtime/routes/events-routes.ts +41 -9
- package/src/runtime/routes/filing-routes.ts +2 -3
- package/src/runtime/routes/host-bash-routes.ts +23 -3
- package/src/runtime/routes/host-cu-routes.ts +33 -6
- package/src/runtime/routes/host-file-routes.ts +32 -6
- package/src/runtime/routes/host-transfer-routes.ts +79 -16
- package/src/runtime/routes/identity-routes.ts +7 -138
- package/src/runtime/routes/inbound-message-handler.ts +77 -12
- package/src/runtime/routes/index.ts +6 -0
- package/src/runtime/routes/memory-item-routes.test.ts +37 -17
- package/src/runtime/routes/memory-item-routes.ts +5 -6
- package/src/runtime/routes/memory-v2-routes.ts +136 -17
- package/src/runtime/routes/oauth-connect-routes.ts +153 -0
- package/src/runtime/verification-outbound-actions.ts +4 -4
- package/src/schedule/run-script.ts +37 -5
- package/src/schedule/scheduler.ts +20 -1
- package/src/security/encrypted-store.ts +2 -0
- package/src/security/secure-keys.ts +55 -0
- package/src/skills/include-graph.ts +35 -13
- package/src/skills/remote-skill-policy.ts +4 -10
- package/src/subagent/index.ts +1 -7
- package/src/subagent/manager.ts +1 -15
- package/src/tasks/task-runner.ts +0 -1
- package/src/tasks/task-store.ts +0 -3
- package/src/tools/background-tool-registry.ts +17 -3
- package/src/tools/document/document-tool.ts +20 -0
- package/src/tools/executor.ts +18 -2
- package/src/tools/host-filesystem/edit.test.ts +151 -0
- package/src/tools/host-filesystem/edit.ts +43 -1
- package/src/tools/host-filesystem/read.test.ts +129 -0
- package/src/tools/host-filesystem/read.ts +43 -1
- package/src/tools/host-filesystem/transfer.test.ts +127 -2
- package/src/tools/host-filesystem/transfer.ts +56 -11
- package/src/tools/host-filesystem/write.test.ts +134 -0
- package/src/tools/host-filesystem/write.ts +43 -1
- package/src/tools/host-terminal/host-shell.ts +13 -6
- package/src/tools/mcp/mcp-tool-factory.ts +2 -1
- package/src/tools/memory/register.test.ts +14 -9
- package/src/tools/memory/register.ts +1 -2
- package/src/tools/permission-checker.ts +15 -0
- package/src/tools/provider-tool-name.ts +28 -0
- package/src/tools/registry.ts +30 -9
- package/src/tools/skills/load.ts +24 -20
- package/src/tools/terminal/shell.ts +9 -1
- package/src/tools/tool-approval-handler.ts +31 -6
- package/src/tools/tool-name-aliases.ts +19 -0
- package/src/tools/types.ts +43 -3
- package/src/tts/provider-catalog.ts +3 -5
- package/src/util/disk-usage.ts +138 -0
- package/src/util/platform.ts +21 -11
- package/src/util/process-liveness.ts +26 -0
- package/src/workspace/heartbeat-service.ts +19 -0
- package/src/workspace/migrations/065-bump-stale-heartbeat-interval.ts +60 -0
- package/src/workspace/migrations/066-seed-heartbeat-callsite-cost-default.ts +146 -0
- package/src/workspace/migrations/067-release-notes-safe-storage-limits.ts +14 -0
- package/src/workspace/migrations/068-release-notes-local-timezone.ts +65 -0
- package/src/workspace/migrations/069-seed-onboarding-threads.ts +28 -0
- package/src/workspace/migrations/070-memory-v2-summary-schema-rebuild.ts +31 -0
- package/src/workspace/migrations/071-remove-safe-storage-release-note.ts +111 -0
- package/src/workspace/migrations/registry.ts +14 -0
- package/src/__tests__/conversation-tool-setup-memory-scope.test.ts +0 -167
- package/src/memory/v2/__tests__/skill-qdrant.test.ts +0 -657
- package/src/memory/v2/skill-qdrant.ts +0 -404
- package/src/signals/bash.ts +0 -198
|
@@ -154,6 +154,88 @@ describe("renderHistoryContent", () => {
|
|
|
154
154
|
]);
|
|
155
155
|
});
|
|
156
156
|
|
|
157
|
+
// ── Persisted risk-option ladders (Phase B of conflation track) ─────────────
|
|
158
|
+
|
|
159
|
+
test("hydrates persisted _risk*Options annotations onto tool calls", () => {
|
|
160
|
+
// Mirrors what `annotatePersistedAssistantMessage` writes to the DB so the
|
|
161
|
+
// rule editor's chip ladder survives chat-history reload. Without these,
|
|
162
|
+
// hydrated chips fall back to the synthesized `*` allowlist (see web's
|
|
163
|
+
// `synthesizeFallbackOption` in RuleEditorModal.tsx).
|
|
164
|
+
const scopeOptions = [
|
|
165
|
+
{ pattern: "exact", label: "exact: rm -rf /tmp" },
|
|
166
|
+
{ pattern: "by-program", label: "All rm" },
|
|
167
|
+
];
|
|
168
|
+
const allowlistOptions = [
|
|
169
|
+
{ label: "exact", description: "exact match", pattern: "rm -rf /tmp" },
|
|
170
|
+
{ label: "All rm", description: "All rm commands", pattern: "rm *" },
|
|
171
|
+
];
|
|
172
|
+
const directoryScopeOptions = [
|
|
173
|
+
{ scope: "/Users/me/code", label: "in code/" },
|
|
174
|
+
{ scope: "everywhere", label: "Everywhere" },
|
|
175
|
+
];
|
|
176
|
+
|
|
177
|
+
const output = renderHistoryContent([
|
|
178
|
+
{
|
|
179
|
+
type: "tool_use",
|
|
180
|
+
id: "tu_1",
|
|
181
|
+
name: "bash",
|
|
182
|
+
input: { command: "rm -rf /tmp" },
|
|
183
|
+
_riskLevel: "high",
|
|
184
|
+
_matchedTrustRuleId: "rule_42",
|
|
185
|
+
_riskScopeOptions: scopeOptions,
|
|
186
|
+
_riskAllowlistOptions: allowlistOptions,
|
|
187
|
+
_riskDirectoryScopeOptions: directoryScopeOptions,
|
|
188
|
+
},
|
|
189
|
+
]);
|
|
190
|
+
|
|
191
|
+
const [entry] = output.toolCalls;
|
|
192
|
+
expect(entry.riskLevel).toBe("high");
|
|
193
|
+
expect(entry.matchedTrustRuleId).toBe("rule_42");
|
|
194
|
+
expect(entry.riskScopeOptions).toEqual(scopeOptions);
|
|
195
|
+
expect(entry.riskAllowlistOptions).toEqual(allowlistOptions);
|
|
196
|
+
expect(entry.riskDirectoryScopeOptions).toEqual(directoryScopeOptions);
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
test("ignores non-array _risk*Options annotations", () => {
|
|
200
|
+
// Defensive: a malformed persisted block should not throw or coerce.
|
|
201
|
+
const output = renderHistoryContent([
|
|
202
|
+
{
|
|
203
|
+
type: "tool_use",
|
|
204
|
+
id: "tu_1",
|
|
205
|
+
name: "bash",
|
|
206
|
+
input: { command: "ls" },
|
|
207
|
+
_riskLevel: "low",
|
|
208
|
+
_riskScopeOptions: "not an array",
|
|
209
|
+
_riskAllowlistOptions: { not: "an array" },
|
|
210
|
+
_riskDirectoryScopeOptions: 42,
|
|
211
|
+
},
|
|
212
|
+
]);
|
|
213
|
+
|
|
214
|
+
const [entry] = output.toolCalls;
|
|
215
|
+
expect(entry.riskLevel).toBe("low");
|
|
216
|
+
expect(entry.riskScopeOptions).toBeUndefined();
|
|
217
|
+
expect(entry.riskAllowlistOptions).toBeUndefined();
|
|
218
|
+
expect(entry.riskDirectoryScopeOptions).toBeUndefined();
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
test("omits absent _risk*Options annotations", () => {
|
|
222
|
+
const output = renderHistoryContent([
|
|
223
|
+
{
|
|
224
|
+
type: "tool_use",
|
|
225
|
+
id: "tu_1",
|
|
226
|
+
name: "bash",
|
|
227
|
+
input: { command: "ls" },
|
|
228
|
+
_riskLevel: "low",
|
|
229
|
+
},
|
|
230
|
+
]);
|
|
231
|
+
|
|
232
|
+
const [entry] = output.toolCalls;
|
|
233
|
+
expect(entry.riskLevel).toBe("low");
|
|
234
|
+
expect(entry.riskScopeOptions).toBeUndefined();
|
|
235
|
+
expect(entry.riskAllowlistOptions).toBeUndefined();
|
|
236
|
+
expect(entry.riskDirectoryScopeOptions).toBeUndefined();
|
|
237
|
+
});
|
|
238
|
+
|
|
157
239
|
test("handles mixed text and tool blocks", () => {
|
|
158
240
|
const output = renderHistoryContent([
|
|
159
241
|
{ type: "text", text: "Let me look that up." },
|
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
getImmediateChildren,
|
|
7
7
|
indexCatalogById,
|
|
8
8
|
traverseIncludes,
|
|
9
|
+
validateIncludeCycles,
|
|
9
10
|
validateIncludes,
|
|
10
11
|
} from "../skills/include-graph.js";
|
|
11
12
|
|
|
@@ -299,6 +300,36 @@ describe("validateIncludes — cycle detection", () => {
|
|
|
299
300
|
});
|
|
300
301
|
});
|
|
301
302
|
|
|
303
|
+
describe("validateIncludeCycles", () => {
|
|
304
|
+
test("skips missing children while still detecting available cycles", () => {
|
|
305
|
+
const catalog = [
|
|
306
|
+
makeSkill("root", ["missing", "a"]),
|
|
307
|
+
makeSkill("a", ["b"]),
|
|
308
|
+
makeSkill("b", ["a"]),
|
|
309
|
+
];
|
|
310
|
+
const index = indexCatalogById(catalog);
|
|
311
|
+
|
|
312
|
+
const result = validateIncludeCycles("root", index);
|
|
313
|
+
|
|
314
|
+
expect(result.ok).toBe(false);
|
|
315
|
+
if (!result.ok && result.error === "cycle") {
|
|
316
|
+
expect(result.cyclePath).toEqual(["a", "b", "a"]);
|
|
317
|
+
}
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
test("returns success when the only invalid edges are missing children", () => {
|
|
321
|
+
const catalog = [makeSkill("root", ["missing"])];
|
|
322
|
+
const index = indexCatalogById(catalog);
|
|
323
|
+
|
|
324
|
+
const result = validateIncludeCycles("root", index);
|
|
325
|
+
|
|
326
|
+
expect(result.ok).toBe(true);
|
|
327
|
+
if (result.ok) {
|
|
328
|
+
expect(result.visited).toEqual(["root"]);
|
|
329
|
+
}
|
|
330
|
+
});
|
|
331
|
+
});
|
|
332
|
+
|
|
302
333
|
describe("collectAllMissing", () => {
|
|
303
334
|
test("returns empty set when skill has no includes", () => {
|
|
304
335
|
const catalog = [makeSkill("root")];
|
|
@@ -266,17 +266,19 @@ describe("skill_load tool", () => {
|
|
|
266
266
|
expect(markers.length).toBe(1);
|
|
267
267
|
});
|
|
268
268
|
|
|
269
|
-
test("
|
|
269
|
+
test("continues when skill has missing include", async () => {
|
|
270
270
|
writeSkillWithIncludes("parent", "Parent", "Has missing child", "Body", [
|
|
271
271
|
"missing-child",
|
|
272
272
|
]);
|
|
273
273
|
writeFileSync(join(TEST_DIR, "skills", "SKILLS.md"), "- parent\n");
|
|
274
274
|
|
|
275
275
|
const result = await executeSkillLoad({ skill: "parent" });
|
|
276
|
-
expect(result.isError).toBe(
|
|
276
|
+
expect(result.isError).toBe(false);
|
|
277
|
+
expect(result.content).toContain("Skill: Parent");
|
|
278
|
+
expect(result.content).toContain("Suggested Included Skills (not loaded):");
|
|
277
279
|
expect(result.content).toContain("missing-child");
|
|
278
|
-
expect(result.content).toContain(
|
|
279
|
-
expect(result.content).not.toContain(
|
|
280
|
+
expect(result.content).toContain('<loaded_skill id="parent"');
|
|
281
|
+
expect(result.content).not.toContain('<loaded_skill id="missing-child"');
|
|
280
282
|
});
|
|
281
283
|
|
|
282
284
|
test("returns error when skill has circular include", async () => {
|
|
@@ -317,7 +319,7 @@ describe("skill_load tool", () => {
|
|
|
317
319
|
expect(result.content).toContain("<loaded_skill");
|
|
318
320
|
});
|
|
319
321
|
|
|
320
|
-
test("
|
|
322
|
+
test("missing include emits only the parent loaded_skill marker", async () => {
|
|
321
323
|
const skillDir = join(TEST_DIR, "skills", "marker-missing");
|
|
322
324
|
mkdirSync(skillDir, { recursive: true });
|
|
323
325
|
writeFileSync(
|
|
@@ -327,9 +329,13 @@ describe("skill_load tool", () => {
|
|
|
327
329
|
writeFileSync(join(TEST_DIR, "skills", "SKILLS.md"), "- marker-missing\n");
|
|
328
330
|
|
|
329
331
|
const result = await executeSkillLoad({ skill: "marker-missing" });
|
|
330
|
-
expect(result.isError).toBe(
|
|
331
|
-
expect(result.content).
|
|
332
|
-
expect(result.content).
|
|
332
|
+
expect(result.isError).toBe(false);
|
|
333
|
+
expect(result.content).toContain("Suggested Included Skills (not loaded):");
|
|
334
|
+
expect(result.content).toContain("nonexistent");
|
|
335
|
+
const markers = result.content.match(/<loaded_skill/g) || [];
|
|
336
|
+
expect(markers.length).toBe(1);
|
|
337
|
+
expect(result.content).toContain('<loaded_skill id="marker-missing"');
|
|
338
|
+
expect(result.content).not.toContain('<loaded_skill id="nonexistent"');
|
|
333
339
|
});
|
|
334
340
|
|
|
335
341
|
test("failed include validation (cycle) emits no loaded_skill marker", async () => {
|
|
@@ -365,6 +371,28 @@ describe("skill_load tool", () => {
|
|
|
365
371
|
expect(result.content).toContain("Skill: No Includes");
|
|
366
372
|
});
|
|
367
373
|
|
|
374
|
+
test("bundled app-builder loads when frontend-design is unavailable", async () => {
|
|
375
|
+
const result = await executeSkillLoad({ skill: "app-builder" });
|
|
376
|
+
|
|
377
|
+
expect(result.isError).toBe(false);
|
|
378
|
+
expect(result.content).toContain("Skill: App Builder");
|
|
379
|
+
expect(result.content).toContain("Suggested Included Skills (not loaded):");
|
|
380
|
+
expect(result.content).toContain("frontend-design");
|
|
381
|
+
expect(result.content).toContain('<loaded_skill id="app-builder"');
|
|
382
|
+
expect(result.content).not.toContain('<loaded_skill id="frontend-design"');
|
|
383
|
+
});
|
|
384
|
+
|
|
385
|
+
test("bundled phone-calls loads when setup includes are unavailable", async () => {
|
|
386
|
+
const result = await executeSkillLoad({ skill: "phone-calls" });
|
|
387
|
+
|
|
388
|
+
expect(result.isError).toBe(false);
|
|
389
|
+
expect(result.content).toContain("Skill: Phone Calls");
|
|
390
|
+
expect(result.content).toContain("Suggested Included Skills (not loaded):");
|
|
391
|
+
expect(result.content).toContain("twilio-setup");
|
|
392
|
+
expect(result.content).toContain('<loaded_skill id="phone-calls"');
|
|
393
|
+
expect(result.content).not.toContain('<loaded_skill id="twilio-setup"');
|
|
394
|
+
});
|
|
395
|
+
|
|
368
396
|
test("skill_load output includes immediate child metadata", async () => {
|
|
369
397
|
writeSkill("child-skill", "Child Skill", "A child skill", "Child body");
|
|
370
398
|
const parentDir = join(TEST_DIR, "skills", "parent-with-children");
|
|
@@ -883,7 +911,7 @@ describe("skill_load tool", () => {
|
|
|
883
911
|
expect(mockAutoInstall).toHaveBeenCalledWith("trans-c");
|
|
884
912
|
});
|
|
885
913
|
|
|
886
|
-
test("
|
|
914
|
+
test("continues when auto-install of missing include fails", async () => {
|
|
887
915
|
writeSkillWithIncludes(
|
|
888
916
|
"fail-parent",
|
|
889
917
|
"Fail Parent",
|
|
@@ -902,10 +930,12 @@ describe("skill_load tool", () => {
|
|
|
902
930
|
});
|
|
903
931
|
|
|
904
932
|
const result = await executeSkillLoad({ skill: "fail-parent" });
|
|
905
|
-
expect(result.isError).toBe(
|
|
933
|
+
expect(result.isError).toBe(false);
|
|
934
|
+
expect(result.content).toContain("Skill: Fail Parent");
|
|
935
|
+
expect(result.content).toContain("Suggested Included Skills (not loaded):");
|
|
906
936
|
expect(result.content).toContain("dep-x");
|
|
907
|
-
expect(result.content).toContain(
|
|
908
|
-
expect(result.content).not.toContain(
|
|
937
|
+
expect(result.content).toContain('<loaded_skill id="fail-parent"');
|
|
938
|
+
expect(result.content).not.toContain('<loaded_skill id="dep-x"');
|
|
909
939
|
});
|
|
910
940
|
|
|
911
941
|
test("stops after MAX_INSTALL_ROUNDS", async () => {
|
|
@@ -941,10 +971,8 @@ describe("skill_load tool", () => {
|
|
|
941
971
|
});
|
|
942
972
|
|
|
943
973
|
const result = await executeSkillLoad({ skill: "loop-root" });
|
|
944
|
-
|
|
945
|
-
expect(result.
|
|
946
|
-
expect(result.content).toContain("not found");
|
|
947
|
-
// Should have terminated — installCount should be bounded by MAX_INSTALL_ROUNDS (5)
|
|
974
|
+
expect(result.isError).toBe(false);
|
|
975
|
+
expect(result.content).toContain("Suggested Included Skills (not loaded):");
|
|
948
976
|
expect(installCount).toBeLessThanOrEqual(5);
|
|
949
977
|
});
|
|
950
978
|
});
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
existsSync,
|
|
3
3
|
mkdirSync,
|
|
4
|
+
readdirSync,
|
|
4
5
|
readFileSync,
|
|
5
6
|
rmSync,
|
|
6
7
|
symlinkSync,
|
|
@@ -678,3 +679,41 @@ describe("bundled computer-use skill", () => {
|
|
|
678
679
|
]);
|
|
679
680
|
});
|
|
680
681
|
});
|
|
682
|
+
|
|
683
|
+
describe("skill source ownership", () => {
|
|
684
|
+
const BUNDLED_SKILLS_DIR = join(
|
|
685
|
+
import.meta.dir,
|
|
686
|
+
"..",
|
|
687
|
+
"config",
|
|
688
|
+
"bundled-skills",
|
|
689
|
+
);
|
|
690
|
+
const FIRST_PARTY_SKILLS_DIR = join(
|
|
691
|
+
import.meta.dir,
|
|
692
|
+
"..",
|
|
693
|
+
"..",
|
|
694
|
+
"..",
|
|
695
|
+
"skills",
|
|
696
|
+
);
|
|
697
|
+
|
|
698
|
+
function collectSourceSkillIds(rootDir: string): string[] {
|
|
699
|
+
return readdirSync(rootDir, { withFileTypes: true })
|
|
700
|
+
.filter(
|
|
701
|
+
(entry) =>
|
|
702
|
+
entry.isDirectory() &&
|
|
703
|
+
existsSync(join(rootDir, entry.name, "SKILL.md")),
|
|
704
|
+
)
|
|
705
|
+
.map((entry) => entry.name)
|
|
706
|
+
.sort((a, b) => a.localeCompare(b));
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
test("bundled skills are not duplicated in the first-party source catalog", () => {
|
|
710
|
+
const firstPartyIds = new Set(
|
|
711
|
+
collectSourceSkillIds(FIRST_PARTY_SKILLS_DIR),
|
|
712
|
+
);
|
|
713
|
+
const duplicates = collectSourceSkillIds(BUNDLED_SKILLS_DIR).filter((id) =>
|
|
714
|
+
firstPartyIds.has(id),
|
|
715
|
+
);
|
|
716
|
+
|
|
717
|
+
expect(duplicates).toEqual([]);
|
|
718
|
+
});
|
|
719
|
+
});
|
|
@@ -561,6 +561,52 @@ describe("GET /v1/suggestion", () => {
|
|
|
561
561
|
expect(options?.config?.callSite).toBe("conversationStarters");
|
|
562
562
|
});
|
|
563
563
|
|
|
564
|
+
test("disables thinking and zeros effort to avoid Anthropic temp/thinking 400", async () => {
|
|
565
|
+
// Regression guard: this call hardcodes `temperature: 0.7` for response
|
|
566
|
+
// variety. Anthropic 400s on `temperature` ≠ 1 whenever thinking is
|
|
567
|
+
// enabled or in adaptive mode, so any user profile that resolves
|
|
568
|
+
// thinking-enabled (Opus 4.x at `effort: high|xhigh`, etc.) would fail
|
|
569
|
+
// unless we explicitly opt out of thinking on this call site.
|
|
570
|
+
//
|
|
571
|
+
// Pinning `thinking: { type: "disabled" }` and `effort: "none"` ensures
|
|
572
|
+
// the call works on every profile shape. A 60-token reply chip doesn't
|
|
573
|
+
// benefit from extended thinking anyway.
|
|
574
|
+
const provider = makeMockProvider("Quick reply");
|
|
575
|
+
mockGetConfiguredProvider.mockImplementation(async () => provider);
|
|
576
|
+
mockGetConversationByKey.mockImplementation(() => ({
|
|
577
|
+
conversationId: "conv-test",
|
|
578
|
+
}));
|
|
579
|
+
mockGetMessages.mockImplementation(() => [
|
|
580
|
+
{
|
|
581
|
+
id: "msg-asst-thinking",
|
|
582
|
+
conversationId: "conv-test",
|
|
583
|
+
role: "assistant",
|
|
584
|
+
content: JSON.stringify([{ type: "text", text: "Hello!" }]),
|
|
585
|
+
createdAt: Date.now(),
|
|
586
|
+
metadata: null,
|
|
587
|
+
},
|
|
588
|
+
]);
|
|
589
|
+
|
|
590
|
+
const args = makeArgs({ conversationKey: "test-key" });
|
|
591
|
+
const deps = makeDeps();
|
|
592
|
+
await handleGetSuggestion(args, deps);
|
|
593
|
+
|
|
594
|
+
expect(provider.sendMessage).toHaveBeenCalledTimes(1);
|
|
595
|
+
const callArgs = provider.sendMessage.mock.calls[0] as unknown[];
|
|
596
|
+
const options = callArgs[3] as
|
|
597
|
+
| {
|
|
598
|
+
config?: {
|
|
599
|
+
temperature?: number;
|
|
600
|
+
thinking?: { type?: string };
|
|
601
|
+
effort?: string;
|
|
602
|
+
};
|
|
603
|
+
}
|
|
604
|
+
| undefined;
|
|
605
|
+
expect(options?.config?.temperature).toBe(0.7);
|
|
606
|
+
expect(options?.config?.thinking).toEqual({ type: "disabled" });
|
|
607
|
+
expect(options?.config?.effort).toBe("none");
|
|
608
|
+
});
|
|
609
|
+
|
|
564
610
|
test("does not send an assistant-role prefill message", async () => {
|
|
565
611
|
// Regression guard: Anthropic rejects assistant-message prefill
|
|
566
612
|
// whenever the request triggers extended thinking (e.g. Opus 4.x at
|
|
@@ -30,16 +30,6 @@ mock.module("../util/logger.js", () => ({
|
|
|
30
30
|
}),
|
|
31
31
|
}));
|
|
32
32
|
|
|
33
|
-
// Allow toggling between no-rule and matched-rule paths
|
|
34
|
-
let mockRuleResponse: import("../permissions/types.js").TrustRule | null = null;
|
|
35
|
-
|
|
36
|
-
mock.module("../permissions/trust-store.js", () => ({
|
|
37
|
-
addRule: () => {},
|
|
38
|
-
findHighestPriorityRule: () => mockRuleResponse,
|
|
39
|
-
onRulesChanged: () => {},
|
|
40
|
-
clearCache: () => {},
|
|
41
|
-
}));
|
|
42
|
-
|
|
43
33
|
mock.module("../config/loader.js", () => ({
|
|
44
34
|
getConfig: () => ({
|
|
45
35
|
ui: {},
|
|
@@ -302,38 +292,6 @@ describe("Tool execution pipeline benchmark", () => {
|
|
|
302
292
|
expect(results[0].decision).toBe("allow");
|
|
303
293
|
});
|
|
304
294
|
|
|
305
|
-
test("check: matched allow-rule path for medium-risk tool", async () => {
|
|
306
|
-
// Exercise the code path where findHighestPriorityRule returns a matching
|
|
307
|
-
// allow rule, rather than always falling through to the no-rule default.
|
|
308
|
-
mockRuleResponse = {
|
|
309
|
-
id: "bench:allow-file_write",
|
|
310
|
-
tool: "file_write",
|
|
311
|
-
pattern: "**",
|
|
312
|
-
scope: "/tmp",
|
|
313
|
-
decision: "allow",
|
|
314
|
-
priority: 90,
|
|
315
|
-
createdAt: Date.now(),
|
|
316
|
-
};
|
|
317
|
-
|
|
318
|
-
try {
|
|
319
|
-
const { timings, results } = await benchmarkAsync(
|
|
320
|
-
() => check("file_write", { path: "/tmp/out.txt" }, "/tmp"),
|
|
321
|
-
ITERATIONS,
|
|
322
|
-
);
|
|
323
|
-
|
|
324
|
-
const p50 = percentile(timings, 50);
|
|
325
|
-
const p95 = percentile(timings, 95);
|
|
326
|
-
|
|
327
|
-
expect(p50).toBeLessThan(10);
|
|
328
|
-
expect(p95).toBeLessThan(20);
|
|
329
|
-
// Medium-risk with a matching allow rule should auto-allow
|
|
330
|
-
expect(results[0].decision).toBe("allow");
|
|
331
|
-
expect(results[0].matchedRule?.id).toBe("bench:allow-file_write");
|
|
332
|
-
} finally {
|
|
333
|
-
mockRuleResponse = null;
|
|
334
|
-
}
|
|
335
|
-
});
|
|
336
|
-
|
|
337
295
|
test("check: permission cost is stable across different input paths", async () => {
|
|
338
296
|
// Verify that the permission check cost doesn't vary with input path length/complexity.
|
|
339
297
|
// Actual tool-execution-time independence is tested in the ToolExecutor section below.
|
|
@@ -73,6 +73,11 @@ let cachedAssessmentOverride:
|
|
|
73
73
|
riskLevel: string;
|
|
74
74
|
reason: string;
|
|
75
75
|
scopeOptions: Array<{ pattern: string; label: string }>;
|
|
76
|
+
allowlistOptions?: Array<{
|
|
77
|
+
label: string;
|
|
78
|
+
description: string;
|
|
79
|
+
pattern: string;
|
|
80
|
+
}>;
|
|
76
81
|
directoryScopeOptions?: Array<{ scope: string; label: string }>;
|
|
77
82
|
matchType: string;
|
|
78
83
|
}
|
|
@@ -202,6 +207,32 @@ describe("ToolExecutor allowedToolNames gating", () => {
|
|
|
202
207
|
expect(result.content).toBe("ok");
|
|
203
208
|
});
|
|
204
209
|
|
|
210
|
+
test("canonicalizes app-builder create_app alias before active-tool gating", async () => {
|
|
211
|
+
const executor = new ToolExecutor(makePrompter());
|
|
212
|
+
const allowed = new Set(["app_create"]);
|
|
213
|
+
const result = await executor.execute(
|
|
214
|
+
"create_app",
|
|
215
|
+
{ name: "Calculator" },
|
|
216
|
+
makeContext({ allowedToolNames: allowed }),
|
|
217
|
+
);
|
|
218
|
+
|
|
219
|
+
expect(result.isError).toBe(false);
|
|
220
|
+
expect(result.content).toBe("ok");
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
test("preserves exact active create_app tool before applying compatibility aliases", async () => {
|
|
224
|
+
const executor = new ToolExecutor(makePrompter());
|
|
225
|
+
const allowed = new Set(["create_app", "app_create"]);
|
|
226
|
+
const result = await executor.execute(
|
|
227
|
+
"create_app",
|
|
228
|
+
{ name: "Custom App" },
|
|
229
|
+
makeContext({ allowedToolNames: allowed }),
|
|
230
|
+
);
|
|
231
|
+
|
|
232
|
+
expect(result.isError).toBe(false);
|
|
233
|
+
expect(lastCheckArgs?.toolName).toBe("create_app");
|
|
234
|
+
});
|
|
235
|
+
|
|
205
236
|
test("blocks execution when tool is NOT in the allowed set", async () => {
|
|
206
237
|
const executor = new ToolExecutor(makePrompter());
|
|
207
238
|
const allowed = new Set(["file_read", "bash"]);
|
|
@@ -1123,4 +1154,128 @@ describe("ToolExecutionResult includes risk metadata from classifier assessment"
|
|
|
1123
1154
|
{ scope: "/tmp", label: "Anywhere in tmp/" },
|
|
1124
1155
|
]);
|
|
1125
1156
|
});
|
|
1157
|
+
|
|
1158
|
+
test("auto-approved tool result includes riskAllowlistOptions when classifier emits them (Minimatch-glob shape for save path)", async () => {
|
|
1159
|
+
cachedAssessmentOverride = {
|
|
1160
|
+
riskLevel: "medium",
|
|
1161
|
+
reason: "Reads workspace files",
|
|
1162
|
+
// Display ladder (regex shape — not for save).
|
|
1163
|
+
scopeOptions: [
|
|
1164
|
+
{ pattern: "^echo\\b.*hello$", label: "echo hello" },
|
|
1165
|
+
{ pattern: "^echo\\b", label: "echo *" },
|
|
1166
|
+
],
|
|
1167
|
+
// Save ladder (Minimatch-glob shape — what gateway matches against).
|
|
1168
|
+
allowlistOptions: [
|
|
1169
|
+
{
|
|
1170
|
+
label: "echo hello",
|
|
1171
|
+
description: "This exact command",
|
|
1172
|
+
pattern: "echo hello",
|
|
1173
|
+
},
|
|
1174
|
+
{
|
|
1175
|
+
label: "echo *",
|
|
1176
|
+
description: "Any echo command",
|
|
1177
|
+
pattern: "action:echo",
|
|
1178
|
+
},
|
|
1179
|
+
],
|
|
1180
|
+
matchType: "registry",
|
|
1181
|
+
};
|
|
1182
|
+
|
|
1183
|
+
const executor = new ToolExecutor(makePrompter());
|
|
1184
|
+
const result = await executor.execute(
|
|
1185
|
+
"file_read",
|
|
1186
|
+
{ path: "README.md" },
|
|
1187
|
+
makeContext({ requireFreshApproval: true }),
|
|
1188
|
+
);
|
|
1189
|
+
|
|
1190
|
+
expect(result.isError).toBe(false);
|
|
1191
|
+
// Both shapes flow through independently — same labels, different patterns.
|
|
1192
|
+
expect(result.riskScopeOptions).toEqual([
|
|
1193
|
+
{ pattern: "^echo\\b.*hello$", label: "echo hello" },
|
|
1194
|
+
{ pattern: "^echo\\b", label: "echo *" },
|
|
1195
|
+
]);
|
|
1196
|
+
expect(result.riskAllowlistOptions).toEqual([
|
|
1197
|
+
{
|
|
1198
|
+
label: "echo hello",
|
|
1199
|
+
description: "This exact command",
|
|
1200
|
+
pattern: "echo hello",
|
|
1201
|
+
},
|
|
1202
|
+
{
|
|
1203
|
+
label: "echo *",
|
|
1204
|
+
description: "Any echo command",
|
|
1205
|
+
pattern: "action:echo",
|
|
1206
|
+
},
|
|
1207
|
+
]);
|
|
1208
|
+
});
|
|
1209
|
+
|
|
1210
|
+
test("riskAllowlistOptions is undefined when classifier did not produce allowlist (e.g. web-risk classifier)", async () => {
|
|
1211
|
+
cachedAssessmentOverride = {
|
|
1212
|
+
riskLevel: "low",
|
|
1213
|
+
reason: "GET request to public URL",
|
|
1214
|
+
scopeOptions: [{ pattern: "https://example.com/.*", label: "example.com" }],
|
|
1215
|
+
// allowlistOptions intentionally omitted — some classifiers don't emit them.
|
|
1216
|
+
matchType: "registry",
|
|
1217
|
+
};
|
|
1218
|
+
|
|
1219
|
+
const executor = new ToolExecutor(makePrompter());
|
|
1220
|
+
const result = await executor.execute(
|
|
1221
|
+
"file_read",
|
|
1222
|
+
{ path: "README.md" },
|
|
1223
|
+
makeContext({ requireFreshApproval: true }),
|
|
1224
|
+
);
|
|
1225
|
+
|
|
1226
|
+
expect(result.isError).toBe(false);
|
|
1227
|
+
// Display ladder still flows; save ladder is absent so the client must
|
|
1228
|
+
// fall back to a synthesized option (or omit save).
|
|
1229
|
+
expect(result.riskScopeOptions).toEqual([
|
|
1230
|
+
{ pattern: "https://example.com/.*", label: "example.com" },
|
|
1231
|
+
]);
|
|
1232
|
+
expect(result.riskAllowlistOptions).toBeUndefined();
|
|
1233
|
+
});
|
|
1234
|
+
|
|
1235
|
+
test("riskAllowlistOptions is undefined when no classifier ran (MCP tools)", async () => {
|
|
1236
|
+
// cachedAssessmentOverride is undefined — no classifier ran.
|
|
1237
|
+
const executor = new ToolExecutor(makePrompter());
|
|
1238
|
+
const result = await executor.execute(
|
|
1239
|
+
"file_read",
|
|
1240
|
+
{ path: "README.md" },
|
|
1241
|
+
makeContext(),
|
|
1242
|
+
);
|
|
1243
|
+
|
|
1244
|
+
expect(result.isError).toBe(false);
|
|
1245
|
+
expect(result.riskScopeOptions).toBeUndefined();
|
|
1246
|
+
expect(result.riskAllowlistOptions).toBeUndefined();
|
|
1247
|
+
});
|
|
1248
|
+
|
|
1249
|
+
test("denied tool result still carries riskAllowlistOptions for the rule editor save path", async () => {
|
|
1250
|
+
checkResultOverride = { decision: "deny", reason: "Blocked by deny rule" };
|
|
1251
|
+
cachedAssessmentOverride = {
|
|
1252
|
+
riskLevel: "high",
|
|
1253
|
+
reason: "Recursive force delete",
|
|
1254
|
+
scopeOptions: [{ pattern: "^rm\\s+-rf", label: "rm -rf *" }],
|
|
1255
|
+
allowlistOptions: [
|
|
1256
|
+
{
|
|
1257
|
+
label: "rm -rf *",
|
|
1258
|
+
description: "Any rm -rf command",
|
|
1259
|
+
pattern: "action:rm",
|
|
1260
|
+
},
|
|
1261
|
+
],
|
|
1262
|
+
matchType: "registry",
|
|
1263
|
+
};
|
|
1264
|
+
|
|
1265
|
+
const executor = new ToolExecutor(makePrompter());
|
|
1266
|
+
const result = await executor.execute(
|
|
1267
|
+
"file_read",
|
|
1268
|
+
{ path: "anything" },
|
|
1269
|
+
makeContext({ requireFreshApproval: true }),
|
|
1270
|
+
);
|
|
1271
|
+
|
|
1272
|
+
expect(result.isError).toBe(true);
|
|
1273
|
+
expect(result.riskAllowlistOptions).toEqual([
|
|
1274
|
+
{
|
|
1275
|
+
label: "rm -rf *",
|
|
1276
|
+
description: "Any rm -rf command",
|
|
1277
|
+
pattern: "action:rm",
|
|
1278
|
+
},
|
|
1279
|
+
]);
|
|
1280
|
+
});
|
|
1126
1281
|
});
|
|
@@ -49,7 +49,7 @@ describe("Twilio validation middleware", () => {
|
|
|
49
49
|
};
|
|
50
50
|
});
|
|
51
51
|
|
|
52
|
-
test("validates signatures against
|
|
52
|
+
test("validates signatures against configured public ingress", async () => {
|
|
53
53
|
mockConfig = {
|
|
54
54
|
ingress: {
|
|
55
55
|
publicBaseUrl: " https://twilio.example.com/// ",
|
|
@@ -77,7 +77,7 @@ describe("Twilio validation middleware", () => {
|
|
|
77
77
|
]);
|
|
78
78
|
});
|
|
79
79
|
|
|
80
|
-
test("
|
|
80
|
+
test("uses configured public ingress for status callbacks", async () => {
|
|
81
81
|
const req = new Request("http://127.0.0.1:7821/v1/calls/twilio/status", {
|
|
82
82
|
method: "POST",
|
|
83
83
|
headers: { "x-twilio-signature": "valid" },
|
|
@@ -652,6 +652,9 @@ describe("voice-session-bridge", () => {
|
|
|
652
652
|
expect(prompt).toContain(
|
|
653
653
|
"If your assistant name is not known, skip the name and just identify yourself as the guardian's assistant.",
|
|
654
654
|
);
|
|
655
|
+
expect(prompt).toContain(
|
|
656
|
+
"Never use a UUID-shaped internal assistant ID as your spoken name.",
|
|
657
|
+
);
|
|
655
658
|
expect(prompt).toContain(
|
|
656
659
|
'Do NOT say "I\'m calling" or "I\'m calling on behalf of".',
|
|
657
660
|
);
|