@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
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import { v4 as uuid } from "uuid";
|
|
2
2
|
|
|
3
3
|
import { peekAcpSessionManager } from "../../acp/index.js";
|
|
4
|
+
import { resolveCanonicalGuardianRequest } from "../../memory/canonical-guardian-store.js";
|
|
4
5
|
import { clearAll, getConversation } from "../../memory/conversation-crud.js";
|
|
5
6
|
import { resolveConversationId } from "../../memory/conversation-key-store.js";
|
|
6
7
|
import { broadcastMessage } from "../../runtime/assistant-event-hub.js";
|
|
7
8
|
import { resolveCapabilities } from "../../runtime/capabilities.js";
|
|
9
|
+
import * as pendingInteractions from "../../runtime/pending-interactions.js";
|
|
8
10
|
import { getSubagentManager } from "../../subagent/index.js";
|
|
9
11
|
import { createAbortReason } from "../../util/abort-reasons.js";
|
|
10
12
|
import { UserError } from "../../util/errors.js";
|
|
@@ -389,6 +391,81 @@ export function steerToMessage(
|
|
|
389
391
|
return { steered: true };
|
|
390
392
|
}
|
|
391
393
|
|
|
394
|
+
/**
|
|
395
|
+
* Supersede an open `ask_question` prompt when a new chat message is enqueued
|
|
396
|
+
* for the same conversation.
|
|
397
|
+
*
|
|
398
|
+
* A queued message while a clarification question is open means the user chose
|
|
399
|
+
* to move on rather than answer it. Steering to that message aborts the parked
|
|
400
|
+
* turn — which settles the open question via its turn-abort signal — repairs
|
|
401
|
+
* the dangling `tool_use`, and drains the message, instead of stranding it
|
|
402
|
+
* behind a prompt no one is going to answer. Only `ask_question` prompts
|
|
403
|
+
* (`kind: "question"`) trigger this; pending confirmations are handled
|
|
404
|
+
* separately by the enqueue path's auto-deny.
|
|
405
|
+
*
|
|
406
|
+
* Returns `true` when a parked question was found and a steer was issued.
|
|
407
|
+
*/
|
|
408
|
+
export function steerOnEnqueuedMessageIfQuestionParked(
|
|
409
|
+
conversationId: string,
|
|
410
|
+
enqueuedRequestId: string,
|
|
411
|
+
): boolean {
|
|
412
|
+
const hasParkedQuestion = pendingInteractions
|
|
413
|
+
.getByConversation(conversationId)
|
|
414
|
+
.some((interaction) => interaction.kind === "question");
|
|
415
|
+
if (!hasParkedQuestion) return false;
|
|
416
|
+
steerToMessage(conversationId, enqueuedRequestId);
|
|
417
|
+
return true;
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
/**
|
|
421
|
+
* Supersede interactions left pending by an in-flight turn when a new message
|
|
422
|
+
* is enqueued for a busy conversation. Centralized so every ingress path (the
|
|
423
|
+
* HTTP send handler and the CLI signal path) gets identical handling:
|
|
424
|
+
*
|
|
425
|
+
* 1. Auto-deny pending confirmations — notify the client and sync the
|
|
426
|
+
* canonical guardian record *before* clearing the prompter-owned
|
|
427
|
+
* confirmations, so a later guardian reply can't match a stale "pending"
|
|
428
|
+
* record and fail with `pending_interaction_not_found`.
|
|
429
|
+
* 2. Supersede a parked ask_question by steering to the enqueued message.
|
|
430
|
+
*
|
|
431
|
+
* Order matters: the steer aborts the turn, which denies the prompter's
|
|
432
|
+
* confirmations as a side effect, so the canonical/notification sync must run
|
|
433
|
+
* first. `removeByConversation` preserves `question` entries, so the parked
|
|
434
|
+
* question is still registered for the steer even after the confirmation sweep.
|
|
435
|
+
*/
|
|
436
|
+
export function supersedePendingInteractionsOnEnqueue(
|
|
437
|
+
conversationId: string,
|
|
438
|
+
enqueuedRequestId: string,
|
|
439
|
+
): void {
|
|
440
|
+
const conversation = findConversation(conversationId);
|
|
441
|
+
if (!conversation) return;
|
|
442
|
+
|
|
443
|
+
if (conversation.hasAnyPendingConfirmation()) {
|
|
444
|
+
for (const interaction of pendingInteractions.getByConversation(
|
|
445
|
+
conversationId,
|
|
446
|
+
)) {
|
|
447
|
+
if (interaction.kind === "confirmation") {
|
|
448
|
+
// sendToClient (wired to the SSE hub) delivers the denial to clients.
|
|
449
|
+
conversation.emitConfirmationStateChanged({
|
|
450
|
+
conversationId,
|
|
451
|
+
requestId: interaction.requestId,
|
|
452
|
+
state: "denied" as const,
|
|
453
|
+
source: "auto_deny" as const,
|
|
454
|
+
});
|
|
455
|
+
// Sync the canonical guardian record so stale "pending" rows aren't
|
|
456
|
+
// matched by later guardian reply routing.
|
|
457
|
+
resolveCanonicalGuardianRequest(interaction.requestId, "pending", {
|
|
458
|
+
status: "denied",
|
|
459
|
+
});
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
conversation.denyAllPendingConfirmations();
|
|
463
|
+
pendingInteractions.removeByConversation(conversationId);
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
steerOnEnqueuedMessageIfQuestionParked(conversationId, enqueuedRequestId);
|
|
467
|
+
}
|
|
468
|
+
|
|
392
469
|
// ---------------------------------------------------------------------------
|
|
393
470
|
// HTTP handler (delegates to shared logic)
|
|
394
471
|
// ---------------------------------------------------------------------------
|
|
@@ -44,6 +44,56 @@ const MAX_HISTORY_ENTRIES = 10;
|
|
|
44
44
|
const LOOP_DETECTION_WINDOW = 3;
|
|
45
45
|
const CONSECUTIVE_UNCHANGED_WARNING_THRESHOLD = 2;
|
|
46
46
|
|
|
47
|
+
// computer_use_key combos that change only selection/cursor/clipboard state.
|
|
48
|
+
// The AX tree models none of these, so they always produce an empty diff —
|
|
49
|
+
// exempt them from the "NO VISIBLE EFFECT" signal (mirrors computer_use_wait).
|
|
50
|
+
// Stored in canonical form (see canonicalizeKeyCombo): modifier aliases
|
|
51
|
+
// normalized and ordered, so `cmd + a`, `command+a`, `alt+tab`, `tab+shift`
|
|
52
|
+
// all match.
|
|
53
|
+
const NO_AX_DIFF_KEY_COMBOS = new Set([
|
|
54
|
+
"cmd+a",
|
|
55
|
+
"cmd+c",
|
|
56
|
+
"up",
|
|
57
|
+
"down",
|
|
58
|
+
"left",
|
|
59
|
+
"right",
|
|
60
|
+
"shift+tab",
|
|
61
|
+
"option+tab",
|
|
62
|
+
]);
|
|
63
|
+
|
|
64
|
+
// Modifier aliases mirror the mac helper's ActionExecutor.pressKey so the
|
|
65
|
+
// exemption check matches exactly what the helper will execute.
|
|
66
|
+
const KEY_MODIFIER_ALIASES: Record<string, string> = {
|
|
67
|
+
cmd: "cmd",
|
|
68
|
+
command: "cmd",
|
|
69
|
+
option: "option",
|
|
70
|
+
alt: "option",
|
|
71
|
+
ctrl: "ctrl",
|
|
72
|
+
control: "ctrl",
|
|
73
|
+
shift: "shift",
|
|
74
|
+
};
|
|
75
|
+
const KEY_MODIFIER_ORDER = ["cmd", "ctrl", "option", "shift"];
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Normalize a key combo the way the mac helper does (lowercase, split on `+`,
|
|
79
|
+
* trim, alias modifiers) into a canonical `mods…+base` string with modifiers
|
|
80
|
+
* in a fixed order — so order/alias/whitespace variants compare equal.
|
|
81
|
+
*/
|
|
82
|
+
function canonicalizeKeyCombo(key: string): string {
|
|
83
|
+
const mods = new Set<string>();
|
|
84
|
+
let base = "";
|
|
85
|
+
for (const raw of key.toLowerCase().split("+")) {
|
|
86
|
+
const part = raw.trim();
|
|
87
|
+
if (part.length === 0) continue;
|
|
88
|
+
const mod = KEY_MODIFIER_ALIASES[part];
|
|
89
|
+
if (mod) mods.add(mod);
|
|
90
|
+
else base = part; // last non-modifier wins, matching the executor
|
|
91
|
+
}
|
|
92
|
+
return [...KEY_MODIFIER_ORDER.filter((m) => mods.has(m)), base]
|
|
93
|
+
.filter((s) => s.length > 0)
|
|
94
|
+
.join("+");
|
|
95
|
+
}
|
|
96
|
+
|
|
47
97
|
// ---------------------------------------------------------------------------
|
|
48
98
|
// Types
|
|
49
99
|
// ---------------------------------------------------------------------------
|
|
@@ -69,6 +119,43 @@ export interface ActionRecord {
|
|
|
69
119
|
reasoning?: string;
|
|
70
120
|
}
|
|
71
121
|
|
|
122
|
+
/**
|
|
123
|
+
* True when `action` is a computer_use_key press whose key only mutates
|
|
124
|
+
* selection/cursor/clipboard state — changes the AX tree cannot represent, so
|
|
125
|
+
* an empty diff is expected rather than a sign the action did nothing.
|
|
126
|
+
*/
|
|
127
|
+
function isNoDiffKeyAction(action: ActionRecord | undefined): boolean {
|
|
128
|
+
if (action?.toolName !== "computer_use_key") return false;
|
|
129
|
+
const key = action.input.key;
|
|
130
|
+
return (
|
|
131
|
+
typeof key === "string" &&
|
|
132
|
+
NO_AX_DIFF_KEY_COMBOS.has(canonicalizeKeyCombo(key))
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Canonical signature for loop detection. Key presses collapse equivalent
|
|
138
|
+
* spellings (`cmd+a`, `command+a`, `cmd + a`) of the same combo so a stuck
|
|
139
|
+
* session retrying it with alias/whitespace variants is still caught —
|
|
140
|
+
* important now that exempt keys no longer emit no-effect warnings. Only the
|
|
141
|
+
* `key` value is normalized; all other input fields (e.g. the routing
|
|
142
|
+
* `target_client_id`) are preserved, so the same combo sent to different
|
|
143
|
+
* desktop clients is not mistaken for a repeat.
|
|
144
|
+
*/
|
|
145
|
+
function actionSignature(record: ActionRecord): string {
|
|
146
|
+
if (
|
|
147
|
+
record.toolName === "computer_use_key" &&
|
|
148
|
+
typeof record.input.key === "string"
|
|
149
|
+
) {
|
|
150
|
+
const normalizedInput = {
|
|
151
|
+
...record.input,
|
|
152
|
+
key: canonicalizeKeyCombo(record.input.key),
|
|
153
|
+
};
|
|
154
|
+
return `computer_use_key:${JSON.stringify(normalizedInput)}`;
|
|
155
|
+
}
|
|
156
|
+
return `${record.toolName}:${JSON.stringify(record.input)}`;
|
|
157
|
+
}
|
|
158
|
+
|
|
72
159
|
// ---------------------------------------------------------------------------
|
|
73
160
|
// HostCuProxy
|
|
74
161
|
// ---------------------------------------------------------------------------
|
|
@@ -385,8 +472,9 @@ export class HostCuProxy {
|
|
|
385
472
|
? this._actionHistory[this._actionHistory.length - 1]
|
|
386
473
|
: undefined;
|
|
387
474
|
const isWaitAction = lastAction?.toolName === "computer_use_wait";
|
|
475
|
+
const isNoDiffKey = isNoDiffKeyAction(lastAction);
|
|
388
476
|
|
|
389
|
-
if (!isWaitAction) {
|
|
477
|
+
if (!isWaitAction && !isNoDiffKey) {
|
|
390
478
|
if (
|
|
391
479
|
this._consecutiveUnchangedSteps >=
|
|
392
480
|
CONSECUTIVE_UNCHANGED_WARNING_THRESHOLD
|
|
@@ -405,10 +493,9 @@ export class HostCuProxy {
|
|
|
405
493
|
|
|
406
494
|
if (this._actionHistory.length >= LOOP_DETECTION_WINDOW) {
|
|
407
495
|
const recent = this._actionHistory.slice(-LOOP_DETECTION_WINDOW);
|
|
496
|
+
const firstSignature = actionSignature(recent[0]);
|
|
408
497
|
const allIdentical = recent.every(
|
|
409
|
-
(r) =>
|
|
410
|
-
r.toolName === recent[0].toolName &&
|
|
411
|
-
JSON.stringify(r.input) === JSON.stringify(recent[0].input),
|
|
498
|
+
(r) => actionSignature(r) === firstSignature,
|
|
412
499
|
);
|
|
413
500
|
if (allIdentical) {
|
|
414
501
|
parts.push(
|
|
@@ -505,14 +592,18 @@ export class HostCuProxy {
|
|
|
505
592
|
|
|
506
593
|
private updateStateFromObservation(obs: CuObservationResult): void {
|
|
507
594
|
if (this._stepCount > 0) {
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
) {
|
|
513
|
-
|
|
514
|
-
|
|
595
|
+
const lastAction =
|
|
596
|
+
this._actionHistory.length > 0
|
|
597
|
+
? this._actionHistory[this._actionHistory.length - 1]
|
|
598
|
+
: undefined;
|
|
599
|
+
if (obs.axDiff != null || isNoDiffKeyAction(lastAction)) {
|
|
600
|
+
// A real diff, or an exempt key whose effect is invisible by design,
|
|
601
|
+
// breaks the no-effect streak — clear it rather than preserving a
|
|
602
|
+
// stale count so an intervening cmd+a can't bridge two no-op actions
|
|
603
|
+
// into a false "consecutive" escalation.
|
|
515
604
|
this._consecutiveUnchangedSteps = 0;
|
|
605
|
+
} else if (this._previousAXTree != null && obs.axTree != null) {
|
|
606
|
+
this._consecutiveUnchangedSteps++;
|
|
516
607
|
}
|
|
517
608
|
}
|
|
518
609
|
|
package/src/daemon/lifecycle.ts
CHANGED
|
@@ -966,6 +966,10 @@ export async function runDaemon(): Promise<void> {
|
|
|
966
966
|
}
|
|
967
967
|
}
|
|
968
968
|
|
|
969
|
+
// `startMemoryJobsWorker` selects the worker implementation based on
|
|
970
|
+
// `memory.worker.enabled` (in-process vs. a separate OS process).
|
|
971
|
+
// Shutdown stops whichever worker is actually running — see
|
|
972
|
+
// shutdown-handlers.ts.
|
|
969
973
|
log.info("Daemon startup: starting memory worker");
|
|
970
974
|
bgRefs.memoryWorker = startMemoryJobsWorker();
|
|
971
975
|
|
|
@@ -59,6 +59,18 @@ function configWithV2(enabled: boolean): AssistantConfig {
|
|
|
59
59
|
return { memory: { v2: { enabled } } } as unknown as AssistantConfig;
|
|
60
60
|
}
|
|
61
61
|
|
|
62
|
+
/** Poll until `m` has been called at least `n` times, or `timeoutMs` elapses. */
|
|
63
|
+
async function waitForCalls(
|
|
64
|
+
m: { mock: { calls: unknown[] } },
|
|
65
|
+
n: number,
|
|
66
|
+
timeoutMs = 1000,
|
|
67
|
+
): Promise<void> {
|
|
68
|
+
const start = Date.now();
|
|
69
|
+
while (m.mock.calls.length < n && Date.now() - start < timeoutMs) {
|
|
70
|
+
await new Promise((resolve) => setTimeout(resolve, 5));
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
62
74
|
afterEach(() => {
|
|
63
75
|
seedSkill.mockClear();
|
|
64
76
|
seedCli.mockClear();
|
|
@@ -128,4 +140,64 @@ describe("maybeReseedCapabilitiesAfterManagedCredential", () => {
|
|
|
128
140
|
|
|
129
141
|
expect(seedCli).toHaveBeenCalledTimes(1);
|
|
130
142
|
});
|
|
143
|
+
|
|
144
|
+
test("enqueues the v3 maintain pass even when one catalog reseed rejects", async () => {
|
|
145
|
+
proxyState.prereqs = true;
|
|
146
|
+
v3State.live = true;
|
|
147
|
+
seedSkill.mockImplementationOnce(async () => {
|
|
148
|
+
throw new Error('Embedding backend "gemini" is not configured');
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
await maybeReseedCapabilitiesAfterManagedCredential(configWithV2(true));
|
|
152
|
+
|
|
153
|
+
// The CLI catalog seeded, so v3 must still rebuild its lanes — a single
|
|
154
|
+
// catalog failure cannot suppress the maintain pass.
|
|
155
|
+
expect(seedCli).toHaveBeenCalledTimes(1);
|
|
156
|
+
expect(enqueueJob).toHaveBeenCalledTimes(1);
|
|
157
|
+
expect(enqueueJob).toHaveBeenCalledWith("memory_v3_maintain", {});
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
test("enqueues the v3 maintain pass without blocking when a catalog reseed exceeds the timeout", async () => {
|
|
161
|
+
proxyState.prereqs = true;
|
|
162
|
+
v3State.live = true;
|
|
163
|
+
// Skill reseed never settles — mirrors the wedged getCatalog()/embed seen in
|
|
164
|
+
// the field. The CLI reseed completes normally.
|
|
165
|
+
seedSkill.mockImplementationOnce(() => new Promise<void>(() => {}));
|
|
166
|
+
|
|
167
|
+
await maybeReseedCapabilitiesAfterManagedCredential(configWithV2(true), {
|
|
168
|
+
reseedTimeoutMs: 20,
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
// An unbounded `Promise.all` barrier would hang here forever; the bounded
|
|
172
|
+
// barrier lets the CLI catalog's maintain pass enqueue regardless.
|
|
173
|
+
expect(seedCli).toHaveBeenCalledTimes(1);
|
|
174
|
+
expect(enqueueJob).toHaveBeenCalledTimes(1);
|
|
175
|
+
expect(enqueueJob).toHaveBeenCalledWith("memory_v3_maintain", {});
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
test("re-enqueues the v3 maintain pass when a straggler catalog finishes after the timeout", async () => {
|
|
179
|
+
proxyState.prereqs = true;
|
|
180
|
+
v3State.live = true;
|
|
181
|
+
let resolveSkill!: () => void;
|
|
182
|
+
seedSkill.mockImplementationOnce(
|
|
183
|
+
() =>
|
|
184
|
+
new Promise<void>((resolve) => {
|
|
185
|
+
resolveSkill = resolve;
|
|
186
|
+
}),
|
|
187
|
+
);
|
|
188
|
+
|
|
189
|
+
await maybeReseedCapabilitiesAfterManagedCredential(configWithV2(true), {
|
|
190
|
+
reseedTimeoutMs: 10,
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
// Post-barrier enqueue fires once even though the skill catalog is still
|
|
194
|
+
// embedding.
|
|
195
|
+
expect(enqueueJob).toHaveBeenCalledTimes(1);
|
|
196
|
+
|
|
197
|
+
// The straggler lands; maintain re-enqueues so its late capability rows are
|
|
198
|
+
// reconciled without waiting out the 6h backstop.
|
|
199
|
+
resolveSkill();
|
|
200
|
+
await waitForCalls(enqueueJob, 2);
|
|
201
|
+
expect(enqueueJob).toHaveBeenCalledTimes(2);
|
|
202
|
+
});
|
|
131
203
|
});
|
|
@@ -49,6 +49,36 @@ export function maybeSeedMemoryV2CliCommands(config: AssistantConfig): void {
|
|
|
49
49
|
.catch((err) => log.warn({ err }, "Failed to seed v2 CLI-command entries"));
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
+
/**
|
|
53
|
+
* Default upper bound on how long
|
|
54
|
+
* {@link maybeReseedCapabilitiesAfterManagedCredential} waits for the capability
|
|
55
|
+
* reseeds before enqueuing the v3 maintain pass. The reseeds keep running
|
|
56
|
+
* detached past this bound — it only stops the barrier from waiting on a wedged
|
|
57
|
+
* catalog (a stalled `getCatalog()` or a managed-proxy embed that never
|
|
58
|
+
* returns). A straggler that finishes later re-enqueues maintain.
|
|
59
|
+
*/
|
|
60
|
+
const RESEED_BARRIER_TIMEOUT_MS = 120_000;
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Resolve to `true` if `p` settles within `ms`, or `false` if the timeout wins.
|
|
64
|
+
* Always clears the timer, so a `p` that settles first leaves no pending timer
|
|
65
|
+
* keeping the event loop (or a test) alive.
|
|
66
|
+
*/
|
|
67
|
+
async function settledWithin(
|
|
68
|
+
p: Promise<unknown>,
|
|
69
|
+
ms: number,
|
|
70
|
+
): Promise<boolean> {
|
|
71
|
+
let timer: ReturnType<typeof setTimeout> | undefined;
|
|
72
|
+
const timedOut = new Promise<false>((resolve) => {
|
|
73
|
+
timer = setTimeout(() => resolve(false), ms);
|
|
74
|
+
});
|
|
75
|
+
try {
|
|
76
|
+
return await Promise.race([p.then(() => true), timedOut]);
|
|
77
|
+
} finally {
|
|
78
|
+
if (timer) clearTimeout(timer);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
52
82
|
/**
|
|
53
83
|
* Re-seed the v2 skill and CLI-command capability entries once a managed-proxy
|
|
54
84
|
* credential lands, closing the first-boot race where the daemon's startup seed
|
|
@@ -83,9 +113,23 @@ export function maybeSeedMemoryV2CliCommands(config: AssistantConfig): void {
|
|
|
83
113
|
* dense store and its lane-invalidation stage forces a rebuild against the now-
|
|
84
114
|
* populated index, so v3 surfaces the skill/CLI pages within seconds instead of
|
|
85
115
|
* waiting out the backstop.
|
|
116
|
+
*
|
|
117
|
+
* The maintain enqueue must NOT be gated on both catalogs settling. The two
|
|
118
|
+
* embeds are independent, and a single wedged catalog (a stalled `getCatalog()`
|
|
119
|
+
* or a managed-proxy embed that never returns) would otherwise block the v3 lane
|
|
120
|
+
* rebuild indefinitely, so the catalog that DID seed never reaches the selector.
|
|
121
|
+
* The barrier is therefore bounded by `reseedTimeoutMs`: maintain is enqueued
|
|
122
|
+
* once the barrier resolves (the pass is idempotent and reconciles whatever the
|
|
123
|
+
* page index currently holds), and a straggler catalog that finishes after the
|
|
124
|
+
* timeout re-enqueues maintain so its late rows are picked up without waiting out
|
|
125
|
+
* the backstop.
|
|
126
|
+
*
|
|
127
|
+
* `reseedTimeoutMs` is injectable for tests; production uses
|
|
128
|
+
* {@link RESEED_BARRIER_TIMEOUT_MS}.
|
|
86
129
|
*/
|
|
87
130
|
export async function maybeReseedCapabilitiesAfterManagedCredential(
|
|
88
131
|
config: AssistantConfig,
|
|
132
|
+
opts: { reseedTimeoutMs?: number } = {},
|
|
89
133
|
): Promise<void> {
|
|
90
134
|
if (!config.memory.v2.enabled) return;
|
|
91
135
|
|
|
@@ -115,35 +159,59 @@ export async function maybeReseedCapabilitiesAfterManagedCredential(
|
|
|
115
159
|
],
|
|
116
160
|
];
|
|
117
161
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
162
|
+
// Each reseed is contained so one catalog's embed failure (or hang) never
|
|
163
|
+
// rejects the caller or aborts the other. Started here but not awaited as a
|
|
164
|
+
// single barrier — the bounded wait below decides when to stop waiting.
|
|
165
|
+
const reseeds = catalogs.map(([label, seed]) =>
|
|
166
|
+
seed().then(
|
|
167
|
+
() =>
|
|
122
168
|
log.info(
|
|
123
169
|
`Memory v2 ${label} entries seeded after managed proxy credential update`,
|
|
124
|
-
)
|
|
125
|
-
|
|
170
|
+
),
|
|
171
|
+
(err: unknown) =>
|
|
126
172
|
log.warn(
|
|
127
173
|
{ err },
|
|
128
174
|
`Failed to seed v2 ${label} entries after managed proxy credential update`,
|
|
129
|
-
)
|
|
130
|
-
|
|
131
|
-
}),
|
|
175
|
+
),
|
|
176
|
+
),
|
|
132
177
|
);
|
|
133
178
|
|
|
134
|
-
//
|
|
135
|
-
//
|
|
136
|
-
//
|
|
179
|
+
// When v3 is live, a maintain pass embeds the freshly-seeded capability rows
|
|
180
|
+
// into `memory_v3_sections` and invalidates the lanes so v3 surfaces the
|
|
181
|
+
// skill/CLI pages within seconds instead of waiting out the 6h backstop.
|
|
182
|
+
// Resolve the gate + enqueuer once and reuse for the post-barrier enqueue and
|
|
183
|
+
// the straggler re-enqueue below.
|
|
137
184
|
const { isMemoryV3Live } = await import("../config/memory-v3-gate.js");
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
185
|
+
const v3Live = isMemoryV3Live(config);
|
|
186
|
+
const enqueueMaintain = async (): Promise<void> => {
|
|
187
|
+
if (!v3Live) return;
|
|
188
|
+
try {
|
|
189
|
+
const { enqueueMemoryJob } = await import("../memory/jobs-store.js");
|
|
190
|
+
enqueueMemoryJob("memory_v3_maintain", {});
|
|
191
|
+
} catch (err) {
|
|
192
|
+
log.warn(
|
|
193
|
+
{ err },
|
|
194
|
+
"Failed to enqueue memory_v3_maintain after managed proxy credential update",
|
|
195
|
+
);
|
|
196
|
+
}
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
// Bound the barrier so a wedged catalog can't block the maintain enqueue
|
|
200
|
+
// indefinitely; the reseeds keep running detached past the timeout.
|
|
201
|
+
const timeoutMs = opts.reseedTimeoutMs ?? RESEED_BARRIER_TIMEOUT_MS;
|
|
202
|
+
const allReseeds = Promise.allSettled(reseeds);
|
|
203
|
+
const settledInTime = await settledWithin(allReseeds, timeoutMs);
|
|
204
|
+
|
|
205
|
+
await enqueueMaintain();
|
|
206
|
+
|
|
207
|
+
if (!settledInTime) {
|
|
143
208
|
log.warn(
|
|
144
|
-
{
|
|
145
|
-
"
|
|
209
|
+
{ timeoutMs },
|
|
210
|
+
"Capability reseed still running after the barrier timeout — enqueued v3 maintain now; will re-enqueue when the straggler catalog finishes",
|
|
146
211
|
);
|
|
212
|
+
// The straggler is still embedding; re-enqueue maintain once it lands so its
|
|
213
|
+
// late capability rows are reconciled without waiting out the 6h backstop.
|
|
214
|
+
void allReseeds.then(() => enqueueMaintain());
|
|
147
215
|
}
|
|
148
216
|
}
|
|
149
217
|
|
package/src/daemon/server.ts
CHANGED
|
@@ -48,7 +48,6 @@ import { parseIdentityFields } from "./handlers/identity.js";
|
|
|
48
48
|
import type { ConversationCreateOptions } from "./handlers/shared.js";
|
|
49
49
|
import { setGlobalSkillIpcSender } from "./meet-host-supervisor.js";
|
|
50
50
|
import { refreshSkillCapabilityMemories } from "./skill-memory-refresh.js";
|
|
51
|
-
import { WorkspaceToolsWatcher } from "./workspace-tools-watcher.js";
|
|
52
51
|
|
|
53
52
|
const log = getLogger("server");
|
|
54
53
|
|
|
@@ -308,8 +307,6 @@ export class DaemonServer {
|
|
|
308
307
|
|
|
309
308
|
this.appSourceWatcher.start((appId) => this.handleAppSourceChange(appId));
|
|
310
309
|
|
|
311
|
-
WorkspaceToolsWatcher.getInstance().start();
|
|
312
|
-
|
|
313
310
|
// Broadcast contacts_changed to all clients when any contact mutation occurs.
|
|
314
311
|
this.unsubscribeContactChange = onContactChange(() => {
|
|
315
312
|
broadcastMessage({ type: "contacts_changed" });
|
|
@@ -324,7 +321,6 @@ export class DaemonServer {
|
|
|
324
321
|
this.evictor.stop();
|
|
325
322
|
this.configWatcher.stop();
|
|
326
323
|
this.appSourceWatcher.stop();
|
|
327
|
-
WorkspaceToolsWatcher.getInstance().stop();
|
|
328
324
|
this.cliIpc.stop();
|
|
329
325
|
this.skillIpc.stop();
|
|
330
326
|
if (this.unsubscribeContactChange) {
|
|
@@ -5,6 +5,7 @@ import type { HeartbeatService } from "../heartbeat/heartbeat-service.js";
|
|
|
5
5
|
import type { McpServerManager } from "../mcp/manager.js";
|
|
6
6
|
import { getSqlite, resetDb } from "../memory/db-connection.js";
|
|
7
7
|
import type { QdrantManager } from "../memory/qdrant-manager.js";
|
|
8
|
+
import { stopMemoryWorkerProcess } from "../memory/worker-control.js";
|
|
8
9
|
import type { RuntimeHttpServer } from "../runtime/http-server.js";
|
|
9
10
|
import { browserManager } from "../tools/browser/browser-manager.js";
|
|
10
11
|
import { cleanupShellOutputTempFiles } from "../tools/shared/shell-output.js";
|
|
@@ -117,8 +118,27 @@ export function installShutdownHandlers(deps: ShutdownDeps): void {
|
|
|
117
118
|
await browserManager.closeAllPages();
|
|
118
119
|
cleanupShellOutputTempFiles();
|
|
119
120
|
deps.scheduler.stop();
|
|
121
|
+
|
|
122
|
+
// Stop the in-process memory worker if one was started on the daemon's
|
|
123
|
+
// event loop (memory.worker.enabled = false).
|
|
120
124
|
deps.getMemoryWorker()?.stop();
|
|
121
125
|
|
|
126
|
+
// Stop the out-of-process memory worker if it's actually running. This is
|
|
127
|
+
// keyed off live state rather than config: the worker may have been
|
|
128
|
+
// spawned at startup (memory.worker.enabled = true) or out of band via
|
|
129
|
+
// `assistant memory worker start`, so we stop whatever is actually there.
|
|
130
|
+
try {
|
|
131
|
+
const workerStatus = stopMemoryWorkerProcess();
|
|
132
|
+
if (workerStatus.status === "running") {
|
|
133
|
+
log.info(
|
|
134
|
+
{ pid: workerStatus.pid },
|
|
135
|
+
"Sent SIGTERM to memory worker process",
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
} catch (err) {
|
|
139
|
+
log.warn({ err }, "Failed to stop memory worker process (non-fatal)");
|
|
140
|
+
}
|
|
141
|
+
|
|
122
142
|
if (deps.mcpManager) {
|
|
123
143
|
try {
|
|
124
144
|
await deps.mcpManager.stop();
|
|
@@ -124,4 +124,13 @@ export interface ToolSetupContext extends SurfaceConversationContext {
|
|
|
124
124
|
* return `undefined` for the in-flight (background) subagent.
|
|
125
125
|
*/
|
|
126
126
|
currentTurnOverrideProfile?: string;
|
|
127
|
+
/**
|
|
128
|
+
* Whether the current turn has no human present to answer clarification
|
|
129
|
+
* prompts. Resolved once per turn by the agent loop — honoring an explicit
|
|
130
|
+
* per-run `isInteractive` option (e.g. scheduled/background turns) over the
|
|
131
|
+
* live client state — so tool execution sees turn-level interactivity rather
|
|
132
|
+
* than re-deriving it from `hasNoClient`/`headlessLock`, which would read a
|
|
133
|
+
* scheduled turn running on a client-attached conversation as interactive.
|
|
134
|
+
*/
|
|
135
|
+
currentTurnIsNonInteractive?: boolean;
|
|
127
136
|
}
|
|
@@ -36,7 +36,7 @@ mock.module("../../config/env.js", () => ({
|
|
|
36
36
|
}));
|
|
37
37
|
|
|
38
38
|
mock.module("../../runtime/local-actor-identity.js", () => ({
|
|
39
|
-
|
|
39
|
+
findLocalGuardianPrincipalIdFromStore: () => fakeLocalPrincipalId,
|
|
40
40
|
}));
|
|
41
41
|
|
|
42
42
|
// ── Real imports (after mocks) ────────────────────────────────────────────
|
|
@@ -33,7 +33,7 @@ import { createServer, type Server, type Socket } from "node:net";
|
|
|
33
33
|
|
|
34
34
|
import { ensureSocketDir, SocketWatchdog } from "@vellumai/ipc-server-utils";
|
|
35
35
|
|
|
36
|
-
import {
|
|
36
|
+
import { findLocalGuardianPrincipalIdFromStore } from "../runtime/local-actor-identity.js";
|
|
37
37
|
import { RouteError } from "../runtime/routes/errors.js";
|
|
38
38
|
import { ROUTES } from "../runtime/routes/index.js";
|
|
39
39
|
import type {
|
|
@@ -637,7 +637,7 @@ function injectLocalActorHeader(
|
|
|
637
637
|
// that require the header will fail-closed on their own.
|
|
638
638
|
let localActor: string | undefined;
|
|
639
639
|
try {
|
|
640
|
-
localActor =
|
|
640
|
+
localActor = findLocalGuardianPrincipalIdFromStore();
|
|
641
641
|
} catch (err) {
|
|
642
642
|
log.debug(
|
|
643
643
|
{ err },
|