@vellumai/assistant 0.10.8 → 0.10.9-dev.202607152225.ec6fdbf
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/AGENTS.md +4 -0
- package/ARCHITECTURE.md +52 -88
- package/README.md +14 -35
- package/docs/activation-funnel-telemetry.md +20 -18
- package/docs/architecture/scheduling.md +0 -86
- package/docs/runbook-trusted-contacts.md +19 -19
- package/docs/trusted-contact-access.md +70 -69
- package/docs/workspace-tools.md +4 -2
- package/eslint-rules/__tests__/cli-no-daemon-internals.test.ts +62 -360
- package/eslint-rules/cli-no-daemon-internals.js +91 -275
- package/eslint.config.mjs +4 -4
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/package.json +1 -0
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/__tests__/redacted-credential.test.ts +200 -0
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/redacted-credential.ts +226 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/package.json +1 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/__tests__/redacted-credential.test.ts +200 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/redacted-credential.ts +226 -0
- package/node_modules/@vellumai/gateway-client/src/__tests__/guardian-request-contract.test.ts +575 -0
- package/node_modules/@vellumai/gateway-client/src/gateway-ipc-contracts.ts +20 -0
- package/node_modules/@vellumai/gateway-client/src/guardian-request-contract.ts +687 -0
- package/node_modules/@vellumai/gateway-client/src/index.ts +82 -0
- package/node_modules/@vellumai/service-contracts/package.json +1 -0
- package/node_modules/@vellumai/service-contracts/src/__tests__/redacted-credential.test.ts +200 -0
- package/node_modules/@vellumai/service-contracts/src/redacted-credential.ts +226 -0
- package/openapi.yaml +353 -742
- package/package.json +2 -2
- package/scripts/build-test-fixtures.ts +3 -2
- package/scripts/test.ts +2 -7
- package/src/__tests__/activation-early-marking.test.ts +1 -8
- package/src/__tests__/actor-token-service.test.ts +0 -7
- package/src/__tests__/add-message-preassigned-id.test.ts +1 -1
- package/src/__tests__/add-message-skip-indexing.test.ts +164 -0
- package/src/__tests__/agent-image-optimize.test.ts +104 -1
- package/src/__tests__/agent-loop-callsite-precedence.test.ts +5 -15
- package/src/__tests__/agent-loop-mutable-latest-user-message.test.ts +0 -6
- package/src/__tests__/agent-loop-override-profile.test.ts +48 -50
- package/src/__tests__/agent-loop-pushes-post-hook-prompt.test.ts +4 -17
- package/src/__tests__/agent-loop-set-system-prompt.test.ts +3 -14
- package/src/__tests__/agent-wake-disk-pressure-callsite.test.ts +0 -4
- package/src/__tests__/agent-wake-override-profile.test.ts +40 -71
- package/src/__tests__/always-loaded-tools-guard.test.ts +4 -7
- package/src/__tests__/annotate-activity-metadata.test.ts +4 -31
- package/src/__tests__/annotate-risk-options.test.ts +4 -31
- package/src/__tests__/anthropic-provider.test.ts +11 -0
- package/src/__tests__/app-asset-bytes.test.ts +72 -0
- package/src/__tests__/app-builder-tool-scripts.test.ts +0 -1
- package/src/__tests__/app-bundler.test.ts +4 -20
- package/src/__tests__/app-conversation-ids-backfill.test.ts +1 -8
- package/src/__tests__/app-executors.test.ts +2 -49
- package/src/__tests__/app-routes-csp.test.ts +126 -157
- package/src/__tests__/approval-cascade.test.ts +0 -82
- package/src/__tests__/approval-interception-trust-gates.test.ts +0 -7
- package/src/__tests__/approval-primitive.test.ts +1 -9
- package/src/__tests__/approval-routes-http.test.ts +5 -63
- package/src/__tests__/assistant-event-hub-machine-name.test.ts +1 -19
- package/src/__tests__/assistant-events-sse-hardening.test.ts +4 -21
- package/src/__tests__/attachment-stored-path-annotation.test.ts +1 -19
- package/src/__tests__/attachment-upload-trusted-source.test.ts +0 -13
- package/src/__tests__/attachments-store-heic-normalize.test.ts +0 -13
- package/src/__tests__/attachments-store.test.ts +5 -19
- package/src/__tests__/audit-log-rotation.test.ts +1 -9
- package/src/__tests__/auth-fallback-events-store.test.ts +43 -59
- package/src/__tests__/available-skills.test.ts +7 -15
- package/src/__tests__/avatar-e2e.test.ts +0 -19
- package/src/__tests__/avatar-generator.test.ts +0 -9
- package/src/__tests__/background-shell-bash.test.ts +0 -20
- package/src/__tests__/background-shell-host-bash.test.ts +0 -34
- package/src/__tests__/background-workers-disk-pressure.test.ts +1 -43
- package/src/__tests__/browser-fill-credential.test.ts +0 -7
- package/src/__tests__/browser-manager.test.ts +1 -8
- package/src/__tests__/browser-skill-endstate.test.ts +1 -5
- package/src/__tests__/btw-routes.test.ts +7 -30
- package/src/__tests__/build-persisted-content.test.ts +96 -29
- package/src/__tests__/bundle-scanner.test.ts +27 -1
- package/src/__tests__/call-controller.test.ts +96 -149
- package/src/__tests__/call-conversation-messages.test.ts +2 -9
- package/src/__tests__/call-domain.test.ts +21 -56
- package/src/__tests__/call-pointer-message-composer.test.ts +0 -8
- package/src/__tests__/call-pointer-messages.test.ts +1 -8
- package/src/__tests__/call-recovery.test.ts +4 -11
- package/src/__tests__/call-routes-http.test.ts +12 -47
- package/src/__tests__/call-site-routing-provider.test.ts +5 -16
- package/src/__tests__/call-start-guardian-guard.test.ts +0 -10
- package/src/__tests__/call-state.test.ts +1 -8
- package/src/__tests__/call-store.test.ts +4 -11
- package/src/__tests__/cancel-resolves-conversation-key.test.ts +0 -32
- package/src/__tests__/catalog-cache.test.ts +0 -8
- package/src/__tests__/catalog-files.test.ts +0 -8
- package/src/__tests__/catalog-install-normalize.test.ts +0 -7
- package/src/__tests__/ces-rpc-credential-backend.test.ts +0 -7
- package/src/__tests__/channel-approval-routes.test.ts +23 -34
- package/src/__tests__/channel-approvals.test.ts +0 -7
- package/src/__tests__/channel-delivery-store.test.ts +6 -17
- package/src/__tests__/channel-guardian.test.ts +0 -7
- package/src/__tests__/channel-inbound-disk-pressure.test.ts +15 -20
- package/src/__tests__/channel-readiness-routes.test.ts +41 -67
- package/src/__tests__/channel-readiness-service.test.ts +13 -45
- package/src/__tests__/channel-readiness-slack-remote.test.ts +5 -22
- package/src/__tests__/channel-reply-delivery.test.ts +18 -5
- package/src/__tests__/channel-retry-sweep.test.ts +0 -7
- package/src/__tests__/chat-credential-redaction.test.ts +1395 -0
- package/src/__tests__/chat-reveal-guard-priming.test.ts +791 -0
- package/src/__tests__/checker.test.ts +0 -24
- package/src/__tests__/clawhub-files.test.ts +0 -8
- package/src/__tests__/clawhub.test.ts +0 -7
- package/src/__tests__/clear-all-lexical.test.ts +0 -6
- package/src/__tests__/cli-memory-v2-reembed-skills.test.ts +3 -0
- package/src/__tests__/client-os-metadata-persistence.test.ts +0 -5
- package/src/__tests__/compaction-events.test.ts +26 -99
- package/src/__tests__/compaction-ledger-store.test.ts +1 -7
- package/src/__tests__/compaction-summary-head-persisted-count.test.ts +0 -27
- package/src/__tests__/compaction-trail-store.test.ts +1 -19
- package/src/__tests__/compaction.benchmark.test.ts +2 -6
- package/src/__tests__/compactor-call-site-logging.test.ts +0 -21
- package/src/__tests__/compactor-fixed-boundary.test.ts +0 -21
- package/src/__tests__/compactor-image-manifest-trust.test.ts +1 -10
- package/src/__tests__/compactor-low-watermark-cut.test.ts +0 -21
- package/src/__tests__/compactor-media-strip.test.ts +0 -21
- package/src/__tests__/compactor-preserved-tail-count.test.ts +0 -21
- package/src/__tests__/compactor-summary-call-truncation.test.ts +0 -21
- package/src/__tests__/compactor-web-search-strip.test.ts +0 -21
- package/src/__tests__/config-get-vision-flag.test.ts +24 -53
- package/src/__tests__/config-loader-backfill.test.ts +9 -25
- package/src/__tests__/config-loader-platform-defaults.test.ts +0 -21
- package/src/__tests__/config-schema-cmd.test.ts +10 -11
- package/src/__tests__/config-schema.test.ts +221 -275
- package/src/__tests__/config-set-route.test.ts +32 -62
- package/src/__tests__/config-watcher-cleanup-throttle.test.ts +44 -83
- package/src/__tests__/config-watcher-skill-reseed.test.ts +0 -16
- package/src/__tests__/config-watcher.test.ts +2 -17
- package/src/__tests__/confirmation-request-guardian-bridge.test.ts +57 -63
- package/src/__tests__/contact-store-interaction-info.test.ts +1 -8
- package/src/__tests__/contact-store-user-file.test.ts +1 -8
- package/src/__tests__/contacts-tools.test.ts +21 -26
- package/src/__tests__/contacts-write.test.ts +1 -8
- package/src/__tests__/context-search-memory-v2-source.test.ts +0 -6
- package/src/__tests__/context-window-manager-compact-retry.test.ts +0 -27
- package/src/__tests__/context-window-manager-overflow-rung.test.ts +0 -21
- package/src/__tests__/conversation-abort-tool-results.test.ts +9 -90
- package/src/__tests__/conversation-agent-loop-disk-pressure.test.ts +5 -41
- package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +16 -54
- package/src/__tests__/conversation-agent-loop-overflow.test.ts +14 -79
- package/src/__tests__/conversation-agent-loop.test.ts +213 -136
- package/src/__tests__/conversation-app-control-instantiation.test.ts +10 -16
- package/src/__tests__/conversation-app-control-lifecycle.test.ts +0 -43
- package/src/__tests__/conversation-attachments.test.ts +0 -17
- package/src/__tests__/conversation-attention-store.test.ts +1 -9
- package/src/__tests__/conversation-attention-telegram.test.ts +0 -8
- package/src/__tests__/conversation-clear-safety.test.ts +20 -20
- package/src/__tests__/conversation-confirmation-signals.test.ts +0 -82
- package/src/__tests__/conversation-crud-enabled-plugins.test.ts +1 -7
- package/src/__tests__/conversation-crud-inference-profile.test.ts +1 -7
- package/src/__tests__/conversation-delete-schedule-cleanup.test.ts +0 -7
- package/src/__tests__/conversation-disk-view-integration.test.ts +1 -26
- package/src/__tests__/conversation-disk-view.test.ts +1 -26
- package/src/__tests__/conversation-enabledplugins-route.test.ts +3 -21
- package/src/__tests__/conversation-error.test.ts +21 -7
- package/src/__tests__/conversation-fork-crud.test.ts +35 -44
- package/src/__tests__/conversation-fork-retrospective.test.ts +9 -24
- package/src/__tests__/conversation-fork-route.test.ts +0 -32
- package/src/__tests__/conversation-history-web-search.test.ts +20 -17
- package/src/__tests__/conversation-inference-profile-list.test.ts +0 -17
- package/src/__tests__/conversation-inference-profile-route.test.ts +2 -24
- package/src/__tests__/conversation-init.benchmark.test.ts +3 -56
- package/src/__tests__/conversation-key-store-disk-view.test.ts +1 -33
- package/src/__tests__/conversation-lifecycle.test.ts +63 -103
- package/src/__tests__/conversation-list-source.test.ts +0 -18
- package/src/__tests__/conversation-load-history-repair.test.ts +39 -91
- package/src/__tests__/conversation-load-history-stripped.test.ts +12 -57
- package/src/__tests__/conversation-loop-db-lock-resilience.test.ts +10 -20
- package/src/__tests__/conversation-message-id-uuidv7.test.ts +1 -18
- package/src/__tests__/conversation-pairing.test.ts +0 -7
- package/src/__tests__/conversation-process-app-control-preactivation.test.ts +0 -9
- package/src/__tests__/conversation-process-callsite.test.ts +15 -97
- package/src/__tests__/conversation-provider-retry-repair.test.ts +3 -84
- package/src/__tests__/conversation-queue.test.ts +12 -108
- package/src/__tests__/conversation-routes-disk-view.test.ts +8 -60
- package/src/__tests__/conversation-routes-enabled-plugins.test.ts +5 -63
- package/src/__tests__/conversation-routes-guardian-reply.test.ts +13 -22
- package/src/__tests__/conversation-routes-hidden-queue.test.ts +5 -64
- package/src/__tests__/conversation-routes-slash-commands.test.ts +19 -87
- package/src/__tests__/conversation-runtime-assembly.test.ts +34 -37
- package/src/__tests__/conversation-seed-composer.test.ts +3 -3
- package/src/__tests__/conversation-skill-tools.test.ts +19 -32
- package/src/__tests__/conversation-slash-queue.test.ts +3 -90
- package/src/__tests__/conversation-slash-unknown.test.ts +0 -87
- package/src/__tests__/conversation-speed-override.test.ts +19 -94
- package/src/__tests__/conversation-starter-routes.test.ts +0 -7
- package/src/__tests__/conversation-store.test.ts +72 -66
- package/src/__tests__/conversation-summarize-route.test.ts +0 -11
- package/src/__tests__/conversation-summarize-up-to.test.ts +4 -89
- package/src/__tests__/conversation-surfaces-action-delivery.test.ts +131 -0
- package/src/__tests__/conversation-surfaces-activation-emit.test.ts +36 -36
- package/src/__tests__/conversation-surfaces-data-persist.test.ts +4 -2
- package/src/__tests__/conversation-title-service.test.ts +0 -7
- package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +0 -1
- package/src/__tests__/conversation-tool-setup-attribution.test.ts +37 -60
- package/src/__tests__/conversation-tool-setup-tools-disabled.test.ts +17 -17
- package/src/__tests__/conversation-unread-route.test.ts +0 -7
- package/src/__tests__/conversation-usage.test.ts +21 -71
- package/src/__tests__/conversation-wait-for-idle.test.ts +0 -82
- package/src/__tests__/conversation-workspace-cache-state.test.ts +0 -45
- package/src/__tests__/conversation-workspace-injection.test.ts +0 -88
- package/src/__tests__/conversation-workspace-tool-tracking.test.ts +0 -88
- package/src/__tests__/conversations-defer-cli.test.ts +3 -0
- package/src/__tests__/conversations-import-system-filter.test.ts +0 -17
- package/src/__tests__/credential-broker-browser-fill.test.ts +0 -9
- package/src/__tests__/credential-broker-server-use.test.ts +0 -9
- package/src/__tests__/credential-health-service.test.ts +14 -31
- package/src/__tests__/credential-metadata-store.test.ts +0 -8
- package/src/__tests__/credential-prompt-route.test.ts +9 -15
- package/src/__tests__/credential-resolve.test.ts +0 -8
- package/src/__tests__/credential-routes.test.ts +250 -0
- package/src/__tests__/credential-security-invariants.test.ts +4 -8
- package/src/__tests__/credential-token-resolver.test.ts +0 -9
- package/src/__tests__/cross-provider-web-search.test.ts +12 -18
- package/src/__tests__/daemon-assistant-events.test.ts +1 -9
- package/src/__tests__/daemon-credential-client.test.ts +0 -7
- package/src/__tests__/db-conversation-fork-lineage-migration.test.ts +1 -8
- package/src/__tests__/db-conversation-inference-profile-migration.test.ts +1 -7
- package/src/__tests__/db-llm-request-log-provider-migration.test.ts +0 -8
- package/src/__tests__/db-readiness-http-gate.test.ts +0 -18
- package/src/__tests__/db-rename-inference-profile-snake-case-migration.test.ts +1 -7
- package/src/__tests__/db-test-helpers.ts +10 -3
- package/src/__tests__/delete-managed-skill-tool.test.ts +0 -7
- package/src/__tests__/delete-propagation.test.ts +0 -7
- package/src/__tests__/deterministic-verification-control-plane.test.ts +0 -7
- package/src/__tests__/device-id.test.ts +1 -9
- package/src/__tests__/disk-pressure-guard.test.ts +0 -4
- package/src/__tests__/disk-pressure-routes.test.ts +0 -4
- package/src/__tests__/disk-pressure-tools.test.ts +4 -42
- package/src/__tests__/dm-backfill.test.ts +0 -7
- package/src/__tests__/dm-persistence.test.ts +0 -5
- package/src/__tests__/docker-signing-key-bootstrap.test.ts +1 -8
- package/src/__tests__/drain-requeue-on-contention.test.ts +1 -8
- package/src/__tests__/dynamic-skill-background-guard.test.ts +0 -43
- package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +2 -27
- package/src/__tests__/edit-propagation.test.ts +1 -8
- package/src/__tests__/email-invite-adapter.test.ts +8 -33
- package/src/__tests__/embedding-managed-proxy-selection.test.ts +0 -8
- package/src/__tests__/emit-event-signal.test.ts +1 -1
- package/src/__tests__/emit-signal-routing-intent.test.ts +0 -7
- package/src/__tests__/empty-state-greeting-cache.test.ts +9 -9
- package/src/__tests__/encrypted-store.test.ts +0 -9
- package/src/__tests__/ensure-conversation-exists.test.ts +1 -18
- package/src/__tests__/events-client-registration.test.ts +1 -19
- package/src/__tests__/events-dev-bypass-actor.test.ts +0 -7
- package/src/__tests__/events-tail-route.test.ts +1 -8
- package/src/__tests__/file-list-tool.test.ts +22 -15
- package/src/__tests__/followup-tools.test.ts +1 -16
- package/src/__tests__/gateway-only-enforcement.test.ts +3 -44
- package/src/__tests__/get-skill-detail-audit.test.ts +0 -7
- package/src/__tests__/google-calendar-watcher.test.ts +175 -0
- package/src/__tests__/guardian-action-sweep.test.ts +0 -7
- package/src/__tests__/guardian-binding-drift-heal.test.ts +0 -7
- package/src/__tests__/guardian-card-withdrawal.test.ts +60 -64
- package/src/__tests__/guardian-decision-primitive-canonical.test.ts +172 -135
- package/src/__tests__/guardian-dispatch.test.ts +58 -157
- package/src/__tests__/guardian-expiry-notifier.test.ts +56 -48
- package/src/__tests__/guardian-gateway-sim.ts +626 -0
- package/src/__tests__/guardian-outbound-http.test.ts +0 -7
- package/src/__tests__/guardian-routing-invariants.test.ts +229 -220
- package/src/__tests__/guardian-routing-state.test.ts +2 -9
- package/src/__tests__/guardian-verification-voice-binding.test.ts +0 -17
- package/src/__tests__/guardian-wait-controller.test.ts +44 -43
- package/src/__tests__/handlers-skills-memory-v2-reseed.test.ts +26 -91
- package/src/__tests__/handlers-user-message-approval-consumption.test.ts +18 -103
- package/src/__tests__/headless-browser-interactions.test.ts +0 -7
- package/src/__tests__/headless-browser-mode.test.ts +0 -9
- package/src/__tests__/headless-browser-navigate.test.ts +0 -7
- package/src/__tests__/headless-browser-read-tools.test.ts +0 -7
- package/src/__tests__/headless-browser-snapshot.test.ts +0 -7
- package/src/__tests__/heartbeat-disk-pressure.test.ts +9 -38
- package/src/__tests__/heartbeat-service.test.ts +83 -106
- package/src/__tests__/helpers/channel-test-adapter.ts +10 -0
- package/src/__tests__/helpers/gateway-guardian-requests-store-bridge.ts +155 -0
- package/src/__tests__/helpers/set-config.ts +40 -0
- package/src/__tests__/host-bash-proxy.test.ts +18 -34
- package/src/__tests__/host-browser-proxy.test.ts +15 -20
- package/src/__tests__/host-shell-tool.test.ts +2 -34
- package/src/__tests__/http-conversation-lineage.test.ts +3 -19
- package/src/__tests__/http-user-message-parity.test.ts +3 -95
- package/src/__tests__/identity-routes.test.ts +1 -9
- package/src/__tests__/image-recovery-hook.test.ts +36 -0
- package/src/__tests__/inbound-admitted-introduction-nudge.test.ts +6 -12
- package/src/__tests__/inbound-invite-redemption.test.ts +0 -7
- package/src/__tests__/inbound-slack-persistence.test.ts +0 -5
- package/src/__tests__/inbound-trust-verdict.test.ts +2 -8
- package/src/__tests__/inference-no-mode-boot-e2e.test.ts +52 -8
- package/src/__tests__/inference-profile-reaper.test.ts +1 -7
- package/src/__tests__/inference-profile-session-handler.test.ts +4 -30
- package/src/__tests__/inference-profile-session-ipc.test.ts +3 -20
- package/src/__tests__/ingress-url-consistency.test.ts +1 -23
- package/src/__tests__/injector-background-turn.test.ts +10 -24
- package/src/__tests__/injector-chain.test.ts +4 -13
- package/src/__tests__/injector-pkb-v2-silenced.test.ts +19 -17
- package/src/__tests__/inline-command-runner.test.ts +0 -34
- package/src/__tests__/inline-skill-load-permissions.test.ts +1 -30
- package/src/__tests__/install-skill-routing.test.ts +7 -6
- package/src/__tests__/integration-status.test.ts +25 -17
- package/src/__tests__/intent-routing.test.ts +1 -50
- package/src/__tests__/internal-telemetry-routes.test.ts +41 -22
- package/src/__tests__/interrupted-turn-reconciler.test.ts +1 -8
- package/src/__tests__/introduction-card-resolver.test.ts +108 -117
- package/src/__tests__/invite-redeemed-ipc.test.ts +0 -7
- package/src/__tests__/invite-routes-http.test.ts +12 -23
- package/src/__tests__/jobs-store-has-pending-job.test.ts +59 -0
- package/src/__tests__/jobs-store-qdrant-breaker.test.ts +1 -14
- package/src/__tests__/jobs-store-skill-card-upsert.test.ts +191 -0
- package/src/__tests__/jobs-store-upsert-debounced.test.ts +1 -14
- package/src/__tests__/lexical-index-dual-write.test.ts +0 -6
- package/src/__tests__/list-all-apps.test.ts +200 -0
- package/src/__tests__/list-messages-attachments.test.ts +11 -20
- package/src/__tests__/list-messages-background-tool-completion.test.ts +6 -18
- package/src/__tests__/list-messages-client-message-id.test.ts +4 -16
- package/src/__tests__/list-messages-hidden-metadata.test.ts +6 -18
- package/src/__tests__/list-messages-page-latest.test.ts +3 -19
- package/src/__tests__/list-messages-queued.test.ts +1 -18
- package/src/__tests__/list-messages-tool-merge.test.ts +7 -18
- package/src/__tests__/llm-context-resolution.test.ts +32 -56
- package/src/__tests__/llm-context-route-provider.test.ts +1 -8
- package/src/__tests__/llm-request-log-agent-loop-exit-reason.test.ts +1 -19
- package/src/__tests__/llm-request-log-call-site.test.ts +1 -19
- package/src/__tests__/llm-request-log-clickhouse-routing.test.ts +114 -0
- package/src/__tests__/llm-request-log-clickhouse-writer-guards.test.ts +108 -0
- package/src/__tests__/llm-request-log-disabled-writes.test.ts +11 -30
- package/src/__tests__/llm-request-log-sink-clickhouse.test.ts +205 -0
- package/src/__tests__/llm-request-log-source-clickhouse.test.ts +1 -22
- package/src/__tests__/llm-request-log-source-factory.test.ts +23 -69
- package/src/__tests__/llm-request-log-turn-query.test.ts +1 -19
- package/src/__tests__/llm-resolver-override-or-default.test.ts +3 -52
- package/src/__tests__/llm-resolver.test.ts +342 -602
- package/src/__tests__/llm-schema.test.ts +79 -37
- package/src/__tests__/llm-usage-store.test.ts +1 -7
- package/src/__tests__/log-export-routes.test.ts +0 -7
- package/src/__tests__/log-export-workspace.test.ts +0 -7
- package/src/__tests__/managed-profile-guard.test.ts +125 -137
- package/src/__tests__/managed-skill-lifecycle.test.ts +6 -50
- package/src/__tests__/managed-speech-defaults.test.ts +138 -0
- package/src/__tests__/managed-store.test.ts +1 -16
- package/src/__tests__/mcp-auth-routes.test.ts +15 -26
- package/src/__tests__/mcp-config-secret-boundary.test.ts +49 -91
- package/src/__tests__/mcp-health-check.test.ts +14 -20
- package/src/__tests__/media-generate-image.test.ts +25 -32
- package/src/__tests__/media-stream-output.test.ts +0 -9
- package/src/__tests__/media-stream-server-integration.test.ts +19 -27
- package/src/__tests__/media-stream-stt-session.test.ts +16 -29
- package/src/__tests__/memory-jobs-worker-backoff.test.ts +6 -19
- package/src/__tests__/memory-jobs-worker-cleanup-cadence.test.ts +33 -12
- package/src/__tests__/memory-jobs-worker-lanes.test.ts +10 -29
- package/src/__tests__/memory-recall-log-store.test.ts +1 -19
- package/src/__tests__/memory-retrieval-hook.test.ts +0 -3
- package/src/__tests__/memory-upsert-concurrency.test.ts +12 -35
- package/src/__tests__/memory-v2-static-injector.test.ts +5 -17
- package/src/__tests__/messages-after-tiebreaker.test.ts +1 -7
- package/src/__tests__/messaging-send-tool.test.ts +67 -7
- package/src/__tests__/migration-cross-version-compatibility.test.ts +0 -18
- package/src/__tests__/migration-export-http.test.ts +10 -23
- package/src/__tests__/migration-export-streaming.test.ts +0 -18
- package/src/__tests__/migration-export-to-gcs.test.ts +0 -19
- package/src/__tests__/migration-import-commit-http.test.ts +11 -23
- package/src/__tests__/migration-import-from-gcs.test.ts +0 -19
- package/src/__tests__/migration-import-from-url.test.ts +0 -19
- package/src/__tests__/migration-import-preflight-http.test.ts +10 -22
- package/src/__tests__/migration-jobs-status.test.ts +8 -23
- package/src/__tests__/migration-validate-http.test.ts +2 -19
- package/src/__tests__/mtime-cache.test.ts +116 -2
- package/src/__tests__/native-web-search.test.ts +0 -21
- package/src/__tests__/non-member-access-request.test.ts +102 -93
- package/src/__tests__/notification-broadcaster.test.ts +32 -21
- package/src/__tests__/notification-candidate-guardian-context.test.ts +36 -34
- package/src/__tests__/notification-decision-fallback.test.ts +0 -7
- package/src/__tests__/notification-decision-identity.test.ts +0 -7
- package/src/__tests__/notification-decision-recipient-context.test.ts +0 -7
- package/src/__tests__/notification-deep-link.test.ts +0 -7
- package/src/__tests__/notification-guardian-path.test.ts +48 -93
- package/src/__tests__/notification-platform-adapter.test.ts +0 -7
- package/src/__tests__/notification-routing-intent.test.ts +1 -9
- package/src/__tests__/notification-schedule-notify-dedup.test.ts +1 -9
- package/src/__tests__/notification-telegram-adapter.test.ts +0 -7
- package/src/__tests__/oauth-cli.test.ts +0 -19
- package/src/__tests__/oauth-commands-routes.test.ts +28 -43
- package/src/__tests__/oauth-connect-orchestrator.test.ts +17 -26
- package/src/__tests__/oauth-connect-routes.test.ts +19 -18
- package/src/__tests__/oauth-provider-profiles.test.ts +3 -9
- package/src/__tests__/oauth-provider-visibility.test.ts +0 -7
- package/src/__tests__/oauth-store.test.ts +0 -7
- package/src/__tests__/oauth2-gateway-transport.test.ts +41 -43
- package/src/__tests__/openai-responses-prompt-cache.test.ts +355 -0
- package/src/__tests__/openai-responses-provider.test.ts +98 -9
- package/src/__tests__/openrouter-responses-transport.test.ts +170 -0
- package/src/__tests__/outbound-slack-persistence.test.ts +13 -46
- package/src/__tests__/outlook-email-watcher.test.ts +0 -9
- package/src/__tests__/outlook-messaging-provider.test.ts +71 -0
- package/src/__tests__/path-policy.test.ts +0 -7
- package/src/__tests__/persist-media-references.test.ts +1 -6
- package/src/__tests__/persist-unsendable-image-downscale.test.ts +6 -3
- package/src/__tests__/persist-unsendable-image.test.ts +39 -7
- package/src/__tests__/persistence-secret-redaction.test.ts +7 -28
- package/src/__tests__/platform-bash-auto-approve.test.ts +0 -46
- package/src/__tests__/platform-callback-registration.test.ts +10 -9
- package/src/__tests__/platform-proxy-context.test.ts +0 -8
- package/src/__tests__/playbook-execution.test.ts +0 -14
- package/src/__tests__/playbook-tools.test.ts +0 -14
- package/src/__tests__/plugin-api-model-profiles.test.ts +12 -35
- package/src/__tests__/plugin-app-serve-routes.test.ts +315 -0
- package/src/__tests__/plugin-import-boundary-guard.test.ts +5 -3
- package/src/__tests__/plugin-import-boundary-reverse-guard.test.ts +0 -2
- package/src/__tests__/plugin-types.test.ts +0 -7
- package/src/__tests__/prechat-onboarding-contract.test.ts +0 -27
- package/src/__tests__/pricing.test.ts +45 -0
- package/src/__tests__/process-message-background-slack.test.ts +7 -21
- package/src/__tests__/process-message-display-content.test.ts +37 -6
- package/src/__tests__/processing-flag-persist-failure.test.ts +0 -44
- package/src/__tests__/provider-commit-message-generator.test.ts +57 -63
- package/src/__tests__/provider-error-scenarios.test.ts +5 -6
- package/src/__tests__/provider-platform-proxy-integration.test.ts +21 -57
- package/src/__tests__/provider-registry-ollama.test.ts +8 -18
- package/src/__tests__/provider-send-message-override-profile.test.ts +28 -39
- package/src/__tests__/provider-streaming.benchmark.test.ts +1 -6
- package/src/__tests__/provider-usage-tracking.test.ts +13 -35
- package/src/__tests__/proxy-approval-callback.test.ts +0 -16
- package/src/__tests__/prune-old-conversations-job.test.ts +29 -21
- package/src/__tests__/prune-old-tool-invocations-job.test.ts +1 -9
- package/src/__tests__/public-ingress-urls.test.ts +1 -24
- package/src/__tests__/published-app-updater.test.ts +22 -16
- package/src/__tests__/qdrant-collection-migration.test.ts +0 -7
- package/src/__tests__/qdrant-manager.test.ts +0 -8
- package/src/__tests__/ratelimit.test.ts +1 -8
- package/src/__tests__/reaction-persistence.test.ts +18 -24
- package/src/__tests__/rebuild-index-graph-nodes.test.ts +0 -14
- package/src/__tests__/recording-handler.test.ts +0 -27
- package/src/__tests__/request-secret-standalone-channel-gate.test.ts +4 -16
- package/src/__tests__/require-fresh-approval.test.ts +0 -45
- package/src/__tests__/retry-openrouter-only-normalization.test.ts +24 -36
- package/src/__tests__/retry-prompt-cache-key.test.ts +123 -0
- package/src/__tests__/retry-thinking-adaptive-only.test.ts +45 -61
- package/src/__tests__/retry-thinking-tool-choice.test.ts +60 -80
- package/src/__tests__/retry-verbosity-normalization.test.ts +27 -37
- package/src/__tests__/reveal-success-registry.test.ts +123 -0
- package/src/__tests__/run-conversation-turn-persistence.test.ts +113 -0
- package/src/__tests__/{scheduler-worker-standdown.test.ts → run-due-schedules.test.ts} +5 -77
- package/src/__tests__/runtime-attachment-metadata.test.ts +5 -18
- package/src/__tests__/runtime-events-sse-bilingual.test.ts +1 -19
- package/src/__tests__/runtime-events-sse-parity.test.ts +1 -20
- package/src/__tests__/runtime-events-sse-reconnect.test.ts +1 -19
- package/src/__tests__/runtime-events-sse.test.ts +1 -20
- package/src/__tests__/scaffold-managed-skill-tool.test.ts +306 -5
- package/src/__tests__/schedule-retry.test.ts +22 -23
- package/src/__tests__/schedule-routes-workflow-validation.test.ts +18 -42
- package/src/__tests__/schedule-routes.test.ts +2 -150
- package/src/__tests__/schedule-store.test.ts +0 -8
- package/src/__tests__/schedule-tools.test.ts +1 -16
- package/src/__tests__/scheduler-disk-pressure.test.ts +0 -26
- package/src/__tests__/scheduler-recurrence.test.ts +43 -76
- package/src/__tests__/scheduler-reuse-conversation.test.ts +19 -35
- package/src/__tests__/scheduler-wake.test.ts +8 -24
- package/src/__tests__/scoped-approval-grants.test.ts +1 -9
- package/src/__tests__/scoped-grant-security-matrix.test.ts +1 -9
- package/src/__tests__/search-skills-unified.test.ts +0 -6
- package/src/__tests__/secret-allowlist.test.ts +1 -8
- package/src/__tests__/secret-fixtures.ts +9 -0
- package/src/__tests__/secret-ingress-channel.test.ts +4 -32
- package/src/__tests__/secret-ingress-cli.test.ts +3 -37
- package/src/__tests__/secret-ingress-http.test.ts +15 -78
- package/src/__tests__/secret-ingress.test.ts +14 -31
- package/src/__tests__/secret-prompt-log-hygiene.test.ts +4 -10
- package/src/__tests__/secret-prompter-channel-fallback.test.ts +3 -9
- package/src/__tests__/secret-routes-platform-proxy.test.ts +3 -40
- package/src/__tests__/secure-keys-managed-failover.test.ts +0 -7
- package/src/__tests__/secure-keys.test.ts +0 -9
- package/src/__tests__/send-endpoint-busy.test.ts +47 -91
- package/src/__tests__/sequence-store.test.ts +0 -9
- package/src/__tests__/server-history-render.test.ts +29 -8
- package/src/__tests__/set-permission-mode.test.ts +8 -16
- package/src/__tests__/settings-routes.test.ts +0 -7
- package/src/__tests__/shell-credential-ref.test.ts +0 -18
- package/src/__tests__/shell-observability.test.ts +0 -21
- package/src/__tests__/shell-tool-proxy-mode.test.ts +0 -34
- package/src/__tests__/skill-load-feature-flag.test.ts +0 -27
- package/src/__tests__/skill-load-inline-command.test.ts +0 -25
- package/src/__tests__/skill-load-inline-includes.test.ts +0 -25
- package/src/__tests__/skill-load-plugin-scope.test.ts +0 -24
- package/src/__tests__/skill-projection-feature-flag.test.ts +14 -33
- package/src/__tests__/skill-projection.benchmark.test.ts +14 -19
- package/src/__tests__/skill-script-runner-sandbox.test.ts +1 -39
- package/src/__tests__/skills-file-content-endpoint.test.ts +0 -7
- package/src/__tests__/skills-files-catalog-fallback.test.ts +0 -14
- package/src/__tests__/slack-channel-config.test.ts +24 -79
- package/src/__tests__/slack-inbound-verification.test.ts +5 -13
- package/src/__tests__/{slack-reaction-canonical-approval.test.ts → slack-reaction-guardian-approval.test.ts} +18 -74
- package/src/__tests__/slack-share-routes.test.ts +0 -1
- package/src/__tests__/slim-skill-category.test.ts +0 -14
- package/src/__tests__/stale-approval-dedup.test.ts +0 -7
- package/src/__tests__/subagent-call-site-routing.test.ts +72 -111
- package/src/__tests__/subagent-detail.test.ts +1 -1
- package/src/__tests__/subagent-fork-prompt-role.test.ts +0 -12
- package/src/__tests__/subagent-notify-parent.test.ts +4 -6
- package/src/__tests__/subagent-spawn-and-await.test.ts +0 -18
- package/src/__tests__/subagent-tool-filtering.test.ts +21 -31
- package/src/__tests__/subagent-tool-gate-mode.test.ts +6 -32
- package/src/__tests__/subagent-tools.test.ts +92 -67
- package/src/__tests__/suggestion-routes.test.ts +28 -42
- package/src/__tests__/summarize-boundary.test.ts +2 -1
- package/src/__tests__/system-prompt-ask-mode.test.ts +0 -25
- package/src/__tests__/system-prompt.test.ts +0 -33
- package/src/__tests__/task-memory-cleanup.test.ts +18 -192
- package/src/__tests__/telegram-bot-username-resolution.test.ts +24 -48
- package/src/__tests__/telegram-config.test.ts +0 -8
- package/src/__tests__/telemetry-routes.test.ts +100 -0
- package/src/__tests__/telephony-credential-preflight.test.ts +7 -11
- package/src/__tests__/telephony-tts-guard.test.ts +38 -21
- package/src/__tests__/terminal-tools.test.ts +0 -21
- package/src/__tests__/thread-backfill.test.ts +0 -7
- package/src/__tests__/title-generate-hook.test.ts +11 -10
- package/src/__tests__/token-manager.test.ts +0 -9
- package/src/__tests__/tool-approval-handler.test.ts +0 -8
- package/src/__tests__/tool-execution-abort-cleanup.test.ts +5 -33
- package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +3 -23
- package/src/__tests__/tool-executor-lifecycle-events.test.ts +0 -48
- package/src/__tests__/tool-executor.test.ts +3 -49
- package/src/__tests__/tool-grant-request-escalation.test.ts +51 -153
- package/src/__tests__/tool-preview-lifecycle.test.ts +2 -29
- package/src/__tests__/tool-profiler.test.ts +72 -1
- package/src/__tests__/tool-result-metadata-plumbing.test.ts +0 -29
- package/src/__tests__/tool-side-effects-slack-dm.test.ts +0 -1
- package/src/__tests__/tool-start-timestamp.test.ts +4 -31
- package/src/__tests__/tools-get-route.test.ts +2 -1
- package/src/__tests__/trust-context-guards.test.ts +19 -7
- package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +75 -79
- package/src/__tests__/trusted-contact-lifecycle-notifications.test.ts +14 -18
- package/src/__tests__/trusted-contact-multichannel.test.ts +0 -7
- package/src/__tests__/trusted-contact-verification.test.ts +1 -9
- package/src/__tests__/turn-boundary-resolution.test.ts +1 -19
- package/src/__tests__/turn-events-store.test.ts +1 -7
- package/src/__tests__/turn-outcome-read.test.ts +1 -7
- package/src/__tests__/twilio-config.test.ts +11 -28
- package/src/__tests__/twilio-provider.test.ts +2 -13
- package/src/__tests__/twilio-routes-twiml.test.ts +1 -8
- package/src/__tests__/twilio-routes.test.ts +52 -103
- package/src/__tests__/twilio-validation.test.ts +5 -22
- package/src/__tests__/ui-file-upload-surface.test.ts +6 -9
- package/src/__tests__/usage-attribution.test.ts +25 -56
- package/src/__tests__/usage-cache-backfill-migration.test.ts +8 -15
- package/src/__tests__/usage-routes.test.ts +1 -8
- package/src/__tests__/vbundle-pax-and-symlink.test.ts +0 -18
- package/src/__tests__/vellum-self-knowledge-inline-command.test.ts +0 -24
- package/src/__tests__/vercel-config.test.ts +0 -7
- package/src/__tests__/verification-control-plane-policy.test.ts +0 -45
- package/src/__tests__/voice-approval-resolver-relay.test.ts +106 -75
- package/src/__tests__/voice-config-update.test.ts +114 -21
- package/src/__tests__/voice-ingress-preflight.test.ts +16 -15
- package/src/__tests__/voice-scoped-grant-consumer.test.ts +0 -30
- package/src/__tests__/voice-session-bridge-processing-wait.test.ts +1 -17
- package/src/__tests__/voice-session-bridge.test.ts +14 -46
- package/src/__tests__/web-search-backend-failure.test.ts +0 -21
- package/src/__tests__/whatsapp-invite-adapter.test.ts +11 -30
- package/src/__tests__/workspace-git-service.test.ts +801 -2
- package/src/__tests__/workspace-migration-009-backfill-conversation-disk-view.test.ts +1 -26
- package/src/__tests__/workspace-migration-013-repair-conversation-disk-view.test.ts +1 -26
- package/src/__tests__/workspace-migration-028-recover-conversations-from-disk-view.test.ts +1 -26
- package/src/a2a/__tests__/e2e-a2a-channel.test.ts +1 -9
- package/src/a2a/__tests__/task-store.test.ts +0 -9
- package/src/acp/__tests__/session-manager-persistence.test.ts +1 -8
- package/src/acp/__tests__/session-manager-resume.test.ts +0 -7
- package/src/acp/__tests__/session-manager-startup.test.ts +1 -8
- package/src/agent/image-optimize.ts +80 -0
- package/src/agent/loop.ts +20 -5
- package/src/api/events/open-conversation.test.ts +64 -0
- package/src/api/events/open-conversation.ts +33 -0
- package/src/api/index.ts +6 -0
- package/src/approvals/AGENTS.md +1 -1
- package/src/approvals/guardian-card-withdrawal.ts +26 -19
- package/src/approvals/guardian-decision-primitive.ts +172 -102
- package/src/approvals/guardian-expiry-notifier.ts +20 -19
- package/src/approvals/guardian-request-resolvers.ts +305 -275
- package/src/apps/app-store.ts +320 -15
- package/src/background-wake/background-wake-routes.test.ts +2 -3
- package/src/background-wake/next-wake.test.ts +23 -20
- package/src/background-wake/wake-intent-hooks.test.ts +0 -10
- package/src/bundler/app-bundler.ts +34 -48
- package/src/bundler/app-compiler.ts +39 -4
- package/src/bundler/bundle-scanner.ts +13 -0
- package/src/bundler/manifest.ts +1 -1
- package/src/calls/__tests__/call-setup-router.test.ts +84 -88
- package/src/calls/__tests__/fish-audio-client.test.ts +6 -11
- package/src/calls/__tests__/voice-control-protocol.test.ts +47 -0
- package/src/calls/__tests__/voice-session-bridge.test.ts +151 -16
- package/src/calls/__tests__/voice-triage-escalate.test.ts +128 -0
- package/src/calls/access-request-wait.ts +35 -35
- package/src/calls/call-controller.ts +48 -45
- package/src/calls/call-setup-flow.ts +2 -2
- package/src/calls/call-setup-router.ts +1 -24
- package/src/calls/guardian-dispatch.ts +47 -30
- package/src/calls/guardian-wait-controller.ts +31 -18
- package/src/calls/media-turn-detector.ts +11 -1
- package/src/calls/telephony-credential-preflight.ts +19 -1
- package/src/calls/telephony-tts-capability.ts +18 -1
- package/src/calls/voice-control-protocol.ts +12 -0
- package/src/calls/voice-session-bridge.ts +110 -15
- package/src/calls/voice-triage-escalate.ts +136 -0
- package/src/channels/__tests__/gateway-guardian-requests.test.ts +669 -0
- package/src/channels/gateway-guardian-requests.ts +410 -0
- package/src/channels/types.ts +10 -5
- package/src/cli/AGENTS.md +77 -51
- package/src/cli/__tests__/cli-command-help-coverage.test.ts +29 -0
- package/src/cli/commands/__tests__/conversations-import.test.ts +23 -24
- package/src/cli/commands/__tests__/gateway.test.ts +55 -0
- package/src/cli/commands/__tests__/inference-models.test.ts +3 -0
- package/src/cli/commands/__tests__/inference-profiles.test.ts +3 -0
- package/src/cli/commands/__tests__/inference-providers.test.ts +3 -0
- package/src/cli/commands/__tests__/inference-send.test.ts +0 -10
- package/src/cli/commands/__tests__/inference-session.test.ts +4 -11
- package/src/cli/commands/__tests__/ui.test.ts +2 -2
- package/src/cli/commands/apps.help.ts +46 -0
- package/src/cli/commands/apps.ts +51 -0
- package/src/cli/commands/avatar.help.ts +229 -0
- package/src/cli/commands/avatar.ts +58 -250
- package/src/cli/commands/backup.help.ts +183 -0
- package/src/cli/commands/backup.ts +107 -276
- package/src/cli/commands/bash.help.ts +38 -0
- package/src/cli/commands/bash.ts +51 -75
- package/src/cli/commands/browser.help.ts +193 -0
- package/src/cli/commands/browser.ts +234 -347
- package/src/cli/commands/cache.help.ts +123 -0
- package/src/cli/commands/cache.ts +72 -166
- package/src/cli/commands/changelog.help.ts +59 -0
- package/src/cli/commands/changelog.ts +29 -61
- package/src/cli/commands/channel-verification-sessions.help.ts +178 -0
- package/src/cli/commands/channel-verification-sessions.ts +71 -226
- package/src/cli/commands/channels/index.help.ts +67 -0
- package/src/cli/commands/channels/index.ts +55 -105
- package/src/cli/commands/clients.help.ts +72 -0
- package/src/cli/commands/clients.ts +100 -153
- package/src/cli/commands/completions.help.ts +33 -0
- package/src/cli/commands/completions.ts +43 -66
- package/src/cli/commands/config.help.ts +126 -0
- package/src/cli/commands/config.ts +59 -174
- package/src/cli/commands/contacts.help.ts +327 -0
- package/src/cli/commands/contacts.ts +318 -603
- package/src/cli/commands/conversations-defer.ts +141 -202
- package/src/cli/commands/conversations-import.ts +80 -113
- package/src/cli/commands/conversations.help.ts +364 -0
- package/src/cli/commands/conversations.ts +143 -328
- package/src/cli/commands/credentials.help.ts +290 -0
- package/src/cli/commands/credentials.ts +271 -487
- package/src/cli/commands/db/index.help.ts +27 -0
- package/src/cli/commands/db/index.ts +7 -3
- package/src/cli/commands/db/refresh.ts +131 -0
- package/src/cli/commands/db/repair-step-conversation-backfill.ts +19 -9
- package/src/cli/commands/db/repair.ts +41 -43
- package/src/cli/commands/db/status.ts +79 -83
- package/src/cli/commands/domain.help.ts +34 -0
- package/src/cli/commands/domain.ts +10 -15
- package/src/cli/commands/email.help.ts +176 -0
- package/src/cli/commands/email.ts +323 -412
- package/src/cli/commands/gateway.help.ts +115 -0
- package/src/cli/commands/gateway.ts +105 -123
- package/src/cli/commands/image-generation.help.ts +72 -0
- package/src/cli/commands/image-generation.ts +13 -62
- package/src/cli/commands/inference-callsites.ts +34 -49
- package/src/cli/commands/inference-models.ts +6 -21
- package/src/cli/commands/inference-profiles.ts +95 -156
- package/src/cli/commands/inference-providers-default.ts +27 -42
- package/src/cli/commands/inference-providers.ts +25 -87
- package/src/cli/commands/inference-session.ts +189 -249
- package/src/cli/commands/inference.help.ts +485 -0
- package/src/cli/commands/inference.ts +104 -165
- package/src/cli/commands/keys.help.ts +46 -0
- package/src/cli/commands/keys.ts +37 -71
- package/src/cli/commands/mcp.help.ts +167 -0
- package/src/cli/commands/mcp.ts +123 -267
- package/src/cli/commands/memory/__tests__/memory-items.test.ts +4 -0
- package/src/cli/commands/memory/__tests__/memory-v2.test.ts +4 -0
- package/src/cli/commands/memory/__tests__/memory-v3.test.ts +4 -0
- package/src/cli/commands/memory/__tests__/worker.test.ts +12 -5
- package/src/cli/commands/memory/index.help.ts +814 -0
- package/src/cli/commands/memory/index.ts +5 -27
- package/src/cli/commands/memory/items.ts +257 -404
- package/src/cli/commands/memory/memory-retrospective.ts +31 -71
- package/src/cli/commands/memory/memory-v2-compare-render.ts +2 -2
- package/src/cli/commands/memory/memory-v2.ts +435 -646
- package/src/cli/commands/memory/memory-v3.ts +158 -283
- package/src/cli/commands/memory/nodes.ts +154 -249
- package/src/cli/commands/memory/worker.ts +83 -128
- package/src/cli/commands/monitoring.help.ts +57 -0
- package/src/cli/commands/monitoring.ts +17 -41
- package/src/cli/commands/notifications.help.ts +293 -0
- package/src/cli/commands/notifications.ts +420 -657
- package/src/cli/commands/oauth/apps.ts +182 -315
- package/src/cli/commands/oauth/connect.ts +4 -35
- package/src/cli/commands/oauth/disconnect.ts +44 -71
- package/src/cli/commands/oauth/index.help.ts +855 -0
- package/src/cli/commands/oauth/index.ts +5 -31
- package/src/cli/commands/oauth/mode.ts +5 -28
- package/src/cli/commands/oauth/ping.ts +44 -66
- package/src/cli/commands/oauth/providers.ts +413 -772
- package/src/cli/commands/oauth/request.ts +6 -35
- package/src/cli/commands/oauth/shared.ts +1 -1
- package/src/cli/commands/oauth/status.test.ts +6 -6
- package/src/cli/commands/oauth/status.ts +45 -62
- package/src/cli/commands/oauth/token.ts +32 -71
- package/src/cli/commands/pending.help.ts +26 -0
- package/src/cli/commands/pending.ts +77 -78
- package/src/cli/commands/platform/__tests__/connect.test.ts +3 -1
- package/src/cli/commands/platform/__tests__/disconnect.test.ts +4 -4
- package/src/cli/commands/platform/__tests__/status.test.ts +0 -90
- package/src/cli/commands/platform/connect.ts +10 -21
- package/src/cli/commands/platform/disconnect.ts +11 -26
- package/src/cli/commands/platform/index.help.ts +187 -0
- package/src/cli/commands/platform/index.ts +22 -169
- package/src/cli/commands/plugins.help.ts +247 -0
- package/src/cli/commands/plugins.ts +356 -437
- package/src/cli/commands/ps.help.ts +26 -0
- package/src/cli/commands/ps.ts +5 -17
- package/src/cli/commands/routes.help.ts +72 -0
- package/src/cli/commands/routes.ts +78 -125
- package/src/cli/commands/schedules-worker.ts +70 -138
- package/src/cli/commands/schedules.help.ts +514 -0
- package/src/cli/commands/schedules.ts +494 -871
- package/src/cli/commands/sequence.help.ts +183 -0
- package/src/cli/commands/sequence.ts +181 -343
- package/src/cli/commands/skills.help.ts +159 -0
- package/src/cli/commands/skills.ts +230 -368
- package/src/cli/commands/status.help.ts +32 -0
- package/src/cli/commands/status.ts +38 -3
- package/src/cli/commands/stt.help.ts +52 -0
- package/src/cli/commands/stt.ts +9 -47
- package/src/cli/commands/telemetry.help.ts +14 -0
- package/src/cli/commands/telemetry.ts +19 -17
- package/src/cli/commands/tools-run.ts +30 -45
- package/src/cli/commands/tools.help.ts +81 -0
- package/src/cli/commands/tools.ts +43 -83
- package/src/cli/commands/trust.help.ts +43 -0
- package/src/cli/commands/trust.ts +82 -109
- package/src/cli/commands/tts.help.ts +88 -0
- package/src/cli/commands/tts.ts +116 -188
- package/src/cli/commands/ui.help.ts +213 -0
- package/src/cli/commands/ui.ts +363 -380
- package/src/cli/commands/usage.help.ts +147 -0
- package/src/cli/commands/usage.ts +87 -206
- package/src/cli/commands/watchers.help.ts +211 -0
- package/src/cli/commands/watchers.ts +261 -417
- package/src/cli/commands/webhooks.help.ts +95 -0
- package/src/cli/commands/webhooks.ts +44 -115
- package/src/cli/index.help.ts +91 -0
- package/src/cli/lib/__tests__/global-json-option.test.ts +97 -0
- package/src/cli/lib/__tests__/install-from-github.test.ts +144 -4
- package/src/cli/lib/__tests__/plugin-catalog-cache.test.ts +96 -132
- package/src/cli/lib/__tests__/plugin-catalog-local.test.ts +65 -0
- package/src/cli/lib/__tests__/plugin-catalog-platform.test.ts +167 -0
- package/src/cli/lib/__tests__/plugin-catalog-resolve.test.ts +197 -0
- package/src/cli/lib/__tests__/plugin-details.test.ts +349 -240
- package/src/cli/lib/__tests__/plugin-fingerprint.test.ts +31 -0
- package/src/cli/lib/__tests__/plugin-icon-file.test.ts +50 -1
- package/src/cli/lib/__tests__/plugin-marketplace.test.ts +94 -0
- package/src/cli/lib/__tests__/plugins-install-offline.test.ts +147 -0
- package/src/cli/lib/__tests__/search-plugins.test.ts +193 -309
- package/src/cli/lib/bundled-marketplace.json +439 -0
- package/src/cli/lib/cli-command-help.ts +83 -10
- package/src/cli/lib/global-json-option.ts +41 -0
- package/src/cli/lib/install-from-github.ts +69 -2
- package/src/cli/lib/plugin-catalog-cache.ts +22 -50
- package/src/cli/lib/plugin-catalog-local.ts +71 -0
- package/src/cli/lib/plugin-catalog-platform.ts +117 -0
- package/src/cli/lib/plugin-catalog-resolve.ts +72 -0
- package/src/cli/lib/plugin-details.ts +100 -34
- package/src/cli/lib/plugin-fingerprint.ts +18 -7
- package/src/cli/lib/plugin-icon-file.ts +38 -26
- package/src/cli/lib/plugin-marketplace.ts +15 -2
- package/src/cli/lib/publish-plugin.ts +3 -3
- package/src/cli/lib/search-plugins.ts +48 -111
- package/src/cli/program.ts +7 -0
- package/src/cli.ts +22 -13
- package/src/config/__tests__/default-profile-catalog.test.ts +8 -12
- package/src/config/__tests__/default-provider.test.ts +0 -21
- package/src/config/__tests__/deployment-context-defaults.test.ts +0 -21
- package/src/config/__tests__/feature-flag-registry-bundled.test.ts +2 -2
- package/src/config/__tests__/loader-callsite-strip-fallback.test.ts +0 -21
- package/src/config/__tests__/loader-last-known-good.test.ts +0 -21
- package/src/config/__tests__/loader-sparse-array-cleanup.test.ts +0 -21
- package/src/config/__tests__/platform-rehydration.test.ts +125 -0
- package/src/config/__tests__/profile-materialization.test.ts +1 -88
- package/src/config/__tests__/seed-default-provider.test.ts +0 -21
- package/src/config/__tests__/sync-gated-profiles.test.ts +0 -21
- package/src/config/bundled-skills/app-builder/SKILL.md +24 -1
- package/src/config/bundled-skills/app-builder/references/CUSTOM_ROUTES.md +30 -0
- package/src/config/bundled-skills/contacts/SKILL.md +1 -1
- package/src/config/bundled-skills/contacts/tools/contact-merge.ts +4 -2
- package/src/config/bundled-skills/messaging/TOOLS.json +2 -2
- package/src/config/bundled-skills/messaging/tools/messaging-send.ts +28 -20
- package/src/config/bundled-skills/phone-calls/references/CONFIG.md +7 -7
- package/src/config/bundled-skills/phone-calls/references/TROUBLESHOOTING.md +3 -3
- package/src/config/bundled-skills/schedule/SKILL.md +0 -1
- package/src/config/bundled-skills/schedule/TOOLS.json +2 -2
- package/src/config/bundled-skills/settings/TOOLS.json +5 -3
- package/src/config/bundled-skills/settings/tools/voice-config-update.ts +84 -10
- package/src/config/bundled-skills/transcribe/tools/transcribe-media.test.ts +0 -7
- package/src/config/call-site-defaults.ts +3 -3
- package/src/config/feature-flag-registry.json +15 -63
- package/src/config/llm-resolver.ts +88 -475
- package/src/config/managed-speech-defaults.ts +115 -0
- package/src/config/platform-rehydration.ts +76 -0
- package/src/config/profile-materialization.ts +11 -11
- package/src/config/schema.ts +93 -129
- package/src/config/schemas/__tests__/live-voice.test.ts +24 -6
- package/src/config/schemas/__tests__/stt.test.ts +37 -0
- package/src/config/schemas/live-voice.ts +10 -4
- package/src/config/schemas/llm.ts +51 -15
- package/src/config/schemas/memory-lifecycle.ts +1 -13
- package/src/config/schemas/memory-retrospective.ts +8 -0
- package/src/config/schemas/memory.ts +0 -4
- package/src/config/schemas/stt.ts +33 -6
- package/src/config/schemas/tts.ts +53 -6
- package/src/config/schemas/workspace-git.ts +8 -0
- package/src/contacts/contact-store.ts +3 -58
- package/src/contacts/types.ts +1 -1
- package/src/conversations/__tests__/message-consolidation.test.ts +6 -5
- package/src/conversations/message-consolidation.ts +34 -112
- package/src/daemon/__tests__/conversation-tool-setup-exclude.test.ts +64 -7
- package/src/daemon/__tests__/conversation-tool-setup-plugin-scope.test.ts +3 -7
- package/src/daemon/__tests__/conversation-tool-setup.test.ts +8 -6
- package/src/daemon/__tests__/embedding-reconcile.test.ts +0 -11
- package/src/daemon/__tests__/inference-profile-notification.test.ts +5 -11
- package/src/daemon/__tests__/inflight-message-content.test.ts +219 -0
- package/src/daemon/__tests__/native-web-search-metadata.test.ts +5 -27
- package/src/daemon/__tests__/route-conversation-post.test.ts +158 -0
- package/src/daemon/__tests__/wake-conversation-ops-completion.test.ts +0 -6
- package/src/daemon/__tests__/web-search-status-text.test.ts +0 -29
- package/src/daemon/app-source-watcher.ts +17 -23
- package/src/daemon/chat-credential-redaction.ts +1365 -0
- package/src/daemon/conversation-agent-loop-handlers.ts +742 -77
- package/src/daemon/conversation-agent-loop.ts +10 -17
- package/src/daemon/conversation-error.ts +53 -16
- package/src/daemon/conversation-history.ts +6 -12
- package/src/daemon/conversation-messaging.ts +13 -1
- package/src/daemon/conversation-process.ts +16 -11
- package/src/daemon/conversation-runtime-assembly.ts +148 -52
- package/src/daemon/conversation-surfaces.ts +217 -100
- package/src/daemon/conversation-tool-setup.ts +59 -112
- package/src/daemon/conversation-turn-finalize.ts +18 -0
- package/src/daemon/conversation.ts +35 -66
- package/src/daemon/event-loop-watchdog.test.ts +1 -6
- package/src/daemon/external-plugins-bootstrap.ts +20 -74
- package/src/daemon/handlers/__tests__/config-a2a-accept.test.ts +0 -7
- package/src/daemon/handlers/__tests__/config-a2a-complete.test.ts +1 -8
- package/src/daemon/handlers/__tests__/config-a2a-invite.test.ts +1 -8
- package/src/daemon/handlers/__tests__/config-a2a-redeem.test.ts +1 -8
- package/src/daemon/handlers/__tests__/config-a2a.test.ts +1 -8
- package/src/daemon/handlers/conversation-history.ts +6 -4
- package/src/daemon/handlers/conversations.ts +19 -9
- package/src/daemon/handlers/shared.ts +24 -1
- package/src/daemon/inflight-message-content.ts +228 -0
- package/src/daemon/lifecycle.ts +21 -31
- package/src/daemon/message-protocol.ts +2 -7
- package/src/daemon/message-provenance.ts +17 -8
- package/src/daemon/message-types/conversations.ts +6 -17
- package/src/daemon/message-types/host-ui-snapshot.ts +44 -0
- package/src/daemon/message-types/inbox.ts +3 -46
- package/src/daemon/orphan-reaper.test.ts +1 -6
- package/src/daemon/process-message.ts +9 -0
- package/src/daemon/providers-setup.ts +5 -75
- package/src/daemon/route-conversation-post.ts +158 -0
- package/src/daemon/shutdown-handlers.ts +11 -17
- package/src/daemon/sqlite-corruption-watchdog.test.ts +0 -6
- package/src/documents/document-comments-store.test.ts +1 -7
- package/src/export/__tests__/transcript-formatter.test.ts +8 -8
- package/src/export/formatter.ts +8 -17
- package/src/export/transcript-formatter.ts +4 -1
- package/src/heartbeat/__tests__/heartbeat-run-store.test.ts +0 -9
- package/src/heartbeat/__tests__/heartbeat-service.test.ts +29 -57
- package/src/home/__tests__/home-content-refresh.test.ts +0 -7
- package/src/home/__tests__/suggested-prompts.test.ts +0 -11
- package/src/ipc/__tests__/contacts-info-ipc.test.ts +12 -10
- package/src/ipc/__tests__/email-ipc.test.ts +90 -0
- package/src/ipc/assistant-server.ts +10 -22
- package/src/ipc/routes/__tests__/guardian-label-ipc-routes.test.ts +74 -0
- package/src/ipc/routes/__tests__/invite-ipc-routes.test.ts +0 -7
- package/src/ipc/routes/guardian-label-ipc-routes.ts +42 -0
- package/src/live-voice/__tests__/live-voice-archive.test.ts +1 -8
- package/src/live-voice/__tests__/live-voice-credential-preflight.test.ts +89 -49
- package/src/live-voice/__tests__/live-voice-integration.test.ts +116 -2
- package/src/live-voice/__tests__/live-voice-resume-registry.test.ts +58 -0
- package/src/live-voice/__tests__/live-voice-resume-turn.test.ts +280 -0
- package/src/live-voice/__tests__/live-voice-triage-escalate.test.ts +337 -0
- package/src/live-voice/__tests__/live-voice-tts.test.ts +0 -4
- package/src/live-voice/__tests__/live-voice-vad.test.ts +432 -12
- package/src/live-voice/__tests__/protocol.test.ts +122 -0
- package/src/live-voice/__tests__/runtime-websocket-shell.test.ts +0 -41
- package/src/live-voice/live-voice-credential-preflight.ts +27 -3
- package/src/live-voice/live-voice-resume-registry.ts +54 -0
- package/src/live-voice/live-voice-session.ts +602 -61
- package/src/live-voice/protocol.ts +143 -1
- package/src/mcp/__tests__/mcp-auth-orchestrator.test.ts +3 -7
- package/src/messaging/provider-types.ts +9 -0
- package/src/messaging/providers/a2a/__tests__/deliver.test.ts +0 -9
- package/src/messaging/providers/outlook/adapter.ts +17 -2
- package/src/messaging/providers/outlook/client.ts +11 -2
- package/src/messaging/providers/outlook/types.ts +9 -0
- package/src/messaging/providers/slack/__tests__/download.test.ts +1 -8
- package/src/messaging/providers/slack/binding-metadata.test.ts +23 -21
- package/src/messaging/providers/slack/send.test.ts +0 -7
- package/src/messaging/providers/slack/withdraw.test.ts +0 -6
- package/src/messaging/providers/telegram-bot/send.test.ts +0 -7
- package/src/monitoring/__tests__/plugin-source-watch.test.ts +40 -1
- package/src/monitoring/plugin-source-watch.ts +56 -62
- package/src/monitoring/recovery/__tests__/inflight-content.test.ts +166 -0
- package/src/monitoring/recovery/inflight-content.ts +260 -0
- package/src/monitoring/recovery/run-recovery.ts +72 -0
- package/src/monitoring/worker.ts +68 -7
- package/src/notifications/AGENTS.md +1 -1
- package/src/notifications/README.md +4 -4
- package/src/notifications/__tests__/broadcaster.test.ts +4 -17
- package/src/notifications/__tests__/connected-channels.test.ts +11 -8
- package/src/notifications/__tests__/decision-engine.test.ts +0 -7
- package/src/notifications/__tests__/destination-resolver.test.ts +18 -12
- package/src/notifications/__tests__/deterministic-checks.test.ts +1 -9
- package/src/notifications/adapters/macos.ts +3 -5
- package/src/notifications/adapters/platform.ts +1 -1
- package/src/notifications/broadcaster.ts +13 -4
- package/src/notifications/conversation-candidates.ts +21 -28
- package/src/notifications/copy-composer.ts +0 -6
- package/src/notifications/decision-engine.ts +1 -1
- package/src/notifications/{canonical-delivery-recorder.ts → guardian-delivery-recorder.ts} +47 -33
- package/src/notifications/home-feed-side-effect.ts +0 -2
- package/src/notifications/signal.ts +0 -4
- package/src/oauth/byo-connection.test.ts +0 -9
- package/src/oauth/connection-resolver.test.ts +18 -46
- package/src/oauth/seed-providers.ts +6 -0
- package/src/onboarding/__tests__/onboarding-events-store.test.ts +122 -44
- package/src/onboarding/onboarding-events-store.ts +67 -115
- package/src/onboarding/onboarding-research-events-store.test.ts +222 -0
- package/src/onboarding/onboarding-research-events-store.ts +92 -0
- package/src/permissions/checker.test.ts +10 -24
- package/src/permissions/confirmation-guardian-request.test.ts +94 -0
- package/src/permissions/{confirmation-canonical-request.ts → confirmation-guardian-request.ts} +44 -29
- package/src/permissions/gateway-threshold-reader.test.ts +0 -8
- package/src/permissions/prompter.ts +6 -4
- package/src/permissions/question-prompter.test.ts +7 -14
- package/src/persistence/__tests__/bulk-write-gate.test.ts +1 -7
- package/src/persistence/__tests__/conversation-queries-search.test.ts +0 -7
- package/src/persistence/__tests__/main-db-readonly-probe.ts +80 -0
- package/src/persistence/__tests__/main-db-readonly.test.ts +35 -0
- package/src/persistence/__tests__/message-content-file.test.ts +161 -52
- package/src/persistence/__tests__/raw-query-telemetry.test.ts +74 -0
- package/src/persistence/conversation-crud.ts +175 -45
- package/src/persistence/conversation-disk-view.ts +32 -15
- package/src/persistence/conversation-group-migration.test.ts +1 -8
- package/src/persistence/conversation-title-service.ts +59 -22
- package/src/persistence/db-connection.ts +46 -1
- package/src/persistence/db-singleton.ts +7 -1
- package/src/persistence/embeddings/__tests__/messages-lexical-index.test.ts +3 -7
- package/src/persistence/embeddings/messages-lexical-index.ts +11 -0
- package/src/persistence/job-handlers/__tests__/message-lexical-backfill.test.ts +0 -7
- package/src/persistence/job-handlers/__tests__/message-lexical-enqueue.test.ts +0 -6
- package/src/persistence/job-handlers/__tests__/message-lexical.test.ts +0 -7
- package/src/persistence/job-handlers/cleanup.ts +30 -16
- package/src/persistence/job-handlers/message-lexical-backfill.ts +8 -5
- package/src/persistence/job-handlers/message-lexical.ts +2 -2
- package/src/persistence/jobs-store.ts +123 -17
- package/src/persistence/lifecycle-events-store.test.ts +84 -0
- package/src/persistence/lifecycle-events-store.ts +27 -52
- package/src/persistence/llm-request-log-sink-clickhouse.ts +325 -0
- package/src/persistence/llm-request-log-store.ts +185 -106
- package/src/persistence/llm-request-log-writer-types.ts +80 -0
- package/src/persistence/llm-usage-store.ts +5 -2
- package/src/persistence/message-content-file.ts +182 -71
- package/src/persistence/message-content.ts +18 -10
- package/src/persistence/migrations/305-drop-contact-acl-columns.ts +3 -2
- package/src/persistence/migrations/325-create-config-setting-events.ts +40 -0
- package/src/persistence/migrations/326-move-injection-events-to-memory-db.ts +104 -0
- package/src/persistence/migrations/327-create-flush-checkpoints.ts +86 -0
- package/src/persistence/migrations/328-drop-memory-v3-learned-edge-tables.ts +16 -0
- package/src/persistence/migrations/329-move-onboarding-events-to-telemetry-db.test.ts +179 -0
- package/src/persistence/migrations/329-move-onboarding-events-to-telemetry-db.ts +119 -0
- package/src/persistence/migrations/330-move-auth-fallback-events-to-telemetry-db.test.ts +197 -0
- package/src/persistence/migrations/330-move-auth-fallback-events-to-telemetry-db.ts +108 -0
- package/src/persistence/migrations/331-move-lifecycle-events-to-telemetry-db.test.ts +159 -0
- package/src/persistence/migrations/331-move-lifecycle-events-to-telemetry-db.ts +97 -0
- package/src/persistence/migrations/332-move-skill-loaded-events-to-telemetry-db.test.ts +219 -0
- package/src/persistence/migrations/332-move-skill-loaded-events-to-telemetry-db.ts +128 -0
- package/src/persistence/migrations/333-create-telemetry-events-table.test.ts +66 -0
- package/src/persistence/migrations/333-create-telemetry-events-table.ts +43 -0
- package/src/persistence/migrations/334-backfill-telemetry-events-outbox.test.ts +584 -0
- package/src/persistence/migrations/334-backfill-telemetry-events-outbox.ts +386 -0
- package/src/persistence/migrations/335-collapse-memory-embed-backlog.test.ts +181 -0
- package/src/persistence/migrations/335-collapse-memory-embed-backlog.ts +177 -0
- package/src/persistence/migrations/__tests__/327-create-flush-checkpoints.test.ts +124 -0
- package/src/persistence/raw-query.ts +27 -1
- package/src/persistence/schema/calls.ts +0 -23
- package/src/persistence/schema/conversation-starters.ts +31 -0
- package/src/persistence/schema/conversations.ts +3 -3
- package/src/persistence/schema/guardian.ts +0 -150
- package/src/persistence/schema/index.ts +1 -0
- package/src/persistence/schema/infrastructure.ts +25 -72
- package/src/persistence/schema/memory-core.ts +0 -25
- package/src/persistence/schema/memory-injection.ts +3 -1
- package/src/persistence/schema/tasks.ts +1 -60
- package/src/persistence/steps.ts +22 -0
- package/src/platform/client.test.ts +110 -0
- package/src/platform/client.ts +24 -4
- package/src/platform/consent-cache.test.ts +5 -18
- package/src/platform/consent-cache.ts +8 -6
- package/src/platform/managed-speech.test.ts +218 -0
- package/src/platform/managed-speech.ts +204 -0
- package/src/plugin-api/conversation-turn.ts +250 -0
- package/src/plugin-api/index.ts +21 -0
- package/src/plugin-api/model-profiles.test.ts +29 -28
- package/src/plugin-api/vision-support.test.ts +25 -38
- package/src/plugin-api/vision-support.ts +26 -17
- package/src/plugins/AGENTS.md +8 -0
- package/src/plugins/__tests__/source-fingerprint.test.ts +41 -0
- package/src/plugins/collect-source-versions.ts +77 -0
- package/src/plugins/defaults/image-recovery/detect.ts +32 -10
- package/src/plugins/defaults/image-recovery/hooks/post-model-call.ts +20 -18
- package/src/plugins/defaults/image-recovery/recover.ts +86 -59
- package/src/plugins/defaults/memory/__tests__/activation-session-store.test.ts +1 -8
- package/src/plugins/defaults/memory/__tests__/clear-stale-processing-flags.test.ts +1 -8
- package/src/plugins/defaults/memory/__tests__/conversation-queries.test.ts +1 -8
- package/src/plugins/defaults/memory/__tests__/conversation-row-batch-delete.test.ts +7 -18
- package/src/plugins/defaults/memory/__tests__/db-memory-attach.test.ts +22 -18
- package/src/plugins/defaults/memory/__tests__/embedding-cache.test.ts +0 -6
- package/src/plugins/defaults/memory/__tests__/find-most-recent-retrospective-for.test.ts +1 -8
- package/src/plugins/defaults/memory/__tests__/fork-message-copy.test.ts +7 -18
- package/src/plugins/defaults/memory/__tests__/jobs-store-enqueue-gate.test.ts +29 -39
- package/src/plugins/defaults/memory/__tests__/jobs-worker-memory-disabled.test.ts +11 -26
- package/src/plugins/defaults/memory/__tests__/jobs-worker-v2-graph-trigger-embed.test.ts +3 -22
- package/src/plugins/defaults/memory/__tests__/jobs-worker-v2-schedule.test.ts +215 -1
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-enqueue.test.ts +0 -7
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-job.test.ts +114 -16
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-prompt.test.ts +180 -0
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-skill-card.test.ts +153 -519
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-startup-cleanup.test.ts +0 -7
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-state.test.ts +6 -17
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-trigger-check.test.ts +0 -7
- package/src/plugins/defaults/memory/__tests__/memory-v2-activation-log-store.test.ts +1 -19
- package/src/plugins/defaults/memory/__tests__/memory-v2-concept-frequency.test.ts +0 -18
- package/src/plugins/defaults/memory/__tests__/shutdown-hook.test.ts +8 -7
- package/src/plugins/defaults/memory/__tests__/table-relocation.test.ts +49 -0
- package/src/{persistence → plugins/defaults/memory}/__tests__/worker-control.test.ts +8 -10
- package/src/plugins/defaults/memory/graph/capability-seed.ts +4 -6
- package/src/plugins/defaults/memory/graph/extraction.ts +5 -1
- package/src/plugins/defaults/memory/graph/graph-search.test.ts +2 -8
- package/src/plugins/defaults/memory/graph/tool-handlers.test.ts +0 -6
- package/src/plugins/defaults/memory/graph/tool-handlers.ts +2 -2
- package/src/plugins/defaults/memory/graph/tools.ts +47 -13
- package/src/plugins/defaults/memory/graph-topology/__tests__/build-memory-graph.test.ts +81 -2
- package/src/plugins/defaults/memory/graph-topology/build-memory-graph.ts +67 -32
- package/src/plugins/defaults/memory/hooks/shutdown.ts +8 -11
- package/src/plugins/defaults/memory/indexer.ts +2 -1
- package/src/plugins/defaults/memory/job-handlers/backfill.ts +8 -5
- package/src/plugins/defaults/memory/job-handlers/embedding.test.ts +0 -7
- package/src/plugins/defaults/memory/job-handlers/embedding.ts +2 -2
- package/src/plugins/defaults/memory/job-handlers/index-maintenance.ts +7 -2
- package/src/plugins/defaults/memory/jobs/__tests__/embed-concept-page.test.ts +73 -8
- package/src/plugins/defaults/memory/jobs/embed-concept-page.ts +5 -3
- package/src/plugins/defaults/memory/jobs/embed-pkb-file.test.ts +0 -13
- package/src/plugins/defaults/memory/jobs-worker.ts +139 -107
- package/src/plugins/defaults/memory/memory-db.ts +27 -0
- package/src/plugins/defaults/memory/memory-retrospective-constants.ts +4 -4
- package/src/plugins/defaults/memory/memory-retrospective-job.ts +56 -180
- package/src/plugins/defaults/memory/memory-retrospective-prompt.ts +216 -0
- package/src/plugins/defaults/memory/memory-retrospective-skill-card.ts +122 -202
- package/src/plugins/defaults/memory/message-media.ts +2 -2
- package/src/plugins/defaults/memory/paths.ts +3 -2
- package/src/plugins/defaults/memory/pkb/pkb-index.test.ts +0 -12
- package/src/plugins/defaults/memory/pkb/pkb-reconcile.test.ts +0 -9
- package/src/plugins/defaults/memory/pkb/pkb-search.test.ts +0 -6
- package/src/plugins/defaults/memory/routes/__tests__/memory-v2-simulate-route.test.ts +15 -92
- package/src/plugins/defaults/memory/routes/__tests__/memory-worker-routes.test.ts +201 -0
- package/src/plugins/defaults/memory/routes/memory-item-routes.test.ts +7 -21
- package/src/plugins/defaults/memory/routes/memory-v2-routes.ts +3 -8
- package/src/plugins/defaults/memory/routes/memory-worker-routes.ts +162 -0
- package/src/plugins/defaults/memory/startup.ts +6 -7
- package/src/plugins/defaults/memory/task-memory-cleanup.ts +11 -22
- package/src/plugins/defaults/memory/tools.test.ts +40 -8
- package/src/plugins/defaults/memory/tools.ts +12 -2
- package/src/plugins/defaults/memory/v2/__tests__/activation.test.ts +0 -19
- package/src/plugins/defaults/memory/v2/__tests__/backfill-jobs.test.ts +63 -40
- package/src/plugins/defaults/memory/v2/__tests__/cli-command-store.test.ts +61 -130
- package/src/plugins/defaults/memory/v2/__tests__/consolidation-job.test.ts +406 -10
- package/src/plugins/defaults/memory/v2/__tests__/harness-compare.test.ts +1 -10
- package/src/plugins/defaults/memory/v2/__tests__/harness-oracle.test.ts +1 -10
- package/src/plugins/defaults/memory/v2/__tests__/harness-replay-input.test.ts +1 -10
- package/src/plugins/defaults/memory/v2/__tests__/injection-events.test.ts +61 -69
- package/src/plugins/defaults/memory/v2/__tests__/injection.test.ts +0 -19
- package/src/plugins/defaults/memory/v2/__tests__/migration.test.ts +11 -10
- package/src/plugins/defaults/memory/v2/__tests__/page-index.test.ts +0 -5
- package/src/plugins/defaults/memory/v2/__tests__/qdrant.test.ts +6 -22
- package/src/plugins/defaults/memory/v2/__tests__/reranker.test.ts +3 -5
- package/src/plugins/defaults/memory/v2/__tests__/router.test.ts +7 -20
- package/src/plugins/defaults/memory/v2/__tests__/sim.test.ts +0 -24
- package/src/plugins/defaults/memory/v2/__tests__/skill-content.test.ts +17 -11
- package/src/plugins/defaults/memory/v2/__tests__/skill-store.test.ts +0 -16
- package/src/plugins/defaults/memory/v2/__tests__/static-context.test.ts +0 -10
- package/src/plugins/defaults/memory/v2/__tests__/sweep-job.test.ts +0 -6
- package/src/plugins/defaults/memory/v2/backfill-jobs.ts +5 -2
- package/src/plugins/defaults/memory/v2/cli-command-content.ts +43 -12
- package/src/plugins/defaults/memory/v2/cli-command-store.ts +34 -50
- package/src/plugins/defaults/memory/v2/consolidation-job.ts +216 -10
- package/src/plugins/defaults/memory/v2/harness/router-retriever.ts +1 -8
- package/src/plugins/defaults/memory/v2/injection-events.ts +16 -14
- package/src/plugins/defaults/memory/v2/injection.ts +1 -2
- package/src/plugins/defaults/memory/v2/memory-v2-startup.test.ts +0 -5
- package/src/plugins/defaults/memory/v2/migration.ts +55 -24
- package/src/plugins/defaults/memory/v2/prompts/consolidation.ts +4 -0
- package/src/plugins/defaults/memory/v2/router.ts +4 -14
- package/src/plugins/defaults/memory/v2/sweep-job.ts +1 -0
- package/src/plugins/defaults/memory/v3/__tests__/carry-integration.test.ts +10 -22
- package/src/plugins/defaults/memory/v3/__tests__/dense.test.ts +0 -5
- package/src/plugins/defaults/memory/v3/__tests__/injection.test.ts +18 -20
- package/src/plugins/defaults/memory/v3/__tests__/section-dense-store.test.ts +0 -5
- package/src/plugins/defaults/memory/v3/__tests__/selection-log-store.test.ts +5 -9
- package/src/plugins/defaults/memory/v3/__tests__/shadow-plugin.test.ts +51 -31
- package/src/plugins/defaults/memory/v3/prune.test.ts +3 -11
- package/src/{persistence → plugins/defaults/memory}/worker-control.ts +12 -21
- package/src/plugins/defaults/memory/worker.ts +19 -2
- package/src/plugins/mtime-cache.ts +166 -70
- package/src/plugins/plugin-tree-walk.ts +41 -4
- package/src/plugins/source-fingerprint.ts +9 -3
- package/src/plugins/surface-import.ts +28 -0
- package/src/plugins/types.ts +9 -23
- package/src/prompts/__tests__/system-prompt.test.ts +0 -28
- package/src/prompts/__tests__/task-progress-hint-section.test.ts +0 -28
- package/src/providers/__tests__/connection-model-compat.test.ts +6 -19
- package/src/providers/__tests__/count-tokens-forwarding.test.ts +1 -21
- package/src/providers/__tests__/dispatch-connection-routing.test.ts +9 -26
- package/src/providers/__tests__/registry-native-web-search.test.ts +4 -10
- package/src/providers/__tests__/retry-callsite.test.ts +240 -235
- package/src/providers/__tests__/satellite-connection-routing.test.ts +11 -36
- package/src/providers/__tests__/vellum-mismatch-routing.test.ts +0 -5
- package/src/providers/anthropic/client.ts +9 -3
- package/src/providers/call-site-routing.ts +7 -3
- package/src/providers/connection-resolution.ts +9 -11
- package/src/providers/content-block-schema.ts +149 -0
- package/src/providers/inference/__tests__/base-url-route-validation.test.ts +7 -11
- package/src/providers/inference/__tests__/codex-token-refresh.test.ts +1 -15
- package/src/providers/inference/auth.ts +23 -0
- package/src/providers/inference/backfill.ts +25 -9
- package/src/providers/model-catalog.ts +213 -5
- package/src/providers/openai/__tests__/chat-completions-provider-reasoning.test.ts +54 -0
- package/src/providers/openai/chat-completions-provider.ts +13 -2
- package/src/providers/openai/responses-provider.ts +232 -5
- package/src/providers/openrouter/client.ts +53 -0
- package/src/providers/retry.ts +25 -2
- package/src/providers/speech-to-text/__tests__/provider-catalog.test.ts +13 -0
- package/src/providers/speech-to-text/__tests__/resolve.test.ts +211 -74
- package/src/providers/speech-to-text/__tests__/vellum-managed-realtime.test.ts +408 -0
- package/src/providers/speech-to-text/__tests__/vellum-managed.test.ts +121 -0
- package/src/providers/speech-to-text/__tests__/vellum-speech-relay-connection.test.ts +126 -0
- package/src/providers/speech-to-text/deepgram-realtime.ts +167 -37
- package/src/providers/speech-to-text/incremental-batch-stream.ts +413 -0
- package/src/providers/speech-to-text/openai-whisper-stream.ts +16 -363
- package/src/providers/speech-to-text/provider-catalog.ts +26 -1
- package/src/providers/speech-to-text/resolve.ts +74 -13
- package/src/providers/speech-to-text/vellum-managed-realtime.ts +309 -0
- package/src/providers/speech-to-text/vellum-managed.ts +71 -0
- package/src/providers/speech-to-text/vellum-speech-relay-connection.ts +174 -0
- package/src/providers/types.ts +23 -4
- package/src/providers/vellum-model-routing.test.ts +26 -0
- package/src/providers/vellum-model-routing.ts +30 -0
- package/src/runtime/AGENTS.md +1 -1
- package/src/runtime/__tests__/actor-trust-resolver.test.ts +2 -8
- package/src/runtime/__tests__/agent-wake.test.ts +100 -27
- package/src/runtime/__tests__/background-job-runner.test.ts +22 -0
- package/src/runtime/__tests__/guardian-vellum-migration.test.ts +0 -5
- package/src/runtime/__tests__/member-verdict-cache.test.ts +2 -9
- package/src/runtime/__tests__/trust-verdict-consumer.test.ts +22 -5
- package/src/runtime/access-request-helper.ts +84 -60
- package/src/runtime/actor-trust-resolver.ts +2 -2
- package/src/runtime/agent-wake.ts +75 -26
- package/src/runtime/assistant-event-hub.ts +1 -0
- package/src/runtime/auth/__tests__/middleware.test.ts +1 -10
- package/src/runtime/auth/__tests__/route-policy.test.ts +0 -7
- package/src/runtime/auth/__tests__/token-service.test.ts +1 -8
- package/src/runtime/auth/same-actor.ts +2 -1
- package/src/runtime/background-job-runner.ts +22 -0
- package/src/runtime/channel-approval-types.ts +1 -1
- package/src/runtime/channel-reply-delivery.ts +38 -19
- package/src/runtime/confirmation-request-guardian-bridge.ts +45 -39
- package/src/runtime/for-chat-mint-registry.ts +118 -0
- package/src/runtime/guardian-action-service.ts +32 -27
- package/src/runtime/guardian-decision-types.ts +2 -2
- package/src/runtime/guardian-reply-router.ts +80 -98
- package/src/runtime/http-server.ts +61 -41
- package/src/runtime/introduction-policy.ts +2 -2
- package/src/runtime/local-actor-identity.ts +19 -7
- package/src/runtime/pending-interactions.ts +1 -0
- package/src/runtime/reveal-nonce.ts +49 -0
- package/src/runtime/reveal-success-registry.ts +306 -0
- package/src/runtime/routes/__tests__/acp-routes.test.ts +0 -7
- package/src/runtime/routes/__tests__/backup-routes.test.ts +195 -125
- package/src/runtime/routes/__tests__/channel-verification-routes.test.ts +4 -1
- package/src/runtime/routes/__tests__/client-routes.test.ts +0 -7
- package/src/runtime/routes/__tests__/connection-routes-vs-cli-parity.test.ts +1 -19
- package/src/runtime/routes/__tests__/consolidation-routes.test.ts +1 -8
- package/src/runtime/routes/__tests__/contact-routes-merge-relay.test.ts +186 -0
- package/src/runtime/routes/__tests__/contact-routes-update-channel-relay.test.ts +2 -3
- package/src/runtime/routes/__tests__/content-source-routes.test.ts +0 -7
- package/src/runtime/routes/__tests__/conversation-compaction-routes.test.ts +5 -27
- package/src/runtime/routes/__tests__/conversation-list-routes.test.ts +0 -7
- package/src/runtime/routes/__tests__/conversation-management-routes.test.ts +12 -30
- package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +59 -56
- package/src/runtime/routes/__tests__/conversation-surface-routes.test.ts +0 -7
- package/src/runtime/routes/__tests__/default-provider-routes.test.ts +53 -73
- package/src/runtime/routes/__tests__/gateway-status-routes.test.ts +84 -0
- package/src/runtime/routes/__tests__/inference-callsites-routes.test.ts +2 -10
- package/src/runtime/routes/__tests__/inference-profiles-routes.test.ts +58 -90
- package/src/runtime/routes/__tests__/inference-provider-connection-routes.test.ts +161 -93
- package/src/runtime/routes/__tests__/llm-call-sites-routes.test.ts +6 -1
- package/src/runtime/routes/__tests__/monitoring-routes.test.ts +0 -8
- package/src/runtime/routes/__tests__/plugins-routes.test.ts +218 -64
- package/src/runtime/routes/__tests__/question-routes.test.ts +2 -4
- package/src/runtime/routes/__tests__/retrospective-routes.test.ts +1 -8
- package/src/runtime/routes/__tests__/sanity-routes.test.ts +0 -7
- package/src/runtime/routes/__tests__/schedule-routes-create.test.ts +2 -1
- package/src/runtime/routes/__tests__/schedule-worker-routes.test.ts +16 -56
- package/src/runtime/routes/__tests__/stt-routes.test.ts +0 -9
- package/src/runtime/routes/__tests__/suggest-trust-rule-routes.test.ts +0 -7
- package/src/runtime/routes/__tests__/surface-action-routes.test.ts +5 -0
- package/src/runtime/routes/__tests__/surface-content-routes.test.ts +354 -1
- package/src/runtime/routes/__tests__/tts-routes.test.ts +0 -11
- package/src/runtime/routes/__tests__/user-route-dispatcher.test.ts +163 -10
- package/src/runtime/routes/__tests__/user-routes-cli.test.ts +154 -0
- package/src/runtime/routes/__tests__/wake-conversation-routes.test.ts +0 -7
- package/src/runtime/routes/acp-routes.ts +21 -9
- package/src/runtime/routes/app-management-routes.ts +231 -60
- package/src/runtime/routes/app-routes.ts +130 -83
- package/src/runtime/routes/auth-routes.ts +3 -1
- package/src/runtime/routes/background-wake-routes.ts +0 -1
- package/src/runtime/routes/cache-routes.ts +3 -3
- package/src/runtime/routes/call-routes.ts +5 -1
- package/src/runtime/routes/channel-verification-routes.ts +2 -1
- package/src/runtime/routes/contact-prompt-routes.ts +11 -5
- package/src/runtime/routes/contact-routes.ts +34 -42
- package/src/runtime/routes/conversation-attention-routes.ts +1 -1
- package/src/runtime/routes/conversation-cli-routes.ts +1 -1
- package/src/runtime/routes/conversation-query-routes.ts +4 -4
- package/src/runtime/routes/conversation-routes.ts +111 -72
- package/src/runtime/routes/credential-routes.ts +116 -3
- package/src/runtime/routes/email-routes.ts +17 -1
- package/src/runtime/routes/events-routes.ts +1 -0
- package/src/runtime/routes/gateway-status-routes.ts +69 -0
- package/src/runtime/routes/guardian-action-routes.ts +18 -16
- package/src/runtime/routes/guardian-approval-interception.ts +6 -4
- package/src/runtime/routes/guardian-approval-prompt.ts +7 -5
- package/src/runtime/routes/guardian-expiry-sweep.ts +130 -0
- package/src/runtime/routes/inbound-message-handler.ts +9 -33
- package/src/runtime/routes/inbound-stages/acl-enforcement.test.ts +0 -5
- package/src/runtime/routes/inbound-stages/acl-enforcement.ts +9 -9
- package/src/runtime/routes/inbound-stages/admission-policy.test.ts +24 -6
- package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +0 -7
- package/src/runtime/routes/inbound-stages/bootstrap-intercept.test.ts +0 -5
- package/src/runtime/routes/inbound-stages/bootstrap-intercept.ts +6 -3
- package/src/runtime/routes/inbound-stages/edit-intercept.ts +5 -1
- package/src/runtime/routes/inbound-stages/guardian-activation-intercept.test.ts +0 -9
- package/src/runtime/routes/inbound-stages/guardian-activation-intercept.ts +4 -1
- package/src/runtime/routes/inbound-stages/guardian-reply-intercept.test.ts +3 -12
- package/src/runtime/routes/inbound-stages/guardian-reply-intercept.ts +16 -14
- package/src/runtime/routes/inbound-stages/reaction-intercept.test.ts +8 -2
- package/src/runtime/routes/inbound-stages/reaction-intercept.ts +15 -7
- package/src/runtime/routes/inbound-stages/transcribe-audio.test.ts +0 -9
- package/src/runtime/routes/index.ts +5 -5
- package/src/runtime/routes/inference-provider-connection-routes.ts +73 -24
- package/src/runtime/routes/inference-send-routes.ts +1 -1
- package/src/runtime/routes/internal-telemetry-routes.ts +11 -2
- package/src/runtime/routes/oauth-connect-routes.ts +27 -7
- package/src/runtime/routes/platform-routes.ts +4 -14
- package/src/runtime/routes/playground/__tests__/force-compact.test.ts +0 -4
- package/src/runtime/routes/playground/__tests__/guard.test.ts +0 -4
- package/src/runtime/routes/playground/__tests__/inject-failures.test.ts +0 -14
- package/src/runtime/routes/playground/__tests__/reset-circuit.test.ts +0 -21
- package/src/runtime/routes/playground/__tests__/seed-conversation.test.ts +0 -4
- package/src/runtime/routes/playground/__tests__/seeded-conversations.test.ts +16 -18
- package/src/runtime/routes/playground/__tests__/state.test.ts +0 -14
- package/src/runtime/routes/playground/seed-conversation.ts +5 -4
- package/src/runtime/routes/playground/seeded-conversations.ts +3 -2
- package/src/runtime/routes/plugins-routes.ts +163 -25
- package/src/runtime/routes/profiler-routes.ts +2 -5
- package/src/runtime/routes/publish-routes.ts +15 -18
- package/src/runtime/routes/question-routes.ts +8 -4
- package/src/runtime/routes/schedule-routes.ts +20 -78
- package/src/runtime/routes/schedule-worker-routes.ts +16 -84
- package/src/runtime/routes/secret-routes.ts +14 -0
- package/src/runtime/routes/settings-routes.ts +21 -25
- package/src/runtime/routes/subagents-routes.ts +17 -18
- package/src/runtime/routes/surface-action-routes.ts +18 -39
- package/src/runtime/routes/surface-content-routes.ts +59 -1
- package/src/runtime/routes/surface-conversation-resolver.ts +148 -4
- package/src/runtime/routes/telemetry-routes.ts +74 -1
- package/src/runtime/routes/tts-routes.ts +9 -5
- package/src/runtime/routes/ui-snapshot-routes.test.ts +219 -0
- package/src/runtime/routes/ui-snapshot-routes.ts +313 -0
- package/src/runtime/routes/user-route-dispatcher.ts +52 -53
- package/src/runtime/routes/user-route-resolution.ts +141 -0
- package/src/runtime/routes/user-routes-cli.ts +56 -35
- package/src/runtime/routes/user-routes.ts +9 -2
- package/src/runtime/routes/vellum-actor-trust.ts +67 -0
- package/src/runtime/services/__tests__/auto-analysis-guard.test.ts +1 -8
- package/src/runtime/tool-grant-request-helper.ts +34 -30
- package/src/runtime/trust-verdict-consumer.ts +4 -6
- package/src/schedule/__tests__/worker-control.test.ts +3 -3
- package/src/schedule/schedule-store.ts +0 -41
- package/src/schedule/scheduler.ts +38 -257
- package/src/schedule/worker-control.ts +11 -21
- package/src/schedule/worker.ts +18 -5
- package/src/security/__tests__/oauth2-device-code.test.ts +12 -60
- package/src/security/__tests__/provider-key-env-fallback.test.ts +0 -10
- package/src/security/auth-fallback-events-store.ts +34 -72
- package/src/security/secret-scanner.ts +26 -1
- package/src/sequence/engine.ts +15 -6
- package/src/services/published-app-updater.ts +6 -11
- package/src/stt/__tests__/daemon-batch-transcriber.test.ts +24 -4
- package/src/stt/daemon-batch-transcriber.ts +33 -2
- package/src/stt/stt-stream-session.ts +17 -11
- package/src/stt/types.ts +2 -1
- package/src/subagent/manager.ts +4 -3
- package/src/telemetry/AGENTS.md +44 -0
- package/src/telemetry/__tests__/config-setting-events-store.test.ts +63 -0
- package/src/telemetry/__tests__/config-setting-snapshot.test.ts +107 -0
- package/src/telemetry/__tests__/flush-checkpoints.test.ts +56 -0
- package/src/telemetry/__tests__/outbox-test-harness.ts +83 -0
- package/src/telemetry/__tests__/telemetry-event-fixtures.ts +258 -0
- package/src/telemetry/__tests__/watchdog-events-store.test.ts +32 -122
- package/src/telemetry/config-setting-events-store.ts +40 -0
- package/src/telemetry/config-setting-snapshot.ts +135 -0
- package/src/telemetry/flush-checkpoints.ts +70 -0
- package/src/telemetry/skill-loaded-events-store.test.ts +54 -113
- package/src/telemetry/skill-loaded-events-store.ts +21 -77
- package/src/telemetry/telemetry-event-sources.test.ts +211 -0
- package/src/telemetry/telemetry-event-sources.ts +582 -0
- package/src/telemetry/telemetry-events-outbox.test.ts +330 -0
- package/src/telemetry/telemetry-events-outbox.ts +204 -0
- package/src/telemetry/telemetry-main-db.ts +48 -0
- package/src/telemetry/telemetry-wire-source.json +4 -0
- package/src/telemetry/telemetry-wire-validation.test.ts +157 -0
- package/src/telemetry/telemetry-wire-validation.ts +125 -0
- package/src/telemetry/telemetry-wire.generated.ts +433 -0
- package/src/telemetry/tool-executed-events-store.test.ts +0 -8
- package/src/telemetry/tool-executed-events-store.ts +2 -2
- package/src/telemetry/turn-trace-store.test.ts +86 -7
- package/src/telemetry/turn-trace-store.ts +29 -1
- package/src/telemetry/types.test.ts +38 -0
- package/src/telemetry/types.ts +302 -87
- package/src/telemetry/usage-telemetry-reporter.test.ts +921 -364
- package/src/telemetry/usage-telemetry-reporter.ts +235 -722
- package/src/telemetry/watchdog-direct-emit.test.ts +0 -5
- package/src/telemetry/watchdog-direct-emit.ts +7 -1
- package/src/telemetry/watchdog-events-store.ts +11 -70
- package/src/tools/acp/spawn.test.ts +0 -7
- package/src/tools/apps/definitions.ts +1 -7
- package/src/tools/apps/executors.ts +11 -36
- package/src/tools/browser/__tests__/browser-execution-acquire.test.ts +11 -24
- package/src/tools/browser/__tests__/browser-status.test.ts +47 -33
- package/src/tools/browser/cdp-client/__tests__/cdp-inspect-client.test.ts +1 -11
- package/src/tools/browser/cdp-client/__tests__/factory.test.ts +75 -58
- package/src/tools/browser/cdp-client/__tests__/local-cdp-client.test.ts +0 -10
- package/src/tools/browser/network-recorder.test.ts +0 -11
- package/src/tools/document/document-comment-tool.test.ts +1 -7
- package/src/tools/host-terminal/host-shell.test.ts +0 -32
- package/src/tools/host-terminal/host-shell.ts +6 -2
- package/src/tools/network/__tests__/web-fetch-firecrawl.test.ts +13 -15
- package/src/tools/network/__tests__/web-search-metadata.test.ts +15 -18
- package/src/tools/network/__tests__/web-search.test.ts +39 -50
- package/src/tools/registry.ts +148 -196
- package/src/tools/skills/find-similar-skills.test.ts +0 -7
- package/src/tools/skills/sandbox-runner.ts +5 -2
- package/src/tools/skills/scaffold-managed.ts +105 -12
- package/src/tools/subagent/read.ts +6 -4
- package/src/tools/subagent/spawn.ts +20 -29
- package/src/tools/terminal/safe-env.ts +7 -2
- package/src/tools/terminal/shell.test.ts +4 -8
- package/src/tools/terminal/shell.ts +5 -2
- package/src/tools/tool-approval-handler.ts +39 -23
- package/src/tools/tool-input-summary.ts +2 -1
- package/src/tools/tool-manifest.ts +20 -6
- package/src/tools/tool-profiler.ts +37 -6
- package/src/tools/ui-surface/definitions.ts +1 -7
- package/src/tools/workflows/run-workflow.test.ts +8 -22
- package/src/tts/__tests__/provider-adapters.test.ts +28 -22
- package/src/tts/__tests__/provider-resolution.test.ts +26 -0
- package/src/tts/__tests__/synthesis-stream.test.ts +6 -6
- package/src/tts/__tests__/vellum-provider.test.ts +251 -0
- package/src/tts/provider-catalog.ts +6 -0
- package/src/tts/providers/__tests__/vellum-tts-socket.test.ts +266 -0
- package/src/tts/providers/vellum-provider.ts +220 -0
- package/src/tts/providers/vellum-tts-socket.ts +377 -0
- package/src/tts/synthesize-text.ts +15 -5
- package/src/tts/tts-config-resolver.ts +2 -7
- package/src/tts/types.ts +1 -0
- package/src/usage/attribution.ts +20 -114
- package/src/util/abort-reasons.ts +1 -3
- package/src/util/image-conversion.ts +23 -5
- package/src/util/logger.ts +8 -3
- package/src/{config/bundled-skills/messaging/tools/gmail-mime-helpers.ts → util/mime-type.ts} +4 -1
- package/src/util/platform.ts +0 -60
- package/src/util/pricing.ts +10 -1
- package/src/util/telemetry-db-path.ts +9 -7
- package/src/watcher/__tests__/telemetry.test.ts +41 -11
- package/src/watcher/providers/google-calendar.ts +81 -49
- package/src/watcher/telemetry.ts +22 -5
- package/src/workflows/engine-integration.test.ts +0 -6
- package/src/workflows/engine.test.ts +1 -9
- package/src/workflows/journal-store.test.ts +1 -9
- package/src/workflows/leaf-runner.test.ts +17 -50
- package/src/workflows/library.test.ts +1 -9
- package/src/workflows/run-manager.test.ts +1 -9
- package/src/workspace/git-service.ts +932 -37
- package/src/workspace/provider-commit-message-generator.ts +6 -4
- package/__tests__/permissions/gateway-threshold-reader.test.ts +0 -450
- package/src/__tests__/canonical-guardian-store.test.ts +0 -928
- package/src/__tests__/guardian-principal-id-roundtrip.test.ts +0 -140
- package/src/__tests__/plugin-route-contribution.test.ts +0 -245
- package/src/__tests__/skill-runtime-path.test.ts +0 -63
- package/src/__tests__/task-compiler.test.ts +0 -317
- package/src/__tests__/task-management-tools.test.ts +0 -1128
- package/src/__tests__/task-scheduler.test.ts +0 -786
- package/src/config/schemas/schedules.ts +0 -35
- package/src/contacts/canonical-guardian-store.ts +0 -902
- package/src/daemon/message-types/work-items.ts +0 -243
- package/src/runtime/routes/__tests__/memory-worker-routes.test.ts +0 -306
- package/src/runtime/routes/__tests__/task-routes.test.ts +0 -644
- package/src/runtime/routes/assets/vellum-design-system.css +0 -1959
- package/src/runtime/routes/canonical-guardian-expiry-sweep.ts +0 -129
- package/src/runtime/routes/inbound-stages/escalation-intercept.test.ts +0 -158
- package/src/runtime/routes/inbound-stages/escalation-intercept.ts +0 -187
- package/src/runtime/routes/memory-worker-routes.ts +0 -227
- package/src/runtime/routes/task-routes.ts +0 -380
- package/src/runtime/routes/work-items-routes.test.ts +0 -75
- package/src/runtime/routes/work-items-routes.ts +0 -881
- package/src/runtime/skill-route-registry.ts +0 -131
- package/src/tasks/SPEC.md +0 -132
- package/src/tasks/task-compiler.ts +0 -194
- package/src/tasks/task-runner.ts +0 -119
- package/src/tasks/task-scheduler.ts +0 -22
- package/src/tasks/task-store.ts +0 -153
- package/src/tasks/tool-sanitizer.ts +0 -38
- package/src/tools/apps/registry.ts +0 -16
- package/src/tools/system/register.ts +0 -9
- package/src/tools/tasks/task-delete.ts +0 -121
- package/src/tools/tasks/task-list.ts +0 -56
- package/src/tools/tasks/task-run.ts +0 -122
- package/src/tools/tasks/task-save.ts +0 -64
- package/src/tools/tasks/work-item-enqueue.ts +0 -300
- package/src/tools/tasks/work-item-list.ts +0 -65
- package/src/tools/tasks/work-item-remove.ts +0 -94
- package/src/tools/tasks/work-item-run.ts +0 -99
- package/src/tools/tasks/work-item-update.ts +0 -166
- package/src/tools/ui-surface/registry.ts +0 -14
- package/src/work-items/resolve-required-tools.test.ts +0 -37
- package/src/work-items/resolve-required-tools.ts +0 -26
- package/src/work-items/work-item-runner.ts +0 -187
- package/src/work-items/work-item-store.ts +0 -416
|
@@ -0,0 +1,1365 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Chat-persist credential redaction — sentinel markers with proven vault
|
|
3
|
+
* identity (LUM-2768).
|
|
4
|
+
*
|
|
5
|
+
* The legacy persist path replaces detected secrets with a dead
|
|
6
|
+
* `<redacted type="…" />` HTML marker that the chat markdown pipeline (no
|
|
7
|
+
* rehype-raw) can never render as an element, and that carries no way back
|
|
8
|
+
* to the vault entry it came from. This module implements the replacement
|
|
9
|
+
* behavior behind the `chat-credential-reveal` feature flag:
|
|
10
|
+
*
|
|
11
|
+
* 1. While a turn runs, every shell-style tool command is scanned for
|
|
12
|
+
* `credentials reveal` invocations and the referenced credentials
|
|
13
|
+
* (`--service`/`--field` flags or a positional UUID) are recorded as
|
|
14
|
+
* *candidates* ({@link collectRevealRefsFromCommand}).
|
|
15
|
+
* 2. At persist time, only those candidates' plaintexts are fetched from
|
|
16
|
+
* the credential store — a scoped read of secrets that were already in
|
|
17
|
+
* daemon memory this turn via the reveal stdout, never a vault scan
|
|
18
|
+
* ({@link resolveRevealCandidates}).
|
|
19
|
+
* 3. Each redacted span in persisted ASSISTANT TEXT is byte-compared
|
|
20
|
+
* against the candidate values (tool_result content keeps the legacy
|
|
21
|
+
* marker until the tool detail panel can render chips — see
|
|
22
|
+
* `buildToolResultBlocks`).
|
|
23
|
+
* An exact match *proves* the span is that credential, so the sentinel
|
|
24
|
+
* is enriched with `service:field` and the client can offer
|
|
25
|
+
* click-to-reveal. Anything else — hand-typed secrets, parse failures,
|
|
26
|
+
* value drift — degrades to a plain type-only sentinel that renders as
|
|
27
|
+
* a static badge ({@link redactSecretsForChat}).
|
|
28
|
+
*
|
|
29
|
+
* Failure direction is the design's spine: a wrong `service:field` would let
|
|
30
|
+
* the client reveal the *wrong* secret, so enrichment only ever happens on
|
|
31
|
+
* exact plaintext equality. Every uncertain path fails toward "badge",
|
|
32
|
+
* never toward "mislabeled chip".
|
|
33
|
+
*
|
|
34
|
+
* The legacy marker path (`redactSecrets`) is untouched — ingress blocking,
|
|
35
|
+
* tool-output scanning, and log redaction depend on its exact shape (see
|
|
36
|
+
* `security/AGENTS.md`). This module is additive and chat-persist-only.
|
|
37
|
+
*/
|
|
38
|
+
|
|
39
|
+
import {
|
|
40
|
+
buildRedactedSentinel,
|
|
41
|
+
createRedactedSentinelRegex,
|
|
42
|
+
decodeRedactedSentinelMatch,
|
|
43
|
+
neutralizeRedactedSentinels,
|
|
44
|
+
REDACTED_SENTINEL_CLOSE,
|
|
45
|
+
REDACTED_SENTINEL_OPEN,
|
|
46
|
+
REDACTED_SENTINEL_TAG,
|
|
47
|
+
} from "@vellumai/service-contracts/redacted-credential";
|
|
48
|
+
|
|
49
|
+
import { revealedValuesSince } from "../runtime/reveal-success-registry.js";
|
|
50
|
+
import { credentialKey } from "../security/credential-key.js";
|
|
51
|
+
import {
|
|
52
|
+
redactSecrets,
|
|
53
|
+
redactSecretsWith,
|
|
54
|
+
scanText,
|
|
55
|
+
} from "../security/secret-scanner.js";
|
|
56
|
+
import { getSecureKeyAsync } from "../security/secure-keys.js";
|
|
57
|
+
import { getCredentialMetadataById } from "../tools/credentials/metadata-store.js";
|
|
58
|
+
import { getLogger } from "../util/logger.js";
|
|
59
|
+
|
|
60
|
+
const log = getLogger("chat-credential-redaction");
|
|
61
|
+
|
|
62
|
+
// ---------------------------------------------------------------------------
|
|
63
|
+
// Types
|
|
64
|
+
// ---------------------------------------------------------------------------
|
|
65
|
+
|
|
66
|
+
/** A credential reference parsed from a `credentials reveal` invocation. */
|
|
67
|
+
export interface RevealCandidateRef {
|
|
68
|
+
service?: string;
|
|
69
|
+
field?: string;
|
|
70
|
+
/** Opaque credential UUID (positional CLI arg) — resolved to service/field. */
|
|
71
|
+
id?: string;
|
|
72
|
+
/**
|
|
73
|
+
* The plaintext the reveal route served for this ref, captured by
|
|
74
|
+
* {@link filterRefsByRevealProof} at proof time. When present, candidate
|
|
75
|
+
* resolution redacts these EXACT bytes and skips the vault read — so a
|
|
76
|
+
* rotation/deletion between reveal and persist can't make the redacted
|
|
77
|
+
* value diverge from what the tool actually printed. Absent only for refs
|
|
78
|
+
* that were never proven (which resolution drops anyway).
|
|
79
|
+
*/
|
|
80
|
+
provenValue?: string;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/** A candidate with its plaintext, ready for byte-comparison. */
|
|
84
|
+
export interface ResolvedRevealCandidate {
|
|
85
|
+
service: string;
|
|
86
|
+
field: string;
|
|
87
|
+
value: string;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// ---------------------------------------------------------------------------
|
|
91
|
+
// Command parsing
|
|
92
|
+
// ---------------------------------------------------------------------------
|
|
93
|
+
|
|
94
|
+
/** Matches `credentials reveal` with any CLI entry point (`assistant`, bin
|
|
95
|
+
* paths, …) — the two words are what identify the subcommand. */
|
|
96
|
+
const REVEAL_INVOCATION_RE = /\bcredentials\s+reveal\b/;
|
|
97
|
+
|
|
98
|
+
// An unquoted flag value ends at whitespace, a quote, or a shell
|
|
99
|
+
// metacharacter that can directly abut a word — `()`/backtick (command
|
|
100
|
+
// substitution) and `<>` (redirection) — so `--field token)` inside
|
|
101
|
+
// `$(… reveal --field token)` parses as `token`, the identity the route
|
|
102
|
+
// records proof under. Separators (`|&;`) never reach a segment unescaped;
|
|
103
|
+
// the splitter consumed them. A backslash escapes the next character.
|
|
104
|
+
const FLAG_VALUE = String.raw`(?:"((?:\\.|[^"\\])*)"|'([^']*)'|((?:\\.|[^\s"'\\()<>\`])+))`;
|
|
105
|
+
const SERVICE_FLAG_RE = new RegExp(String.raw`--service(?:=|\s+)${FLAG_VALUE}`);
|
|
106
|
+
const FIELD_FLAG_RE = new RegExp(String.raw`--field(?:=|\s+)${FLAG_VALUE}`);
|
|
107
|
+
const UUID_RE =
|
|
108
|
+
/\b[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\b/i;
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Extract a flag's value from a segment, undoing the shell quoting the
|
|
112
|
+
* value arrived with. The shell strips quoting before the CLI sees argv,
|
|
113
|
+
* so the reveal route records proof under the UNESCAPED identity —
|
|
114
|
+
* `--service foo\ bar` must parse to `foo bar` or the staged ref can never
|
|
115
|
+
* match its proof and the reveal's output would bypass the guard and the
|
|
116
|
+
* candidate fallback. Single quotes are literal; a backslash escapes the
|
|
117
|
+
* next character when unquoted, and inside double quotes it escapes only
|
|
118
|
+
* the characters POSIX lets it escape that can appear in an identifier
|
|
119
|
+
* (`"` and `\` — the rest stay literal, matching the shell).
|
|
120
|
+
*/
|
|
121
|
+
function flagValue(segment: string, re: RegExp): string | undefined {
|
|
122
|
+
const m = re.exec(segment);
|
|
123
|
+
if (!m) {
|
|
124
|
+
return undefined;
|
|
125
|
+
}
|
|
126
|
+
if (m[1] !== undefined) {
|
|
127
|
+
return m[1].replace(/\\(["\\])/g, "$1");
|
|
128
|
+
}
|
|
129
|
+
if (m[2] !== undefined) {
|
|
130
|
+
return m[2];
|
|
131
|
+
}
|
|
132
|
+
return m[3]?.replace(/\\(.)/g, "$1");
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Split a shell command into segments at UNQUOTED separators (`&&`, `||`,
|
|
137
|
+
* `;`, `|`, a lone `&`, newline). A separator inside a quoted flag value —
|
|
138
|
+
* `--service 'R&D'` — is part of the value, and cutting there would orphan
|
|
139
|
+
* the invocation's flags across two broken segments: no ref gets staged,
|
|
140
|
+
* so the reveal's output could stream or persist raw even though the route
|
|
141
|
+
* served it. Minimal POSIX-ish quoting: single quotes are literal to the
|
|
142
|
+
* next single quote, double quotes honor backslash escapes, and a
|
|
143
|
+
* backslash outside quotes escapes the next character. An unterminated
|
|
144
|
+
* quote runs to the end of the string — the remainder stays one segment,
|
|
145
|
+
* which at worst stages nothing (the same safe direction as before).
|
|
146
|
+
* Quotes are kept in the output so the flag regexes see them.
|
|
147
|
+
*/
|
|
148
|
+
function splitShellSegments(command: string): string[] {
|
|
149
|
+
const segments: string[] = [];
|
|
150
|
+
let current = "";
|
|
151
|
+
let quote: "'" | '"' | undefined;
|
|
152
|
+
let i = 0;
|
|
153
|
+
while (i < command.length) {
|
|
154
|
+
const ch = command[i]!;
|
|
155
|
+
if (quote === "'") {
|
|
156
|
+
current += ch;
|
|
157
|
+
if (ch === "'") {
|
|
158
|
+
quote = undefined;
|
|
159
|
+
}
|
|
160
|
+
i += 1;
|
|
161
|
+
continue;
|
|
162
|
+
}
|
|
163
|
+
if (quote === '"') {
|
|
164
|
+
if (ch === "\\" && i + 1 < command.length) {
|
|
165
|
+
current += ch + command[i + 1];
|
|
166
|
+
i += 2;
|
|
167
|
+
continue;
|
|
168
|
+
}
|
|
169
|
+
current += ch;
|
|
170
|
+
if (ch === '"') {
|
|
171
|
+
quote = undefined;
|
|
172
|
+
}
|
|
173
|
+
i += 1;
|
|
174
|
+
continue;
|
|
175
|
+
}
|
|
176
|
+
if (ch === "\\" && i + 1 < command.length) {
|
|
177
|
+
current += ch + command[i + 1];
|
|
178
|
+
i += 2;
|
|
179
|
+
continue;
|
|
180
|
+
}
|
|
181
|
+
if (ch === "'" || ch === '"') {
|
|
182
|
+
quote = ch;
|
|
183
|
+
current += ch;
|
|
184
|
+
i += 1;
|
|
185
|
+
continue;
|
|
186
|
+
}
|
|
187
|
+
if (ch === "&" || ch === "|") {
|
|
188
|
+
// `&&`/`||` consume both characters so the two-char operator is one
|
|
189
|
+
// boundary; a lone `&` (background job separator) or `|` splits too,
|
|
190
|
+
// so a second backgrounded/piped reveal is staged rather than
|
|
191
|
+
// swallowed into the first segment.
|
|
192
|
+
segments.push(current);
|
|
193
|
+
current = "";
|
|
194
|
+
i += command[i + 1] === ch ? 2 : 1;
|
|
195
|
+
continue;
|
|
196
|
+
}
|
|
197
|
+
if (ch === ";" || ch === "\n") {
|
|
198
|
+
segments.push(current);
|
|
199
|
+
current = "";
|
|
200
|
+
i += 1;
|
|
201
|
+
continue;
|
|
202
|
+
}
|
|
203
|
+
current += ch;
|
|
204
|
+
i += 1;
|
|
205
|
+
}
|
|
206
|
+
segments.push(current);
|
|
207
|
+
return segments;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Extract credential refs from a shell command that may contain one or more
|
|
212
|
+
* `credentials reveal` invocations (compound commands split on shell
|
|
213
|
+
* separators). Returns an empty array for commands that never touch reveal —
|
|
214
|
+
* the hot-path cost is one substring check.
|
|
215
|
+
*
|
|
216
|
+
* The parse is deliberately conservative: it recognizes the documented CLI
|
|
217
|
+
* shapes (`--service X --field Y`, `--service=X`, quoted values, positional
|
|
218
|
+
* UUID) and nothing else. An unparseable invocation yields no ref, which
|
|
219
|
+
* downstream means no candidate and a plain (non-revealable) sentinel —
|
|
220
|
+
* the safe direction.
|
|
221
|
+
*/
|
|
222
|
+
/**
|
|
223
|
+
* Slice a segment at each `credentials reveal` occurrence: one slice per
|
|
224
|
+
* invocation, running from the invocation to the start of the next.
|
|
225
|
+
* Command substitution can nest several reveals in ONE segment —
|
|
226
|
+
* `echo "$(… reveal --service a …)" "$(… reveal --service b …)"` — and
|
|
227
|
+
* parsing only the segment's first flag pair would leave every later
|
|
228
|
+
* identity unstaged even though the route proves each one, so their
|
|
229
|
+
* opaque values could stream and persist raw.
|
|
230
|
+
*/
|
|
231
|
+
function revealInvocationSlices(segment: string): string[] {
|
|
232
|
+
const re = new RegExp(REVEAL_INVOCATION_RE, "g");
|
|
233
|
+
const starts: number[] = [];
|
|
234
|
+
for (let m = re.exec(segment); m !== null; m = re.exec(segment)) {
|
|
235
|
+
starts.push(m.index);
|
|
236
|
+
}
|
|
237
|
+
return starts.map((start, i) => segment.slice(start, starts[i + 1]));
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
export function collectRevealRefsFromCommand(
|
|
241
|
+
command: string,
|
|
242
|
+
): RevealCandidateRef[] {
|
|
243
|
+
if (!command.includes("reveal")) {
|
|
244
|
+
return [];
|
|
245
|
+
}
|
|
246
|
+
const refs: RevealCandidateRef[] = [];
|
|
247
|
+
// Split compound commands so flags from one invocation can't bleed into
|
|
248
|
+
// another (`reveal --service a --field b && reveal --service c --field d`),
|
|
249
|
+
// honoring quotes so a separator inside a flag value can't cut the
|
|
250
|
+
// invocation apart (see `splitShellSegments`). Within a segment, each
|
|
251
|
+
// reveal invocation is parsed from its own slice (see
|
|
252
|
+
// `revealInvocationSlices`).
|
|
253
|
+
const segments = splitShellSegments(command);
|
|
254
|
+
for (const segment of segments) {
|
|
255
|
+
if (!REVEAL_INVOCATION_RE.test(segment)) {
|
|
256
|
+
continue;
|
|
257
|
+
}
|
|
258
|
+
for (const invocation of revealInvocationSlices(segment)) {
|
|
259
|
+
const service = flagValue(invocation, SERVICE_FLAG_RE);
|
|
260
|
+
const field = flagValue(invocation, FIELD_FLAG_RE);
|
|
261
|
+
if (service !== undefined && field !== undefined) {
|
|
262
|
+
refs.push({ service, field });
|
|
263
|
+
continue;
|
|
264
|
+
}
|
|
265
|
+
const id = UUID_RE.exec(invocation)?.[0];
|
|
266
|
+
if (id) {
|
|
267
|
+
refs.push({ id });
|
|
268
|
+
}
|
|
269
|
+
// No parseable identity → no ref. The span still gets redacted at
|
|
270
|
+
// persist; it just won't be revealable.
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
return refs;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* Resolve id-form refs to service/field EAGERLY, at staging time — a
|
|
278
|
+
* metadata-only lookup, no secret access. Proof runs when the tool result
|
|
279
|
+
* arrives, which can be well after the reveal executed, and a `credentials
|
|
280
|
+
* remove` in between (even in the same compound command) makes the id
|
|
281
|
+
* unresolvable then — dropping the ref although the route recorded the
|
|
282
|
+
* success, so the printed value would persist raw. The id stays on the ref
|
|
283
|
+
* so proof time can still fall back to a lookup for a ref whose metadata
|
|
284
|
+
* only appears later.
|
|
285
|
+
*/
|
|
286
|
+
export function resolveRefIdentities(
|
|
287
|
+
refs: readonly RevealCandidateRef[],
|
|
288
|
+
): RevealCandidateRef[] {
|
|
289
|
+
return refs.map((ref) => {
|
|
290
|
+
if (
|
|
291
|
+
ref.id === undefined ||
|
|
292
|
+
(ref.service !== undefined && ref.field !== undefined)
|
|
293
|
+
) {
|
|
294
|
+
return ref;
|
|
295
|
+
}
|
|
296
|
+
try {
|
|
297
|
+
const meta = getCredentialMetadataById(ref.id);
|
|
298
|
+
if (!meta) {
|
|
299
|
+
return ref;
|
|
300
|
+
}
|
|
301
|
+
return { ...ref, service: meta.service, field: meta.field };
|
|
302
|
+
} catch (err) {
|
|
303
|
+
log.debug(
|
|
304
|
+
{ err },
|
|
305
|
+
"eager reveal ref id lookup failed; deferring to proof time",
|
|
306
|
+
);
|
|
307
|
+
return ref;
|
|
308
|
+
}
|
|
309
|
+
});
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
/**
|
|
313
|
+
* Keep only refs whose identity the reveal route ACTUALLY served after
|
|
314
|
+
* `watermark` was captured (see `reveal-success-registry`). A shell tool's
|
|
315
|
+
* overall success is not proof the nested reveal ran — `reveal … || true`
|
|
316
|
+
* succeeds when the route failed, and an `echo` containing the command
|
|
317
|
+
* text never calls the route — so candidate resolution (which reads
|
|
318
|
+
* plaintext from the store) must be gated on the route's own record.
|
|
319
|
+
* Identity for id-form refs is normally captured at staging (see
|
|
320
|
+
* `resolveRefIdentities`); the metadata lookup here is only a fallback —
|
|
321
|
+
* metadata-only, no secret access. Unresolvable refs are dropped — the
|
|
322
|
+
* safe direction.
|
|
323
|
+
*/
|
|
324
|
+
export function filterRefsByRevealProof(
|
|
325
|
+
refs: readonly RevealCandidateRef[],
|
|
326
|
+
watermark: number,
|
|
327
|
+
nonce: string,
|
|
328
|
+
): RevealCandidateRef[] {
|
|
329
|
+
const proven: RevealCandidateRef[] = [];
|
|
330
|
+
const seenValues = new Set<string>();
|
|
331
|
+
for (const ref of refs) {
|
|
332
|
+
let service = ref.service;
|
|
333
|
+
let field = ref.field;
|
|
334
|
+
if (
|
|
335
|
+
ref.id !== undefined &&
|
|
336
|
+
(service === undefined || field === undefined)
|
|
337
|
+
) {
|
|
338
|
+
try {
|
|
339
|
+
const meta = getCredentialMetadataById(ref.id);
|
|
340
|
+
if (!meta) {
|
|
341
|
+
continue;
|
|
342
|
+
}
|
|
343
|
+
service = meta.service;
|
|
344
|
+
field = meta.field;
|
|
345
|
+
} catch (err) {
|
|
346
|
+
log.debug({ err }, "reveal proof id lookup failed; dropping ref");
|
|
347
|
+
continue;
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
if (service === undefined || field === undefined) {
|
|
351
|
+
continue;
|
|
352
|
+
}
|
|
353
|
+
// Capture the plaintexts the route served alongside the proof, in the
|
|
354
|
+
// same lookup. Resolving service/field here too means each returned ref
|
|
355
|
+
// is fully self-describing, so candidate resolution needs neither the
|
|
356
|
+
// metadata lookup nor the vault read again. One ref PER DISTINCT VALUE:
|
|
357
|
+
// a rotate-and-re-reveal inside the window (reveal v1 → `credentials
|
|
358
|
+
// set` → reveal v2) prints two different plaintexts to stdout, and
|
|
359
|
+
// keeping only one would let the other persist raw whenever the scanner
|
|
360
|
+
// cannot classify it.
|
|
361
|
+
for (const provenValue of revealedValuesSince(
|
|
362
|
+
watermark,
|
|
363
|
+
service,
|
|
364
|
+
field,
|
|
365
|
+
nonce,
|
|
366
|
+
)) {
|
|
367
|
+
const dedupeKey = JSON.stringify([service, field, provenValue]);
|
|
368
|
+
if (seenValues.has(dedupeKey)) {
|
|
369
|
+
continue;
|
|
370
|
+
}
|
|
371
|
+
seenValues.add(dedupeKey);
|
|
372
|
+
proven.push({ service, field, provenValue });
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
return proven;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
// ---------------------------------------------------------------------------
|
|
379
|
+
// Candidate resolution (scoped store read)
|
|
380
|
+
// ---------------------------------------------------------------------------
|
|
381
|
+
|
|
382
|
+
/**
|
|
383
|
+
* Minimum plaintext length for the exact-match candidate fallback. The
|
|
384
|
+
* fallback replaces EVERY occurrence of a candidate's bytes across the
|
|
385
|
+
* rest of the turn — assistant text, tool output, persisted rows — and
|
|
386
|
+
* the set route validates values only as non-empty, so a credential whose
|
|
387
|
+
* plaintext is `1` or `ok` would turn every unrelated occurrence of that
|
|
388
|
+
* token into a marker/chip. Below this floor the value carries too little
|
|
389
|
+
* entropy for redaction to protect anything, while the false-positive
|
|
390
|
+
* damage is guaranteed; at or above it, collisions with incidental text
|
|
391
|
+
* are rare. Scanner-recognized secrets are unaffected — every scanner
|
|
392
|
+
* pattern requires far longer tokens than this.
|
|
393
|
+
*/
|
|
394
|
+
const MIN_CANDIDATE_VALUE_LENGTH = 6;
|
|
395
|
+
|
|
396
|
+
/**
|
|
397
|
+
* Append a candidate in EVERY stdout encoding of its plaintext, deduped on
|
|
398
|
+
* identity+value. `credentials reveal --json` writes the value through
|
|
399
|
+
* `JSON.stringify` (see the CLI's `writeOutput`), so a value containing
|
|
400
|
+
* quotes, backslashes, or control characters — a PEM key's newlines —
|
|
401
|
+
* appears in the tool's stdout ESCAPED: bytes a raw exact-match list can
|
|
402
|
+
* never find, leaving the escaped body to the scanner (which only knows
|
|
403
|
+
* the header). Registering the escaped encoding as its own candidate
|
|
404
|
+
* covers that representation on every surface (persist byte-compare, live
|
|
405
|
+
* guard, tool_result fallback, output chunks). `JSON.stringify` is
|
|
406
|
+
* injective on the value, so an escaped-form match proves the same
|
|
407
|
+
* identity the raw form does.
|
|
408
|
+
*/
|
|
409
|
+
function appendCandidateEncodings(
|
|
410
|
+
candidates: ResolvedRevealCandidate[],
|
|
411
|
+
seen: Set<string>,
|
|
412
|
+
service: string,
|
|
413
|
+
field: string,
|
|
414
|
+
value: string,
|
|
415
|
+
): void {
|
|
416
|
+
for (const encoded of [value, JSON.stringify(value).slice(1, -1)]) {
|
|
417
|
+
if (encoded.length < MIN_CANDIDATE_VALUE_LENGTH) {
|
|
418
|
+
continue;
|
|
419
|
+
}
|
|
420
|
+
const dedupeKey = JSON.stringify([service, field, encoded]);
|
|
421
|
+
if (seen.has(dedupeKey)) {
|
|
422
|
+
continue;
|
|
423
|
+
}
|
|
424
|
+
seen.add(dedupeKey);
|
|
425
|
+
candidates.push({ service, field, value: encoded });
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
/**
|
|
430
|
+
* Sync twin of {@link resolveRevealCandidates} for refs that already carry
|
|
431
|
+
* a proven value (the shape `filterRefsByRevealProof` returns) — used on
|
|
432
|
+
* the live tool-output path, whose handler must stay synchronous and may
|
|
433
|
+
* not touch the vault. Refs without a proven value are skipped, never
|
|
434
|
+
* vault-resolved.
|
|
435
|
+
*/
|
|
436
|
+
export function resolveProvenRevealCandidates(
|
|
437
|
+
refs: readonly RevealCandidateRef[],
|
|
438
|
+
): ResolvedRevealCandidate[] {
|
|
439
|
+
const seen = new Set<string>();
|
|
440
|
+
const candidates: ResolvedRevealCandidate[] = [];
|
|
441
|
+
for (const ref of refs) {
|
|
442
|
+
if (ref.service === undefined || ref.field === undefined) {
|
|
443
|
+
continue;
|
|
444
|
+
}
|
|
445
|
+
if (ref.provenValue === undefined || ref.provenValue.length === 0) {
|
|
446
|
+
continue;
|
|
447
|
+
}
|
|
448
|
+
appendCandidateEncodings(
|
|
449
|
+
candidates,
|
|
450
|
+
seen,
|
|
451
|
+
ref.service,
|
|
452
|
+
ref.field,
|
|
453
|
+
ref.provenValue,
|
|
454
|
+
);
|
|
455
|
+
}
|
|
456
|
+
return candidates;
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
/**
|
|
460
|
+
* Resolve parsed refs to `{service, field, value}` candidates via scoped
|
|
461
|
+
* credential-store reads — one `getSecureKeyAsync` per distinct ref, only
|
|
462
|
+
* for credentials a reveal command in this turn already named. Refs that
|
|
463
|
+
* fail to resolve (unknown id, missing secret, store unreachable) are
|
|
464
|
+
* dropped; the corresponding spans persist as plain sentinels.
|
|
465
|
+
*/
|
|
466
|
+
export async function resolveRevealCandidates(
|
|
467
|
+
refs: readonly RevealCandidateRef[],
|
|
468
|
+
): Promise<ResolvedRevealCandidate[]> {
|
|
469
|
+
// Candidates dedupe on identity AND value, not identity alone: the same
|
|
470
|
+
// credential legitimately yields two candidates when it was rotated and
|
|
471
|
+
// re-revealed within one turn — both plaintexts hit a tool's stdout, so
|
|
472
|
+
// both must stay redactable. Vault reads still dedupe per identity (one
|
|
473
|
+
// scoped read per credential, and the vault only has the current value).
|
|
474
|
+
const seenCandidates = new Set<string>();
|
|
475
|
+
const vaultReadKeys = new Set<string>();
|
|
476
|
+
const candidates: ResolvedRevealCandidate[] = [];
|
|
477
|
+
const pushCandidate = (service: string, field: string, value: string) =>
|
|
478
|
+
appendCandidateEncodings(candidates, seenCandidates, service, field, value);
|
|
479
|
+
for (const ref of refs) {
|
|
480
|
+
let service = ref.service;
|
|
481
|
+
let field = ref.field;
|
|
482
|
+
if (
|
|
483
|
+
ref.id !== undefined &&
|
|
484
|
+
(service === undefined || field === undefined)
|
|
485
|
+
) {
|
|
486
|
+
try {
|
|
487
|
+
const meta = getCredentialMetadataById(ref.id);
|
|
488
|
+
if (!meta) {
|
|
489
|
+
continue;
|
|
490
|
+
}
|
|
491
|
+
service = meta.service;
|
|
492
|
+
field = meta.field;
|
|
493
|
+
} catch (err) {
|
|
494
|
+
log.debug({ err }, "reveal candidate id lookup failed; skipping");
|
|
495
|
+
continue;
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
if (service === undefined || field === undefined) {
|
|
499
|
+
continue;
|
|
500
|
+
}
|
|
501
|
+
// Prefer the plaintext the reveal route actually served (captured at proof
|
|
502
|
+
// time). Re-reading the vault here would return a rotated or deleted
|
|
503
|
+
// value, so the guard/fallback could miss the exact bytes the tool
|
|
504
|
+
// printed. Only fall back to a store read when a ref carries no proven
|
|
505
|
+
// value — which today means it was constructed outside the proof path.
|
|
506
|
+
if (ref.provenValue !== undefined && ref.provenValue.length > 0) {
|
|
507
|
+
pushCandidate(service, field, ref.provenValue);
|
|
508
|
+
continue;
|
|
509
|
+
}
|
|
510
|
+
const key = credentialKey(service, field);
|
|
511
|
+
if (vaultReadKeys.has(key)) {
|
|
512
|
+
continue;
|
|
513
|
+
}
|
|
514
|
+
vaultReadKeys.add(key);
|
|
515
|
+
try {
|
|
516
|
+
const value = await getSecureKeyAsync(key);
|
|
517
|
+
if (value != null && value.length > 0) {
|
|
518
|
+
pushCandidate(service, field, value);
|
|
519
|
+
}
|
|
520
|
+
} catch (err) {
|
|
521
|
+
// Store unreachable → no candidate → plain sentinel. Never block or
|
|
522
|
+
// fail the persist over enrichment.
|
|
523
|
+
log.debug(
|
|
524
|
+
{ err, service, field },
|
|
525
|
+
"reveal candidate fetch failed; span will persist as plain sentinel",
|
|
526
|
+
);
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
return candidates;
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
// ---------------------------------------------------------------------------
|
|
533
|
+
// Daemon-minted sentinels and re-mint authority
|
|
534
|
+
// ---------------------------------------------------------------------------
|
|
535
|
+
|
|
536
|
+
/**
|
|
537
|
+
* A sentinel identity the guard may restore to its canonical daemon-minted
|
|
538
|
+
* form. Sources: route-recorded `--for-chat` mints (validated against the
|
|
539
|
+
* conversation's reveal nonce and staged identities by the agent loop
|
|
540
|
+
* before they reach the guard) and candidates proven by this
|
|
541
|
+
* conversation's own plain reveals (nonce-bound at the proof registry).
|
|
542
|
+
*/
|
|
543
|
+
export interface SentinelRemintAuthority {
|
|
544
|
+
service: string;
|
|
545
|
+
field: string;
|
|
546
|
+
/** The canonical sentinel to restore for this identity. */
|
|
547
|
+
sentinel: string;
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
/**
|
|
551
|
+
* The canonical daemon-minted sentinel for a credential. Used by the
|
|
552
|
+
* `--for-chat` reveal channel (the route returns this instead of the
|
|
553
|
+
* plaintext) and by candidate-derived re-mint authorities, so every path
|
|
554
|
+
* that mints for the same identity produces the same bytes. The type label
|
|
555
|
+
* comes from the same anchored scanner classification persist-time
|
|
556
|
+
* enrichment uses ({@link classifyCandidateType}).
|
|
557
|
+
*/
|
|
558
|
+
export function buildForChatSentinel(candidate: {
|
|
559
|
+
service: string;
|
|
560
|
+
field: string;
|
|
561
|
+
value: string;
|
|
562
|
+
}): string {
|
|
563
|
+
return buildRedactedSentinel({
|
|
564
|
+
type: classifyCandidateType(candidate.value),
|
|
565
|
+
service: candidate.service,
|
|
566
|
+
field: candidate.field,
|
|
567
|
+
});
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
/**
|
|
571
|
+
* Re-mint authorities derived from this turn's PROVEN reveal candidates.
|
|
572
|
+
*
|
|
573
|
+
* A model that legitimately revealed a credential this turn holds its
|
|
574
|
+
* plaintext and could echo it verbatim — which the guards would swap into
|
|
575
|
+
* a genuine enriched sentinel. When it instead retypes the sentinel it saw
|
|
576
|
+
* in its (redacted) history, byte-equality can't prove anything, but the
|
|
577
|
+
* identity can: a sentinel-shaped span claiming a `service:field` that was
|
|
578
|
+
* proven this turn grants nothing a plaintext echo wouldn't. Re-minting it
|
|
579
|
+
* (from the candidate's own metadata — never from the span's claimed
|
|
580
|
+
* label) turns the confusing neutralized-glyph outcome into the same chip
|
|
581
|
+
* the echo would have produced. Unproven identities still neutralize.
|
|
582
|
+
*
|
|
583
|
+
* Deduped by identity: encoding-variant candidates share a service:field
|
|
584
|
+
* and must not produce duplicate authorities. Conversation scoping is
|
|
585
|
+
* inherent — candidates live in per-run state that only this
|
|
586
|
+
* conversation's own proven reveals populate.
|
|
587
|
+
*/
|
|
588
|
+
export function remintAuthoritiesFromCandidates(
|
|
589
|
+
candidates: readonly ResolvedRevealCandidate[],
|
|
590
|
+
): SentinelRemintAuthority[] {
|
|
591
|
+
const byIdentity = new Map<string, SentinelRemintAuthority>();
|
|
592
|
+
for (const candidate of candidates) {
|
|
593
|
+
const key = credentialKey(candidate.service, candidate.field);
|
|
594
|
+
if (byIdentity.has(key)) {
|
|
595
|
+
continue;
|
|
596
|
+
}
|
|
597
|
+
byIdentity.set(key, {
|
|
598
|
+
service: candidate.service,
|
|
599
|
+
field: candidate.field,
|
|
600
|
+
sentinel: buildForChatSentinel(candidate),
|
|
601
|
+
});
|
|
602
|
+
}
|
|
603
|
+
return [...byIdentity.values()];
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
/**
|
|
607
|
+
* Sentinel guard for text that may legitimately contain daemon-minted
|
|
608
|
+
* sentinels. The forgery-guard spine assumes raw model output never
|
|
609
|
+
* carries a genuine sentinel — with two sanctioned, turn-scoped
|
|
610
|
+
* exceptions, both keyed by identity rather than trust in the span's own
|
|
611
|
+
* bytes:
|
|
612
|
+
*
|
|
613
|
+
* 1. `--for-chat` reveals: the daemon returned the sentinel as the reveal
|
|
614
|
+
* tool's result precisely so the model can copy it into its reply
|
|
615
|
+
* without ever touching the plaintext. Authority is the mint the
|
|
616
|
+
* reveal ROUTE recorded this turn ({@link ForChatMint}) — the route is
|
|
617
|
+
* the sole authority; a string parse of the requested command is not,
|
|
618
|
+
* because a segment that merely quotes a reveal invocation never
|
|
619
|
+
* executed one.
|
|
620
|
+
* 2. Proven plain-reveal candidates
|
|
621
|
+
* ({@link remintAuthoritiesFromCandidates}): the model provably held
|
|
622
|
+
* the plaintext this turn, so a span claiming that identity grants
|
|
623
|
+
* nothing a verbatim echo wouldn't.
|
|
624
|
+
*
|
|
625
|
+
* An enriched span whose decoded `service:field` identity matches an
|
|
626
|
+
* authority is RE-MINTED — replaced with the daemon's own canonical
|
|
627
|
+
* sentinel, so a tampered type label can't survive. Everything else —
|
|
628
|
+
* plain shapes, unknown identities, malformed escapes, partial triggers —
|
|
629
|
+
* flows through `transformRest` (neutralization by default; the persist
|
|
630
|
+
* path passes its neutralize+scan pipeline so a secret embedded inside a
|
|
631
|
+
* forged sentinel is still caught). Re-minted spans are inserted verbatim
|
|
632
|
+
* and never fed to `transformRest`: a minted sentinel is not
|
|
633
|
+
* scanner-inert — its service/field segments are arbitrary user strings
|
|
634
|
+
* that can look secret-shaped, and a scanner rewrite inside a fresh
|
|
635
|
+
* sentinel would corrupt it. With no authorities this is byte-identical
|
|
636
|
+
* to `transformRest(text)`.
|
|
637
|
+
*/
|
|
638
|
+
export function guardForChatSentinels(
|
|
639
|
+
text: string,
|
|
640
|
+
mints: readonly SentinelRemintAuthority[],
|
|
641
|
+
transformRest: (segment: string) => string = neutralizeRedactedSentinels,
|
|
642
|
+
): string {
|
|
643
|
+
if (mints.length === 0) {
|
|
644
|
+
return transformRest(text);
|
|
645
|
+
}
|
|
646
|
+
const re = createRedactedSentinelRegex();
|
|
647
|
+
let out = "";
|
|
648
|
+
let last = 0;
|
|
649
|
+
for (const m of text.matchAll(re)) {
|
|
650
|
+
const decoded = decodeRedactedSentinelMatch(m as RegExpExecArray);
|
|
651
|
+
const hit =
|
|
652
|
+
decoded.service !== undefined && decoded.field !== undefined
|
|
653
|
+
? mints.find(
|
|
654
|
+
(c) => c.service === decoded.service && c.field === decoded.field,
|
|
655
|
+
)
|
|
656
|
+
: undefined;
|
|
657
|
+
// Between-match text can still hold partial/malformed triggers —
|
|
658
|
+
// transform it segment-wise. Splits are at full-sentinel boundaries,
|
|
659
|
+
// so no trigger ever spans a segment edge.
|
|
660
|
+
out += transformRest(text.slice(last, m.index));
|
|
661
|
+
out += hit ? hit.sentinel : transformRest(m[0]);
|
|
662
|
+
last = m.index + m[0].length;
|
|
663
|
+
}
|
|
664
|
+
out += transformRest(text.slice(last));
|
|
665
|
+
return out;
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
// ---------------------------------------------------------------------------
|
|
669
|
+
// Live-stream forgery guard
|
|
670
|
+
// ---------------------------------------------------------------------------
|
|
671
|
+
|
|
672
|
+
const SENTINEL_TRIGGER = `${REDACTED_SENTINEL_OPEN}${REDACTED_SENTINEL_TAG}:`;
|
|
673
|
+
|
|
674
|
+
/**
|
|
675
|
+
* A reveal candidate prepared for live-stream substitution: when the model
|
|
676
|
+
* echoes `value` into its reply, the stream guard swaps it for `replacement`
|
|
677
|
+
* (the same enriched sentinel persist-time redaction produces) before the
|
|
678
|
+
* bytes reach the wire — so the plaintext never flashes in the live
|
|
679
|
+
* transcript and never leaves the daemon.
|
|
680
|
+
*/
|
|
681
|
+
export interface LiveRevealGuardEntry {
|
|
682
|
+
/** Credential plaintext exactly as it would appear in model output. */
|
|
683
|
+
value: string;
|
|
684
|
+
/** The sentinel `redactSecretsForChat` produces for those bytes. */
|
|
685
|
+
replacement: string;
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
/**
|
|
689
|
+
* Fallback type label for a candidate whose bare value the scanner cannot
|
|
690
|
+
* classify on its own (context-sensitive patterns like `password=…`).
|
|
691
|
+
*/
|
|
692
|
+
const FALLBACK_SENTINEL_TYPE = "Credential";
|
|
693
|
+
|
|
694
|
+
/**
|
|
695
|
+
* Precompute live-swap entries from resolved reveal candidates. Every
|
|
696
|
+
* candidate gets an entry — the guard's job is to keep revealed plaintext
|
|
697
|
+
* off the wire, whatever the surrounding text turns out to be — and each
|
|
698
|
+
* replacement is exactly what persist-time redaction produces for the
|
|
699
|
+
* bare value, so the live stream and the persisted row agree
|
|
700
|
+
* byte-for-byte. `redactSecretsForChat` guarantees a candidate value
|
|
701
|
+
* never survives: the scanner classifies what it can, and the exact-match
|
|
702
|
+
* fallback (see `swapCandidateValueFallbacks`) covers the rest — opaque
|
|
703
|
+
* manual tokens and values the scanner only matches with surrounding
|
|
704
|
+
* context. The lone residual divergence: a contextual persist match
|
|
705
|
+
* (`password=<value>`) may carry a more specific type label than the
|
|
706
|
+
* bare-value chip streamed live — cosmetic, same service:field identity.
|
|
707
|
+
*/
|
|
708
|
+
export function buildLiveRevealGuardEntries(
|
|
709
|
+
candidates: readonly ResolvedRevealCandidate[],
|
|
710
|
+
): LiveRevealGuardEntry[] {
|
|
711
|
+
const entries: LiveRevealGuardEntry[] = [];
|
|
712
|
+
const seen = new Set<string>();
|
|
713
|
+
for (const candidate of candidates) {
|
|
714
|
+
// Dedupe by value — duplicate plaintexts would otherwise register two
|
|
715
|
+
// entries and split/join order would silently pick one.
|
|
716
|
+
if (seen.has(candidate.value)) {
|
|
717
|
+
continue;
|
|
718
|
+
}
|
|
719
|
+
seen.add(candidate.value);
|
|
720
|
+
// Derive the replacement against the FULL candidate list, exactly as
|
|
721
|
+
// the persist seam does: a value shared by two identities degrades to
|
|
722
|
+
// the plain sentinel there (see `uniqueCandidateForValue`), and the
|
|
723
|
+
// live swap must emit the same bytes or the streamed text and the
|
|
724
|
+
// persisted row would disagree on reconnect.
|
|
725
|
+
entries.push({
|
|
726
|
+
value: candidate.value,
|
|
727
|
+
replacement: redactSecretsForChat(candidate.value, candidates),
|
|
728
|
+
});
|
|
729
|
+
}
|
|
730
|
+
return entries;
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
/** A non-overlapping occurrence of a target in raw text. */
|
|
734
|
+
interface RawSpan {
|
|
735
|
+
start: number;
|
|
736
|
+
end: number;
|
|
737
|
+
/** Index into the caller's target list — lower = higher priority. */
|
|
738
|
+
priority: number;
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
/**
|
|
742
|
+
* Resolve the non-overlapping occurrence spans of `targets` in `text`:
|
|
743
|
+
* targets in priority order (list index — callers sort longest-first so a
|
|
744
|
+
* value that is a substring of another cannot pre-empt the larger match),
|
|
745
|
+
* each target's occurrences greedy left-to-right, skipping any occurrence
|
|
746
|
+
* that overlaps a higher-priority span. This is the single definition of
|
|
747
|
+
* the transforms' occurrence semantics: every swap in this module rebuilds
|
|
748
|
+
* its output from these spans, and the streaming stability analysis
|
|
749
|
+
* (`stableEmitLen`) resolves through the same function, so the two can
|
|
750
|
+
* never drift.
|
|
751
|
+
*
|
|
752
|
+
* `skipOccurrence` vetoes individual occurrences (the persist paths use it
|
|
753
|
+
* to let a longer scanner match outrank a candidate substring inside it —
|
|
754
|
+
* see `containedInLongerScannerMatch`); a vetoed occurrence is skipped
|
|
755
|
+
* exactly like an overlap loss, and scanning continues from the next byte.
|
|
756
|
+
*/
|
|
757
|
+
function resolveRawSpans(
|
|
758
|
+
text: string,
|
|
759
|
+
targets: readonly string[],
|
|
760
|
+
skipOccurrence?: (start: number, end: number) => boolean,
|
|
761
|
+
): RawSpan[] {
|
|
762
|
+
const spans: RawSpan[] = [];
|
|
763
|
+
targets.forEach((target, priority) => {
|
|
764
|
+
if (target.length === 0) {
|
|
765
|
+
return;
|
|
766
|
+
}
|
|
767
|
+
let scanFrom = 0;
|
|
768
|
+
for (;;) {
|
|
769
|
+
const idx = text.indexOf(target, scanFrom);
|
|
770
|
+
if (idx === -1) {
|
|
771
|
+
break;
|
|
772
|
+
}
|
|
773
|
+
const end = idx + target.length;
|
|
774
|
+
// An occurrence overlapping a higher-priority span loses to it — the
|
|
775
|
+
// higher-priority replacement consumes those bytes — but keep
|
|
776
|
+
// scanning from the next byte for a later disjoint occurrence.
|
|
777
|
+
if (
|
|
778
|
+
spans.some((s) => idx < s.end && end > s.start) ||
|
|
779
|
+
skipOccurrence?.(idx, end) === true
|
|
780
|
+
) {
|
|
781
|
+
scanFrom = idx + 1;
|
|
782
|
+
continue;
|
|
783
|
+
}
|
|
784
|
+
spans.push({ start: idx, end, priority });
|
|
785
|
+
scanFrom = end;
|
|
786
|
+
}
|
|
787
|
+
});
|
|
788
|
+
return spans;
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
/**
|
|
792
|
+
* Replace each resolved raw span with its target's replacement, rebuilding
|
|
793
|
+
* the string in ONE pass over the original text. A sequential
|
|
794
|
+
* split/join-per-target pass would re-scan earlier replacements — and a
|
|
795
|
+
* replacement sentinel is not inert text: it embeds type/service/field
|
|
796
|
+
* segments, so a candidate whose plaintext happens to equal another's
|
|
797
|
+
* service name (e.g. `openai`) would match inside the just-emitted
|
|
798
|
+
* sentinel and corrupt it into nested, unrenderable markers. Resolving all
|
|
799
|
+
* spans against the raw text first makes replacements structurally
|
|
800
|
+
* invisible to later targets.
|
|
801
|
+
*
|
|
802
|
+
* `transformGap` is applied to the text BETWEEN spans (and to the whole
|
|
803
|
+
* text when nothing matches). Callers use it for forgery neutralization:
|
|
804
|
+
* it must run over raw bytes the spans do not claim, and never over the
|
|
805
|
+
* spans themselves — a candidate value may itself contain the sentinel
|
|
806
|
+
* trigger, and mutating it before the exact-match resolution would break
|
|
807
|
+
* the strongest guarantee this module makes.
|
|
808
|
+
*/
|
|
809
|
+
function replaceRawSpans(
|
|
810
|
+
text: string,
|
|
811
|
+
targets: readonly string[],
|
|
812
|
+
replacementFor: (priority: number) => string,
|
|
813
|
+
transformGap: (segment: string) => string = (segment) => segment,
|
|
814
|
+
skipOccurrence?: (start: number, end: number) => boolean,
|
|
815
|
+
): string {
|
|
816
|
+
const spans = resolveRawSpans(text, targets, skipOccurrence);
|
|
817
|
+
if (spans.length === 0) {
|
|
818
|
+
return transformGap(text);
|
|
819
|
+
}
|
|
820
|
+
spans.sort((a, b) => a.start - b.start);
|
|
821
|
+
let out = "";
|
|
822
|
+
let cursor = 0;
|
|
823
|
+
for (const span of spans) {
|
|
824
|
+
out +=
|
|
825
|
+
transformGap(text.slice(cursor, span.start)) +
|
|
826
|
+
replacementFor(span.priority);
|
|
827
|
+
cursor = span.end;
|
|
828
|
+
}
|
|
829
|
+
return out + transformGap(text.slice(cursor));
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
/**
|
|
833
|
+
* Replace every occurrence of each entry's plaintext with its sentinel.
|
|
834
|
+
* Longest value first: when one candidate's plaintext is a prefix (or
|
|
835
|
+
* substring) of another's, insertion order would let the shorter entry
|
|
836
|
+
* consume the head of the longer value — emitting the shorter chip plus
|
|
837
|
+
* the longer credential's unmatched suffix as raw text, while persist-time
|
|
838
|
+
* redaction (whose scanner prefers the longer span) redacts the whole
|
|
839
|
+
* value. Descending-length ordering keeps the live swap consistent with
|
|
840
|
+
* persistence, and the span-based rebuild (`replaceRawSpans`) guarantees a
|
|
841
|
+
* later entry can never rewrite inside an earlier entry's emitted
|
|
842
|
+
* sentinel.
|
|
843
|
+
*/
|
|
844
|
+
export function swapLiveRevealValues(
|
|
845
|
+
text: string,
|
|
846
|
+
entries: readonly LiveRevealGuardEntry[],
|
|
847
|
+
): string {
|
|
848
|
+
const ordered = [...entries].sort((a, b) => b.value.length - a.value.length);
|
|
849
|
+
return replaceRawSpans(
|
|
850
|
+
text,
|
|
851
|
+
ordered.map((entry) => entry.value),
|
|
852
|
+
(priority) => ordered[priority].replacement,
|
|
853
|
+
);
|
|
854
|
+
}
|
|
855
|
+
|
|
856
|
+
/**
|
|
857
|
+
* The live-emit transform: swap candidate values for their sentinels AND
|
|
858
|
+
* neutralize forged sentinels — resolved together on the RAW bytes, with
|
|
859
|
+
* neutralization applied only to the text between candidate spans.
|
|
860
|
+
* Sequencing them instead (`swapLiveRevealValues(neutralizeRedactedSentinels(text))`)
|
|
861
|
+
* breaks when a candidate's plaintext itself contains the sentinel trigger
|
|
862
|
+
* (manual values are arbitrary strings): neutralization would mutate the
|
|
863
|
+
* value's occurrence first, the exact-match swap would miss it, and an
|
|
864
|
+
* opaque secret would cross the wire almost intact. Candidate spans win;
|
|
865
|
+
* forgery neutralization covers everything they don't claim — exactly the
|
|
866
|
+
* occurrence semantics `stableEmitLen` models when it analyzes the trigger
|
|
867
|
+
* and the values as one priority-ordered target set.
|
|
868
|
+
*/
|
|
869
|
+
export function neutralizeAndSwapLiveRevealValues(
|
|
870
|
+
text: string,
|
|
871
|
+
entries: readonly LiveRevealGuardEntry[],
|
|
872
|
+
forChatMints: readonly SentinelRemintAuthority[] = [],
|
|
873
|
+
): string {
|
|
874
|
+
const ordered = [...entries].sort((a, b) => b.value.length - a.value.length);
|
|
875
|
+
return replaceRawSpans(
|
|
876
|
+
text,
|
|
877
|
+
ordered.map((entry) => entry.value),
|
|
878
|
+
(priority) => ordered[priority].replacement,
|
|
879
|
+
(segment) => guardForChatSentinels(segment, forChatMints),
|
|
880
|
+
);
|
|
881
|
+
}
|
|
882
|
+
|
|
883
|
+
/**
|
|
884
|
+
* Length of the prefix of `buffer` whose transform result is STABLE —
|
|
885
|
+
* i.e. cannot change no matter what bytes arrive next — under the
|
|
886
|
+
* transform's occurrence semantics: targets resolved in priority order
|
|
887
|
+
* (the caller passes them highest-priority first, matching
|
|
888
|
+
* `swapLiveRevealValues`' longest-value-first stable sort), each target's
|
|
889
|
+
* occurrences greedy left-to-right, non-overlapping with higher-priority
|
|
890
|
+
* spans. Everything past the returned offset must be held back.
|
|
891
|
+
*
|
|
892
|
+
* Two steps:
|
|
893
|
+
*
|
|
894
|
+
* 1. Resolve the consumed spans of the CURRENT buffer exactly as the
|
|
895
|
+
* swap would.
|
|
896
|
+
* 2. Find every "threat": a suffix of the buffer that is a proper prefix
|
|
897
|
+
* of some target `T` — bytes a future chunk could complete into a
|
|
898
|
+
* `T`-occurrence. Each threat pulls the stable boundary back:
|
|
899
|
+
* - threat starts OUTSIDE any consumed span → hold from the threat
|
|
900
|
+
* start (the classic split-value hold);
|
|
901
|
+
* - threat starts INSIDE a consumed span of occurrence `E`:
|
|
902
|
+
* - `priority(T)` higher than `priority(E)` → the future `T` match
|
|
903
|
+
* would beat `E` in the full-text swap, so `E` cannot be
|
|
904
|
+
* committed yet — hold from `E`'s START (emitting any part of
|
|
905
|
+
* `E` would leak raw bytes on one continuation or emit the wrong
|
|
906
|
+
* replacement on the other);
|
|
907
|
+
* - otherwise → `E` wins regardless of future bytes (greedy
|
|
908
|
+
* left-to-right for the same target, priority order across
|
|
909
|
+
* targets), so the threat is moot — `E` commits whole.
|
|
910
|
+
*
|
|
911
|
+
* Each case guards a real leak shape: a self-overlapping value chunked at
|
|
912
|
+
* the overlap boundary (`abcabc` as `abc`+`abc`: the own-tail threat is
|
|
913
|
+
* moot, the occurrence commits whole); a candidate whose tail is a prefix
|
|
914
|
+
* of a longer candidate (the threat outranks it, so it is held whole —
|
|
915
|
+
* emitting it would leak the longer value's suffix raw once it
|
|
916
|
+
* completes); equal-length overlapping entries (`aba`/`bab`: the
|
|
917
|
+
* earlier-sorted entry outranks, so a completed `bab` holds while `aba`
|
|
918
|
+
* may still complete — keeping chunked output identical to the unchunked
|
|
919
|
+
* swap).
|
|
920
|
+
*
|
|
921
|
+
* The boundary never splits a consumed span: it lands either outside
|
|
922
|
+
* all spans or exactly on a span's start.
|
|
923
|
+
*/
|
|
924
|
+
function stableEmitLen(buffer: string, targets: readonly string[]): number {
|
|
925
|
+
// Same span resolution as the swap transforms — shared so the stability
|
|
926
|
+
// analysis can never disagree with what the transform actually consumes.
|
|
927
|
+
const consumed = resolveRawSpans(buffer, targets);
|
|
928
|
+
let emitLen = buffer.length;
|
|
929
|
+
targets.forEach((target, priority) => {
|
|
930
|
+
const maxLen = Math.min(target.length - 1, buffer.length);
|
|
931
|
+
for (let len = maxLen; len > 0; len--) {
|
|
932
|
+
if (!buffer.endsWith(target.slice(0, len))) {
|
|
933
|
+
continue;
|
|
934
|
+
}
|
|
935
|
+
const start = buffer.length - len;
|
|
936
|
+
const host = consumed.find((s) => s.start <= start && start < s.end);
|
|
937
|
+
if (host === undefined) {
|
|
938
|
+
if (start < emitLen) {
|
|
939
|
+
emitLen = start;
|
|
940
|
+
}
|
|
941
|
+
} else if (priority < host.priority && host.start < emitLen) {
|
|
942
|
+
emitLen = host.start;
|
|
943
|
+
}
|
|
944
|
+
}
|
|
945
|
+
});
|
|
946
|
+
return emitLen;
|
|
947
|
+
}
|
|
948
|
+
|
|
949
|
+
/**
|
|
950
|
+
* Streaming redaction guard for live `assistant_text_delta` emission
|
|
951
|
+
* (mirrors `drainDirectiveDisplayBuffer`'s hold-back pattern). Two jobs:
|
|
952
|
+
*
|
|
953
|
+
* 1. **Forgery neutralization** — genuine sentinels are created by the
|
|
954
|
+
* daemon, so a sentinel-shaped string the model streams neutralizes
|
|
955
|
+
* unless a re-mint authority for its identity exists this turn
|
|
956
|
+
* (`--for-chat` mints and proven reveal candidates — see
|
|
957
|
+
* `guardForChatSentinels`), in which case it is restored to the
|
|
958
|
+
* daemon's canonical mint.
|
|
959
|
+
* 2. **Live reveal swap** — a complete occurrence of a reveal candidate's
|
|
960
|
+
* plaintext is replaced with its enriched sentinel (`entries`), so the
|
|
961
|
+
* secret never flashes in the live stream: the client renders the chip
|
|
962
|
+
* immediately and the persisted row (which redacts the same bytes at
|
|
963
|
+
* persist time) matches what was streamed.
|
|
964
|
+
*
|
|
965
|
+
* A trailing PARTIAL trigger or candidate-value prefix (split across
|
|
966
|
+
* streaming chunks) is held back in `bufferedRemainder` so the next chunk
|
|
967
|
+
* decides whether it completes; when that partial prefix could outrank a
|
|
968
|
+
* completed occurrence it starts inside, the whole occurrence is held with
|
|
969
|
+
* it (see `stableEmitLen`), so the remainder is bounded by roughly twice
|
|
970
|
+
* the longest target. Callers must re-prepend the remainder to
|
|
971
|
+
* the next chunk and, at end-of-message, flush it through
|
|
972
|
+
* `neutralizeAndSwapLiveRevealValues` — nothing can complete a partial
|
|
973
|
+
* prefix after the message ends.
|
|
974
|
+
*
|
|
975
|
+
* `consumedRaw` is the untransformed input the emit covers. Callers that
|
|
976
|
+
* mirror streamed text for partial persistence must mirror `consumedRaw`,
|
|
977
|
+
* not `emitText`: persist-time redaction re-derives the sentinel from the
|
|
978
|
+
* raw bytes, whereas a mirrored already-swapped sentinel would be
|
|
979
|
+
* indistinguishable from a forgery and get neutralized.
|
|
980
|
+
*/
|
|
981
|
+
/**
|
|
982
|
+
* Upper bound on how much tail the guard will hold back waiting for an
|
|
983
|
+
* in-flight daemon-minted sentinel to close. A genuine sentinel is far
|
|
984
|
+
* shorter (type + encoded service/field); a trigger that runs this long
|
|
985
|
+
* without closing is forged garbage and is released (neutralized) rather
|
|
986
|
+
* than buffered forever.
|
|
987
|
+
*/
|
|
988
|
+
const UNCLOSED_SENTINEL_HOLD_CAP = 512;
|
|
989
|
+
|
|
990
|
+
export function drainSentinelGuardedText(
|
|
991
|
+
buffer: string,
|
|
992
|
+
entries: readonly LiveRevealGuardEntry[] = [],
|
|
993
|
+
forChatMints: readonly SentinelRemintAuthority[] = [],
|
|
994
|
+
): {
|
|
995
|
+
emitText: string;
|
|
996
|
+
consumedRaw: string;
|
|
997
|
+
bufferedRemainder: string;
|
|
998
|
+
} {
|
|
999
|
+
// Priority order must mirror the transforms exactly:
|
|
1000
|
+
// `swapLiveRevealValues` sorts entries longest-value-first (stable),
|
|
1001
|
+
// and the trigger joins the same ordering so its complete occurrences
|
|
1002
|
+
// (consumed by neutralization) participate in the stability analysis.
|
|
1003
|
+
const targets = [
|
|
1004
|
+
SENTINEL_TRIGGER,
|
|
1005
|
+
...entries.map((entry) => entry.value),
|
|
1006
|
+
].sort((a, b) => b.length - a.length);
|
|
1007
|
+
let emitLen = stableEmitLen(buffer, targets);
|
|
1008
|
+
// With re-mint authorities in play, a COMPLETE trigger in the emit
|
|
1009
|
+
// region may be the head of a genuine daemon-minted sentinel still
|
|
1010
|
+
// streaming in. The plain guard neutralizes complete triggers eagerly
|
|
1011
|
+
// (any live sentinel is forged by definition); here that would mangle
|
|
1012
|
+
// the sanctioned case — so hold from the first trigger whose sentinel
|
|
1013
|
+
// has not fully landed inside the emit region (bounded by the cap
|
|
1014
|
+
// above), letting `guardForChatSentinels` decide re-mint vs neutralize
|
|
1015
|
+
// on the whole span once it closes. Triggers consumed inside a
|
|
1016
|
+
// candidate-value span are skipped: the span swaps wholesale and its
|
|
1017
|
+
// bytes never reach the gap transform.
|
|
1018
|
+
if (forChatMints.length > 0) {
|
|
1019
|
+
const spans = resolveRawSpans(buffer, targets);
|
|
1020
|
+
let searchFrom = 0;
|
|
1021
|
+
for (;;) {
|
|
1022
|
+
const t = buffer.indexOf(SENTINEL_TRIGGER, searchFrom);
|
|
1023
|
+
if (t === -1) {
|
|
1024
|
+
break;
|
|
1025
|
+
}
|
|
1026
|
+
const host = spans.find((s) => s.start <= t && t < s.end);
|
|
1027
|
+
if (host !== undefined && targets[host.priority] !== SENTINEL_TRIGGER) {
|
|
1028
|
+
searchFrom = t + SENTINEL_TRIGGER.length;
|
|
1029
|
+
continue;
|
|
1030
|
+
}
|
|
1031
|
+
const close = buffer.indexOf(REDACTED_SENTINEL_CLOSE, t);
|
|
1032
|
+
const sentinelEnd = close === -1 ? -1 : close + 1;
|
|
1033
|
+
if (close !== -1 && sentinelEnd <= emitLen) {
|
|
1034
|
+
searchFrom = sentinelEnd;
|
|
1035
|
+
continue;
|
|
1036
|
+
}
|
|
1037
|
+
if (buffer.length - t <= UNCLOSED_SENTINEL_HOLD_CAP) {
|
|
1038
|
+
emitLen = Math.min(emitLen, t);
|
|
1039
|
+
}
|
|
1040
|
+
break;
|
|
1041
|
+
}
|
|
1042
|
+
}
|
|
1043
|
+
const consumedRaw = buffer.slice(0, emitLen);
|
|
1044
|
+
return {
|
|
1045
|
+
emitText: neutralizeAndSwapLiveRevealValues(
|
|
1046
|
+
consumedRaw,
|
|
1047
|
+
entries,
|
|
1048
|
+
forChatMints,
|
|
1049
|
+
),
|
|
1050
|
+
consumedRaw,
|
|
1051
|
+
bufferedRemainder: buffer.slice(emitLen),
|
|
1052
|
+
};
|
|
1053
|
+
}
|
|
1054
|
+
|
|
1055
|
+
/**
|
|
1056
|
+
* Streaming redaction guard for live `tool_output_chunk` emission — the
|
|
1057
|
+
* tool-output twin of {@link drainSentinelGuardedText}. A foreground
|
|
1058
|
+
* reveal's stdout streams to the client WHILE the tool runs, before the
|
|
1059
|
+
* tool_result seam redacts the final content, so each chunk must be
|
|
1060
|
+
* redacted on the way out or the plaintext shows in the tool drawer until
|
|
1061
|
+
* the redacted result replaces it. Complete occurrences of candidate
|
|
1062
|
+
* values become the legacy marker (this surface renders no chips, and the
|
|
1063
|
+
* final tool_result row uses the same treatment, so live and persisted
|
|
1064
|
+
* bytes agree); a trailing partial occurrence is held back in
|
|
1065
|
+
* `bufferedRemainder` for the tool's next chunk. The tool_result seam
|
|
1066
|
+
* DISCARDS whatever remains: a held partial is raw credential prefix
|
|
1067
|
+
* bytes that complete-value redaction cannot mask, and the redacted final
|
|
1068
|
+
* result supersedes the streamed view immediately.
|
|
1069
|
+
*/
|
|
1070
|
+
export function drainCandidateGuardedChunk(
|
|
1071
|
+
buffer: string,
|
|
1072
|
+
candidates: readonly ResolvedRevealCandidate[],
|
|
1073
|
+
): { emitText: string; bufferedRemainder: string } {
|
|
1074
|
+
const seen = new Set<string>();
|
|
1075
|
+
const values: string[] = [];
|
|
1076
|
+
for (const candidate of candidates) {
|
|
1077
|
+
if (candidate.value.length === 0 || seen.has(candidate.value)) {
|
|
1078
|
+
continue;
|
|
1079
|
+
}
|
|
1080
|
+
seen.add(candidate.value);
|
|
1081
|
+
values.push(candidate.value);
|
|
1082
|
+
}
|
|
1083
|
+
values.sort((a, b) => b.length - a.length);
|
|
1084
|
+
const emitLen = stableEmitLen(buffer, values);
|
|
1085
|
+
return {
|
|
1086
|
+
emitText: redactCandidateValuesLegacy(buffer.slice(0, emitLen), candidates),
|
|
1087
|
+
bufferedRemainder: buffer.slice(emitLen),
|
|
1088
|
+
};
|
|
1089
|
+
}
|
|
1090
|
+
|
|
1091
|
+
// ---------------------------------------------------------------------------
|
|
1092
|
+
// Sentinel redaction
|
|
1093
|
+
// ---------------------------------------------------------------------------
|
|
1094
|
+
|
|
1095
|
+
/**
|
|
1096
|
+
* Redact secrets in chat-persisted text using sentinels instead of the
|
|
1097
|
+
* legacy HTML marker. A span whose bytes exactly equal a candidate's
|
|
1098
|
+
* plaintext gets the enriched (revealable) shape — but only when that value
|
|
1099
|
+
* maps to exactly one vault identity; everything else gets the plain
|
|
1100
|
+
* type-only shape. Detection itself is unchanged — same scanner, same
|
|
1101
|
+
* patterns, same overlap handling as `redactSecrets`.
|
|
1102
|
+
*/
|
|
1103
|
+
export function redactSecretsForChat(
|
|
1104
|
+
text: string,
|
|
1105
|
+
candidates: readonly ResolvedRevealCandidate[],
|
|
1106
|
+
forChatMints: readonly SentinelRemintAuthority[] = [],
|
|
1107
|
+
): string {
|
|
1108
|
+
// Candidate spans resolve FIRST, on the raw bytes; forgery
|
|
1109
|
+
// neutralization and the scanner then run ONLY over the text between
|
|
1110
|
+
// them (the gap transform below). Raw bytes first because a manual
|
|
1111
|
+
// candidate value may itself contain the sentinel trigger — mutating it
|
|
1112
|
+
// up front would break the exact-match pass, the strongest guarantee
|
|
1113
|
+
// here. Before the scanner because the scanner may only recognize a
|
|
1114
|
+
// SUBSTRING of a proven plaintext (the `Private Key` pattern consumes
|
|
1115
|
+
// just the `-----BEGIN … -----` header, leaving the key body), and once
|
|
1116
|
+
// it has replaced that substring the full candidate value is no longer
|
|
1117
|
+
// present for an exact-match pass to find — the body would
|
|
1118
|
+
// stream/persist raw. Gaps only because a minted sentinel is not
|
|
1119
|
+
// scanner-inert: its service/field segments are arbitrary user strings
|
|
1120
|
+
// that can themselves look secret-shaped, and a scanner match inside a
|
|
1121
|
+
// fresh sentinel would corrupt it into a nested, unrenderable marker.
|
|
1122
|
+
//
|
|
1123
|
+
// Within each gap the scanner still redacts secrets the reveal registry
|
|
1124
|
+
// never proved (defense in depth against any secret the model emitted
|
|
1125
|
+
// that the vault did not serve), with the precise type label for values
|
|
1126
|
+
// it classifies inline. Sentinel-shaped spans in the gaps neutralize —
|
|
1127
|
+
// except identities a re-mint authority vouches for this turn, which are
|
|
1128
|
+
// restored to the daemon's canonical mint and kept out of the scanner's
|
|
1129
|
+
// reach (see `guardForChatSentinels`); everything around them still runs
|
|
1130
|
+
// the full neutralize+scan pipeline, so a secret embedded inside a
|
|
1131
|
+
// forged sentinel cannot ride through unredacted.
|
|
1132
|
+
const neutralizeAndScan = (segment: string): string =>
|
|
1133
|
+
redactSecretsWith(
|
|
1134
|
+
neutralizeRedactedSentinels(segment),
|
|
1135
|
+
(match, rawValue) => {
|
|
1136
|
+
const hit = uniqueCandidateForValue(candidates, rawValue);
|
|
1137
|
+
return buildRedactedSentinel(
|
|
1138
|
+
hit
|
|
1139
|
+
? { type: match.type, service: hit.service, field: hit.field }
|
|
1140
|
+
: { type: match.type },
|
|
1141
|
+
);
|
|
1142
|
+
},
|
|
1143
|
+
);
|
|
1144
|
+
const redactGap = (segment: string): string =>
|
|
1145
|
+
guardForChatSentinels(segment, forChatMints, neutralizeAndScan);
|
|
1146
|
+
return protectCandidateSpans(text, candidates, redactGap);
|
|
1147
|
+
}
|
|
1148
|
+
|
|
1149
|
+
/**
|
|
1150
|
+
* Replace every occurrence of a proven candidate plaintext with its sentinel,
|
|
1151
|
+
* applying `transformGap` (the caller's neutralize+scan pipeline) only to the
|
|
1152
|
+
* text between the spans. Candidate spans are resolved on the RAW bytes (a
|
|
1153
|
+
* manual value may itself contain the sentinel trigger, and neutralizing
|
|
1154
|
+
* first would break the exact match), and protection precedes the scanner so
|
|
1155
|
+
* a value the scanner would only partially match (e.g. a PEM key, where only
|
|
1156
|
+
* the header is recognized) is still redacted as a whole — its body can
|
|
1157
|
+
* never survive into the streamed/persisted text. Minted sentinels are never
|
|
1158
|
+
* fed back through `transformGap`: their service/field segments are
|
|
1159
|
+
* arbitrary user strings that can look secret-shaped, and a scanner rewrite
|
|
1160
|
+
* inside a fresh sentinel would corrupt it.
|
|
1161
|
+
*
|
|
1162
|
+
* Longest value first (mirroring `swapLiveRevealValues`) so a value that is a
|
|
1163
|
+
* substring of another does not pre-empt the larger match; the span-based
|
|
1164
|
+
* rebuild (`replaceRawSpans`) keeps one value's sentinel safe from another
|
|
1165
|
+
* value that happens to appear inside it. Each value is
|
|
1166
|
+
* classified by scanning it in isolation: when a single scanner match spans
|
|
1167
|
+
* the ENTIRE value the precise type label is preserved (a recognizable key
|
|
1168
|
+
* keeps "Anthropic API Key" rather than degrading to the generic type);
|
|
1169
|
+
* otherwise the generic `Credential` type is used. Identity enrichment still
|
|
1170
|
+
* requires a unique vault match, exactly as the scanner path.
|
|
1171
|
+
*/
|
|
1172
|
+
function protectCandidateSpans(
|
|
1173
|
+
text: string,
|
|
1174
|
+
candidates: readonly ResolvedRevealCandidate[],
|
|
1175
|
+
transformGap: (segment: string) => string,
|
|
1176
|
+
): string {
|
|
1177
|
+
if (candidates.length === 0) {
|
|
1178
|
+
return transformGap(text);
|
|
1179
|
+
}
|
|
1180
|
+
const seen = new Set<string>();
|
|
1181
|
+
const values: string[] = [];
|
|
1182
|
+
for (const candidate of candidates) {
|
|
1183
|
+
if (candidate.value.length === 0 || seen.has(candidate.value)) {
|
|
1184
|
+
continue;
|
|
1185
|
+
}
|
|
1186
|
+
seen.add(candidate.value);
|
|
1187
|
+
values.push(candidate.value);
|
|
1188
|
+
}
|
|
1189
|
+
values.sort((a, b) => b.length - a.length);
|
|
1190
|
+
const sentinelCache = new Map<string, string>();
|
|
1191
|
+
const sentinelForValue = (value: string): string => {
|
|
1192
|
+
let sentinel = sentinelCache.get(value);
|
|
1193
|
+
if (sentinel === undefined) {
|
|
1194
|
+
const hit = uniqueCandidateForValue(candidates, value);
|
|
1195
|
+
sentinel = buildRedactedSentinel(
|
|
1196
|
+
hit
|
|
1197
|
+
? {
|
|
1198
|
+
type: classifyCandidateType(value),
|
|
1199
|
+
service: hit.service,
|
|
1200
|
+
field: hit.field,
|
|
1201
|
+
}
|
|
1202
|
+
: { type: classifyCandidateType(value) },
|
|
1203
|
+
);
|
|
1204
|
+
sentinelCache.set(value, sentinel);
|
|
1205
|
+
}
|
|
1206
|
+
return sentinel;
|
|
1207
|
+
};
|
|
1208
|
+
return replaceRawSpans(
|
|
1209
|
+
text,
|
|
1210
|
+
values,
|
|
1211
|
+
(priority) => sentinelForValue(values[priority]),
|
|
1212
|
+
transformGap,
|
|
1213
|
+
containedInLongerScannerMatch(text),
|
|
1214
|
+
);
|
|
1215
|
+
}
|
|
1216
|
+
|
|
1217
|
+
/**
|
|
1218
|
+
* Occurrence veto that lets a LONGER scanner match outrank a candidate
|
|
1219
|
+
* substring inside it. A proven value can be a strict substring of a
|
|
1220
|
+
* DIFFERENT scanner-detectable secret — revealing a manual value `sk-pro`
|
|
1221
|
+
* must not carve the prefix out of a full `sk-proj-…` key in the same
|
|
1222
|
+
* text and leave the key's suffix raw. Vetoing the occurrence keeps the
|
|
1223
|
+
* enclosing secret contiguous in a gap, where the scanner pass redacts it
|
|
1224
|
+
* whole. The reverse containment keeps the candidate: a scanner match
|
|
1225
|
+
* INSIDE a candidate value (a PEM key, where only the header is
|
|
1226
|
+
* recognized) must not break whole-value protection, and an equal-length
|
|
1227
|
+
* match is the candidate itself — the enriched sentinel wins.
|
|
1228
|
+
*/
|
|
1229
|
+
function containedInLongerScannerMatch(
|
|
1230
|
+
text: string,
|
|
1231
|
+
): (start: number, end: number) => boolean {
|
|
1232
|
+
const matches = scanText(text);
|
|
1233
|
+
if (matches.length === 0) {
|
|
1234
|
+
return () => false;
|
|
1235
|
+
}
|
|
1236
|
+
return (start, end) =>
|
|
1237
|
+
matches.some(
|
|
1238
|
+
(m) =>
|
|
1239
|
+
m.startIndex <= start &&
|
|
1240
|
+
end <= m.endIndex &&
|
|
1241
|
+
m.endIndex - m.startIndex > end - start,
|
|
1242
|
+
);
|
|
1243
|
+
}
|
|
1244
|
+
|
|
1245
|
+
/**
|
|
1246
|
+
* The scanner type label for a candidate value, or the generic `Credential`
|
|
1247
|
+
* type when the scanner does not recognize it. The value is scanned in
|
|
1248
|
+
* isolation (so no neighbouring match bleeds in) and a match ANCHORED AT THE
|
|
1249
|
+
* START is used — a value that begins with a recognized signature is that
|
|
1250
|
+
* type even when the pattern consumes only a prefix (the `Private Key`
|
|
1251
|
+
* pattern matches just the `-----BEGIN … -----` header, yet the value is
|
|
1252
|
+
* unambiguously a private key). The longest such anchored match wins.
|
|
1253
|
+
* Matches that merely appear mid-value are ignored: they could be a
|
|
1254
|
+
* coincidental substring, and mislabelling a chip is worse than the generic
|
|
1255
|
+
* type. Identity enrichment is still gated on a unique vault match upstream,
|
|
1256
|
+
* so the label never drives which secret a chip can reveal.
|
|
1257
|
+
*/
|
|
1258
|
+
function classifyCandidateType(value: string): string {
|
|
1259
|
+
let best: { type: string; endIndex: number } | undefined;
|
|
1260
|
+
for (const m of scanText(value)) {
|
|
1261
|
+
if (m.startIndex !== 0) {
|
|
1262
|
+
continue;
|
|
1263
|
+
}
|
|
1264
|
+
if (best === undefined || m.endIndex > best.endIndex) {
|
|
1265
|
+
best = { type: m.type, endIndex: m.endIndex };
|
|
1266
|
+
}
|
|
1267
|
+
}
|
|
1268
|
+
return best?.type ?? FALLBACK_SENTINEL_TYPE;
|
|
1269
|
+
}
|
|
1270
|
+
|
|
1271
|
+
/**
|
|
1272
|
+
* Legacy-marker twin of the persist fallback, for surfaces that keep the
|
|
1273
|
+
* `<redacted type/>` marker instead of sentinels (persisted tool_result
|
|
1274
|
+
* blocks — see `buildToolResultBlocks`). A `credentials reveal` whose
|
|
1275
|
+
* stdout is an opaque/manual value with no scanner-recognizable shape
|
|
1276
|
+
* would otherwise persist raw in the tool-result row and surface in the
|
|
1277
|
+
* tool detail panel and history, even though every other surface redacts
|
|
1278
|
+
* it. Exact occurrences of proven candidate plaintexts (deduped, longest
|
|
1279
|
+
* value first) become the generic legacy marker; no identity is carried —
|
|
1280
|
+
* these surfaces render no chips.
|
|
1281
|
+
*/
|
|
1282
|
+
export function redactCandidateValuesLegacy(
|
|
1283
|
+
text: string,
|
|
1284
|
+
candidates: readonly ResolvedRevealCandidate[],
|
|
1285
|
+
): string {
|
|
1286
|
+
// Protect every proven candidate value as an atomic legacy marker
|
|
1287
|
+
// (neutralizing forged sentinels in the text between the spans), THEN run
|
|
1288
|
+
// the scanner over what remains. Candidate spans resolve on the RAW
|
|
1289
|
+
// bytes: a manual value may itself contain the sentinel trigger, and
|
|
1290
|
+
// neutralizing the whole text first would mutate the occurrence and
|
|
1291
|
+
// break the exact match. Candidate protection must precede the scanner:
|
|
1292
|
+
// a value the scanner only partially recognizes (a PEM key, where the
|
|
1293
|
+
// `Private Key` pattern consumes just the header) would otherwise have
|
|
1294
|
+
// its recognized substring replaced first, destroying the full-value
|
|
1295
|
+
// match so the key body persists raw. The inserted `<redacted … />`
|
|
1296
|
+
// markers contain no secret-shaped bytes, so the scanner pass can
|
|
1297
|
+
// neither re-match nor corrupt them.
|
|
1298
|
+
const protectedText = protectCandidateValuesLegacy(text, candidates);
|
|
1299
|
+
return redactSecrets(protectedText);
|
|
1300
|
+
}
|
|
1301
|
+
|
|
1302
|
+
/**
|
|
1303
|
+
* Replace every occurrence of a proven candidate plaintext with the generic
|
|
1304
|
+
* legacy marker BEFORE the scanner runs (longest value first, mirroring
|
|
1305
|
+
* `swapLiveRevealValues`). Twin of {@link protectCandidateSpans} for surfaces
|
|
1306
|
+
* that keep the `<redacted type="…" />` marker instead of sentinels; no
|
|
1307
|
+
* identity is carried — these surfaces render no chips, so the generic type
|
|
1308
|
+
* is always used.
|
|
1309
|
+
*/
|
|
1310
|
+
function protectCandidateValuesLegacy(
|
|
1311
|
+
text: string,
|
|
1312
|
+
candidates: readonly ResolvedRevealCandidate[],
|
|
1313
|
+
): string {
|
|
1314
|
+
if (candidates.length === 0) {
|
|
1315
|
+
return neutralizeRedactedSentinels(text);
|
|
1316
|
+
}
|
|
1317
|
+
const seen = new Set<string>();
|
|
1318
|
+
const values: string[] = [];
|
|
1319
|
+
for (const candidate of candidates) {
|
|
1320
|
+
if (candidate.value.length === 0 || seen.has(candidate.value)) {
|
|
1321
|
+
continue;
|
|
1322
|
+
}
|
|
1323
|
+
seen.add(candidate.value);
|
|
1324
|
+
values.push(candidate.value);
|
|
1325
|
+
}
|
|
1326
|
+
values.sort((a, b) => b.length - a.length);
|
|
1327
|
+
return replaceRawSpans(
|
|
1328
|
+
text,
|
|
1329
|
+
values,
|
|
1330
|
+
() => `<redacted type="${FALLBACK_SENTINEL_TYPE}" />`,
|
|
1331
|
+
neutralizeRedactedSentinels,
|
|
1332
|
+
containedInLongerScannerMatch(text),
|
|
1333
|
+
);
|
|
1334
|
+
}
|
|
1335
|
+
|
|
1336
|
+
/**
|
|
1337
|
+
* Resolve a matched plaintext to a candidate identity — but only when the
|
|
1338
|
+
* mapping is unambiguous. A byte match proves the VALUE, not the vault
|
|
1339
|
+
* identity: if two revealed credentials happen to share the same plaintext
|
|
1340
|
+
* (the same API key stored under two services), picking either would mint a
|
|
1341
|
+
* chip whose label — and whose click-to-reveal path — names a credential the
|
|
1342
|
+
* user may not have revealed, and whose value silently diverges if that
|
|
1343
|
+
* credential later rotates. Duplicate-value spans therefore degrade to the
|
|
1344
|
+
* plain type-only sentinel. Multiple candidate entries that agree on
|
|
1345
|
+
* service:field (the same reveal parsed twice) are still unique.
|
|
1346
|
+
*/
|
|
1347
|
+
function uniqueCandidateForValue(
|
|
1348
|
+
candidates: readonly ResolvedRevealCandidate[],
|
|
1349
|
+
rawValue: string,
|
|
1350
|
+
): ResolvedRevealCandidate | undefined {
|
|
1351
|
+
let hit: ResolvedRevealCandidate | undefined;
|
|
1352
|
+
for (const candidate of candidates) {
|
|
1353
|
+
if (candidate.value !== rawValue) {
|
|
1354
|
+
continue;
|
|
1355
|
+
}
|
|
1356
|
+
if (
|
|
1357
|
+
hit !== undefined &&
|
|
1358
|
+
(hit.service !== candidate.service || hit.field !== candidate.field)
|
|
1359
|
+
) {
|
|
1360
|
+
return undefined;
|
|
1361
|
+
}
|
|
1362
|
+
hit = candidate;
|
|
1363
|
+
}
|
|
1364
|
+
return hit;
|
|
1365
|
+
}
|