@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
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for `annotatePersistedAssistantMessage` persisting the 3 risk-option
|
|
3
|
+
* arrays alongside the existing `_risk*` scalars.
|
|
4
|
+
*
|
|
5
|
+
* Phase B of the conflation track. Without these annotations, the Rule Editor
|
|
6
|
+
* Modal's chip ladder loses its scope/allowlist/directory options on chat-
|
|
7
|
+
* history reload and falls back to the synthesized `*` allowlist.
|
|
8
|
+
*
|
|
9
|
+
* The test exercises the full populate → annotate → persist round-trip:
|
|
10
|
+
* handleToolResult(event with 3 arrays)
|
|
11
|
+
* → state.toolRiskOutcomes captures them
|
|
12
|
+
* → annotatePersistedAssistantMessage writes _risk*Options onto the row
|
|
13
|
+
* → updateMessageContent receives the JSON-serialized output
|
|
14
|
+
*
|
|
15
|
+
* Read-side coverage (renderHistoryContent in handlers/shared.ts) lives in
|
|
16
|
+
* server-history-render.test.ts.
|
|
17
|
+
*/
|
|
18
|
+
import { beforeEach, describe, expect, mock, test } from "bun:test";
|
|
19
|
+
|
|
20
|
+
// ── Mock platform (must precede imports that read it) ─────────────────────────
|
|
21
|
+
mock.module("../util/logger.js", () => ({
|
|
22
|
+
getLogger: () =>
|
|
23
|
+
new Proxy({} as Record<string, unknown>, {
|
|
24
|
+
get: () => () => {},
|
|
25
|
+
}),
|
|
26
|
+
}));
|
|
27
|
+
|
|
28
|
+
mock.module("../config/loader.js", () => ({
|
|
29
|
+
getConfig: () => ({
|
|
30
|
+
skills: {
|
|
31
|
+
entries: {},
|
|
32
|
+
load: { extraDirs: [], watch: false, watchDebounceMs: 0 },
|
|
33
|
+
install: { nodeManager: "npm" },
|
|
34
|
+
allowBundled: null,
|
|
35
|
+
remoteProviders: {
|
|
36
|
+
skillssh: { enabled: true },
|
|
37
|
+
clawhub: { enabled: true },
|
|
38
|
+
},
|
|
39
|
+
remotePolicy: {
|
|
40
|
+
blockSuspicious: true,
|
|
41
|
+
blockMalware: true,
|
|
42
|
+
maxSkillsShRisk: "medium",
|
|
43
|
+
},
|
|
44
|
+
},
|
|
45
|
+
}),
|
|
46
|
+
loadConfig: () => ({}),
|
|
47
|
+
}));
|
|
48
|
+
|
|
49
|
+
let mockedRowContent = "";
|
|
50
|
+
const updates: Array<{ id: string; content: string }> = [];
|
|
51
|
+
|
|
52
|
+
mock.module("../memory/conversation-crud.js", () => ({
|
|
53
|
+
addMessage: () => ({ id: "mock-msg-id" }),
|
|
54
|
+
getMessageById: (id: string) =>
|
|
55
|
+
mockedRowContent ? { id, content: mockedRowContent } : null,
|
|
56
|
+
updateMessageContent: (id: string, content: string) => {
|
|
57
|
+
updates.push({ id, content });
|
|
58
|
+
},
|
|
59
|
+
provenanceFromTrustContext: () => ({}),
|
|
60
|
+
}));
|
|
61
|
+
|
|
62
|
+
mock.module("../memory/llm-request-log-store.js", () => ({
|
|
63
|
+
recordRequestLog: () => {},
|
|
64
|
+
backfillMessageIdOnLogs: () => {},
|
|
65
|
+
}));
|
|
66
|
+
|
|
67
|
+
// ── Imports (after mocks) ─────────────────────────────────────────────────────
|
|
68
|
+
import type {
|
|
69
|
+
EventHandlerDeps,
|
|
70
|
+
EventHandlerState,
|
|
71
|
+
} from "../daemon/conversation-agent-loop-handlers.js";
|
|
72
|
+
import {
|
|
73
|
+
createEventHandlerState,
|
|
74
|
+
handleToolResult,
|
|
75
|
+
} from "../daemon/conversation-agent-loop-handlers.js";
|
|
76
|
+
|
|
77
|
+
// ── Helpers ───────────────────────────────────────────────────────────────────
|
|
78
|
+
|
|
79
|
+
function makeDeps(): EventHandlerDeps {
|
|
80
|
+
return {
|
|
81
|
+
ctx: {
|
|
82
|
+
conversationId: "test-conv",
|
|
83
|
+
provider: { name: "anthropic" },
|
|
84
|
+
traceEmitter: { emit: () => {} },
|
|
85
|
+
streamThinking: false,
|
|
86
|
+
emitActivityState: () => {},
|
|
87
|
+
markWorkspaceTopLevelDirty: () => {},
|
|
88
|
+
currentTurnSurfaces: [],
|
|
89
|
+
} as unknown as EventHandlerDeps["ctx"],
|
|
90
|
+
onEvent: () => {},
|
|
91
|
+
reqId: "test-req",
|
|
92
|
+
isFirstMessage: false,
|
|
93
|
+
shouldGenerateTitle: false,
|
|
94
|
+
rlog: new Proxy({} as Record<string, unknown>, {
|
|
95
|
+
get: () => () => {},
|
|
96
|
+
}) as unknown as EventHandlerDeps["rlog"],
|
|
97
|
+
turnChannelContext: {
|
|
98
|
+
userMessageChannel: "vellum",
|
|
99
|
+
assistantMessageChannel: "vellum",
|
|
100
|
+
} as unknown as EventHandlerDeps["turnChannelContext"],
|
|
101
|
+
turnInterfaceContext: {
|
|
102
|
+
userMessageInterface: "web",
|
|
103
|
+
assistantMessageInterface: "web",
|
|
104
|
+
} as unknown as EventHandlerDeps["turnInterfaceContext"],
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function setupState(toolUseId: string): EventHandlerState {
|
|
109
|
+
const state = createEventHandlerState();
|
|
110
|
+
state.lastAssistantMessageId = "msg-1";
|
|
111
|
+
state.toolUseIdToName.set(toolUseId, "bash");
|
|
112
|
+
state.toolCallTimestamps.set(toolUseId, { startedAt: Date.now() });
|
|
113
|
+
state.currentTurnToolUseIds.push(toolUseId);
|
|
114
|
+
return state;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function findPersistedToolUse(
|
|
118
|
+
rawContent: string,
|
|
119
|
+
toolUseId: string,
|
|
120
|
+
): Record<string, unknown> {
|
|
121
|
+
const parsed = JSON.parse(rawContent) as Array<Record<string, unknown>>;
|
|
122
|
+
const block = parsed.find(
|
|
123
|
+
(b) => b.type === "tool_use" && b.id === toolUseId,
|
|
124
|
+
);
|
|
125
|
+
if (!block) throw new Error(`tool_use block ${toolUseId} not found`);
|
|
126
|
+
return block;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// ── Tests ─────────────────────────────────────────────────────────────────────
|
|
130
|
+
|
|
131
|
+
describe("annotatePersistedAssistantMessage — risk-option arrays (Phase B)", () => {
|
|
132
|
+
beforeEach(() => {
|
|
133
|
+
updates.length = 0;
|
|
134
|
+
mockedRowContent = "";
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
test("persists all 3 risk-option arrays from the live tool_result event", () => {
|
|
138
|
+
const toolUseId = "tu_persist_full";
|
|
139
|
+
const state = setupState(toolUseId);
|
|
140
|
+
|
|
141
|
+
mockedRowContent = JSON.stringify([
|
|
142
|
+
{
|
|
143
|
+
type: "tool_use",
|
|
144
|
+
id: toolUseId,
|
|
145
|
+
name: "bash",
|
|
146
|
+
input: { command: "rm -rf /tmp" },
|
|
147
|
+
},
|
|
148
|
+
]);
|
|
149
|
+
|
|
150
|
+
const scopeOptions = [
|
|
151
|
+
{ pattern: "exact", label: "exact: rm -rf /tmp" },
|
|
152
|
+
{ pattern: "by-program", label: "All rm" },
|
|
153
|
+
];
|
|
154
|
+
const allowlistOptions = [
|
|
155
|
+
{ label: "exact", description: "exact match", pattern: "rm -rf /tmp" },
|
|
156
|
+
{ label: "All rm", description: "All rm commands", pattern: "rm *" },
|
|
157
|
+
];
|
|
158
|
+
const directoryScopeOptions = [
|
|
159
|
+
{ scope: "/Users/me/code", label: "in code/" },
|
|
160
|
+
{ scope: "everywhere", label: "Everywhere" },
|
|
161
|
+
];
|
|
162
|
+
|
|
163
|
+
handleToolResult(state, makeDeps(), {
|
|
164
|
+
type: "tool_result",
|
|
165
|
+
toolUseId,
|
|
166
|
+
content: "ok",
|
|
167
|
+
isError: false,
|
|
168
|
+
riskLevel: "high",
|
|
169
|
+
riskReason: "Modifies state",
|
|
170
|
+
matchedTrustRuleId: "rule_42",
|
|
171
|
+
riskScopeOptions: scopeOptions,
|
|
172
|
+
riskAllowlistOptions: allowlistOptions,
|
|
173
|
+
riskDirectoryScopeOptions: directoryScopeOptions,
|
|
174
|
+
approvalMode: "prompted",
|
|
175
|
+
approvalReason: "user_approved",
|
|
176
|
+
riskThreshold: "relaxed",
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
expect(updates).toHaveLength(1);
|
|
180
|
+
const block = findPersistedToolUse(updates[0].content, toolUseId);
|
|
181
|
+
// Existing scalars still flow through.
|
|
182
|
+
expect(block._riskLevel).toBe("high");
|
|
183
|
+
expect(block._riskReason).toBe("Modifies state");
|
|
184
|
+
expect(block._matchedTrustRuleId).toBe("rule_42");
|
|
185
|
+
expect(block._approvalMode).toBe("prompted");
|
|
186
|
+
expect(block._approvalReason).toBe("user_approved");
|
|
187
|
+
expect(block._riskThreshold).toBe("relaxed");
|
|
188
|
+
// New: 3 risk-option arrays persisted verbatim.
|
|
189
|
+
expect(block._riskScopeOptions).toEqual(scopeOptions);
|
|
190
|
+
expect(block._riskAllowlistOptions).toEqual(allowlistOptions);
|
|
191
|
+
expect(block._riskDirectoryScopeOptions).toEqual(directoryScopeOptions);
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
test("omits empty arrays from the persisted block (saves DB space)", () => {
|
|
195
|
+
const toolUseId = "tu_persist_empty";
|
|
196
|
+
const state = setupState(toolUseId);
|
|
197
|
+
|
|
198
|
+
mockedRowContent = JSON.stringify([
|
|
199
|
+
{
|
|
200
|
+
type: "tool_use",
|
|
201
|
+
id: toolUseId,
|
|
202
|
+
name: "bash",
|
|
203
|
+
input: { command: "ls" },
|
|
204
|
+
},
|
|
205
|
+
]);
|
|
206
|
+
|
|
207
|
+
handleToolResult(state, makeDeps(), {
|
|
208
|
+
type: "tool_result",
|
|
209
|
+
toolUseId,
|
|
210
|
+
content: "ok",
|
|
211
|
+
isError: false,
|
|
212
|
+
riskLevel: "low",
|
|
213
|
+
riskScopeOptions: [],
|
|
214
|
+
riskAllowlistOptions: [],
|
|
215
|
+
riskDirectoryScopeOptions: [],
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
expect(updates).toHaveLength(1);
|
|
219
|
+
const block = findPersistedToolUse(updates[0].content, toolUseId);
|
|
220
|
+
expect(block._riskLevel).toBe("low");
|
|
221
|
+
expect(block._riskScopeOptions).toBeUndefined();
|
|
222
|
+
expect(block._riskAllowlistOptions).toBeUndefined();
|
|
223
|
+
expect(block._riskDirectoryScopeOptions).toBeUndefined();
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
test("omits absent (undefined) arrays from the persisted block", () => {
|
|
227
|
+
// Mirrors classic bash/file tools that don't always emit all 3 arrays —
|
|
228
|
+
// e.g. recall, file_read with riskLevel=low and no allowlist coverage.
|
|
229
|
+
const toolUseId = "tu_persist_absent";
|
|
230
|
+
const state = setupState(toolUseId);
|
|
231
|
+
|
|
232
|
+
mockedRowContent = JSON.stringify([
|
|
233
|
+
{
|
|
234
|
+
type: "tool_use",
|
|
235
|
+
id: toolUseId,
|
|
236
|
+
name: "recall",
|
|
237
|
+
input: { query: "anything" },
|
|
238
|
+
},
|
|
239
|
+
]);
|
|
240
|
+
|
|
241
|
+
handleToolResult(state, makeDeps(), {
|
|
242
|
+
type: "tool_result",
|
|
243
|
+
toolUseId,
|
|
244
|
+
content: "ok",
|
|
245
|
+
isError: false,
|
|
246
|
+
riskLevel: "low",
|
|
247
|
+
// No risk-option arrays passed at all.
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
expect(updates).toHaveLength(1);
|
|
251
|
+
const block = findPersistedToolUse(updates[0].content, toolUseId);
|
|
252
|
+
expect(block._riskLevel).toBe("low");
|
|
253
|
+
expect(block._riskScopeOptions).toBeUndefined();
|
|
254
|
+
expect(block._riskAllowlistOptions).toBeUndefined();
|
|
255
|
+
expect(block._riskDirectoryScopeOptions).toBeUndefined();
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
test("partial coverage — only allowlist options present (e.g. tools with classifier but no scope ladder)", () => {
|
|
259
|
+
const toolUseId = "tu_partial";
|
|
260
|
+
const state = setupState(toolUseId);
|
|
261
|
+
|
|
262
|
+
mockedRowContent = JSON.stringify([
|
|
263
|
+
{
|
|
264
|
+
type: "tool_use",
|
|
265
|
+
id: toolUseId,
|
|
266
|
+
name: "file_write",
|
|
267
|
+
input: { path: "/tmp/foo.txt" },
|
|
268
|
+
},
|
|
269
|
+
]);
|
|
270
|
+
|
|
271
|
+
const allowlistOptions = [
|
|
272
|
+
{ label: "exact", description: "exact match", pattern: "/tmp/foo.txt" },
|
|
273
|
+
];
|
|
274
|
+
|
|
275
|
+
handleToolResult(state, makeDeps(), {
|
|
276
|
+
type: "tool_result",
|
|
277
|
+
toolUseId,
|
|
278
|
+
content: "ok",
|
|
279
|
+
isError: false,
|
|
280
|
+
riskLevel: "medium",
|
|
281
|
+
riskAllowlistOptions: allowlistOptions,
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
expect(updates).toHaveLength(1);
|
|
285
|
+
const block = findPersistedToolUse(updates[0].content, toolUseId);
|
|
286
|
+
expect(block._riskLevel).toBe("medium");
|
|
287
|
+
expect(block._riskAllowlistOptions).toEqual(allowlistOptions);
|
|
288
|
+
expect(block._riskScopeOptions).toBeUndefined();
|
|
289
|
+
expect(block._riskDirectoryScopeOptions).toBeUndefined();
|
|
290
|
+
});
|
|
291
|
+
});
|
|
@@ -13,8 +13,9 @@
|
|
|
13
13
|
* differs in two notable ways:
|
|
14
14
|
* 1. Result payloads carry `pngBase64` (not screenshots-as-strings) and
|
|
15
15
|
* surface as image content blocks with `media_type: "image/png"`.
|
|
16
|
-
* 2. A module-level
|
|
17
|
-
* conversation may hold an active session at a time
|
|
16
|
+
* 2. A module-level session lock binds `(conversationId, app)` — only
|
|
17
|
+
* one conversation may hold an active session at a time, and non-start
|
|
18
|
+
* tools must target the same `app` the user approved at start time.
|
|
18
19
|
*/
|
|
19
20
|
import { afterEach, beforeEach, describe, expect, mock, test } from "bun:test";
|
|
20
21
|
|
|
@@ -87,8 +88,9 @@ mock.module("../runtime/pending-interactions.js", () => ({
|
|
|
87
88
|
|
|
88
89
|
const {
|
|
89
90
|
HostAppControlProxy,
|
|
90
|
-
|
|
91
|
-
|
|
91
|
+
_getActiveAppControlSession,
|
|
92
|
+
_resetActiveAppControlSession,
|
|
93
|
+
_setActiveAppControlSession,
|
|
92
94
|
} = await import("../daemon/host-app-control-proxy.js");
|
|
93
95
|
const { ROUTES } = await import("../runtime/routes/host-app-control-routes.js");
|
|
94
96
|
const { surfaceProxyResolver } =
|
|
@@ -176,11 +178,11 @@ describe("app-control end-to-end flow", () => {
|
|
|
176
178
|
pending.clear();
|
|
177
179
|
clearConversations();
|
|
178
180
|
mockHasClient = true;
|
|
179
|
-
|
|
181
|
+
_resetActiveAppControlSession();
|
|
180
182
|
});
|
|
181
183
|
|
|
182
184
|
afterEach(() => {
|
|
183
|
-
|
|
185
|
+
_resetActiveAppControlSession();
|
|
184
186
|
clearConversations();
|
|
185
187
|
});
|
|
186
188
|
|
|
@@ -238,8 +240,10 @@ describe("app-control end-to-end flow", () => {
|
|
|
238
240
|
},
|
|
239
241
|
});
|
|
240
242
|
|
|
241
|
-
//
|
|
242
|
-
|
|
243
|
+
// Session lock is held by this conversation now, bound to the started app.
|
|
244
|
+
const session = _getActiveAppControlSession();
|
|
245
|
+
expect(session?.conversationId).toBe(conversationId);
|
|
246
|
+
expect(session?.app).toBe("com.example.app");
|
|
243
247
|
|
|
244
248
|
proxy.dispose();
|
|
245
249
|
});
|
|
@@ -253,6 +257,12 @@ describe("app-control end-to-end flow", () => {
|
|
|
253
257
|
const proxy = new HostAppControlProxy(conversationId);
|
|
254
258
|
const ctx = buildContext(proxy, conversationId);
|
|
255
259
|
registerConversation(conversationId, proxy);
|
|
260
|
+
// Prime a session so observe passes the auth gate. This test exercises
|
|
261
|
+
// the result-formatting path, not the start flow.
|
|
262
|
+
_setActiveAppControlSession({
|
|
263
|
+
conversationId,
|
|
264
|
+
app: "com.example.app",
|
|
265
|
+
});
|
|
256
266
|
|
|
257
267
|
const resultPromise = surfaceProxyResolver(ctx, "app_control_observe", {
|
|
258
268
|
tool: "observe",
|
|
@@ -306,7 +316,7 @@ describe("app-control end-to-end flow", () => {
|
|
|
306
316
|
});
|
|
307
317
|
await postResult({ state: "running", pngBase64: TINY_PNG_B64 });
|
|
308
318
|
await startPromise;
|
|
309
|
-
expect(
|
|
319
|
+
expect(_getActiveAppControlSession()?.conversationId).toBe(conversationId);
|
|
310
320
|
|
|
311
321
|
// Wrap dispose to verify it was called by the resolver.
|
|
312
322
|
let disposeCalls = 0;
|
|
@@ -328,7 +338,7 @@ describe("app-control end-to-end flow", () => {
|
|
|
328
338
|
expect(sentMessages).toHaveLength(0);
|
|
329
339
|
expect(disposeCalls).toBe(1);
|
|
330
340
|
// Lock released.
|
|
331
|
-
expect(
|
|
341
|
+
expect(_getActiveAppControlSession()).toBeUndefined();
|
|
332
342
|
});
|
|
333
343
|
|
|
334
344
|
// -------------------------------------------------------------------------
|
|
@@ -347,7 +357,7 @@ describe("app-control end-to-end flow", () => {
|
|
|
347
357
|
await postResult({ state: "running", pngBase64: TINY_PNG_B64 });
|
|
348
358
|
const resultA = await startA;
|
|
349
359
|
expect(resultA.isError).toBe(false);
|
|
350
|
-
expect(
|
|
360
|
+
expect(_getActiveAppControlSession()?.conversationId).toBe("conv-a");
|
|
351
361
|
|
|
352
362
|
// Second conversation tries to start while conv-a holds the lock.
|
|
353
363
|
sentMessages.length = 0;
|
|
@@ -291,21 +291,13 @@ function seedPendingConfirmation(
|
|
|
291
291
|
conversation: Conversation,
|
|
292
292
|
requestId: string,
|
|
293
293
|
): void {
|
|
294
|
+
// Access private ownedIds so denyAllPending/dispose can find this request.
|
|
295
|
+
// promptResolve/promptReject callbacks are stored in pendingInteractions via
|
|
296
|
+
// registerPendingInteraction, which is called separately in each test.
|
|
294
297
|
const prompter = conversation["prompter"] as unknown as {
|
|
295
|
-
|
|
296
|
-
string,
|
|
297
|
-
{
|
|
298
|
-
resolve: (...args: unknown[]) => void;
|
|
299
|
-
reject: (...args: unknown[]) => void;
|
|
300
|
-
timer: ReturnType<typeof setTimeout>;
|
|
301
|
-
}
|
|
302
|
-
>;
|
|
298
|
+
ownedIds: Set<string>;
|
|
303
299
|
};
|
|
304
|
-
prompter.
|
|
305
|
-
resolve: () => {},
|
|
306
|
-
reject: () => {},
|
|
307
|
-
timer: setTimeout(() => {}, 60_000),
|
|
308
|
-
});
|
|
300
|
+
prompter.ownedIds.add(requestId);
|
|
309
301
|
}
|
|
310
302
|
|
|
311
303
|
/**
|
|
@@ -439,12 +431,12 @@ describe("approval cascading", () => {
|
|
|
439
431
|
makeConfirmationDetails(["bash:echo stale"]),
|
|
440
432
|
);
|
|
441
433
|
|
|
442
|
-
// Remove req-stale from the prompter's
|
|
434
|
+
// Remove req-stale from the prompter's ownedIds (simulating it was
|
|
443
435
|
// already resolved by another path before cascade reaches it)
|
|
444
436
|
const prompter = conversationObj["prompter"] as unknown as {
|
|
445
|
-
|
|
437
|
+
ownedIds: Set<string>;
|
|
446
438
|
};
|
|
447
|
-
prompter.
|
|
439
|
+
prompter.ownedIds.delete("req-stale");
|
|
448
440
|
|
|
449
441
|
// This should not throw — cascade should skip req-stale gracefully
|
|
450
442
|
expect(() => {
|
|
@@ -192,6 +192,8 @@ function makeIdleSession(opts?: {
|
|
|
192
192
|
processing = false;
|
|
193
193
|
},
|
|
194
194
|
handleConfirmationResponse: (requestId: string, decision: string) => {
|
|
195
|
+
// Simulate PermissionPrompter.resolveConfirmation(): prompter owns deregistration.
|
|
196
|
+
pendingInteractions.resolve(requestId);
|
|
195
197
|
opts?.onConfirmation?.(requestId, decision);
|
|
196
198
|
},
|
|
197
199
|
handleSecretResponse: (
|
|
@@ -199,6 +201,8 @@ function makeIdleSession(opts?: {
|
|
|
199
201
|
value?: string,
|
|
200
202
|
delivery?: string,
|
|
201
203
|
) => {
|
|
204
|
+
// Simulate SecretPrompter.resolveSecret(): prompter owns deregistration.
|
|
205
|
+
pendingInteractions.resolve(requestId);
|
|
202
206
|
opts?.onSecret?.(requestId, value, delivery);
|
|
203
207
|
},
|
|
204
208
|
} as unknown as Conversation;
|
|
@@ -285,6 +289,8 @@ function makeConfirmationEmittingSession(opts?: {
|
|
|
285
289
|
await new Promise<void>(() => {});
|
|
286
290
|
},
|
|
287
291
|
handleConfirmationResponse: (requestId: string, decision: string) => {
|
|
292
|
+
// Simulate PermissionPrompter.resolveConfirmation(): prompter owns deregistration.
|
|
293
|
+
pendingInteractions.resolve(requestId);
|
|
288
294
|
opts?.onConfirmation?.(requestId, decision);
|
|
289
295
|
},
|
|
290
296
|
handleSecretResponse: () => {},
|
|
@@ -356,6 +356,54 @@ describe("AssistantEventHub — re-entrancy / snapshot isolation", () => {
|
|
|
356
356
|
});
|
|
357
357
|
});
|
|
358
358
|
|
|
359
|
+
// ── ClientEntry actorPrincipalId capture ────────────────────────────────────
|
|
360
|
+
|
|
361
|
+
describe("AssistantEventHub — actorPrincipalId on ClientEntry", () => {
|
|
362
|
+
test("stores actorPrincipalId provided at subscribe time", () => {
|
|
363
|
+
const hub = new AssistantEventHub();
|
|
364
|
+
|
|
365
|
+
hub.subscribe({
|
|
366
|
+
type: "client" as const,
|
|
367
|
+
clientId: "client-with-principal",
|
|
368
|
+
interfaceId: "macos",
|
|
369
|
+
capabilities: ["host_bash"],
|
|
370
|
+
actorPrincipalId: "user-A",
|
|
371
|
+
callback: () => {},
|
|
372
|
+
});
|
|
373
|
+
|
|
374
|
+
expect(hub.getClientById("client-with-principal")?.actorPrincipalId).toBe(
|
|
375
|
+
"user-A",
|
|
376
|
+
);
|
|
377
|
+
expect(hub.getActorPrincipalIdForClient("client-with-principal")).toBe(
|
|
378
|
+
"user-A",
|
|
379
|
+
);
|
|
380
|
+
});
|
|
381
|
+
|
|
382
|
+
test("actorPrincipalId is undefined when omitted at subscribe time", () => {
|
|
383
|
+
const hub = new AssistantEventHub();
|
|
384
|
+
|
|
385
|
+
hub.subscribe({
|
|
386
|
+
type: "client" as const,
|
|
387
|
+
clientId: "client-no-principal",
|
|
388
|
+
interfaceId: "macos",
|
|
389
|
+
capabilities: ["host_bash"],
|
|
390
|
+
callback: () => {},
|
|
391
|
+
});
|
|
392
|
+
|
|
393
|
+
expect(
|
|
394
|
+
hub.getClientById("client-no-principal")?.actorPrincipalId,
|
|
395
|
+
).toBeUndefined();
|
|
396
|
+
expect(
|
|
397
|
+
hub.getActorPrincipalIdForClient("client-no-principal"),
|
|
398
|
+
).toBeUndefined();
|
|
399
|
+
});
|
|
400
|
+
|
|
401
|
+
test("getActorPrincipalIdForClient returns undefined for unknown clientId", () => {
|
|
402
|
+
const hub = new AssistantEventHub();
|
|
403
|
+
expect(hub.getActorPrincipalIdForClient("does-not-exist")).toBeUndefined();
|
|
404
|
+
});
|
|
405
|
+
});
|
|
406
|
+
|
|
359
407
|
// ── capabilityForMessageType — host-prefix routing ───────────────────────────
|
|
360
408
|
|
|
361
409
|
describe("capabilityForMessageType — host-prefix routing", () => {
|
|
@@ -4,7 +4,6 @@ import type { AssistantEvent } from "../runtime/assistant-event.js";
|
|
|
4
4
|
import {
|
|
5
5
|
formatSseFrame,
|
|
6
6
|
formatSseHeartbeat,
|
|
7
|
-
formatSseHeartbeatWithData,
|
|
8
7
|
} from "../runtime/assistant-event.js";
|
|
9
8
|
|
|
10
9
|
// ── Type / shape tests ────────────────────────────────────────────────────────
|
|
@@ -126,12 +125,3 @@ describe("formatSseHeartbeat", () => {
|
|
|
126
125
|
expect(formatSseHeartbeat().startsWith(":")).toBe(true);
|
|
127
126
|
});
|
|
128
127
|
});
|
|
129
|
-
|
|
130
|
-
describe("formatSseHeartbeatWithData", () => {
|
|
131
|
-
test("includes both a comment and a data-bearing event", () => {
|
|
132
|
-
const hb = formatSseHeartbeatWithData();
|
|
133
|
-
expect(hb).toContain(": heartbeat\n\n");
|
|
134
|
-
expect(hb).toContain("event: assistant_event\n");
|
|
135
|
-
expect(hb).toContain('data: {"type":"heartbeat"}\n');
|
|
136
|
-
});
|
|
137
|
-
});
|
|
@@ -261,8 +261,7 @@ describe("SSE route — heartbeat", () => {
|
|
|
261
261
|
reader.cancel();
|
|
262
262
|
|
|
263
263
|
const text = new TextDecoder().decode(value);
|
|
264
|
-
expect(text).
|
|
265
|
-
expect(text).toContain('{"type":"heartbeat"}');
|
|
264
|
+
expect(text).toBe(": heartbeat\n\n");
|
|
266
265
|
});
|
|
267
266
|
|
|
268
267
|
test("emits multiple heartbeats over time", async () => {
|
|
@@ -298,11 +297,7 @@ describe("SSE route — heartbeat", () => {
|
|
|
298
297
|
reader.cancel();
|
|
299
298
|
|
|
300
299
|
expect(chunks.length).toBeGreaterThan(0);
|
|
301
|
-
expect(
|
|
302
|
-
chunks.every(
|
|
303
|
-
(c) => c.includes(": heartbeat") && c.includes('{"type":"heartbeat"}'),
|
|
304
|
-
),
|
|
305
|
-
).toBe(true);
|
|
300
|
+
expect(chunks.every((c) => c === ": heartbeat\n\n")).toBe(true);
|
|
306
301
|
});
|
|
307
302
|
});
|
|
308
303
|
|
|
@@ -14,6 +14,7 @@ import { afterEach, beforeEach, describe, expect, test } from "bun:test";
|
|
|
14
14
|
|
|
15
15
|
const DECLARED_FLAG_ID = "email-channel";
|
|
16
16
|
const DECLARED_FLAG_KEY = DECLARED_FLAG_ID;
|
|
17
|
+
const SAFE_STORAGE_LIMITS_FLAG = "safe-storage-limits";
|
|
17
18
|
|
|
18
19
|
const { isAssistantFeatureFlagEnabled, _setOverridesForTesting } =
|
|
19
20
|
await import("../config/assistant-feature-flags.js");
|
|
@@ -62,6 +63,23 @@ describe("isAssistantFeatureFlagEnabled", () => {
|
|
|
62
63
|
);
|
|
63
64
|
});
|
|
64
65
|
|
|
66
|
+
test("safe-storage-limits defaults to disabled", () => {
|
|
67
|
+
const config = {} as any;
|
|
68
|
+
|
|
69
|
+
expect(
|
|
70
|
+
isAssistantFeatureFlagEnabled(SAFE_STORAGE_LIMITS_FLAG, config),
|
|
71
|
+
).toBe(false);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
test("safe-storage-limits respects explicit override", () => {
|
|
75
|
+
_setOverridesForTesting({ [SAFE_STORAGE_LIMITS_FLAG]: true });
|
|
76
|
+
const config = {} as any;
|
|
77
|
+
|
|
78
|
+
expect(
|
|
79
|
+
isAssistantFeatureFlagEnabled(SAFE_STORAGE_LIMITS_FLAG, config),
|
|
80
|
+
).toBe(true);
|
|
81
|
+
});
|
|
82
|
+
|
|
65
83
|
test("unknown flag defaults to true when no persisted override", () => {
|
|
66
84
|
const config = {} as any;
|
|
67
85
|
|
|
@@ -389,13 +389,19 @@ describe("auto-analysis batch trigger uses analysis.batchSize cadence", () => {
|
|
|
389
389
|
const originalExtractionBatch = TEST_CONFIG.memory.extraction.batchSize;
|
|
390
390
|
const originalAnalysisBatch = TEST_CONFIG.analysis.batchSize;
|
|
391
391
|
|
|
392
|
+
const originalV2Enabled = TEST_CONFIG.memory.v2.enabled;
|
|
393
|
+
|
|
392
394
|
beforeEach(() => {
|
|
393
395
|
_setOverridesForTesting({ "auto-analyze": true });
|
|
396
|
+
// memory.v2.enabled gates v1 graph_extract enqueue; force off so
|
|
397
|
+
// these cadence tests can observe the v1 path.
|
|
398
|
+
TEST_CONFIG.memory.v2.enabled = false;
|
|
394
399
|
TEST_CONFIG.memory.extraction.batchSize = 2;
|
|
395
400
|
TEST_CONFIG.analysis.batchSize = 5;
|
|
396
401
|
});
|
|
397
402
|
|
|
398
403
|
afterEach(() => {
|
|
404
|
+
TEST_CONFIG.memory.v2.enabled = originalV2Enabled;
|
|
399
405
|
TEST_CONFIG.memory.extraction.batchSize = originalExtractionBatch;
|
|
400
406
|
TEST_CONFIG.analysis.batchSize = originalAnalysisBatch;
|
|
401
407
|
});
|
|
@@ -537,3 +543,45 @@ describe("auto-analysis batch trigger uses analysis.batchSize cadence", () => {
|
|
|
537
543
|
expect(row.runAfter).toBeLessThanOrEqual(after + 1_000);
|
|
538
544
|
});
|
|
539
545
|
});
|
|
546
|
+
|
|
547
|
+
// ─────────────────────────────────────────────────────────────────
|
|
548
|
+
// Indexer v1/v2 mutual exclusion: when memory.v2.enabled is on, the
|
|
549
|
+
// v1 graph_extract enqueue is suppressed (v2 reads from buffer.md,
|
|
550
|
+
// so v1 graph data is unread). When v2 is disabled, v1 graph_extract
|
|
551
|
+
// fires.
|
|
552
|
+
// ─────────────────────────────────────────────────────────────────
|
|
553
|
+
|
|
554
|
+
describe("indexer v1/v2 mutual exclusion for graph_extract", () => {
|
|
555
|
+
// Force the v1 batch trigger so any enqueued row is observable.
|
|
556
|
+
const originalExtractionBatch = TEST_CONFIG.memory.extraction.batchSize;
|
|
557
|
+
const originalV2Enabled = TEST_CONFIG.memory.v2.enabled;
|
|
558
|
+
|
|
559
|
+
beforeEach(() => {
|
|
560
|
+
TEST_CONFIG.memory.extraction.batchSize = 1;
|
|
561
|
+
});
|
|
562
|
+
|
|
563
|
+
afterEach(() => {
|
|
564
|
+
TEST_CONFIG.memory.extraction.batchSize = originalExtractionBatch;
|
|
565
|
+
TEST_CONFIG.memory.v2.enabled = originalV2Enabled;
|
|
566
|
+
});
|
|
567
|
+
|
|
568
|
+
test("v2 active (config on) → graph_extract not enqueued", async () => {
|
|
569
|
+
TEST_CONFIG.memory.v2.enabled = true;
|
|
570
|
+
|
|
571
|
+
const source = createConversation("v2-active");
|
|
572
|
+
await indexMessages(source.id, 2);
|
|
573
|
+
|
|
574
|
+
expect(countJobsOfType("graph_extract", source.id)).toBe(0);
|
|
575
|
+
});
|
|
576
|
+
|
|
577
|
+
test("config gate off → graph_extract enqueued", async () => {
|
|
578
|
+
TEST_CONFIG.memory.v2.enabled = false;
|
|
579
|
+
|
|
580
|
+
const source = createConversation("v2-config-off");
|
|
581
|
+
await indexMessages(source.id, 2);
|
|
582
|
+
|
|
583
|
+
expect(countJobsOfType("graph_extract", source.id)).toBeGreaterThanOrEqual(
|
|
584
|
+
1,
|
|
585
|
+
);
|
|
586
|
+
});
|
|
587
|
+
});
|