@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
|
@@ -33,16 +33,17 @@ export interface TrustVerdictTransport {
|
|
|
33
33
|
}
|
|
34
34
|
|
|
35
35
|
/**
|
|
36
|
-
*
|
|
36
|
+
* Reassemble an {@link ActorTrustContext} from a gateway verdict + transport
|
|
37
|
+
* identity (mirroring `resolveActorTrust`), without any local DB/IPC reads.
|
|
37
38
|
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
39
|
+
* Pure: the voice path consumes this directly for routing on
|
|
40
|
+
* `actorTrust.trustClass`; {@link trustContextFromVerdict} routes it through
|
|
41
|
+
* {@link toTrustContext}.
|
|
41
42
|
*/
|
|
42
|
-
export function
|
|
43
|
+
export function actorTrustContextFromVerdict(
|
|
43
44
|
verdict: TrustVerdict,
|
|
44
45
|
input: TrustVerdictTransport,
|
|
45
|
-
):
|
|
46
|
+
): ActorTrustContext {
|
|
46
47
|
const canonicalSenderId = verdict.canonicalSenderId;
|
|
47
48
|
const memberDisplayName = verdict.memberDisplayName;
|
|
48
49
|
const senderDisplayName = input.actorDisplayName;
|
|
@@ -51,7 +52,7 @@ export function trustContextFromVerdict(
|
|
|
51
52
|
? `@${username}`
|
|
52
53
|
: (canonicalSenderId ?? undefined);
|
|
53
54
|
|
|
54
|
-
|
|
55
|
+
return {
|
|
55
56
|
canonicalSenderId,
|
|
56
57
|
guardianBindingMatch: verdict.guardianExternalUserId
|
|
57
58
|
? {
|
|
@@ -60,7 +61,12 @@ export function trustContextFromVerdict(
|
|
|
60
61
|
}
|
|
61
62
|
: null,
|
|
62
63
|
guardianPrincipalId: verdict.guardianPrincipalId,
|
|
63
|
-
|
|
64
|
+
// Populate from the verdict so the voice path's ACL gates (which read
|
|
65
|
+
// actorTrust.memberRecord.channel status/policy) enforce blocked/revoked/
|
|
66
|
+
// deny/escalate. Null for memberless verdicts. Text path is unaffected:
|
|
67
|
+
// toTrustContext derives the same member fields trustContextFromVerdict
|
|
68
|
+
// already stamps.
|
|
69
|
+
memberRecord: resolvedMemberFromVerdict(verdict),
|
|
64
70
|
trustClass: verdict.trustClass,
|
|
65
71
|
actorMetadata: {
|
|
66
72
|
identifier,
|
|
@@ -72,9 +78,21 @@ export function trustContextFromVerdict(
|
|
|
72
78
|
trustStatus: verdict.trustClass,
|
|
73
79
|
},
|
|
74
80
|
};
|
|
81
|
+
}
|
|
75
82
|
|
|
83
|
+
/**
|
|
84
|
+
* Build a {@link TrustContext} from a gateway verdict + transport identity.
|
|
85
|
+
*
|
|
86
|
+
* Reassembles an {@link ActorTrustContext} (mirroring `resolveActorTrust`) and
|
|
87
|
+
* routes it through {@link toTrustContext}, so the output is byte-identical to
|
|
88
|
+
* the local resolution path.
|
|
89
|
+
*/
|
|
90
|
+
export function trustContextFromVerdict(
|
|
91
|
+
verdict: TrustVerdict,
|
|
92
|
+
input: TrustVerdictTransport,
|
|
93
|
+
): TrustContext {
|
|
76
94
|
const context = toTrustContext(
|
|
77
|
-
|
|
95
|
+
actorTrustContextFromVerdict(verdict, input),
|
|
78
96
|
input.conversationExternalId,
|
|
79
97
|
);
|
|
80
98
|
|
|
@@ -91,6 +109,26 @@ export function trustContextFromVerdict(
|
|
|
91
109
|
return context;
|
|
92
110
|
}
|
|
93
111
|
|
|
112
|
+
/**
|
|
113
|
+
* True when the verdict carries a member identity (contactId or channelId),
|
|
114
|
+
* regardless of whether that member resolves to a usable {@link ResolvedMember}.
|
|
115
|
+
*/
|
|
116
|
+
export function verdictHasMemberIdentity(verdict: TrustVerdict): boolean {
|
|
117
|
+
return !!(verdict.contactId || verdict.channelId);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* True when the verdict claims a member identity but that member can't be
|
|
122
|
+
* synthesized (partial/mixed-version verdict). Such a verdict is unusable —
|
|
123
|
+
* callers fall back to local resolution.
|
|
124
|
+
*/
|
|
125
|
+
export function verdictMemberUnresolvable(verdict: TrustVerdict): boolean {
|
|
126
|
+
return (
|
|
127
|
+
verdictHasMemberIdentity(verdict) &&
|
|
128
|
+
resolvedMemberFromVerdict(verdict) === null
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
|
|
94
132
|
// Allowed ACL enum values, kept in sync with the ContactChannel union types.
|
|
95
133
|
const CHANNEL_STATUS_VALUES: readonly ChannelStatus[] = [
|
|
96
134
|
"active",
|
|
@@ -113,6 +151,44 @@ function isChannelPolicy(value: string): value is ChannelPolicy {
|
|
|
113
151
|
return (CHANNEL_POLICY_VALUES as readonly string[]).includes(value);
|
|
114
152
|
}
|
|
115
153
|
|
|
154
|
+
/**
|
|
155
|
+
* The ACL fields a gateway verdict carries for a resolved member, decoupled
|
|
156
|
+
* from the schema-derived {@link ContactChannel}.
|
|
157
|
+
*/
|
|
158
|
+
export interface VerdictMember {
|
|
159
|
+
contactId: string;
|
|
160
|
+
channelId: string;
|
|
161
|
+
status: ChannelStatus;
|
|
162
|
+
policy: ChannelPolicy;
|
|
163
|
+
verifiedAt: number | null;
|
|
164
|
+
displayName: string | null;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Extract the narrow {@link VerdictMember} ACL view from a gateway verdict.
|
|
169
|
+
*
|
|
170
|
+
* Mirrors {@link resolvedMemberFromVerdict}'s guards (contactId/channelId
|
|
171
|
+
* present + known status/policy enums), failing closed to null otherwise.
|
|
172
|
+
*/
|
|
173
|
+
export function verdictMemberFromVerdict(
|
|
174
|
+
verdict: TrustVerdict,
|
|
175
|
+
): VerdictMember | null {
|
|
176
|
+
if (!verdict.contactId || !verdict.channelId) return null;
|
|
177
|
+
if (!verdict.status || !verdict.policy) return null;
|
|
178
|
+
if (!isChannelStatus(verdict.status) || !isChannelPolicy(verdict.policy)) {
|
|
179
|
+
return null;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
return {
|
|
183
|
+
contactId: verdict.contactId,
|
|
184
|
+
channelId: verdict.channelId,
|
|
185
|
+
status: verdict.status,
|
|
186
|
+
policy: verdict.policy,
|
|
187
|
+
verifiedAt: verdict.verifiedAt ?? null,
|
|
188
|
+
displayName: verdict.memberDisplayName ?? null,
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
|
|
116
192
|
/**
|
|
117
193
|
* Build a synthetic {@link ResolvedMember} from a gateway verdict.
|
|
118
194
|
*
|
|
@@ -21,7 +21,7 @@ import {
|
|
|
21
21
|
countRecentSendsToDestination,
|
|
22
22
|
createOutboundSession,
|
|
23
23
|
findActiveSession,
|
|
24
|
-
|
|
24
|
+
isGuardianBoundForChannel,
|
|
25
25
|
updateSessionDelivery,
|
|
26
26
|
updateSessionStatus,
|
|
27
27
|
} from "./channel-verification-service.js";
|
|
@@ -224,7 +224,7 @@ export async function startOutbound(
|
|
|
224
224
|
originConversationId,
|
|
225
225
|
);
|
|
226
226
|
} else if (channel === "phone") {
|
|
227
|
-
return startOutboundVoice(
|
|
227
|
+
return await startOutboundVoice(
|
|
228
228
|
params.destination,
|
|
229
229
|
assistantId,
|
|
230
230
|
channel,
|
|
@@ -232,7 +232,7 @@ export async function startOutbound(
|
|
|
232
232
|
originConversationId,
|
|
233
233
|
);
|
|
234
234
|
} else if (channel === "slack") {
|
|
235
|
-
return startOutboundSlack(
|
|
235
|
+
return await startOutboundSlack(
|
|
236
236
|
params.destination,
|
|
237
237
|
assistantId,
|
|
238
238
|
channel,
|
|
@@ -240,7 +240,7 @@ export async function startOutbound(
|
|
|
240
240
|
originConversationId,
|
|
241
241
|
);
|
|
242
242
|
} else if (channel === "email") {
|
|
243
|
-
return startOutboundEmail(
|
|
243
|
+
return await startOutboundEmail(
|
|
244
244
|
params.destination,
|
|
245
245
|
assistantId,
|
|
246
246
|
channel,
|
|
@@ -274,8 +274,8 @@ async function startOutboundTelegram(
|
|
|
274
274
|
};
|
|
275
275
|
}
|
|
276
276
|
|
|
277
|
-
const
|
|
278
|
-
if (
|
|
277
|
+
const alreadyBound = await isGuardianBoundForChannel(channel);
|
|
278
|
+
if (alreadyBound && !rebind) {
|
|
279
279
|
return {
|
|
280
280
|
success: false,
|
|
281
281
|
error: "already_bound",
|
|
@@ -394,13 +394,13 @@ async function startOutboundTelegram(
|
|
|
394
394
|
};
|
|
395
395
|
}
|
|
396
396
|
|
|
397
|
-
function startOutboundVoice(
|
|
397
|
+
async function startOutboundVoice(
|
|
398
398
|
rawDestination: string | undefined,
|
|
399
399
|
assistantId: string,
|
|
400
400
|
channel: ChannelId,
|
|
401
401
|
rebind?: boolean,
|
|
402
402
|
originConversationId?: string,
|
|
403
|
-
): OutboundActionResult {
|
|
403
|
+
): Promise<OutboundActionResult> {
|
|
404
404
|
if (!rawDestination) {
|
|
405
405
|
return {
|
|
406
406
|
success: false,
|
|
@@ -422,8 +422,8 @@ function startOutboundVoice(
|
|
|
422
422
|
};
|
|
423
423
|
}
|
|
424
424
|
|
|
425
|
-
const
|
|
426
|
-
if (
|
|
425
|
+
const alreadyBound = await isGuardianBoundForChannel(channel);
|
|
426
|
+
if (alreadyBound && !rebind) {
|
|
427
427
|
return {
|
|
428
428
|
success: false,
|
|
429
429
|
error: "already_bound",
|
|
@@ -591,13 +591,13 @@ export function deliverVerificationEmail(
|
|
|
591
591
|
})();
|
|
592
592
|
}
|
|
593
593
|
|
|
594
|
-
function startOutboundSlack(
|
|
594
|
+
async function startOutboundSlack(
|
|
595
595
|
destination: string | undefined,
|
|
596
596
|
assistantId: string,
|
|
597
597
|
channel: ChannelId,
|
|
598
598
|
rebind?: boolean,
|
|
599
599
|
originConversationId?: string,
|
|
600
|
-
): OutboundActionResult {
|
|
600
|
+
): Promise<OutboundActionResult> {
|
|
601
601
|
if (!destination) {
|
|
602
602
|
return {
|
|
603
603
|
success: false,
|
|
@@ -607,8 +607,8 @@ function startOutboundSlack(
|
|
|
607
607
|
};
|
|
608
608
|
}
|
|
609
609
|
|
|
610
|
-
const
|
|
611
|
-
if (
|
|
610
|
+
const alreadyBound = await isGuardianBoundForChannel(channel);
|
|
611
|
+
if (alreadyBound && !rebind) {
|
|
612
612
|
return {
|
|
613
613
|
success: false,
|
|
614
614
|
error: "already_bound",
|
|
@@ -669,13 +669,13 @@ function startOutboundSlack(
|
|
|
669
669
|
};
|
|
670
670
|
}
|
|
671
671
|
|
|
672
|
-
function startOutboundEmail(
|
|
672
|
+
async function startOutboundEmail(
|
|
673
673
|
destination: string | undefined,
|
|
674
674
|
assistantId: string,
|
|
675
675
|
channel: ChannelId,
|
|
676
676
|
rebind?: boolean,
|
|
677
677
|
originConversationId?: string,
|
|
678
|
-
): OutboundActionResult {
|
|
678
|
+
): Promise<OutboundActionResult> {
|
|
679
679
|
if (!destination) {
|
|
680
680
|
return {
|
|
681
681
|
success: false,
|
|
@@ -688,8 +688,8 @@ function startOutboundEmail(
|
|
|
688
688
|
|
|
689
689
|
const normalizedEmail = destination.trim().toLowerCase();
|
|
690
690
|
|
|
691
|
-
const
|
|
692
|
-
if (
|
|
691
|
+
const alreadyBound = await isGuardianBoundForChannel(channel);
|
|
692
|
+
if (alreadyBound && !rebind) {
|
|
693
693
|
return {
|
|
694
694
|
success: false,
|
|
695
695
|
error: "already_bound",
|
|
@@ -15,6 +15,7 @@ import { readFileSync, statSync, unlinkSync, writeFileSync } from "node:fs";
|
|
|
15
15
|
import { join } from "node:path";
|
|
16
16
|
|
|
17
17
|
import { getOrCreateConversation } from "../daemon/conversation-store.js";
|
|
18
|
+
import { supersedePendingInteractionsOnEnqueue } from "../daemon/handlers/conversations.js";
|
|
18
19
|
import type { UserMessageAttachment } from "../daemon/message-types/shared.js";
|
|
19
20
|
import {
|
|
20
21
|
processMessageInBackground,
|
|
@@ -131,6 +132,21 @@ async function dispatchUserMessage(params: {
|
|
|
131
132
|
assistantMessageInterface: resolvedInterface,
|
|
132
133
|
},
|
|
133
134
|
});
|
|
135
|
+
if (!result.rejected) {
|
|
136
|
+
// Mirror the HTTP send path: a follow-up enqueued while the turn is busy
|
|
137
|
+
// auto-denies pending confirmations and supersedes a parked ask_question
|
|
138
|
+
// so it isn't stranded behind the prompt until the response backstop.
|
|
139
|
+
// Best-effort — the message is already queued, so a cleanup failure must
|
|
140
|
+
// not surface as an error that makes the CLI retry and enqueue a duplicate.
|
|
141
|
+
try {
|
|
142
|
+
supersedePendingInteractionsOnEnqueue(conversationId, requestId);
|
|
143
|
+
} catch (err) {
|
|
144
|
+
log.warn(
|
|
145
|
+
{ err, conversationId },
|
|
146
|
+
"Post-enqueue supersession failed — queued message unaffected",
|
|
147
|
+
);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
134
150
|
return { accepted: !result.rejected };
|
|
135
151
|
}
|
|
136
152
|
|
package/src/subagent/manager.ts
CHANGED
|
@@ -553,6 +553,15 @@ export class SubagentManager {
|
|
|
553
553
|
),
|
|
554
554
|
);
|
|
555
555
|
managed.state.completedAt = Date.now();
|
|
556
|
+
// Capture the conversation's latest usage before emitting the terminal
|
|
557
|
+
// status. `subagent_status_changed` ships `state.usage`, and the abort path
|
|
558
|
+
// (unlike the completion/failure paths, which sync at agent-loop exit) would
|
|
559
|
+
// otherwise send the {0,0,0} init usage — zeroing the client's token counts
|
|
560
|
+
// even though those tokens were already spent. `usageStats` accrues per LLM
|
|
561
|
+
// turn (see conversation-usage.ts), so this is the most recent total.
|
|
562
|
+
if (managed.conversation) {
|
|
563
|
+
managed.state.usage = { ...managed.conversation.usageStats };
|
|
564
|
+
}
|
|
556
565
|
if (parentSendToClient) {
|
|
557
566
|
// Route the status update through the stored parent sender so the
|
|
558
567
|
// owning conversation's UI chip updates, even when the abort comes from a
|
package/src/telemetry/types.ts
CHANGED
|
@@ -391,6 +391,38 @@ export interface SkillLoadedTelemetryEvent extends ModelTelemetryEventBase {
|
|
|
391
391
|
conversation_id: string | null;
|
|
392
392
|
}
|
|
393
393
|
|
|
394
|
+
/**
|
|
395
|
+
* Watchdog health event — one per watchdog check firing. The daemon's
|
|
396
|
+
* watchdog observes liveness/health signals (event-loop block, stream-idle
|
|
397
|
+
* stalls, restarts, ...) and emits one event per check firing.
|
|
398
|
+
*
|
|
399
|
+
* Deliberately minimal and forward-compatible, mirroring the platform
|
|
400
|
+
* `WatchdogTelemetryEventSerializer`:
|
|
401
|
+
*
|
|
402
|
+
* - `check_name` — which watchdog check fired. Open string set (not a
|
|
403
|
+
* closed enum) so the daemon can introduce a new check without a
|
|
404
|
+
* coordinated serializer release; it is the primary group-by dimension
|
|
405
|
+
* downstream. The infrastructure admin chart filters this to
|
|
406
|
+
* `event_loop_blocked`.
|
|
407
|
+
* - `value` — the single measured magnitude for the check (block ms, idle
|
|
408
|
+
* ms, ...). Nullable: not every check carries a scalar. The platform
|
|
409
|
+
* coerces ints to float, so the daemon need not distinguish.
|
|
410
|
+
* - `detail` — open JSON bag for any extra fields the daemon attaches
|
|
411
|
+
* (reason codes, secondary numbers, a human message) without a
|
|
412
|
+
* platform-coordinated schema change. Null when the daemon attaches
|
|
413
|
+
* nothing. Bounded server-side (4096 bytes serialized); an oversize bag
|
|
414
|
+
* rejects only the single event, never the batch.
|
|
415
|
+
*
|
|
416
|
+
* Metadata only — no conversation content. Dedupe downstream on
|
|
417
|
+
* `daemon_event_id` (the daemon retries a batch on transient POST failure).
|
|
418
|
+
*/
|
|
419
|
+
export interface WatchdogTelemetryEvent extends TelemetryEventBase {
|
|
420
|
+
type: "watchdog";
|
|
421
|
+
check_name: string;
|
|
422
|
+
value: number | null;
|
|
423
|
+
detail: Record<string, unknown> | null;
|
|
424
|
+
}
|
|
425
|
+
|
|
394
426
|
/** Discriminated union of all telemetry event types. */
|
|
395
427
|
export type TelemetryEvent =
|
|
396
428
|
| LlmUsageTelemetryEvent
|
|
@@ -399,4 +431,5 @@ export type TelemetryEvent =
|
|
|
399
431
|
| OnboardingTelemetryEvent
|
|
400
432
|
| AuthFallbackTelemetryEvent
|
|
401
433
|
| ToolExecutedTelemetryEvent
|
|
402
|
-
| SkillLoadedTelemetryEvent
|
|
434
|
+
| SkillLoadedTelemetryEvent
|
|
435
|
+
| WatchdogTelemetryEvent;
|
|
@@ -1547,11 +1547,11 @@ describe("UsageTelemetryReporter", () => {
|
|
|
1547
1547
|
// No HTTP call should have been made
|
|
1548
1548
|
expect(mockFetch).not.toHaveBeenCalled();
|
|
1549
1549
|
|
|
1550
|
-
// All
|
|
1550
|
+
// All 8 timestamp watermarks should have been advanced, and all 8 ID
|
|
1551
1551
|
// watermarks pinned to the high-sorting sentinel (a truthy value keeps
|
|
1552
1552
|
// the compound-cursor branch active while closing its same-millisecond
|
|
1553
1553
|
// arm against opt-out rows).
|
|
1554
|
-
expect(mockSetMemoryCheckpoint).toHaveBeenCalledTimes(
|
|
1554
|
+
expect(mockSetMemoryCheckpoint).toHaveBeenCalledTimes(16);
|
|
1555
1555
|
|
|
1556
1556
|
const calls = mockSetMemoryCheckpoint.mock.calls;
|
|
1557
1557
|
const keys = calls.map((c) => c[0]);
|
|
@@ -1563,6 +1563,7 @@ describe("UsageTelemetryReporter", () => {
|
|
|
1563
1563
|
"auth_fallback",
|
|
1564
1564
|
"tool_executed",
|
|
1565
1565
|
"skill_loaded",
|
|
1566
|
+
"watchdog",
|
|
1566
1567
|
];
|
|
1567
1568
|
for (const eventType of eventTypes) {
|
|
1568
1569
|
expect(keys).toContain(`telemetry:${eventType}:last_reported_at`);
|
|
@@ -28,6 +28,7 @@ import {
|
|
|
28
28
|
assembleBoundedTurnTrace,
|
|
29
29
|
isTurnSettled,
|
|
30
30
|
} from "../memory/turn-trace-store.js";
|
|
31
|
+
import { queryUnreportedWatchdogEvents } from "../memory/watchdog-events-store.js";
|
|
31
32
|
import { VellumPlatformClient } from "../platform/client.js";
|
|
32
33
|
import {
|
|
33
34
|
getCachedShareAnalytics,
|
|
@@ -76,6 +77,9 @@ const CHECKPOINT_KEY_SKILL_LOADED_WATERMARK =
|
|
|
76
77
|
"telemetry:skill_loaded:last_reported_at";
|
|
77
78
|
const CHECKPOINT_KEY_SKILL_LOADED_WATERMARK_ID =
|
|
78
79
|
"telemetry:skill_loaded:last_reported_id";
|
|
80
|
+
const CHECKPOINT_KEY_WATCHDOG_WATERMARK = "telemetry:watchdog:last_reported_at";
|
|
81
|
+
const CHECKPOINT_KEY_WATCHDOG_WATERMARK_ID =
|
|
82
|
+
"telemetry:watchdog:last_reported_id";
|
|
79
83
|
// Written into the `*_id` watermark checkpoints by the opt-out flush branch.
|
|
80
84
|
// Sorts lexicographically above every real row ID (all event stores generate
|
|
81
85
|
// lowercase v4 UUIDs), so the compound cursor's same-millisecond arm
|
|
@@ -100,6 +104,7 @@ const WATERMARK_KEY_PAIRS: ReadonlyArray<readonly [string, string]> = [
|
|
|
100
104
|
CHECKPOINT_KEY_SKILL_LOADED_WATERMARK,
|
|
101
105
|
CHECKPOINT_KEY_SKILL_LOADED_WATERMARK_ID,
|
|
102
106
|
],
|
|
107
|
+
[CHECKPOINT_KEY_WATCHDOG_WATERMARK, CHECKPOINT_KEY_WATCHDOG_WATERMARK_ID],
|
|
103
108
|
];
|
|
104
109
|
const REPORT_INTERVAL_MS = 5 * 60 * 1000;
|
|
105
110
|
const INITIAL_FLUSH_DELAY_MS = 30_000; // Delay first flush to let CES handshake complete
|
|
@@ -123,6 +128,37 @@ export function setUsageTelemetryReporter(
|
|
|
123
128
|
_instance = reporter;
|
|
124
129
|
}
|
|
125
130
|
|
|
131
|
+
// ---------------------------------------------------------------------------
|
|
132
|
+
// Helpers
|
|
133
|
+
// ---------------------------------------------------------------------------
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Parse a stored `watchdog_events.detail` JSON text column into the object the
|
|
137
|
+
* platform expects. Returns null for a null column or an unparseable/corrupted
|
|
138
|
+
* blob (mirroring the turn `client` metadata parse: a bad blob emits null
|
|
139
|
+
* rather than failing the batch). A non-object (e.g. a bare number or string)
|
|
140
|
+
* also resolves to null, since the platform serializer treats `detail` as a
|
|
141
|
+
* JSON object bag.
|
|
142
|
+
*/
|
|
143
|
+
function parseWatchdogDetail(
|
|
144
|
+
raw: string | null,
|
|
145
|
+
): Record<string, unknown> | null {
|
|
146
|
+
if (!raw) return null;
|
|
147
|
+
try {
|
|
148
|
+
const parsed = JSON.parse(raw) as unknown;
|
|
149
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
150
|
+
return parsed as Record<string, unknown>;
|
|
151
|
+
}
|
|
152
|
+
return null;
|
|
153
|
+
} catch {
|
|
154
|
+
log.warn(
|
|
155
|
+
{ rawDetail: raw.slice(0, 200) },
|
|
156
|
+
"Telemetry watchdog: failed to parse detail; emitting null",
|
|
157
|
+
);
|
|
158
|
+
return null;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
126
162
|
// ---------------------------------------------------------------------------
|
|
127
163
|
// Reporter
|
|
128
164
|
// ---------------------------------------------------------------------------
|
|
@@ -307,7 +343,8 @@ export class UsageTelemetryReporter {
|
|
|
307
343
|
undefined;
|
|
308
344
|
|
|
309
345
|
// Read skill-loaded watermark (compound cursor: createdAt + id).
|
|
310
|
-
//
|
|
346
|
+
// Writes are gated on share_analytics consent, so opted-out rows
|
|
347
|
+
// cannot exist and the standard 0 default is safe.
|
|
311
348
|
const skillLoadedWatermark = Number(
|
|
312
349
|
getMemoryCheckpoint(CHECKPOINT_KEY_SKILL_LOADED_WATERMARK) ?? "0",
|
|
313
350
|
);
|
|
@@ -315,6 +352,15 @@ export class UsageTelemetryReporter {
|
|
|
315
352
|
getMemoryCheckpoint(CHECKPOINT_KEY_SKILL_LOADED_WATERMARK_ID) ??
|
|
316
353
|
undefined;
|
|
317
354
|
|
|
355
|
+
// Read watchdog watermark (compound cursor: createdAt + id).
|
|
356
|
+
// Writes are gated on share_analytics consent, so opted-out rows
|
|
357
|
+
// cannot exist and the standard 0 default is safe.
|
|
358
|
+
const watchdogWatermark = Number(
|
|
359
|
+
getMemoryCheckpoint(CHECKPOINT_KEY_WATCHDOG_WATERMARK) ?? "0",
|
|
360
|
+
);
|
|
361
|
+
const watchdogWatermarkId =
|
|
362
|
+
getMemoryCheckpoint(CHECKPOINT_KEY_WATCHDOG_WATERMARK_ID) ?? undefined;
|
|
363
|
+
|
|
318
364
|
// Query unreported events
|
|
319
365
|
const events = queryUnreportedUsageEvents(
|
|
320
366
|
watermark,
|
|
@@ -351,6 +397,11 @@ export class UsageTelemetryReporter {
|
|
|
351
397
|
skillLoadedWatermarkId,
|
|
352
398
|
BATCH_SIZE,
|
|
353
399
|
);
|
|
400
|
+
const watchdogEvents = queryUnreportedWatchdogEvents(
|
|
401
|
+
watchdogWatermark,
|
|
402
|
+
watchdogWatermarkId,
|
|
403
|
+
BATCH_SIZE,
|
|
404
|
+
);
|
|
354
405
|
|
|
355
406
|
// Trace completeness barrier (trace-eligible owners only).
|
|
356
407
|
//
|
|
@@ -418,7 +469,8 @@ export class UsageTelemetryReporter {
|
|
|
418
469
|
onboardingEvents.length === 0 &&
|
|
419
470
|
authFallbackEvents.length === 0 &&
|
|
420
471
|
toolExecutedEvents.length === 0 &&
|
|
421
|
-
skillLoadedEvents.length === 0
|
|
472
|
+
skillLoadedEvents.length === 0 &&
|
|
473
|
+
watchdogEvents.length === 0
|
|
422
474
|
)
|
|
423
475
|
return;
|
|
424
476
|
|
|
@@ -442,6 +494,7 @@ export class UsageTelemetryReporter {
|
|
|
442
494
|
authFallbackCount: authFallbackEvents.length,
|
|
443
495
|
toolExecutedCount: toolExecutedEvents.length,
|
|
444
496
|
skillLoadedCount: skillLoadedEvents.length,
|
|
497
|
+
watchdogCount: watchdogEvents.length,
|
|
445
498
|
},
|
|
446
499
|
"Telemetry flush: resolved auth context",
|
|
447
500
|
);
|
|
@@ -674,6 +727,23 @@ export class UsageTelemetryReporter {
|
|
|
674
727
|
assistant_version: APP_VERSION,
|
|
675
728
|
}),
|
|
676
729
|
),
|
|
730
|
+
...watchdogEvents.map(
|
|
731
|
+
(e): TelemetryEvent => ({
|
|
732
|
+
type: "watchdog",
|
|
733
|
+
daemon_event_id: e.id,
|
|
734
|
+
recorded_at: e.createdAt,
|
|
735
|
+
check_name: e.checkName,
|
|
736
|
+
value: e.value,
|
|
737
|
+
// `detail` is stored as JSON text; parse defensively so a
|
|
738
|
+
// corrupted blob never fails the batch flush. A parse failure
|
|
739
|
+
// emits null rather than dropping the event.
|
|
740
|
+
detail: parseWatchdogDetail(e.detail),
|
|
741
|
+
// `watchdog_events` has no record-time version column — same
|
|
742
|
+
// upload-time APP_VERSION stamping as the other non-llm_usage
|
|
743
|
+
// event types.
|
|
744
|
+
assistant_version: APP_VERSION,
|
|
745
|
+
}),
|
|
746
|
+
),
|
|
677
747
|
];
|
|
678
748
|
|
|
679
749
|
const organizationId = getPlatformOrganizationId() || undefined;
|
|
@@ -796,6 +866,19 @@ export class UsageTelemetryReporter {
|
|
|
796
866
|
);
|
|
797
867
|
}
|
|
798
868
|
|
|
869
|
+
// Advance watchdog watermark (compound cursor)
|
|
870
|
+
if (watchdogEvents.length > 0) {
|
|
871
|
+
const lastWatchdog = watchdogEvents[watchdogEvents.length - 1];
|
|
872
|
+
setMemoryCheckpoint(
|
|
873
|
+
CHECKPOINT_KEY_WATCHDOG_WATERMARK,
|
|
874
|
+
String(lastWatchdog.createdAt),
|
|
875
|
+
);
|
|
876
|
+
setMemoryCheckpoint(
|
|
877
|
+
CHECKPOINT_KEY_WATCHDOG_WATERMARK_ID,
|
|
878
|
+
lastWatchdog.id,
|
|
879
|
+
);
|
|
880
|
+
}
|
|
881
|
+
|
|
799
882
|
// If we got a full batch of any type, there may be more — recurse.
|
|
800
883
|
// Turns use the REPORTED count: when the completeness barrier truncates
|
|
801
884
|
// the batch, the deferred turns must wait for a later flush (by which
|
|
@@ -808,7 +891,8 @@ export class UsageTelemetryReporter {
|
|
|
808
891
|
onboardingEvents.length === BATCH_SIZE ||
|
|
809
892
|
authFallbackEvents.length === BATCH_SIZE ||
|
|
810
893
|
toolExecutedEvents.length === BATCH_SIZE ||
|
|
811
|
-
skillLoadedEvents.length === BATCH_SIZE
|
|
894
|
+
skillLoadedEvents.length === BATCH_SIZE ||
|
|
895
|
+
watchdogEvents.length === BATCH_SIZE
|
|
812
896
|
) {
|
|
813
897
|
await this._doFlush(batchCount + 1);
|
|
814
898
|
}
|
|
@@ -206,6 +206,35 @@ describe("AskQuestionTool.execute", () => {
|
|
|
206
206
|
expect(result.content).toBe("Question aborted");
|
|
207
207
|
});
|
|
208
208
|
|
|
209
|
+
test("short-circuits without prompting when no interactive user is present", async () => {
|
|
210
|
+
setNextResult(singleCompleted({ decision: "option", optionId: "a" }));
|
|
211
|
+
|
|
212
|
+
const result = await askQuestionTool.execute(
|
|
213
|
+
validInput,
|
|
214
|
+
makeContext({ isInteractive: false }),
|
|
215
|
+
);
|
|
216
|
+
|
|
217
|
+
// The prompter must never be invoked — there is no one to answer, so the
|
|
218
|
+
// turn proceeds with defaults instead of parking on the response backstop.
|
|
219
|
+
expect(calls).toHaveLength(0);
|
|
220
|
+
expect(result.isError).toBe(false);
|
|
221
|
+
expect(result.content.toLowerCase()).toContain("no interactive user");
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
test("still prompts when isInteractive is true or unset", async () => {
|
|
225
|
+
setNextResult(singleCompleted({ decision: "option", optionId: "a" }));
|
|
226
|
+
|
|
227
|
+
// The short-circuit keys off an explicit `false`, not a missing flag, so an
|
|
228
|
+
// interactive turn (or one that never set the flag) still prompts.
|
|
229
|
+
await askQuestionTool.execute(
|
|
230
|
+
validInput,
|
|
231
|
+
makeContext({ isInteractive: true }),
|
|
232
|
+
);
|
|
233
|
+
await askQuestionTool.execute(validInput, makeContext());
|
|
234
|
+
|
|
235
|
+
expect(calls).toHaveLength(2);
|
|
236
|
+
});
|
|
237
|
+
|
|
209
238
|
test("rejects a question with fewer than 2 options", async () => {
|
|
210
239
|
setNextResult(singleCompleted({ decision: "option", optionId: "a" }));
|
|
211
240
|
const result = await askQuestionTool.execute(
|
|
@@ -182,6 +182,19 @@ export const askQuestionTool = {
|
|
|
182
182
|
|
|
183
183
|
const questions: SingleQuestion[] = parsed.data.questions;
|
|
184
184
|
|
|
185
|
+
// No interactive user is present to answer (scheduled/headless/background
|
|
186
|
+
// turn). Don't park the turn on a prompt no one can resolve — proceed with
|
|
187
|
+
// defaults immediately. Non-interactive turns are already instructed not to
|
|
188
|
+
// ask (NON_INTERACTIVE_CONTEXT_BLOCK); this is the backstop for when the
|
|
189
|
+
// model asks anyway, so it doesn't wait out the full response timeout.
|
|
190
|
+
if (context.isInteractive === false) {
|
|
191
|
+
return {
|
|
192
|
+
content:
|
|
193
|
+
"No interactive user is present to answer; proceeding with reasonable defaults.",
|
|
194
|
+
isError: false,
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
|
|
185
198
|
const prompter = new QuestionPrompter();
|
|
186
199
|
const result = await prompter.prompt({
|
|
187
200
|
conversationId: context.conversationId,
|
|
@@ -437,7 +437,7 @@ export const computerUseRespondTool = {
|
|
|
437
437
|
// observe
|
|
438
438
|
// ---------------------------------------------------------------------------
|
|
439
439
|
|
|
440
|
-
const computerUseObserveTool = {
|
|
440
|
+
export const computerUseObserveTool = {
|
|
441
441
|
name: "computer_use_observe",
|
|
442
442
|
description:
|
|
443
443
|
"Capture the current screen state. Returns the accessibility tree with [ID] element references and optionally a screenshot.\n\nThe accessibility tree shows interactive elements like [3] AXButton 'Save' or [17] AXTextField 'Search'. Use element_id to target these elements in subsequent actions - this is much more reliable than pixel coordinates.\n\nCall this before your first computer use action, or to check screen state without acting.",
|
|
@@ -447,7 +447,13 @@ const computerUseObserveTool = {
|
|
|
447
447
|
|
|
448
448
|
input_schema: {
|
|
449
449
|
type: "object",
|
|
450
|
-
properties: {
|
|
450
|
+
properties: {
|
|
451
|
+
target_client_id: {
|
|
452
|
+
type: "string",
|
|
453
|
+
description:
|
|
454
|
+
"ID of the specific client to target. Required when multiple clients support host_cu; omit when only one is connected. Obtain IDs from `assistant clients list --capability host_cu`.",
|
|
455
|
+
},
|
|
456
|
+
},
|
|
451
457
|
required: [],
|
|
452
458
|
},
|
|
453
459
|
|
package/src/tools/executor.ts
CHANGED
|
@@ -528,8 +528,8 @@ export { isSideEffectTool } from "./side-effects.js";
|
|
|
528
528
|
* handles cleanup before the executor wrapper trips.
|
|
529
529
|
*
|
|
530
530
|
* `ask_question` blocks on user input inside `execute()` via `QuestionPrompter`,
|
|
531
|
-
* which waits up to `
|
|
532
|
-
* buffer over that deadline so the prompter's own timeout fires first and
|
|
531
|
+
* which waits up to `questionResponseTimeoutSec`. We give the wrapper the same
|
|
532
|
+
* 5s buffer over that deadline so the prompter's own timeout fires first and
|
|
533
533
|
* returns its clean "User did not respond within timeout" result — otherwise
|
|
534
534
|
* the shorter generic budget trips first, orphaning the still-pending prompt
|
|
535
535
|
* behind the confusing "may still be running in the background" error.
|
|
@@ -556,8 +556,8 @@ export function computePerToolTimeoutMs(
|
|
|
556
556
|
return (shellTimeoutSec + 5) * 1000;
|
|
557
557
|
}
|
|
558
558
|
if (name === "ask_question") {
|
|
559
|
-
const {
|
|
560
|
-
return (
|
|
559
|
+
const { questionResponseTimeoutSec } = getConfig().timeouts;
|
|
560
|
+
return (questionResponseTimeoutSec + 5) * 1000;
|
|
561
561
|
}
|
|
562
562
|
const rawTimeoutSec = getConfig().timeouts.toolExecutionTimeoutSec;
|
|
563
563
|
return safeTimeoutMs(rawTimeoutSec);
|