@vellumai/assistant 0.10.1 → 0.10.2-dev.202606241651.2d2b40d
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/docs/workspace-tools.md +42 -33
- package/eslint-rules/cli-no-daemon-internals.js +6 -0
- package/node_modules/@vellumai/gateway-client/src/__tests__/guardian-delivery-contract.test.ts +91 -0
- package/node_modules/@vellumai/gateway-client/src/__tests__/trust-verdict-contract.test.ts +31 -0
- package/node_modules/@vellumai/gateway-client/src/guardian-delivery-contract.ts +48 -0
- package/node_modules/@vellumai/gateway-client/src/index.ts +14 -0
- package/node_modules/@vellumai/gateway-client/src/trust-verdict-contract.ts +17 -0
- package/openapi.yaml +74 -1
- package/package.json +1 -1
- package/scripts/test.sh +36 -15
- package/src/__tests__/actor-token-service.test.ts +36 -14
- package/src/__tests__/agent-loop-override-profile.test.ts +1 -0
- package/src/__tests__/agent-wake-disk-pressure-callsite.test.ts +2 -0
- package/src/__tests__/agent-wake-override-profile.test.ts +2 -0
- package/src/__tests__/annotate-activity-metadata.test.ts +2 -0
- package/src/__tests__/annotate-risk-options.test.ts +2 -0
- package/src/__tests__/approval-cascade.test.ts +2 -0
- package/src/__tests__/background-workers-disk-pressure.test.ts +2 -0
- package/src/__tests__/btw-routes.test.ts +2 -0
- package/src/__tests__/build-persisted-content.test.ts +2 -0
- package/src/__tests__/call-controller.test.ts +19 -0
- package/src/__tests__/channel-guardian.test.ts +94 -58
- package/src/__tests__/channel-reply-delivery.test.ts +2 -0
- package/src/__tests__/compaction-events.test.ts +2 -0
- package/src/__tests__/compaction.benchmark.test.ts +2 -0
- package/src/__tests__/compactor-call-site-logging.test.ts +2 -0
- package/src/__tests__/compactor-low-watermark-cut.test.ts +2 -0
- package/src/__tests__/compactor-preserved-tail-count.test.ts +2 -0
- package/src/__tests__/compactor-summary-call-truncation.test.ts +2 -0
- package/src/__tests__/compactor-web-search-strip.test.ts +2 -0
- package/src/__tests__/computer-use-tools.test.ts +13 -0
- package/src/__tests__/config-loader-backfill.test.ts +5 -1
- package/src/__tests__/config-schema.test.ts +1 -0
- package/src/__tests__/confirmation-request-guardian-bridge.test.ts +31 -29
- package/src/__tests__/contacts-relay-reads.test.ts +13 -15
- package/src/__tests__/conversation-abort-tool-results.test.ts +2 -0
- package/src/__tests__/conversation-agent-loop-disk-pressure.test.ts +2 -0
- package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +2 -0
- package/src/__tests__/conversation-agent-loop-overflow.test.ts +2 -0
- package/src/__tests__/conversation-agent-loop.test.ts +7 -0
- package/src/__tests__/conversation-analysis-routes.test.ts +2 -0
- package/src/__tests__/conversation-app-control-lifecycle.test.ts +2 -0
- package/src/__tests__/conversation-confirmation-signals.test.ts +2 -0
- package/src/__tests__/conversation-history-web-search.test.ts +2 -0
- package/src/__tests__/conversation-load-history-repair.test.ts +2 -0
- package/src/__tests__/conversation-load-history-stripped.test.ts +2 -0
- package/src/__tests__/conversation-pairing.test.ts +2 -0
- package/src/__tests__/conversation-process-app-control-preactivation.test.ts +2 -0
- package/src/__tests__/conversation-process-callsite.test.ts +2 -0
- package/src/__tests__/conversation-provider-retry-repair.test.ts +2 -0
- package/src/__tests__/conversation-queue.test.ts +91 -0
- package/src/__tests__/conversation-routes-guardian-reply.test.ts +14 -0
- package/src/__tests__/conversation-routes-slash-commands.test.ts +14 -0
- package/src/__tests__/conversation-slash-queue.test.ts +2 -0
- package/src/__tests__/conversation-slash-unknown.test.ts +2 -0
- package/src/__tests__/conversation-speed-override.test.ts +2 -0
- package/src/__tests__/conversation-surfaces-action-delivery.test.ts +65 -0
- package/src/__tests__/conversation-title-service.test.ts +2 -0
- package/src/__tests__/conversation-tool-setup-attribution.test.ts +47 -0
- package/src/__tests__/conversation-usage.test.ts +2 -0
- package/src/__tests__/conversation-workspace-cache-state.test.ts +2 -0
- package/src/__tests__/conversation-workspace-injection.test.ts +2 -0
- package/src/__tests__/conversation-workspace-tool-tracking.test.ts +2 -0
- package/src/__tests__/credential-security-invariants.test.ts +0 -1
- package/src/__tests__/db-migration-rollback.test.ts +205 -171
- package/src/__tests__/db-test-helpers.ts +5 -4
- package/src/__tests__/deterministic-verification-control-plane.test.ts +4 -2
- package/src/__tests__/disk-pressure-guard.test.ts +41 -0
- package/src/__tests__/dm-persistence.test.ts +2 -0
- package/src/__tests__/emit-signal-routing-intent.test.ts +10 -5
- package/src/__tests__/events-dev-bypass-actor.test.ts +7 -1
- package/src/__tests__/filing-service.test.ts +2 -0
- package/src/__tests__/guardian-binding-drift-heal.test.ts +75 -10
- package/src/__tests__/guardian-dispatch.test.ts +95 -1
- package/src/__tests__/guardian-outbound-http.test.ts +13 -0
- package/src/__tests__/heartbeat-disk-pressure.test.ts +2 -0
- package/src/__tests__/heartbeat-service.test.ts +2 -0
- package/src/__tests__/helpers/channel-test-adapter.ts +1 -7
- package/src/__tests__/host-app-control-routes.test.ts +24 -30
- package/src/__tests__/host-bash-routes.test.ts +31 -41
- package/src/__tests__/host-browser-routes.test.ts +26 -32
- package/src/__tests__/host-cu-proxy.test.ts +299 -0
- package/src/__tests__/host-cu-routes-targeted.test.ts +25 -33
- package/src/__tests__/host-file-routes-targeted.test.ts +40 -52
- package/src/__tests__/host-transfer-routes-targeted.test.ts +31 -43
- package/src/__tests__/http-user-message-parity.test.ts +167 -8
- package/src/__tests__/inbound-slack-persistence.test.ts +2 -0
- package/src/__tests__/invite-redemption-service.test.ts +43 -0
- package/src/__tests__/llm-context-normalization.test.ts +105 -0
- package/src/__tests__/llm-usage-store.test.ts +25 -0
- package/src/__tests__/media-stream-server-integration.test.ts +127 -0
- package/src/__tests__/memory-retrieval-hook.test.ts +2 -0
- package/src/__tests__/messaging-send-tool.test.ts +2 -0
- package/src/__tests__/migration-import-from-url.test.ts +2 -2
- package/src/__tests__/native-web-search.test.ts +2 -0
- package/src/__tests__/non-member-access-request.test.ts +189 -17
- package/src/__tests__/notification-broadcaster.test.ts +4 -0
- package/src/__tests__/notification-decision-recipient-context.test.ts +33 -32
- package/src/__tests__/notification-deep-link.test.ts +6 -0
- package/src/__tests__/notification-guardian-path.test.ts +19 -0
- package/src/__tests__/outbound-slack-persistence.test.ts +2 -0
- package/src/__tests__/pending-interactions-resolved-event.test.ts +7 -4
- package/src/__tests__/persistence-secret-redaction.test.ts +2 -0
- package/src/__tests__/plugin-bootstrap.test.ts +3 -73
- package/src/__tests__/plugin-route-contribution.test.ts +4 -17
- package/src/__tests__/plugin-tool-contribution.test.ts +3 -18
- package/src/__tests__/plugin-types.test.ts +0 -2
- package/src/__tests__/process-message-background-slack.test.ts +2 -0
- package/src/__tests__/process-message-display-content.test.ts +2 -0
- package/src/__tests__/provider-usage-tracking.test.ts +39 -0
- package/src/__tests__/regenerate-fire-and-forget-trace.test.ts +2 -0
- package/src/__tests__/registry.test.ts +3 -0
- package/src/__tests__/relay-server.test.ts +694 -25
- package/src/__tests__/runtime-attachment-metadata.test.ts +0 -1
- package/src/__tests__/secret-ingress-http.test.ts +14 -0
- package/src/__tests__/send-endpoint-busy.test.ts +30 -8
- package/src/__tests__/skills.test.ts +44 -0
- package/src/__tests__/slack-inbound-verification.test.ts +47 -2
- package/src/__tests__/sse-actor-principal-guardian-source.test.ts +102 -0
- package/src/__tests__/steer-on-enqueue-question.test.ts +181 -0
- package/src/__tests__/stt-hints.test.ts +44 -13
- package/src/__tests__/subagent-detail.test.ts +27 -0
- package/src/__tests__/subagent-disposal.test.ts +65 -0
- package/src/__tests__/subagent-notify-parent.test.ts +2 -0
- package/src/__tests__/subagent-spawn-tool-fork.test.ts +2 -0
- package/src/__tests__/subagent-tools.test.ts +2 -0
- package/src/__tests__/suggestion-routes.test.ts +2 -0
- package/src/__tests__/title-generate-hook.test.ts +2 -0
- package/src/__tests__/tool-executor-lifecycle-events.test.ts +2 -0
- package/src/__tests__/tool-executor.test.ts +16 -11
- package/src/__tests__/tool-preview-lifecycle.test.ts +2 -0
- package/src/__tests__/tool-result-metadata-plumbing.test.ts +2 -0
- package/src/__tests__/tool-start-timestamp.test.ts +2 -0
- package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +10 -10
- package/src/__tests__/twilio-routes.test.ts +96 -0
- package/src/__tests__/verification-control-plane-policy.test.ts +2 -0
- package/src/__tests__/web-search-backend-failure.test.ts +2 -0
- package/src/__tests__/workspace-tool-loader.test.ts +195 -2
- package/src/agent/loop-exclusive-tool.test.ts +150 -0
- package/src/agent/loop.ts +56 -0
- package/src/api/constants/sse-replay.ts +41 -0
- package/src/api/index.ts +6 -0
- package/src/api/responses/llm-request-log-entry.ts +25 -0
- package/src/api/responses/subagent-detail.ts +17 -0
- package/src/calls/__tests__/relay-setup-router.test.ts +262 -4
- package/src/calls/call-domain.ts +3 -3
- package/src/calls/guardian-dispatch.ts +10 -8
- package/src/calls/inbound-trust-reader.ts +17 -1
- package/src/calls/media-stream-server.ts +21 -0
- package/src/calls/relay-server.ts +167 -50
- package/src/calls/relay-setup-router.ts +37 -7
- package/src/calls/relay-verification.ts +4 -4
- package/src/calls/stt-hints.ts +9 -12
- package/src/calls/twilio-routes.ts +14 -4
- package/src/cli/commands/__tests__/cache.test.ts +8 -1
- package/src/cli/commands/cache.ts +194 -181
- package/src/cli/commands/db/__tests__/repair.test.ts +6 -5
- package/src/cli/commands/db/status.ts +37 -1
- package/src/cli/commands/mcp.ts +252 -218
- package/src/cli/commands/memory/__tests__/worker.test.ts +302 -0
- package/src/cli/commands/memory/index.ts +2 -0
- package/src/cli/commands/memory/worker.ts +175 -0
- package/src/cli/commands/plugins.ts +75 -3
- package/src/cli/lib/__tests__/install-from-github.test.ts +102 -0
- package/src/cli/lib/__tests__/list-installed-plugins.test.ts +160 -1
- package/src/cli/lib/list-installed-plugins.ts +179 -1
- package/src/config/__tests__/loader-callsite-strip-fallback.test.ts +143 -0
- package/src/config/bundled-skills/computer-use/TOOLS.json +6 -1
- package/src/config/bundled-skills/contacts/tools/contact-merge.ts +27 -17
- package/src/config/bundled-skills/contacts/tools/contact-search.ts +13 -3
- package/src/config/feature-flag-registry.json +0 -8
- package/src/config/loader.ts +36 -5
- package/src/config/schemas/__tests__/memory-v3.test.ts +1 -0
- package/src/config/schemas/memory-lifecycle.ts +12 -0
- package/src/config/schemas/memory-v3.ts +7 -0
- package/src/config/schemas/memory.ts +4 -0
- package/src/config/schemas/timeouts.ts +8 -0
- package/src/config/seed-inference-profiles.ts +14 -5
- package/src/config/skills.ts +27 -5
- package/src/contacts/__tests__/guardian-delivery-reader.test.ts +312 -0
- package/src/contacts/contacts-write.ts +3 -0
- package/src/contacts/guardian-delivery-reader.ts +223 -0
- package/src/daemon/conversation-agent-loop.ts +9 -0
- package/src/daemon/conversation-process.ts +39 -17
- package/src/daemon/conversation-surfaces.ts +8 -0
- package/src/daemon/conversation-tool-setup.ts +49 -16
- package/src/daemon/conversation.ts +21 -2
- package/src/daemon/disk-pressure-guard.ts +12 -2
- package/src/daemon/event-loop-watchdog.ts +28 -1
- package/src/daemon/external-plugins-bootstrap.ts +4 -34
- package/src/daemon/handlers/__tests__/config-a2a-redeem.test.ts +25 -0
- package/src/daemon/handlers/__tests__/config-channels.test.ts +225 -0
- package/src/daemon/handlers/config-a2a.ts +6 -14
- package/src/daemon/handlers/config-channels.ts +78 -22
- package/src/daemon/handlers/conversations.ts +77 -0
- package/src/daemon/host-cu-proxy.ts +102 -11
- package/src/daemon/lifecycle.ts +4 -0
- package/src/daemon/memory-v2-startup.test.ts +72 -0
- package/src/daemon/memory-v2-startup.ts +87 -19
- package/src/daemon/server.ts +0 -4
- package/src/daemon/shutdown-handlers.ts +20 -0
- package/src/daemon/tool-setup-types.ts +9 -0
- package/src/ipc/__tests__/clients-list-ipc.test.ts +1 -1
- package/src/ipc/assistant-server.ts +2 -2
- package/src/memory/__tests__/301-create-watchdog-events.test.ts +110 -0
- package/src/memory/__tests__/memory-retrospective-job.test.ts +8 -0
- package/src/memory/__tests__/prompt-override.test.ts +192 -0
- package/src/memory/__tests__/watchdog-events-store.test.ts +161 -0
- package/src/memory/conversation-crud.ts +38 -0
- package/src/memory/db-connection.ts +22 -3
- package/src/memory/db-init.ts +36 -502
- package/src/memory/db-singleton.ts +6 -4
- package/src/memory/jobs-worker.ts +58 -0
- package/src/memory/llm-usage-store.ts +48 -20
- package/src/memory/memory-retrospective-job.ts +9 -8
- package/src/memory/migrations/014-backfill-inbox-thread-state.ts +13 -3
- package/src/memory/migrations/136-drop-assistant-id-columns.ts +52 -27
- package/src/memory/migrations/209-strip-thinking-from-consolidated.ts +130 -56
- package/src/memory/migrations/300-add-processing-started-at.ts +30 -0
- package/src/memory/migrations/301-create-watchdog-events.ts +45 -0
- package/src/memory/migrations/__tests__/014-backfill-inbox-thread-state.test.ts +108 -0
- package/src/memory/migrations/__tests__/136-drop-assistant-id-columns.test.ts +82 -0
- package/src/memory/migrations/__tests__/209-strip-thinking-from-consolidated.test.ts +224 -0
- package/src/memory/migrations/__tests__/run-migrations.test.ts +2 -2
- package/src/memory/migrations/run-migrations.ts +90 -6
- package/src/memory/migrations/schema-introspection.ts +14 -0
- package/src/memory/migrations/validate-migration-state.ts +101 -66
- package/src/memory/prompt-override.ts +129 -0
- package/src/memory/schema/conversations.ts +9 -0
- package/src/memory/schema/infrastructure.ts +20 -0
- package/src/memory/steps.ts +573 -0
- package/src/memory/v2/__tests__/cli-command-store.test.ts +25 -0
- package/src/memory/v2/__tests__/skill-store.test.ts +80 -0
- package/src/memory/v2/cli-command-store.ts +75 -38
- package/src/memory/v2/prompts/consolidation.ts +13 -82
- package/src/memory/v2/prompts/router.ts +21 -93
- package/src/memory/v2/skill-store.ts +68 -31
- package/src/memory/watchdog-events-store.ts +87 -0
- package/src/memory/worker-control.ts +118 -0
- package/src/memory/worker-process.ts +72 -0
- package/src/notifications/__tests__/broadcaster.test.ts +16 -8
- package/src/notifications/__tests__/connected-channels.test.ts +114 -0
- package/src/notifications/__tests__/decision-engine.test.ts +78 -9
- package/src/notifications/__tests__/destination-resolver.test.ts +256 -0
- package/src/notifications/broadcaster.ts +8 -1
- package/src/notifications/decision-engine.ts +15 -7
- package/src/notifications/destination-resolver.ts +68 -24
- package/src/notifications/emit-signal.ts +39 -14
- package/src/onboarding/checkin-event.test.ts +220 -0
- package/src/onboarding/checkin-event.ts +321 -0
- package/src/onboarding/schedule-checkin.ts +190 -0
- package/src/permissions/question-prompter.test.ts +1 -1
- package/src/permissions/question-prompter.ts +7 -4
- package/src/plugin-api/index.ts +6 -6
- package/src/plugin-api/types.ts +3 -5
- package/src/plugin-api/vision-support.test.ts +28 -4
- package/src/plugin-api/vision-support.ts +66 -31
- package/src/plugins/defaults/advisor/__tests__/consult.test.ts +161 -0
- package/src/plugins/defaults/advisor/__tests__/context-pack-gating.test.ts +106 -0
- package/src/plugins/defaults/advisor/__tests__/context-pack.test.ts +60 -0
- package/src/plugins/defaults/advisor/consult.ts +110 -6
- package/src/plugins/defaults/advisor/context-pack.ts +288 -0
- package/src/plugins/defaults/advisor/steering.ts +14 -2
- package/src/plugins/defaults/advisor/tools/advisor.ts +32 -5
- package/src/plugins/defaults/image-fallback/__tests__/image-fallback.test.ts +47 -7
- package/src/plugins/defaults/image-fallback/hooks/post-tool-use.ts +10 -11
- package/src/plugins/defaults/image-fallback/hooks/user-prompt-submit.ts +12 -20
- package/src/plugins/defaults/image-fallback/src/caption-blocks.ts +42 -11
- package/src/plugins/defaults/memory-v3-shadow/orchestrate.ts +11 -2
- package/src/plugins/defaults/memory-v3-shadow/pool-select.test.ts +146 -0
- package/src/plugins/defaults/memory-v3-shadow/pool-select.ts +29 -1
- package/src/plugins/defaults/memory-v3-shadow/shadow-plugin.ts +8 -1
- package/src/plugins/mtime-cache.ts +7 -2
- package/src/plugins/types.ts +0 -2
- package/src/providers/anthropic/client.ts +5 -0
- package/src/providers/call-site-routing.ts +4 -0
- package/src/providers/model-catalog.ts +16 -0
- package/src/providers/openai/responses-provider.ts +5 -0
- package/src/providers/openrouter/client.ts +5 -0
- package/src/providers/provider-send-message.ts +4 -0
- package/src/providers/ratelimit.ts +4 -0
- package/src/providers/retry.ts +4 -0
- package/src/providers/types.ts +9 -0
- package/src/providers/usage-tracking.ts +4 -0
- package/src/runtime/__tests__/channel-verification-service.test.ts +133 -0
- package/src/runtime/__tests__/guardian-vellum-migration.test.ts +181 -0
- package/src/runtime/__tests__/is-guardian-bound-for-channel.test.ts +66 -0
- package/src/runtime/__tests__/local-principal-trust.test.ts +164 -0
- package/src/runtime/__tests__/trust-verdict-consumer.test.ts +335 -3
- package/src/runtime/access-request-helper.ts +19 -39
- package/src/runtime/actor-trust-resolver.ts +2 -2
- package/src/runtime/anchored-guardian.test.ts +156 -0
- package/src/runtime/anchored-guardian.ts +135 -0
- package/src/runtime/assistant-event-hub.ts +1 -1
- package/src/runtime/assistant-stream-state.ts +9 -2
- package/src/runtime/auth/__tests__/require-bound-guardian.test.ts +99 -0
- package/src/runtime/auth/require-bound-guardian.ts +21 -11
- package/src/runtime/channel-verification-service.ts +56 -31
- package/src/runtime/confirmation-request-guardian-bridge.ts +3 -3
- package/src/runtime/guardian-vellum-migration.ts +66 -7
- package/src/runtime/invite-redemption-service.ts +50 -18
- package/src/runtime/local-actor-identity.ts +76 -11
- package/src/runtime/local-principal-trust.ts +52 -0
- package/src/runtime/pending-interactions.ts +11 -1
- package/src/runtime/routes/__tests__/channel-verification-revoke.test.ts +56 -5
- package/src/runtime/routes/__tests__/channel-verification-routes.test.ts +1 -1
- package/src/runtime/routes/__tests__/contact-routes.test.ts +212 -0
- package/src/runtime/routes/__tests__/global-search-routes.test.ts +93 -0
- package/src/runtime/routes/__tests__/surface-action-routes.test.ts +215 -1
- package/src/runtime/routes/browser-routes.ts +1 -1
- package/src/runtime/routes/channel-verification-routes.ts +3 -3
- package/src/runtime/routes/contact-routes.ts +8 -32
- package/src/runtime/routes/conversation-cli-routes.ts +4 -5
- package/src/runtime/routes/conversation-list-routes.ts +4 -7
- package/src/runtime/routes/conversation-routes.ts +74 -81
- package/src/runtime/routes/events-routes.ts +2 -2
- package/src/runtime/routes/global-search-routes.ts +3 -1
- package/src/runtime/routes/guardian-action-routes.ts +4 -5
- package/src/runtime/routes/host-app-control-routes.ts +5 -4
- package/src/runtime/routes/host-bash-routes.ts +5 -4
- package/src/runtime/routes/host-browser-routes.ts +9 -11
- package/src/runtime/routes/host-cu-routes.ts +5 -4
- package/src/runtime/routes/host-file-routes.ts +5 -4
- package/src/runtime/routes/host-transfer-routes.ts +6 -6
- package/src/runtime/routes/http-adapter.ts +1 -1
- package/src/runtime/routes/identity-routes.ts +3 -2
- package/src/runtime/routes/inbound-message-handler.ts +5 -5
- package/src/runtime/routes/inbound-stages/acl-enforcement.test.ts +97 -5
- package/src/runtime/routes/inbound-stages/acl-enforcement.ts +61 -49
- package/src/runtime/routes/inbound-stages/background-dispatch.ts +16 -4
- package/src/runtime/routes/inbound-stages/escalation-intercept.ts +7 -7
- package/src/runtime/routes/inbound-stages/guardian-activation-intercept.test.ts +21 -8
- package/src/runtime/routes/inbound-stages/guardian-activation-intercept.ts +14 -3
- package/src/runtime/routes/index.ts +2 -0
- package/src/runtime/routes/llm-context-normalization.ts +71 -0
- package/src/runtime/routes/mcp-auth-routes.ts +38 -15
- package/src/runtime/routes/migration-rollback-routes.ts +4 -3
- package/src/runtime/routes/migration-routes.ts +4 -1
- package/src/runtime/routes/onboarding-checkin-routes.ts +86 -0
- package/src/runtime/routes/subagents-routes.ts +5 -0
- package/src/runtime/routes/surface-action-routes.ts +51 -55
- package/src/runtime/services/__tests__/conversation-serializer.test.ts +1 -0
- package/src/runtime/services/conversation-serializer.ts +7 -9
- package/src/runtime/tool-grant-request-helper.ts +3 -3
- package/src/runtime/trust-verdict-consumer.ts +85 -9
- package/src/runtime/verification-outbound-actions.ts +18 -18
- package/src/signals/user-message.ts +16 -0
- package/src/subagent/manager.ts +9 -0
- package/src/telemetry/types.ts +34 -1
- package/src/telemetry/usage-telemetry-reporter.test.ts +3 -2
- package/src/telemetry/usage-telemetry-reporter.ts +87 -3
- package/src/tools/ask-question/ask-question-tool.test.ts +29 -0
- package/src/tools/ask-question/ask-question-tool.ts +13 -0
- package/src/tools/computer-use/definitions.ts +8 -2
- package/src/tools/executor.ts +4 -4
- package/src/tools/registry.ts +18 -0
- package/src/tools/tool-approval-handler.ts +1 -1
- package/src/tools/tool-defaults.ts +9 -2
- package/src/tools/types.ts +17 -2
- package/src/tools/workspace-tools/loader.ts +348 -244
- package/src/util/platform.ts +5 -0
- package/src/util/telemetry-db-path.ts +24 -0
- package/src/workspace/migrations/017-seed-persona-dirs.ts +3 -34
- package/src/workspace/migrations/019-scope-journal-to-guardian.ts +3 -24
- package/src/__tests__/workspace-tools-watcher-flag.test.ts +0 -70
- package/src/daemon/workspace-tools-watcher.ts +0 -328
- package/src/memory/migrations/registry.ts +0 -573
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { mkdtempSync } from "node:fs";
|
|
2
|
+
import { tmpdir } from "node:os";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { Database } from "bun:sqlite";
|
|
5
|
+
import { describe, expect, mock, test } from "bun:test";
|
|
6
|
+
|
|
7
|
+
import { drizzle } from "drizzle-orm/bun-sqlite";
|
|
8
|
+
|
|
9
|
+
// The migration opens the telemetry connection via getTelemetrySqlite(), which
|
|
10
|
+
// routes through the singleton. Mock the path helper so the connection opens a
|
|
11
|
+
// temp file we control, then mock the db-connection getter so it returns that
|
|
12
|
+
// same file's Database.
|
|
13
|
+
const tmpDir = mkdtempSync(join(tmpdir(), "wd-migration-"));
|
|
14
|
+
const telemetryPath = join(tmpDir, "assistant-telemetry.db");
|
|
15
|
+
let openedRaw: Database | null = null;
|
|
16
|
+
|
|
17
|
+
mock.module("../../util/telemetry-db-path.js", () => ({
|
|
18
|
+
getTelemetryDbPath: () => telemetryPath,
|
|
19
|
+
}));
|
|
20
|
+
|
|
21
|
+
mock.module("../db-connection.js", () => ({
|
|
22
|
+
// The migration calls getTelemetrySqlite(); return our temp Database so the
|
|
23
|
+
// DDL runs against it. getSqliteFrom is used in other contexts but not by
|
|
24
|
+
// this migration when the mock is active.
|
|
25
|
+
getTelemetrySqlite: () => openedRaw,
|
|
26
|
+
getSqliteFrom: (db: unknown) =>
|
|
27
|
+
(db as unknown as { $client: Database }).$client,
|
|
28
|
+
}));
|
|
29
|
+
|
|
30
|
+
import { createWatchdogEventsTable } from "../migrations/301-create-watchdog-events.js";
|
|
31
|
+
import * as schema from "../schema.js";
|
|
32
|
+
|
|
33
|
+
function createTestDb() {
|
|
34
|
+
const sqlite = new Database(":memory:");
|
|
35
|
+
return { sqlite, db: drizzle(sqlite, { schema }) };
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function openTelemetryRaw(): Database {
|
|
39
|
+
openedRaw = new Database(telemetryPath);
|
|
40
|
+
return openedRaw;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function columnNames(raw: Database): string[] {
|
|
44
|
+
return (
|
|
45
|
+
raw.query("PRAGMA table_info(watchdog_events)").all() as Array<{
|
|
46
|
+
name: string;
|
|
47
|
+
}>
|
|
48
|
+
).map((c) => c.name);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function indexNames(raw: Database): string[] {
|
|
52
|
+
return (
|
|
53
|
+
raw.query("PRAGMA index_list(watchdog_events)").all() as Array<{
|
|
54
|
+
name: string;
|
|
55
|
+
}>
|
|
56
|
+
).map((i) => i.name);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
describe("migration 300: watchdog_events table on telemetry DB", () => {
|
|
60
|
+
test("creates the table with the expected columns", () => {
|
|
61
|
+
const raw = openTelemetryRaw();
|
|
62
|
+
const { db } = createTestDb();
|
|
63
|
+
|
|
64
|
+
createWatchdogEventsTable(db);
|
|
65
|
+
|
|
66
|
+
expect(columnNames(raw)).toEqual([
|
|
67
|
+
"id",
|
|
68
|
+
"created_at",
|
|
69
|
+
"check_name",
|
|
70
|
+
"value",
|
|
71
|
+
"detail",
|
|
72
|
+
]);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
test("creates the (created_at, id) cursor index", () => {
|
|
76
|
+
const raw = openTelemetryRaw();
|
|
77
|
+
const { db } = createTestDb();
|
|
78
|
+
|
|
79
|
+
createWatchdogEventsTable(db);
|
|
80
|
+
|
|
81
|
+
expect(indexNames(raw)).toContain("idx_watchdog_events_created_at_id");
|
|
82
|
+
const columns = (
|
|
83
|
+
raw
|
|
84
|
+
.query("PRAGMA index_info(idx_watchdog_events_created_at_id)")
|
|
85
|
+
.all() as Array<{ name: string }>
|
|
86
|
+
).map((c) => c.name);
|
|
87
|
+
expect(columns).toEqual(["created_at", "id"]);
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
test("is idempotent — re-run is a no-op and preserves existing rows", () => {
|
|
91
|
+
const raw = openTelemetryRaw();
|
|
92
|
+
const { db } = createTestDb();
|
|
93
|
+
|
|
94
|
+
createWatchdogEventsTable(db);
|
|
95
|
+
raw.exec(/*sql*/ `
|
|
96
|
+
INSERT INTO watchdog_events (id, created_at, check_name, value)
|
|
97
|
+
VALUES ('wd-1', 1000, 'event_loop_blocked', 60000)
|
|
98
|
+
`);
|
|
99
|
+
|
|
100
|
+
expect(() => createWatchdogEventsTable(db)).not.toThrow();
|
|
101
|
+
|
|
102
|
+
const rows = raw.query("SELECT id FROM watchdog_events").all();
|
|
103
|
+
expect(rows).toEqual([{ id: "wd-1" }]);
|
|
104
|
+
expect(
|
|
105
|
+
indexNames(raw).filter(
|
|
106
|
+
(name) => name === "idx_watchdog_events_created_at_id",
|
|
107
|
+
),
|
|
108
|
+
).toHaveLength(1);
|
|
109
|
+
});
|
|
110
|
+
});
|
|
@@ -161,6 +161,14 @@ mock.module("../conversation-crud.js", () => ({
|
|
|
161
161
|
title: "Source conversation",
|
|
162
162
|
};
|
|
163
163
|
},
|
|
164
|
+
// Mirrors the real `isConversationProcessing` which reads the persisted
|
|
165
|
+
// `processing_started_at` column. In tests, the mock registry
|
|
166
|
+
// (`loadedConversations`) is the source of truth — a conversation id
|
|
167
|
+
// present with `processing: true` simulates a mid-turn conversation.
|
|
168
|
+
isConversationProcessing: (id: string) => {
|
|
169
|
+
const entry = loadedConversations[id];
|
|
170
|
+
return entry?.processing ?? false;
|
|
171
|
+
},
|
|
164
172
|
forkConversationForRetrospective: async (params: {
|
|
165
173
|
conversationId: string;
|
|
166
174
|
throughMessageId?: string;
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for the shared prompt-override loader
|
|
3
|
+
* (`assistant/src/memory/prompt-override.ts`) and the memory-v3
|
|
4
|
+
* `resolveSelectorPrompt` built on it. Mirrors the v2 router/consolidation
|
|
5
|
+
* prompt-path suites: a configured file replaces the bundled prompt, and any
|
|
6
|
+
* missing / empty / oversized / non-regular / unreadable file degrades to the
|
|
7
|
+
* bundled prompt with a diagnostic warning.
|
|
8
|
+
*/
|
|
9
|
+
import { execFileSync } from "node:child_process";
|
|
10
|
+
import { mkdtempSync, rmSync, symlinkSync, writeFileSync } from "node:fs";
|
|
11
|
+
import { homedir, tmpdir } from "node:os";
|
|
12
|
+
import { join } from "node:path";
|
|
13
|
+
import { afterEach, beforeEach, describe, expect, test } from "bun:test";
|
|
14
|
+
|
|
15
|
+
import { resolveSelectorPrompt } from "../../plugins/defaults/memory-v3-shadow/pool-select.js";
|
|
16
|
+
import {
|
|
17
|
+
loadPromptOverride,
|
|
18
|
+
MAX_PROMPT_OVERRIDE_BYTES,
|
|
19
|
+
resolveOverridePath,
|
|
20
|
+
} from "../prompt-override.js";
|
|
21
|
+
|
|
22
|
+
const warnCalls: Array<{ data: Record<string, unknown>; msg: string }> = [];
|
|
23
|
+
const recordingLogger = {
|
|
24
|
+
warn: (data: object, msg: string) => {
|
|
25
|
+
warnCalls.push({ data: data as Record<string, unknown>, msg });
|
|
26
|
+
},
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
let tmpDir: string;
|
|
30
|
+
|
|
31
|
+
beforeEach(() => {
|
|
32
|
+
warnCalls.length = 0;
|
|
33
|
+
tmpDir = mkdtempSync(join(tmpdir(), "prompt-override-"));
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
afterEach(() => {
|
|
37
|
+
rmSync(tmpDir, { recursive: true, force: true });
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
/** Load against the per-test temp dir with the recording logger. */
|
|
41
|
+
const load = (
|
|
42
|
+
overridePath: string | null | undefined,
|
|
43
|
+
label = "test prompt",
|
|
44
|
+
): string | null =>
|
|
45
|
+
loadPromptOverride({
|
|
46
|
+
overridePath,
|
|
47
|
+
workspaceDir: tmpDir,
|
|
48
|
+
log: recordingLogger,
|
|
49
|
+
label,
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
describe("resolveOverridePath", () => {
|
|
53
|
+
test("expands a leading ~/ to the home directory", () => {
|
|
54
|
+
expect(resolveOverridePath("~/sub/x.md", tmpDir)).toBe(
|
|
55
|
+
join(homedir(), "sub/x.md"),
|
|
56
|
+
);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
test("uses an absolute path as-is", () => {
|
|
60
|
+
expect(resolveOverridePath("/abs/x.md", tmpDir)).toBe("/abs/x.md");
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
test("resolves a relative path under the workspace dir", () => {
|
|
64
|
+
expect(resolveOverridePath("rel/x.md", tmpDir)).toBe(
|
|
65
|
+
join(tmpDir, "rel/x.md"),
|
|
66
|
+
);
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
describe("loadPromptOverride — usable override", () => {
|
|
71
|
+
test("null overridePath returns null without touching the filesystem", () => {
|
|
72
|
+
expect(load(null)).toBeNull();
|
|
73
|
+
expect(warnCalls).toHaveLength(0);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
test("undefined overridePath (unset config field) returns null without throwing", () => {
|
|
77
|
+
expect(load(undefined)).toBeNull();
|
|
78
|
+
expect(warnCalls).toHaveLength(0);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
test("returns an absolute-path file's contents verbatim", () => {
|
|
82
|
+
const p = join(tmpDir, "abs.md");
|
|
83
|
+
writeFileSync(p, "absolute body\n");
|
|
84
|
+
expect(load(p)).toBe("absolute body\n");
|
|
85
|
+
expect(warnCalls).toHaveLength(0);
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
test("resolves a relative path under the workspace dir", () => {
|
|
89
|
+
writeFileSync(join(tmpDir, "rel.md"), "relative body\n");
|
|
90
|
+
expect(load("rel.md")).toBe("relative body\n");
|
|
91
|
+
expect(warnCalls).toHaveLength(0);
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
test("expands a leading ~/ to the home directory", () => {
|
|
95
|
+
const filename = `.vellum-prompt-override-test-${process.pid}.md`;
|
|
96
|
+
const p = join(homedir(), filename);
|
|
97
|
+
writeFileSync(p, "home body\n");
|
|
98
|
+
try {
|
|
99
|
+
expect(load(`~/${filename}`)).toBe("home body\n");
|
|
100
|
+
expect(warnCalls).toHaveLength(0);
|
|
101
|
+
} finally {
|
|
102
|
+
rmSync(p, { force: true });
|
|
103
|
+
}
|
|
104
|
+
});
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
describe("loadPromptOverride — fallback to null with a diagnostic warning", () => {
|
|
108
|
+
test("missing file logs ENOENT and returns null", () => {
|
|
109
|
+
expect(load(join(tmpDir, "missing.md"))).toBeNull();
|
|
110
|
+
expect(warnCalls).toHaveLength(1);
|
|
111
|
+
expect(warnCalls[0].data.code).toBe("ENOENT");
|
|
112
|
+
expect(warnCalls[0].data.fallback).toBe("bundled");
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
test("empty file is rejected", () => {
|
|
116
|
+
const p = join(tmpDir, "empty.md");
|
|
117
|
+
writeFileSync(p, "");
|
|
118
|
+
expect(load(p)).toBeNull();
|
|
119
|
+
expect(warnCalls[0].data.reason).toBe("empty_override");
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
test("whitespace-only file is rejected", () => {
|
|
123
|
+
const p = join(tmpDir, "ws.md");
|
|
124
|
+
writeFileSync(p, " \n\t\n");
|
|
125
|
+
expect(load(p)).toBeNull();
|
|
126
|
+
expect(warnCalls[0].data.reason).toBe("empty_override");
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
test("oversized file is rejected with its size", () => {
|
|
130
|
+
const p = join(tmpDir, "huge.md");
|
|
131
|
+
// 1 MiB + 1 byte — just over the cap so we don't waste test memory.
|
|
132
|
+
writeFileSync(p, Buffer.alloc(MAX_PROMPT_OVERRIDE_BYTES + 1, 0x61));
|
|
133
|
+
expect(load(p)).toBeNull();
|
|
134
|
+
expect(warnCalls[0].data.reason).toBe("oversized_override");
|
|
135
|
+
expect(warnCalls[0].data.size).toBe(MAX_PROMPT_OVERRIDE_BYTES + 1);
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
test("a directory is not a regular file", () => {
|
|
139
|
+
expect(load(tmpDir)).toBeNull();
|
|
140
|
+
expect(warnCalls[0].data.reason).toBe("not_regular_file");
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
test("a symlink is not a regular file (lstat does not follow it)", () => {
|
|
144
|
+
const target = join(tmpDir, "target.md");
|
|
145
|
+
writeFileSync(target, "real body\n");
|
|
146
|
+
const link = join(tmpDir, "link.md");
|
|
147
|
+
symlinkSync(target, link);
|
|
148
|
+
expect(load(link)).toBeNull();
|
|
149
|
+
expect(warnCalls[0].data.reason).toBe("not_regular_file");
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
test("a FIFO is not a regular file", () => {
|
|
153
|
+
const fifoPath = join(tmpDir, "fifo");
|
|
154
|
+
try {
|
|
155
|
+
execFileSync("mkfifo", [fifoPath]);
|
|
156
|
+
} catch {
|
|
157
|
+
// mkfifo unavailable on this platform — skip without failing.
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
expect(load(fifoPath)).toBeNull();
|
|
161
|
+
expect(warnCalls[0].data.reason).toBe("not_regular_file");
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
test("the label names the prompt in the warning message", () => {
|
|
165
|
+
load(join(tmpDir, "missing.md"), "router prompt");
|
|
166
|
+
expect(warnCalls[0].msg).toContain("router prompt override");
|
|
167
|
+
});
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
describe("resolveSelectorPrompt", () => {
|
|
171
|
+
// A stable phrase from the bundled selector prompt (`SYSTEM_PROMPT`).
|
|
172
|
+
const BUNDLED_PHRASE =
|
|
173
|
+
"Select EVERY candidate whose content the upcoming reply would draw on";
|
|
174
|
+
|
|
175
|
+
test("null path returns the bundled selector prompt", () => {
|
|
176
|
+
expect(resolveSelectorPrompt(null, tmpDir)).toContain(BUNDLED_PHRASE);
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
test("a configured file replaces the bundled prompt verbatim — no placeholder substitution", () => {
|
|
180
|
+
const body = "Custom selector instructions {{NOT_A_PLACEHOLDER}}\n";
|
|
181
|
+
writeFileSync(join(tmpDir, "selector.md"), body);
|
|
182
|
+
const out = resolveSelectorPrompt("selector.md", tmpDir);
|
|
183
|
+
expect(out).toBe(body);
|
|
184
|
+
expect(out).not.toContain(BUNDLED_PHRASE);
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
test("a missing override falls back to the bundled prompt", () => {
|
|
188
|
+
expect(resolveSelectorPrompt(join(tmpDir, "nope.md"), tmpDir)).toContain(
|
|
189
|
+
BUNDLED_PHRASE,
|
|
190
|
+
);
|
|
191
|
+
});
|
|
192
|
+
});
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, mock, test } from "bun:test";
|
|
2
|
+
|
|
3
|
+
// Silence the logger.
|
|
4
|
+
mock.module("../../util/logger.js", () => ({
|
|
5
|
+
getLogger: () =>
|
|
6
|
+
new Proxy({} as Record<string, unknown>, {
|
|
7
|
+
get: () => () => {},
|
|
8
|
+
}),
|
|
9
|
+
}));
|
|
10
|
+
|
|
11
|
+
let shareAnalytics = true;
|
|
12
|
+
|
|
13
|
+
mock.module("../../platform/consent-cache.js", () => ({
|
|
14
|
+
getCachedShareAnalytics: () => shareAnalytics,
|
|
15
|
+
}));
|
|
16
|
+
|
|
17
|
+
import { getTelemetryDb } from "../db-connection.js";
|
|
18
|
+
import { initializeDb } from "../db-init.js";
|
|
19
|
+
import { watchdogEvents } from "../schema.js";
|
|
20
|
+
import {
|
|
21
|
+
queryUnreportedWatchdogEvents,
|
|
22
|
+
recordWatchdogEvent,
|
|
23
|
+
} from "../watchdog-events-store.js";
|
|
24
|
+
|
|
25
|
+
await initializeDb();
|
|
26
|
+
|
|
27
|
+
function insertEvent(
|
|
28
|
+
id: string,
|
|
29
|
+
createdAt: number,
|
|
30
|
+
checkName = "event_loop_blocked",
|
|
31
|
+
): void {
|
|
32
|
+
const db = getTelemetryDb();
|
|
33
|
+
if (!db) throw new Error("telemetry DB unavailable in test");
|
|
34
|
+
db.insert(watchdogEvents).values({ id, createdAt, checkName }).run();
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function clearEvents(): void {
|
|
38
|
+
const db = getTelemetryDb();
|
|
39
|
+
if (!db) throw new Error("telemetry DB unavailable in test");
|
|
40
|
+
db.delete(watchdogEvents).run();
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
describe("watchdog-events-store", () => {
|
|
44
|
+
beforeEach(() => {
|
|
45
|
+
shareAnalytics = true;
|
|
46
|
+
clearEvents();
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
test("honors the share_analytics opt-out (records nothing)", () => {
|
|
50
|
+
shareAnalytics = false;
|
|
51
|
+
recordWatchdogEvent({ checkName: "event_loop_blocked", value: 60000 });
|
|
52
|
+
expect(queryUnreportedWatchdogEvents(0, undefined, 10)).toHaveLength(0);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
test("record + query round-trips all fields", () => {
|
|
56
|
+
recordWatchdogEvent({
|
|
57
|
+
checkName: "event_loop_blocked",
|
|
58
|
+
value: 12345,
|
|
59
|
+
detail: { reason: "no_bytes_60s", threshold_ms: 5000 },
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
const rows = queryUnreportedWatchdogEvents(0, undefined, 10);
|
|
63
|
+
expect(rows).toHaveLength(1);
|
|
64
|
+
const row = rows[0]!;
|
|
65
|
+
expect(row.id).toBeString();
|
|
66
|
+
expect(row.createdAt).toBeGreaterThan(0);
|
|
67
|
+
expect(row.checkName).toBe("event_loop_blocked");
|
|
68
|
+
expect(row.value).toBe(12345);
|
|
69
|
+
expect(row.detail).toBe(
|
|
70
|
+
JSON.stringify({ reason: "no_bytes_60s", threshold_ms: 5000 }),
|
|
71
|
+
);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
test("optional fields persist as null", () => {
|
|
75
|
+
recordWatchdogEvent({ checkName: "stream_idle" });
|
|
76
|
+
|
|
77
|
+
const rows = queryUnreportedWatchdogEvents(0, undefined, 10);
|
|
78
|
+
expect(rows).toHaveLength(1);
|
|
79
|
+
expect(rows[0]).toMatchObject({
|
|
80
|
+
checkName: "stream_idle",
|
|
81
|
+
value: null,
|
|
82
|
+
detail: null,
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
test("explicit null value and detail persist as null", () => {
|
|
87
|
+
recordWatchdogEvent({
|
|
88
|
+
checkName: "restart",
|
|
89
|
+
value: null,
|
|
90
|
+
detail: null,
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
const rows = queryUnreportedWatchdogEvents(0, undefined, 10);
|
|
94
|
+
expect(rows).toHaveLength(1);
|
|
95
|
+
expect(rows[0]?.value).toBeNull();
|
|
96
|
+
expect(rows[0]?.detail).toBeNull();
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
test("returns rows in (createdAt, id) order", () => {
|
|
100
|
+
insertEvent("wd-b", 2000);
|
|
101
|
+
insertEvent("wd-a", 1000);
|
|
102
|
+
|
|
103
|
+
const rows = queryUnreportedWatchdogEvents(0, undefined, 100);
|
|
104
|
+
expect(rows.map((r) => r.id)).toEqual(["wd-a", "wd-b"]);
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
test("query advances past the compound (createdAt, id) cursor", () => {
|
|
108
|
+
// Two rows in the same millisecond: pagination must use the id
|
|
109
|
+
// tiebreaker to make forward progress, not loop.
|
|
110
|
+
insertEvent("wd-1", 5000);
|
|
111
|
+
insertEvent("wd-2", 5000);
|
|
112
|
+
insertEvent("wd-3", 6000);
|
|
113
|
+
|
|
114
|
+
const first = queryUnreportedWatchdogEvents(0, undefined, 1);
|
|
115
|
+
expect(first.map((r) => r.id)).toEqual(["wd-1"]);
|
|
116
|
+
|
|
117
|
+
const second = queryUnreportedWatchdogEvents(
|
|
118
|
+
first[0]!.createdAt,
|
|
119
|
+
first[0]!.id,
|
|
120
|
+
100,
|
|
121
|
+
);
|
|
122
|
+
expect(second.map((r) => r.id)).toEqual(["wd-2", "wd-3"]);
|
|
123
|
+
|
|
124
|
+
// Without an id cursor the timestamp-only branch is used.
|
|
125
|
+
expect(
|
|
126
|
+
queryUnreportedWatchdogEvents(5000, undefined, 100).map((r) => r.id),
|
|
127
|
+
).toEqual(["wd-3"]);
|
|
128
|
+
|
|
129
|
+
// Cursor past the last row returns nothing.
|
|
130
|
+
const last = second[second.length - 1]!;
|
|
131
|
+
expect(
|
|
132
|
+
queryUnreportedWatchdogEvents(last.createdAt, last.id, 100).length,
|
|
133
|
+
).toBe(0);
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
test("resumes from a persisted watermark without re-reporting", () => {
|
|
137
|
+
insertEvent("wd-w1", 1000);
|
|
138
|
+
insertEvent("wd-w2", 2000);
|
|
139
|
+
|
|
140
|
+
const batch = queryUnreportedWatchdogEvents(0, undefined, 100);
|
|
141
|
+
const watermark = batch[batch.length - 1]!;
|
|
142
|
+
|
|
143
|
+
insertEvent("wd-w3", 3000);
|
|
144
|
+
|
|
145
|
+
const resumed = queryUnreportedWatchdogEvents(
|
|
146
|
+
watermark.createdAt,
|
|
147
|
+
watermark.id,
|
|
148
|
+
100,
|
|
149
|
+
);
|
|
150
|
+
expect(resumed.map((r) => r.id)).toEqual(["wd-w3"]);
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
test("honors the limit", () => {
|
|
154
|
+
insertEvent("wd-l1", 1000);
|
|
155
|
+
insertEvent("wd-l2", 2000);
|
|
156
|
+
insertEvent("wd-l3", 3000);
|
|
157
|
+
|
|
158
|
+
const rows = queryUnreportedWatchdogEvents(0, undefined, 2);
|
|
159
|
+
expect(rows.map((r) => r.id)).toEqual(["wd-l1", "wd-l2"]);
|
|
160
|
+
});
|
|
161
|
+
});
|
|
@@ -25,6 +25,7 @@ import { parseChannelId, parseInterfaceId } from "../channels/types.js";
|
|
|
25
25
|
import { CHANNEL_IDS, isChannelId } from "../channels/types.js";
|
|
26
26
|
import { getConfig } from "../config/loader.js";
|
|
27
27
|
import { findDisplayTurnEndIndex } from "../conversations/message-consolidation.js";
|
|
28
|
+
import { findConversation } from "../daemon/conversation-registry.js";
|
|
28
29
|
import { conversationMetadataSyncTag } from "../daemon/message-types/sync.js";
|
|
29
30
|
import type { TrustContext } from "../daemon/trust-context.js";
|
|
30
31
|
import { clearAllConversationIds } from "../home/feed-writer.js";
|
|
@@ -304,6 +305,7 @@ export interface ConversationRow {
|
|
|
304
305
|
inferenceProfileSessionId: string | null;
|
|
305
306
|
inferenceProfileExpiresAt: number | null;
|
|
306
307
|
lastNotifiedInferenceProfile: string | null;
|
|
308
|
+
processingStartedAt: number | null;
|
|
307
309
|
}
|
|
308
310
|
|
|
309
311
|
export const parseConversation = createRowMapper<
|
|
@@ -339,6 +341,7 @@ export const parseConversation = createRowMapper<
|
|
|
339
341
|
inferenceProfileSessionId: "inferenceProfileSessionId",
|
|
340
342
|
inferenceProfileExpiresAt: "inferenceProfileExpiresAt",
|
|
341
343
|
lastNotifiedInferenceProfile: "lastNotifiedInferenceProfile",
|
|
344
|
+
processingStartedAt: "processingStartedAt",
|
|
342
345
|
});
|
|
343
346
|
|
|
344
347
|
/** Allowed values for the `role` column on `messages`. */
|
|
@@ -2180,6 +2183,41 @@ export function unarchiveConversation(id: string): boolean {
|
|
|
2180
2183
|
return true;
|
|
2181
2184
|
}
|
|
2182
2185
|
|
|
2186
|
+
/**
|
|
2187
|
+
* Persist the processing-start timestamp for a conversation. Called by
|
|
2188
|
+
* `Conversation.setProcessing(true)` so out-of-process callers can detect
|
|
2189
|
+
* mid-turn state by reading the `conversations` row directly. Pass `null`
|
|
2190
|
+
* to clear (turn ended).
|
|
2191
|
+
*/
|
|
2192
|
+
export function setConversationProcessingStartedAt(
|
|
2193
|
+
id: string,
|
|
2194
|
+
startedAt: number | null,
|
|
2195
|
+
): void {
|
|
2196
|
+
rawRun(
|
|
2197
|
+
"UPDATE conversations SET processing_started_at = ? WHERE id = ?",
|
|
2198
|
+
startedAt,
|
|
2199
|
+
id,
|
|
2200
|
+
);
|
|
2201
|
+
}
|
|
2202
|
+
|
|
2203
|
+
/**
|
|
2204
|
+
* Read whether a conversation is currently processing. Checks the in-memory
|
|
2205
|
+
* `Conversation._processing` flag first (hot path for resident conversations),
|
|
2206
|
+
* falling back to the persisted `processing_started_at` column for cold
|
|
2207
|
+
* (evicted / never-loaded) conversations. This is the single entry point for
|
|
2208
|
+
* processing state — callers don't need to layer `findConversation` themselves.
|
|
2209
|
+
* Returns `false` when the conversation row doesn't exist.
|
|
2210
|
+
*/
|
|
2211
|
+
export function isConversationProcessing(id: string): boolean {
|
|
2212
|
+
const inMemory = findConversation(id)?.isProcessing();
|
|
2213
|
+
if (inMemory != null) return inMemory;
|
|
2214
|
+
const row = rawGet<{ processing_started_at: number | null }>(
|
|
2215
|
+
"SELECT processing_started_at FROM conversations WHERE id = ?",
|
|
2216
|
+
id,
|
|
2217
|
+
);
|
|
2218
|
+
return row?.processing_started_at != null;
|
|
2219
|
+
}
|
|
2220
|
+
|
|
2183
2221
|
/**
|
|
2184
2222
|
* Set or clear the `surfaced_at` promotion marker for a conversation.
|
|
2185
2223
|
*
|
|
@@ -8,6 +8,7 @@ import { drizzle } from "drizzle-orm/bun-sqlite";
|
|
|
8
8
|
import { getLogsDbPath } from "../util/logs-db-path.js";
|
|
9
9
|
import { getMemoryDbPath } from "../util/memory-db-path.js";
|
|
10
10
|
import { ensureDataDir, getDbPath } from "../util/platform.js";
|
|
11
|
+
import { getTelemetryDbPath } from "../util/telemetry-db-path.js";
|
|
11
12
|
import { clearStoredDb, getStoredDb, setStoredDb } from "./db-singleton.js";
|
|
12
13
|
import * as schema from "./schema.js";
|
|
13
14
|
|
|
@@ -136,7 +137,7 @@ export function getSqliteFrom(drizzleDb: DrizzleDb): Database {
|
|
|
136
137
|
* path so this file stays import-light. Callers tolerate a `null` result.
|
|
137
138
|
*/
|
|
138
139
|
function openDedicatedDb(
|
|
139
|
-
key: "logs" | "memory",
|
|
140
|
+
key: "logs" | "memory" | "telemetry",
|
|
140
141
|
dbPath: string,
|
|
141
142
|
): DrizzleDb | null {
|
|
142
143
|
assertTestDbIsIsolated();
|
|
@@ -192,11 +193,28 @@ export function getMemorySqlite(): Database | null {
|
|
|
192
193
|
return db ? getSqliteFrom(db) : null;
|
|
193
194
|
}
|
|
194
195
|
|
|
196
|
+
/**
|
|
197
|
+
* The telemetry database (`assistant-telemetry.db`), opened lazily as its own
|
|
198
|
+
* connection. Houses telemetry event tables (starting with `watchdog_events`).
|
|
199
|
+
* Returns `null` if the file cannot be opened (logged; the daemon stays up).
|
|
200
|
+
*/
|
|
201
|
+
export function getTelemetryDb(): DrizzleDb | null {
|
|
202
|
+
const existing = getStoredDb<DrizzleDb>("telemetry");
|
|
203
|
+
if (existing) return existing;
|
|
204
|
+
return openDedicatedDb("telemetry", getTelemetryDbPath());
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/** Underlying bun:sqlite Database for the telemetry connection, or `null`. */
|
|
208
|
+
export function getTelemetrySqlite(): Database | null {
|
|
209
|
+
const db = getTelemetryDb();
|
|
210
|
+
return db ? getSqliteFrom(db) : null;
|
|
211
|
+
}
|
|
212
|
+
|
|
195
213
|
/**
|
|
196
214
|
* Reset all DB singletons. Used by production callers that need to close the
|
|
197
215
|
* live connections so the files can be replaced (post-migration, post-restore,
|
|
198
|
-
* post-vbundle-import) and on graceful shutdown. Clears the main, logs,
|
|
199
|
-
*
|
|
216
|
+
* post-vbundle-import) and on graceful shutdown. Clears the main, logs, memory,
|
|
217
|
+
* and telemetry slots together so none lingers open against a swapped-out file.
|
|
200
218
|
*
|
|
201
219
|
* Tests should use `resetDbForTesting()` from
|
|
202
220
|
* `__tests__/db-test-helpers.ts` instead so they don't depend on this
|
|
@@ -206,4 +224,5 @@ export function resetDb(): void {
|
|
|
206
224
|
clearStoredDb("main");
|
|
207
225
|
clearStoredDb("logs");
|
|
208
226
|
clearStoredDb("memory");
|
|
227
|
+
clearStoredDb("telemetry");
|
|
209
228
|
}
|