@vellumai/assistant 0.8.10 → 0.8.11-staging.1
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/bun.lock +62 -1
- package/docs/workspace-tools.md +196 -0
- package/examples/plugins/echo/README.md +3 -3
- package/knip.json +1 -0
- package/openapi.yaml +460 -128
- package/package.json +2 -1
- package/scripts/build-plugin-api.ts +299 -0
- package/src/__tests__/agent-loop-callsite-precedence.test.ts +7 -0
- package/src/__tests__/agent-loop-compaction-events.test.ts +197 -0
- package/src/__tests__/agent-loop-exit-reason.test.ts +93 -96
- package/src/__tests__/agent-loop-mutable-latest-user-message.test.ts +2 -0
- package/src/__tests__/agent-loop-output-hooks.test.ts +274 -1
- package/src/__tests__/agent-loop-override-profile.test.ts +3 -0
- package/src/__tests__/agent-loop-provider-error-recording.test.ts +4 -0
- package/src/__tests__/agent-loop-thinking.test.ts +4 -0
- package/src/__tests__/agent-loop.test.ts +578 -5
- package/src/__tests__/agent-wake-disk-pressure-callsite.test.ts +0 -1
- package/src/__tests__/approval-cascade.test.ts +1 -0
- package/src/__tests__/background-workers-disk-pressure.test.ts +0 -2
- package/src/__tests__/btw-routes.test.ts +0 -1
- package/src/__tests__/build-persisted-content.test.ts +75 -1
- package/src/__tests__/catalog-install-normalize.test.ts +141 -0
- package/src/__tests__/ces-startup-timeout.test.ts +60 -0
- package/src/__tests__/compaction-events.test.ts +1 -0
- package/src/__tests__/config-managed-gemini-defaults.test.ts +2 -46
- package/src/__tests__/context-overflow-reducer.test.ts +264 -124
- package/src/__tests__/context-window-manager-overflow-rung.test.ts +351 -0
- package/src/__tests__/conversation-abort-tool-results.test.ts +1 -1
- package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +13 -5
- package/src/__tests__/conversation-agent-loop-overflow.test.ts +284 -455
- package/src/__tests__/conversation-agent-loop.test.ts +131 -551
- package/src/__tests__/conversation-app-control-instantiation.test.ts +13 -0
- package/src/__tests__/conversation-confirmation-signals.test.ts +1 -0
- package/src/__tests__/conversation-fork-crud.test.ts +259 -0
- package/src/__tests__/conversation-history-web-search.test.ts +1 -1
- package/src/__tests__/conversation-lifecycle.test.ts +257 -1
- package/src/__tests__/conversation-process-callsite.test.ts +1 -0
- package/src/__tests__/conversation-provider-retry-repair.test.ts +38 -377
- package/src/__tests__/conversation-queue.test.ts +1 -39
- package/src/__tests__/conversation-runtime-assembly.test.ts +119 -8
- package/src/__tests__/conversation-skill-tools.test.ts +491 -5
- package/src/__tests__/conversation-slash-queue.test.ts +1 -1
- package/src/__tests__/conversation-slash-unknown.test.ts +1 -0
- package/src/__tests__/conversation-speed-override.test.ts +1 -0
- package/src/__tests__/conversation-store.test.ts +74 -0
- package/src/__tests__/conversation-surfaces-app-control.test.ts +4 -1
- package/src/__tests__/conversation-tool-setup-attribution.test.ts +323 -0
- package/src/__tests__/conversation-tool-setup-tools-disabled.test.ts +34 -0
- package/src/__tests__/conversation-workspace-cache-state.test.ts +1 -0
- package/src/__tests__/conversation-workspace-injection.test.ts +1 -1
- package/src/__tests__/conversation-workspace-tool-tracking.test.ts +1 -0
- package/src/__tests__/corrected-target.test.ts +93 -0
- package/src/__tests__/credential-execution-feature-gates.test.ts +3 -5
- package/src/__tests__/credential-execution-tools.test.ts +23 -11
- package/src/__tests__/credential-security-invariants.test.ts +6 -1
- package/src/__tests__/db-schedule-syntax-migration.test.ts +80 -0
- package/src/__tests__/device-id.test.ts +70 -1
- package/src/__tests__/embedding-managed-proxy-selection.test.ts +6 -40
- package/src/__tests__/empty-response-hook.test.ts +242 -66
- package/src/__tests__/external-plugin-loader.test.ts +0 -31
- package/src/__tests__/get-skill-detail-audit.test.ts +43 -1
- package/src/__tests__/guardian-routing-invariants.test.ts +91 -0
- package/src/__tests__/history-repair-hook.test.ts +228 -3
- package/src/__tests__/host-app-control-proxy.test.ts +45 -0
- package/src/__tests__/host-browser-proxy.test.ts +254 -9
- package/src/__tests__/identity-routes.test.ts +1 -0
- package/src/__tests__/image-recovery-hook.test.ts +387 -0
- package/src/__tests__/injector-chain.test.ts +5 -4
- package/src/__tests__/injector-v3-suppression.test.ts +373 -47
- package/src/__tests__/intent-routing.test.ts +7 -0
- package/src/__tests__/memory-retrieval-hook.test.ts +117 -15
- package/src/__tests__/notification-decision-strategy.test.ts +3 -3
- package/src/__tests__/oauth-store.test.ts +0 -85
- package/src/__tests__/{context-overflow-policy.test.ts → overflow-policy.test.ts} +1 -1
- package/src/__tests__/parallel-tool.benchmark.test.ts +4 -0
- package/src/__tests__/persist-unsendable-image-downscale.test.ts +29 -9
- package/src/__tests__/persist-unsendable-image.test.ts +4 -4
- package/src/__tests__/persistence-secret-redaction.test.ts +78 -0
- package/src/__tests__/plugin-bootstrap.test.ts +82 -73
- package/src/__tests__/plugin-tool-contribution.test.ts +7 -4
- package/src/__tests__/plugin-types.test.ts +0 -8
- package/src/__tests__/provider-catalog-visibility.test.ts +1 -9
- package/src/__tests__/prune-old-conversations-job.test.ts +99 -0
- package/src/__tests__/registry.test.ts +240 -1
- package/src/__tests__/require-fresh-approval.test.ts +3 -0
- package/src/__tests__/schedule-routes.test.ts +116 -1
- package/src/__tests__/schedule-store.test.ts +28 -0
- package/src/__tests__/schedule-tools.test.ts +94 -1
- package/src/__tests__/server-history-render.test.ts +39 -0
- package/src/__tests__/skill-projection-feature-flag.test.ts +13 -0
- package/src/__tests__/skill-projection.benchmark.test.ts +25 -7
- package/src/__tests__/skills.test.ts +202 -0
- package/src/__tests__/slim-skill-category.test.ts +195 -0
- package/src/__tests__/strip-memory-injections.test.ts +33 -39
- package/src/__tests__/test-support/tool-invocation-seed.ts +79 -0
- package/src/__tests__/title-generate-hook.test.ts +9 -7
- package/src/__tests__/tool-audit-listener.test.ts +264 -1
- package/src/__tests__/tool-error-hook.test.ts +4 -3
- package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +1 -0
- package/src/__tests__/tool-executor-lifecycle-events.test.ts +273 -0
- package/src/__tests__/tool-result-truncate-hook.test.ts +1 -0
- package/src/__tests__/tool-start-timestamp.test.ts +218 -0
- package/src/__tests__/tools-get-route.test.ts +202 -0
- package/src/__tests__/workspace-tool-loader.test.ts +319 -0
- package/src/__tests__/workspace-tools-watcher-flag.test.ts +70 -0
- package/src/agent/loop.ts +569 -319
- package/src/api/events/tool-result.ts +9 -0
- package/src/api/events/tool-use-start.ts +7 -0
- package/src/api/index.ts +10 -0
- package/src/api/responses/conversation-message.ts +135 -27
- package/src/api/responses/memory-v3-selection-log.ts +4 -4
- package/src/approvals/guardian-request-resolvers.ts +26 -0
- package/src/browser-session/backends/host-bridge.ts +29 -0
- package/src/browser-session/index.ts +1 -0
- package/src/browser-session/types.ts +5 -1
- package/src/cli/commands/__tests__/schedules.test.ts +62 -4
- package/src/cli/commands/__tests__/skills.test.ts +53 -0
- package/src/cli/commands/channel-verification-sessions.ts +6 -6
- package/src/cli/commands/inference-providers.ts +0 -8
- package/src/cli/commands/plugins.ts +2 -2
- package/src/cli/commands/schedules.ts +27 -4
- package/src/cli/commands/skills.ts +187 -146
- package/src/cli/commands/tools.ts +106 -0
- package/src/cli/lib/__tests__/install-from-github.test.ts +256 -328
- package/src/cli/lib/__tests__/plugin-catalog-cache.test.ts +6 -2
- package/src/cli/lib/__tests__/plugin-details.test.ts +10 -16
- package/src/cli/lib/__tests__/plugin-marketplace.test.ts +2 -2
- package/src/cli/lib/__tests__/search-plugins.test.ts +145 -240
- package/src/cli/lib/install-from-github.ts +187 -117
- package/src/cli/lib/plugin-catalog-cache.ts +9 -9
- package/src/cli/lib/plugin-details.ts +38 -68
- package/src/cli/lib/plugin-marketplace.ts +42 -14
- package/src/cli/lib/search-plugins.ts +29 -129
- package/src/cli/program.ts +2 -0
- package/src/config/bundled-skills/acp/SKILL.md +1 -0
- package/src/config/bundled-skills/app-builder/SKILL.md +1 -0
- package/src/config/bundled-skills/app-control/SKILL.md +1 -0
- package/src/config/bundled-skills/computer-use/SKILL.md +1 -0
- package/src/config/bundled-skills/contacts/SKILL.md +1 -0
- package/src/config/bundled-skills/document-editor/SKILL.md +1 -0
- package/src/config/bundled-skills/followups/SKILL.md +1 -0
- package/src/config/bundled-skills/image-studio/SKILL.md +1 -0
- package/src/config/bundled-skills/media-processing/SKILL.md +1 -0
- package/src/config/bundled-skills/messaging/SKILL.md +1 -0
- package/src/config/bundled-skills/phone-calls/SKILL.md +1 -0
- package/src/config/bundled-skills/playbooks/SKILL.md +1 -0
- package/src/config/bundled-skills/schedule/SKILL.md +1 -0
- package/src/config/bundled-skills/schedule/TOOLS.json +11 -3
- package/src/config/bundled-skills/sequences/SKILL.md +1 -0
- package/src/config/bundled-skills/settings/SKILL.md +1 -0
- package/src/config/bundled-skills/skill-management/SKILL.md +101 -1
- package/src/config/bundled-skills/subagent/SKILL.md +1 -0
- package/src/config/bundled-skills/transcribe/SKILL.md +1 -0
- package/src/config/env-registry.ts +23 -0
- package/src/config/feature-flag-registry.json +17 -81
- package/src/config/loader.ts +5 -22
- package/src/config/schema.ts +2 -0
- package/src/config/schemas/__tests__/compaction-logs.test.ts +56 -0
- package/src/config/schemas/__tests__/memory-v2.test.ts +0 -1
- package/src/config/schemas/__tests__/memory-v3.test.ts +61 -1
- package/src/config/schemas/compaction-logs.ts +79 -0
- package/src/config/schemas/memory-v2.ts +0 -8
- package/src/config/schemas/memory-v3.ts +104 -33
- package/src/config/seed-inference-profiles.ts +1 -1
- package/src/config/skills.ts +117 -47
- package/src/context/compactor.ts +11 -0
- package/src/context/strip-injections.ts +38 -4
- package/src/credential-execution/feature-gates.ts +0 -21
- package/src/credential-execution/startup-timeout.ts +32 -4
- package/src/daemon/__tests__/conversation-tool-setup-exclude.test.ts +18 -0
- package/src/daemon/conversation-agent-loop-handlers.ts +140 -91
- package/src/daemon/conversation-agent-loop.ts +123 -658
- package/src/daemon/conversation-error.ts +6 -33
- package/src/daemon/conversation-lifecycle.ts +1 -1
- package/src/daemon/conversation-runtime-assembly.ts +183 -22
- package/src/daemon/conversation-skill-tools.ts +137 -8
- package/src/daemon/conversation-slash.ts +0 -14
- package/src/daemon/conversation-store.ts +2 -19
- package/src/daemon/conversation-tool-setup.ts +87 -1
- package/src/daemon/conversation.ts +119 -50
- package/src/daemon/external-plugins-bootstrap.ts +36 -95
- package/src/daemon/handlers/config-channels.ts +11 -2
- package/src/daemon/handlers/shared.ts +9 -1
- package/src/daemon/handlers/skills.ts +10 -4
- package/src/daemon/host-app-control-proxy.ts +72 -57
- package/src/daemon/host-browser-proxy.ts +117 -22
- package/src/daemon/lifecycle.ts +34 -5
- package/src/daemon/message-protocol.ts +0 -7
- package/src/daemon/message-types/schedules.ts +1 -0
- package/src/daemon/message-types/skills.ts +17 -0
- package/src/daemon/providers-setup.ts +3 -0
- package/src/daemon/server.ts +3 -3
- package/src/daemon/tool-setup-types.ts +9 -3
- package/src/daemon/trust-context.ts +23 -0
- package/src/daemon/workspace-tools-watcher.ts +324 -0
- package/src/events/tool-audit-listener.ts +78 -16
- package/src/events/tool-metrics-listener.ts +2 -5
- package/src/memory/__tests__/compaction-log-writer-clickhouse.test.ts +227 -0
- package/src/memory/__tests__/conversation-queries.test.ts +176 -0
- package/src/memory/__tests__/jobs-worker-v2-schedule.test.ts +20 -32
- package/src/memory/compaction-log-writer-clickhouse.ts +418 -0
- package/src/memory/conversation-crud.ts +134 -3
- package/src/memory/conversation-queries.ts +64 -7
- package/src/memory/db-init.ts +14 -0
- package/src/memory/embedding-backend.test.ts +130 -1
- package/src/memory/embedding-backend.ts +79 -106
- package/src/memory/embedding-gemini.ts +5 -0
- package/src/memory/graph/__tests__/conversation-graph-memory-v2-routing.test.ts +12 -0
- package/src/memory/graph/__tests__/handle-remember-v2.test.ts +19 -0
- package/src/memory/graph/conversation-graph-memory.ts +36 -25
- package/src/memory/graph/tool-handlers.ts +3 -0
- package/src/memory/job-handlers/cleanup.ts +3 -1
- package/src/memory/jobs-store.ts +0 -28
- package/src/memory/jobs-worker.ts +10 -22
- package/src/memory/memory-marker.ts +29 -0
- package/src/memory/memory-retrospective-startup-cleanup.ts +1 -1
- package/src/memory/migrations/268-add-memory-v3-selections.ts +6 -0
- package/src/memory/migrations/270-schedule-description.ts +36 -0
- package/src/memory/migrations/275-tool-invocations-add-skill-id.test.ts +81 -0
- package/src/memory/migrations/275-tool-invocations-add-skill-id.ts +20 -0
- package/src/memory/migrations/276-tool-invocations-created-at-id-index.test.ts +68 -0
- package/src/memory/migrations/276-tool-invocations-created-at-id-index.ts +20 -0
- package/src/memory/migrations/277-add-memory-v3-ever-injected.ts +29 -0
- package/src/memory/migrations/278-tool-invocations-telemetry-columns.test.ts +96 -0
- package/src/memory/migrations/278-tool-invocations-telemetry-columns.ts +39 -0
- package/src/memory/migrations/279-create-skill-loaded-events.test.ts +84 -0
- package/src/memory/migrations/279-create-skill-loaded-events.ts +26 -0
- package/src/memory/migrations/280-conversations-surfaced-at.test.ts +88 -0
- package/src/memory/migrations/280-conversations-surfaced-at.ts +24 -0
- package/src/memory/migrations/index.ts +10 -0
- package/src/memory/migrations/registry.ts +8 -0
- package/src/memory/schema/conversations.ts +16 -0
- package/src/memory/schema/infrastructure.ts +26 -0
- package/src/memory/skill-loaded-events-store.test.ts +160 -0
- package/src/memory/skill-loaded-events-store.ts +95 -0
- package/src/memory/tool-executed-events-store.test.ts +219 -0
- package/src/memory/tool-executed-events-store.ts +102 -0
- package/src/memory/tool-usage-store.ts +15 -3
- package/src/memory/v2/__tests__/consolidation-job.test.ts +117 -12
- package/src/memory/v2/__tests__/consolidation-prompt-flag-gating-guard.test.ts +189 -0
- package/src/memory/v2/__tests__/injected-block-slugs.test.ts +90 -0
- package/src/memory/v2/__tests__/page-store.test.ts +33 -0
- package/src/memory/v2/__tests__/prompts-consolidation.test.ts +88 -15
- package/src/memory/v2/activation-store.ts +50 -1
- package/src/memory/v2/consolidation-job.ts +113 -29
- package/src/memory/v2/injected-block-slugs.ts +79 -0
- package/src/memory/v2/injection.ts +6 -1
- package/src/memory/v2/prompts/consolidation.ts +414 -13
- package/src/memory/v2/router.ts +2 -28
- package/src/memory/v2/static-context.ts +1 -1
- package/src/memory/v2/types.ts +16 -0
- package/src/notifications/__tests__/copy-composer.test.ts +244 -0
- package/src/notifications/access-request-copy.ts +298 -0
- package/src/notifications/adapters/slack.ts +3 -3
- package/src/notifications/adapters/telegram.ts +2 -1
- package/src/notifications/copy-composer.ts +49 -267
- package/src/notifications/decision-engine.ts +16 -35
- package/src/notifications/home-feed-side-effect.ts +1 -6
- package/src/oauth/oauth-store.ts +0 -9
- package/src/permissions/checker.test.ts +83 -1
- package/src/permissions/checker.ts +25 -2
- package/src/permissions/gateway-threshold-reader.test.ts +182 -0
- package/src/permissions/gateway-threshold-reader.ts +80 -0
- package/src/platform/client.ts +1 -3
- package/src/platform/feature-gate.ts +3 -12
- package/src/plugin-api/constants.ts +4 -2
- package/src/plugin-api/index.ts +63 -11
- package/src/plugin-api/types.ts +236 -71
- package/src/plugins/defaults/compaction/compact.ts +66 -2
- package/src/plugins/defaults/compaction/context-overflow-reducer.ts +240 -32
- package/src/plugins/defaults/compaction/corrected-target.ts +53 -0
- package/src/{daemon/context-overflow-policy.ts → plugins/defaults/compaction/overflow-policy.ts} +1 -1
- package/src/plugins/defaults/compaction/window-manager.ts +303 -1
- package/src/plugins/defaults/empty-response/hooks/post-model-call.ts +173 -0
- package/src/plugins/defaults/empty-response/hooks/stop.ts +11 -115
- package/src/plugins/defaults/empty-response/nudge-state-store.ts +46 -0
- package/src/plugins/defaults/history-repair/hooks/post-model-call.ts +50 -0
- package/src/plugins/defaults/history-repair/hooks/stop.ts +22 -0
- package/src/plugins/defaults/history-repair/repair-state-store.ts +51 -0
- package/src/plugins/defaults/history-repair/terminal.ts +39 -2
- package/src/plugins/defaults/image-recovery/detect.ts +25 -0
- package/src/plugins/defaults/image-recovery/hooks/post-model-call.ts +73 -0
- package/src/plugins/defaults/image-recovery/hooks/stop.ts +22 -0
- package/src/plugins/defaults/image-recovery/image-recovery-state-store.ts +48 -0
- package/src/plugins/defaults/image-recovery/package.json +14 -0
- package/src/{daemon/persist-unsendable-image.ts → plugins/defaults/image-recovery/recover.ts} +67 -14
- package/src/plugins/defaults/index.ts +71 -5
- package/src/plugins/defaults/memory-retrieval/hooks/post-compact.ts +76 -112
- package/src/plugins/defaults/memory-retrieval/hooks/{user-prompt-submit-temp.ts → user-prompt-submit.ts} +83 -74
- package/src/plugins/defaults/memory-retrieval/injector-chain.ts +14 -8
- package/src/plugins/defaults/memory-retrieval/injectors.ts +2 -18
- package/src/plugins/defaults/memory-retrieval/package.json +14 -0
- package/src/plugins/defaults/memory-v3-shadow/__tests__/carry-integration.test.ts +1157 -0
- package/src/plugins/defaults/memory-v3-shadow/__tests__/injection.test.ts +683 -0
- package/src/plugins/defaults/memory-v3-shadow/__tests__/live-integration.test.ts +161 -140
- package/src/plugins/defaults/memory-v3-shadow/__tests__/maintain-job.test.ts +160 -0
- package/src/plugins/defaults/memory-v3-shadow/__tests__/orchestrate.test.ts +335 -316
- package/src/plugins/defaults/memory-v3-shadow/__tests__/pool-select.test.ts +145 -53
- package/src/plugins/defaults/memory-v3-shadow/__tests__/render-injection.test.ts +38 -1
- package/src/plugins/defaults/memory-v3-shadow/__tests__/selection-log-store.test.ts +18 -8
- package/src/plugins/defaults/memory-v3-shadow/__tests__/shadow-integration.test.ts +112 -71
- package/src/plugins/defaults/memory-v3-shadow/__tests__/shadow-plugin.test.ts +192 -51
- package/src/plugins/defaults/memory-v3-shadow/__tests__/types.test.ts +4 -16
- package/src/plugins/defaults/memory-v3-shadow/card.test.ts +173 -0
- package/src/plugins/defaults/memory-v3-shadow/card.ts +116 -0
- package/src/plugins/defaults/memory-v3-shadow/core-set.test.ts +104 -0
- package/src/plugins/defaults/memory-v3-shadow/core-set.ts +59 -0
- package/src/plugins/defaults/memory-v3-shadow/ever-injected-store.test.ts +305 -0
- package/src/plugins/defaults/memory-v3-shadow/ever-injected-store.ts +278 -0
- package/src/plugins/defaults/memory-v3-shadow/hot-set.test.ts +138 -0
- package/src/plugins/defaults/memory-v3-shadow/hot-set.ts +85 -0
- package/src/plugins/defaults/memory-v3-shadow/injector.ts +331 -24
- package/src/plugins/defaults/memory-v3-shadow/maintain-job.ts +119 -13
- package/src/plugins/defaults/memory-v3-shadow/orchestrate.ts +169 -114
- package/src/plugins/defaults/memory-v3-shadow/page-content.ts +47 -16
- package/src/plugins/defaults/memory-v3-shadow/pool-select.ts +144 -66
- package/src/plugins/defaults/memory-v3-shadow/prune.test.ts +758 -0
- package/src/plugins/defaults/memory-v3-shadow/prune.ts +471 -0
- package/src/plugins/defaults/memory-v3-shadow/render-injection.ts +68 -16
- package/src/plugins/defaults/memory-v3-shadow/selection-log-store.ts +19 -12
- package/src/plugins/defaults/memory-v3-shadow/shadow-plugin.ts +96 -43
- package/src/plugins/defaults/memory-v3-shadow/types.ts +34 -17
- package/src/plugins/defaults/title-generate/hooks/stop.ts +9 -11
- package/src/plugins/pipeline.ts +8 -5
- package/src/plugins/types.ts +5 -51
- package/src/providers/cache-control.ts +26 -0
- package/src/providers/inference/__tests__/base-url-route-validation.test.ts +1 -2
- package/src/providers/model-catalog.ts +13 -1
- package/src/providers/openai/__tests__/tool-choice-mapping.test.ts +147 -0
- package/src/providers/openai/chat-completions-provider.ts +46 -0
- package/src/providers/openai/responses-provider.ts +45 -0
- package/src/providers/registry.ts +0 -8
- package/src/runtime/__tests__/agent-wake.test.ts +0 -1
- package/src/runtime/agent-wake.ts +13 -0
- package/src/runtime/routes/__tests__/acp-routes.test.ts +151 -0
- package/src/runtime/routes/__tests__/consolidation-routes.test.ts +12 -50
- package/src/runtime/routes/__tests__/conversation-surface-routes.test.ts +322 -0
- package/src/runtime/routes/__tests__/inference-provider-connection-routes.test.ts +0 -62
- package/src/runtime/routes/__tests__/plugins-routes.test.ts +31 -11
- package/src/runtime/routes/acp-routes.test.ts +106 -0
- package/src/runtime/routes/acp-routes.ts +248 -2
- package/src/runtime/routes/browser-tabs-routes.ts +1 -1
- package/src/runtime/routes/channel-verification-routes.ts +14 -5
- package/src/runtime/routes/chatgpt-subscription-auth-routes.ts +0 -14
- package/src/runtime/routes/consolidation-routes.ts +6 -82
- package/src/runtime/routes/conversation-list-routes.ts +6 -0
- package/src/runtime/routes/conversation-management-routes.ts +71 -0
- package/src/runtime/routes/identity-routes.ts +8 -0
- package/src/runtime/routes/inbound-stages/acl-enforcement.ts +33 -34
- package/src/runtime/routes/inference-provider-connection-routes.ts +0 -45
- package/src/runtime/routes/plugins-routes.ts +34 -45
- package/src/runtime/routes/schedule-routes.ts +43 -5
- package/src/runtime/routes/settings-routes.ts +140 -15
- package/src/runtime/routes/skills-routes.ts +18 -6
- package/src/runtime/services/__tests__/conversation-serializer.test.ts +140 -0
- package/src/runtime/services/conversation-serializer.ts +38 -1
- package/src/runtime/verification-outbound-actions.ts +147 -2
- package/src/runtime/verification-templates.ts +29 -3
- package/src/schedule/schedule-store.ts +19 -0
- package/src/skills/catalog-install.ts +77 -13
- package/src/tasks/task-scheduler.ts +1 -0
- package/src/telemetry/types.ts +66 -1
- package/src/telemetry/usage-telemetry-reporter.test.ts +542 -13
- package/src/telemetry/usage-telemetry-reporter.ts +213 -20
- package/src/tools/browser/__tests__/browser-execution-acquire.test.ts +49 -2
- package/src/tools/browser/__tests__/browser-status.test.ts +29 -5
- package/src/tools/browser/browser-execution.ts +27 -9
- package/src/tools/browser/cdp-client/__tests__/factory.test.ts +380 -4
- package/src/tools/browser/cdp-client/__tests__/host-bridge-cdp-client.test.ts +107 -0
- package/src/tools/browser/cdp-client/__tests__/types.test.ts +6 -1
- package/src/tools/browser/cdp-client/factory.ts +217 -17
- package/src/tools/browser/cdp-client/host-bridge-cdp-client.ts +67 -0
- package/src/tools/browser/cdp-client/types.ts +22 -2
- package/src/tools/credential-execution/make-authenticated-request.ts +2 -1
- package/src/tools/credential-execution/manage-secure-command-tool.ts +169 -164
- package/src/tools/credential-execution/run-authenticated-command.ts +2 -1
- package/src/tools/executor.ts +39 -7
- package/src/tools/registry.ts +387 -5
- package/src/tools/schedule/create.ts +16 -0
- package/src/tools/schedule/list.ts +12 -4
- package/src/tools/schedule/update.ts +12 -0
- package/src/tools/skills/load.ts +11 -6
- package/src/tools/terminal/safe-env.ts +2 -0
- package/src/tools/types.ts +65 -9
- package/src/tools/workspace-tools/loader.ts +673 -0
- package/src/usage/attribution.ts +28 -0
- package/src/util/device-id.ts +17 -3
- package/src/util/platform.ts +16 -0
- package/tsconfig.plugin-api.json +13 -0
- package/src/__tests__/plugin-external-api.test.ts +0 -68
- package/src/__tests__/plugin-skill-contribution.test.ts +0 -355
- package/src/daemon/message-types/browser.ts +0 -10
- package/src/notifications/__tests__/emit-signal-home-feed.test.ts +0 -187
- package/src/plugins/defaults/memory-v3-shadow/__tests__/fixtures/eval-turns.json +0 -36
- package/src/plugins/defaults/memory-v3-shadow/__tests__/fixtures/live-turns.json +0 -37
- package/src/plugins/defaults/memory-v3-shadow/__tests__/working-set-eviction.test.ts +0 -106
- package/src/plugins/defaults/memory-v3-shadow/__tests__/working-set-skeleton.test.ts +0 -44
- package/src/plugins/defaults/memory-v3-shadow/working-set.ts +0 -91
- package/src/plugins/external-api.ts +0 -114
- package/src/plugins/plugin-skill-contributions.ts +0 -292
|
@@ -1,9 +1,21 @@
|
|
|
1
|
-
import { describe, expect, test } from "bun:test";
|
|
1
|
+
import { beforeEach, describe, expect, mock, test } from "bun:test";
|
|
2
|
+
|
|
3
|
+
// Toggle for the collectUsageData opt-out gate the listener consults when
|
|
4
|
+
// populating the telemetry columns.
|
|
5
|
+
let collectUsageData = true;
|
|
6
|
+
|
|
7
|
+
mock.module("../config/loader.js", () => ({
|
|
8
|
+
getConfig: () => ({ collectUsageData }),
|
|
9
|
+
}));
|
|
2
10
|
|
|
3
11
|
import { createToolAuditListener } from "../events/tool-audit-listener.js";
|
|
4
12
|
import type { ToolInvocationRecord } from "../memory/tool-usage-store.js";
|
|
5
13
|
|
|
6
14
|
describe("tool audit listener", () => {
|
|
15
|
+
beforeEach(() => {
|
|
16
|
+
collectUsageData = true;
|
|
17
|
+
});
|
|
18
|
+
|
|
7
19
|
test("records executed events with truncated output", () => {
|
|
8
20
|
const records: ToolInvocationRecord[] = [];
|
|
9
21
|
const listener = createToolAuditListener((record) => records.push(record));
|
|
@@ -152,5 +164,256 @@ describe("tool audit listener", () => {
|
|
|
152
164
|
expect(records).toHaveLength(1);
|
|
153
165
|
expect(records[0].result).toBe("error: boom");
|
|
154
166
|
expect(records[0].decision).toBe("error");
|
|
167
|
+
expect(records[0].argBytes).toBe(
|
|
168
|
+
Buffer.byteLength(JSON.stringify({ path: "/tmp/secret" }), "utf8"),
|
|
169
|
+
);
|
|
170
|
+
expect(records[0].resultBytes).toBe(Buffer.byteLength("error: boom"));
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
test("records byte sizes from the full payloads, before truncation and redaction", () => {
|
|
174
|
+
const records: ToolInvocationRecord[] = [];
|
|
175
|
+
const listener = createToolAuditListener((record) => records.push(record));
|
|
176
|
+
|
|
177
|
+
listener({
|
|
178
|
+
type: "executed",
|
|
179
|
+
toolName: "file_read",
|
|
180
|
+
input: { path: "/tmp/a" },
|
|
181
|
+
workingDir: "/tmp",
|
|
182
|
+
conversationId: "conv-bytes",
|
|
183
|
+
riskLevel: "low",
|
|
184
|
+
decision: "allow",
|
|
185
|
+
durationMs: 12,
|
|
186
|
+
// 1200 chars: the stored result is capped at 1000 but the size must
|
|
187
|
+
// reflect the full payload. "é" is 1 char / 2 utf8 bytes, proving
|
|
188
|
+
// byte (not char) accounting.
|
|
189
|
+
result: { content: "é".repeat(1200), isError: false },
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
expect(records).toHaveLength(1);
|
|
193
|
+
expect(records[0].result).toHaveLength(1000);
|
|
194
|
+
expect(records[0].argBytes).toBe(
|
|
195
|
+
Buffer.byteLength(JSON.stringify({ path: "/tmp/a" }), "utf8"),
|
|
196
|
+
);
|
|
197
|
+
expect(records[0].resultBytes).toBe(2400);
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
test("prefers the executor-stamped raw inputBytes over sizing the (sanitized) event input", () => {
|
|
201
|
+
const records: ToolInvocationRecord[] = [];
|
|
202
|
+
const listener = createToolAuditListener((record) => records.push(record));
|
|
203
|
+
|
|
204
|
+
// The executor sanitizes event.input before listeners run and stamps
|
|
205
|
+
// inputBytes from the RAW input — the listener must report that size,
|
|
206
|
+
// not the redacted payload's.
|
|
207
|
+
const rawInput = { path: "/tmp/a", token: "t-1" };
|
|
208
|
+
const rawSize = Buffer.byteLength(JSON.stringify(rawInput), "utf8");
|
|
209
|
+
const sanitizedInput = { path: "/tmp/a", token: "<redacted />" };
|
|
210
|
+
|
|
211
|
+
listener({
|
|
212
|
+
type: "executed",
|
|
213
|
+
toolName: "file_read",
|
|
214
|
+
input: sanitizedInput,
|
|
215
|
+
inputBytes: rawSize,
|
|
216
|
+
workingDir: "/tmp",
|
|
217
|
+
conversationId: "conv-raw-bytes",
|
|
218
|
+
riskLevel: "low",
|
|
219
|
+
decision: "allow",
|
|
220
|
+
durationMs: 4,
|
|
221
|
+
result: { content: "ok", isError: false },
|
|
222
|
+
});
|
|
223
|
+
listener({
|
|
224
|
+
type: "error",
|
|
225
|
+
toolName: "file_read",
|
|
226
|
+
input: sanitizedInput,
|
|
227
|
+
inputBytes: rawSize,
|
|
228
|
+
workingDir: "/tmp",
|
|
229
|
+
conversationId: "conv-raw-bytes",
|
|
230
|
+
riskLevel: "low",
|
|
231
|
+
decision: "error",
|
|
232
|
+
durationMs: 4,
|
|
233
|
+
errorMessage: "boom",
|
|
234
|
+
isExpected: false,
|
|
235
|
+
errorCategory: "tool_failure",
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
expect(records).toHaveLength(2);
|
|
239
|
+
for (const record of records) {
|
|
240
|
+
expect(record.argBytes).toBe(rawSize);
|
|
241
|
+
expect(record.argBytes).not.toBe(
|
|
242
|
+
Buffer.byteLength(JSON.stringify(sanitizedInput), "utf8"),
|
|
243
|
+
);
|
|
244
|
+
}
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
test("prefers the executor-stamped raw resultBytes over sizing the (sanitized) event result", () => {
|
|
248
|
+
const records: ToolInvocationRecord[] = [];
|
|
249
|
+
const listener = createToolAuditListener((record) => records.push(record));
|
|
250
|
+
|
|
251
|
+
// For sensitive-output tools, the executor sanitizes result.content
|
|
252
|
+
// before listeners run and stamps resultBytes from the RAW content —
|
|
253
|
+
// the listener must report that size, not the placeholder-rewritten
|
|
254
|
+
// payload's.
|
|
255
|
+
const sanitizedContent = "code: VELLUM_ASSISTANT_INVITE_CODE_AB12CD34";
|
|
256
|
+
const rawSize = 4096;
|
|
257
|
+
|
|
258
|
+
listener({
|
|
259
|
+
type: "executed",
|
|
260
|
+
toolName: "create_invite",
|
|
261
|
+
input: { count: 1 },
|
|
262
|
+
workingDir: "/tmp",
|
|
263
|
+
conversationId: "conv-raw-result-bytes",
|
|
264
|
+
riskLevel: "low",
|
|
265
|
+
decision: "allow",
|
|
266
|
+
durationMs: 4,
|
|
267
|
+
result: { content: sanitizedContent, isError: false },
|
|
268
|
+
resultBytes: rawSize,
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
expect(records).toHaveLength(1);
|
|
272
|
+
expect(records[0].resultBytes).toBe(rawSize);
|
|
273
|
+
expect(records[0].resultBytes).not.toBe(
|
|
274
|
+
Buffer.byteLength(sanitizedContent, "utf8"),
|
|
275
|
+
);
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
test("persists NULL telemetry columns when usage data collection is opted out", () => {
|
|
279
|
+
collectUsageData = false;
|
|
280
|
+
const records: ToolInvocationRecord[] = [];
|
|
281
|
+
const listener = createToolAuditListener((record) => records.push(record));
|
|
282
|
+
|
|
283
|
+
const attribution = {
|
|
284
|
+
callSite: "mainAgent" as const,
|
|
285
|
+
activeProfile: "balanced",
|
|
286
|
+
overrideProfile: null,
|
|
287
|
+
callSiteProfile: null,
|
|
288
|
+
appliedProfile: "balanced",
|
|
289
|
+
profileSource: "active" as const,
|
|
290
|
+
resolvedProvider: "anthropic",
|
|
291
|
+
resolvedModel: "model-a",
|
|
292
|
+
resolvedMixArm: null,
|
|
293
|
+
};
|
|
294
|
+
listener({
|
|
295
|
+
type: "executed",
|
|
296
|
+
toolName: "file_read",
|
|
297
|
+
input: { path: "/tmp/a" },
|
|
298
|
+
inputBytes: 42,
|
|
299
|
+
resultBytes: 99,
|
|
300
|
+
workingDir: "/tmp",
|
|
301
|
+
conversationId: "conv-opt-out",
|
|
302
|
+
riskLevel: "low",
|
|
303
|
+
decision: "allow",
|
|
304
|
+
durationMs: 12,
|
|
305
|
+
result: { content: "ok", isError: false },
|
|
306
|
+
attribution,
|
|
307
|
+
});
|
|
308
|
+
listener({
|
|
309
|
+
type: "error",
|
|
310
|
+
toolName: "file_read",
|
|
311
|
+
input: { path: "/tmp/b" },
|
|
312
|
+
inputBytes: 42,
|
|
313
|
+
workingDir: "/tmp",
|
|
314
|
+
conversationId: "conv-opt-out",
|
|
315
|
+
riskLevel: "low",
|
|
316
|
+
decision: "error",
|
|
317
|
+
durationMs: 9,
|
|
318
|
+
errorMessage: "boom",
|
|
319
|
+
isExpected: false,
|
|
320
|
+
errorCategory: "tool_failure",
|
|
321
|
+
attribution,
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
// Telemetry columns are NULL — the tool_executed projection's
|
|
325
|
+
// arg_bytes IS NOT NULL filter makes these rows permanently
|
|
326
|
+
// unreportable, even from a zero watermark.
|
|
327
|
+
expect(records).toHaveLength(2);
|
|
328
|
+
for (const record of records) {
|
|
329
|
+
expect(record.argBytes).toBeNull();
|
|
330
|
+
expect(record.resultBytes).toBeNull();
|
|
331
|
+
expect(record.provider).toBeNull();
|
|
332
|
+
expect(record.model).toBeNull();
|
|
333
|
+
expect(record.inferenceProfile).toBeNull();
|
|
334
|
+
expect(record.inferenceProfileSource).toBeNull();
|
|
335
|
+
}
|
|
336
|
+
// The audit row itself is unaffected by the opt-out.
|
|
337
|
+
expect(records[0]).toMatchObject({
|
|
338
|
+
conversationId: "conv-opt-out",
|
|
339
|
+
toolName: "file_read",
|
|
340
|
+
input: JSON.stringify({ path: "/tmp/a" }),
|
|
341
|
+
result: "ok",
|
|
342
|
+
decision: "allow",
|
|
343
|
+
durationMs: 12,
|
|
344
|
+
});
|
|
345
|
+
expect(records[1]).toMatchObject({
|
|
346
|
+
result: "error: boom",
|
|
347
|
+
decision: "error",
|
|
348
|
+
durationMs: 9,
|
|
349
|
+
});
|
|
350
|
+
});
|
|
351
|
+
|
|
352
|
+
test("maps attribution onto executed records and leaves denied records null", () => {
|
|
353
|
+
const records: ToolInvocationRecord[] = [];
|
|
354
|
+
const listener = createToolAuditListener((record) => records.push(record));
|
|
355
|
+
|
|
356
|
+
listener({
|
|
357
|
+
type: "executed",
|
|
358
|
+
toolName: "file_read",
|
|
359
|
+
input: { path: "/tmp/a" },
|
|
360
|
+
workingDir: "/tmp",
|
|
361
|
+
conversationId: "conv-attr",
|
|
362
|
+
riskLevel: "low",
|
|
363
|
+
decision: "allow",
|
|
364
|
+
durationMs: 12,
|
|
365
|
+
result: { content: "ok", isError: false },
|
|
366
|
+
attribution: {
|
|
367
|
+
callSite: "mainAgent",
|
|
368
|
+
activeProfile: "balanced",
|
|
369
|
+
overrideProfile: null,
|
|
370
|
+
callSiteProfile: null,
|
|
371
|
+
appliedProfile: "balanced",
|
|
372
|
+
profileSource: "active",
|
|
373
|
+
resolvedProvider: "anthropic",
|
|
374
|
+
resolvedModel: "model-a",
|
|
375
|
+
resolvedMixArm: null,
|
|
376
|
+
},
|
|
377
|
+
});
|
|
378
|
+
listener({
|
|
379
|
+
type: "executed",
|
|
380
|
+
toolName: "file_read",
|
|
381
|
+
input: { path: "/tmp/b" },
|
|
382
|
+
workingDir: "/tmp",
|
|
383
|
+
conversationId: "conv-attr",
|
|
384
|
+
riskLevel: "low",
|
|
385
|
+
decision: "allow",
|
|
386
|
+
durationMs: 3,
|
|
387
|
+
result: { content: "ok", isError: false },
|
|
388
|
+
attribution: null,
|
|
389
|
+
});
|
|
390
|
+
listener({
|
|
391
|
+
type: "permission_denied",
|
|
392
|
+
toolName: "bash",
|
|
393
|
+
input: { command: "rm -rf /tmp" },
|
|
394
|
+
workingDir: "/tmp",
|
|
395
|
+
conversationId: "conv-attr",
|
|
396
|
+
riskLevel: "high",
|
|
397
|
+
decision: "deny",
|
|
398
|
+
reason: "Permission denied by user",
|
|
399
|
+
durationMs: 1,
|
|
400
|
+
});
|
|
401
|
+
|
|
402
|
+
expect(records).toHaveLength(3);
|
|
403
|
+
expect(records[0]).toMatchObject({
|
|
404
|
+
provider: "anthropic",
|
|
405
|
+
model: "model-a",
|
|
406
|
+
inferenceProfile: "balanced",
|
|
407
|
+
inferenceProfileSource: "active",
|
|
408
|
+
});
|
|
409
|
+
expect(records[1]).toMatchObject({
|
|
410
|
+
provider: null,
|
|
411
|
+
model: null,
|
|
412
|
+
inferenceProfile: null,
|
|
413
|
+
inferenceProfileSource: null,
|
|
414
|
+
});
|
|
415
|
+
expect(records[2].provider).toBeUndefined();
|
|
416
|
+
expect(records[2].argBytes).toBeUndefined();
|
|
417
|
+
expect(records[2].resultBytes).toBeUndefined();
|
|
155
418
|
});
|
|
156
419
|
});
|
|
@@ -78,6 +78,7 @@ function makeCtx(
|
|
|
78
78
|
conversationId: "conv-tool-error-test",
|
|
79
79
|
toolResponse,
|
|
80
80
|
messages,
|
|
81
|
+
additionalContext: null,
|
|
81
82
|
maxInputTokens: 10_000,
|
|
82
83
|
logger: noopLogger,
|
|
83
84
|
};
|
|
@@ -139,7 +140,7 @@ describe("tool-error post-tool-use hook — direct", () => {
|
|
|
139
140
|
await postToolUse(ctx);
|
|
140
141
|
|
|
141
142
|
// THEN no coaching is surfaced and the content is left untouched.
|
|
142
|
-
expect(ctx.additionalContext).
|
|
143
|
+
expect(ctx.additionalContext).toBeNull();
|
|
143
144
|
expect(ctx.toolResponse.content).toBe(BASE_CONTENT);
|
|
144
145
|
});
|
|
145
146
|
|
|
@@ -175,7 +176,7 @@ describe("tool-error post-tool-use hook — direct", () => {
|
|
|
175
176
|
|
|
176
177
|
// THEN no coaching is surfaced (the error is likely unrecoverable) and the
|
|
177
178
|
// result is left untouched.
|
|
178
|
-
expect(ctx.additionalContext).
|
|
179
|
+
expect(ctx.additionalContext).toBeNull();
|
|
179
180
|
expect(ctx.toolResponse.content).toBe(BASE_CONTENT);
|
|
180
181
|
});
|
|
181
182
|
|
|
@@ -251,7 +252,7 @@ describe("tool-error post-tool-use hook — via runHook", () => {
|
|
|
251
252
|
test("default hook runs before a later-registered user hook", async () => {
|
|
252
253
|
// GIVEN the default plugin is registered first, then a user plugin whose
|
|
253
254
|
// hook records the additionalContext it observes.
|
|
254
|
-
let observed: string | undefined;
|
|
255
|
+
let observed: string | null | undefined;
|
|
255
256
|
registerPlugin(defaultToolErrorPlugin);
|
|
256
257
|
registerPlugin({
|
|
257
258
|
manifest: { name: "observer-plugin", version: "0.0.1" },
|
|
@@ -75,6 +75,7 @@ mock.module("../util/platform.js", () => ({
|
|
|
75
75
|
|
|
76
76
|
mock.module("../permissions/gateway-threshold-reader.js", () => ({
|
|
77
77
|
getAutoApproveThreshold: async () => undefined,
|
|
78
|
+
refreshAutoApproveThreshold: async () => null,
|
|
78
79
|
}));
|
|
79
80
|
|
|
80
81
|
mock.module("../permissions/workspace-policy.js", () => ({
|
|
@@ -4,6 +4,7 @@ import type {
|
|
|
4
4
|
ToolExecutionResult,
|
|
5
5
|
ToolLifecycleEvent,
|
|
6
6
|
} from "../tools/types.js";
|
|
7
|
+
import type { UsageAttributionSnapshot } from "../usage/attribution.js";
|
|
7
8
|
|
|
8
9
|
const mockConfig = {
|
|
9
10
|
provider: "anthropic",
|
|
@@ -33,6 +34,9 @@ const mockConfig = {
|
|
|
33
34
|
permissions: {
|
|
34
35
|
mode: "workspace" as const,
|
|
35
36
|
},
|
|
37
|
+
// The audit listener nulls the telemetry columns when this is false; the
|
|
38
|
+
// end-to-end listener tests below assert the opted-in sizing behavior.
|
|
39
|
+
collectUsageData: true,
|
|
36
40
|
};
|
|
37
41
|
|
|
38
42
|
let checkerDecision: "allow" | "prompt" | "deny" = "allow";
|
|
@@ -515,6 +519,275 @@ describe("ToolExecutor lifecycle events", () => {
|
|
|
515
519
|
expect(executed.executionTarget).toBe("sandbox");
|
|
516
520
|
});
|
|
517
521
|
|
|
522
|
+
// ── attribution forwarding tests ──────────────────────────
|
|
523
|
+
|
|
524
|
+
// Uses a non-main call site (voice turn) so these tests also prove the
|
|
525
|
+
// executor forwards the snapshot verbatim — non-main turns must not be
|
|
526
|
+
// rewritten to the main agent's attribution.
|
|
527
|
+
const testAttribution: UsageAttributionSnapshot = {
|
|
528
|
+
callSite: "callAgent",
|
|
529
|
+
activeProfile: "balanced",
|
|
530
|
+
overrideProfile: null,
|
|
531
|
+
callSiteProfile: "voice-profile",
|
|
532
|
+
appliedProfile: "voice-profile",
|
|
533
|
+
profileSource: "call_site",
|
|
534
|
+
resolvedProvider: "anthropic",
|
|
535
|
+
resolvedModel: "test-model",
|
|
536
|
+
resolvedMixArm: null,
|
|
537
|
+
};
|
|
538
|
+
|
|
539
|
+
test("forwards context.attribution into the executed lifecycle event", async () => {
|
|
540
|
+
const events: ToolLifecycleEvent[] = [];
|
|
541
|
+
const executor = new ToolExecutor(makePrompter());
|
|
542
|
+
|
|
543
|
+
await executor.execute(
|
|
544
|
+
"file_read",
|
|
545
|
+
{ path: "README.md" },
|
|
546
|
+
makeContext(events, { attribution: testAttribution }),
|
|
547
|
+
);
|
|
548
|
+
|
|
549
|
+
const executed = events.find((event) => event.type === "executed");
|
|
550
|
+
if (executed?.type !== "executed")
|
|
551
|
+
throw new Error("Expected executed event");
|
|
552
|
+
expect(executed.attribution).toEqual(testAttribution);
|
|
553
|
+
});
|
|
554
|
+
|
|
555
|
+
test("forwards context.attribution into the error lifecycle event", async () => {
|
|
556
|
+
toolThrow = new Error("boom");
|
|
557
|
+
|
|
558
|
+
const events: ToolLifecycleEvent[] = [];
|
|
559
|
+
const executor = new ToolExecutor(makePrompter());
|
|
560
|
+
|
|
561
|
+
await executor.execute(
|
|
562
|
+
"file_read",
|
|
563
|
+
{},
|
|
564
|
+
makeContext(events, { attribution: testAttribution }),
|
|
565
|
+
);
|
|
566
|
+
|
|
567
|
+
const errorEvent = events.find((event) => event.type === "error");
|
|
568
|
+
if (errorEvent?.type !== "error") throw new Error("Expected error event");
|
|
569
|
+
expect(errorEvent.attribution).toEqual(testAttribution);
|
|
570
|
+
});
|
|
571
|
+
|
|
572
|
+
test("missing context.attribution yields null on the executed event without throwing", async () => {
|
|
573
|
+
const events: ToolLifecycleEvent[] = [];
|
|
574
|
+
const executor = new ToolExecutor(makePrompter());
|
|
575
|
+
|
|
576
|
+
const result = await executor.execute(
|
|
577
|
+
"file_read",
|
|
578
|
+
{ path: "README.md" },
|
|
579
|
+
makeContext(events),
|
|
580
|
+
);
|
|
581
|
+
|
|
582
|
+
expect(result).toMatchObject({ content: "ok", isError: false });
|
|
583
|
+
const executed = events.find((event) => event.type === "executed");
|
|
584
|
+
if (executed?.type !== "executed")
|
|
585
|
+
throw new Error("Expected executed event");
|
|
586
|
+
expect(executed.attribution).toBeNull();
|
|
587
|
+
});
|
|
588
|
+
|
|
589
|
+
test("missing context.attribution yields null on the error event without throwing", async () => {
|
|
590
|
+
toolThrow = new Error("boom");
|
|
591
|
+
|
|
592
|
+
const events: ToolLifecycleEvent[] = [];
|
|
593
|
+
const executor = new ToolExecutor(makePrompter());
|
|
594
|
+
|
|
595
|
+
const result = await executor.execute("file_read", {}, makeContext(events));
|
|
596
|
+
|
|
597
|
+
expect(result.isError).toBe(true);
|
|
598
|
+
const errorEvent = events.find((event) => event.type === "error");
|
|
599
|
+
if (errorEvent?.type !== "error") throw new Error("Expected error event");
|
|
600
|
+
expect(errorEvent.attribution).toBeNull();
|
|
601
|
+
});
|
|
602
|
+
|
|
603
|
+
test("stamps attribution on pre-execution gate error events (unknown tool)", async () => {
|
|
604
|
+
const events: ToolLifecycleEvent[] = [];
|
|
605
|
+
const executor = new ToolExecutor(makePrompter());
|
|
606
|
+
|
|
607
|
+
const result = await executor.execute(
|
|
608
|
+
"unknown_tool",
|
|
609
|
+
{ test: true },
|
|
610
|
+
makeContext(events, { attribution: testAttribution }),
|
|
611
|
+
);
|
|
612
|
+
|
|
613
|
+
expect(result.isError).toBe(true);
|
|
614
|
+
const errorEvent = events.find((event) => event.type === "error");
|
|
615
|
+
if (errorEvent?.type !== "error") throw new Error("Expected error event");
|
|
616
|
+
expect(errorEvent.errorMessage).toContain("Unknown tool: unknown_tool");
|
|
617
|
+
expect(errorEvent.attribution).toEqual(testAttribution);
|
|
618
|
+
});
|
|
619
|
+
|
|
620
|
+
test("missing attribution yields null on pre-execution gate error events", async () => {
|
|
621
|
+
const events: ToolLifecycleEvent[] = [];
|
|
622
|
+
const executor = new ToolExecutor(makePrompter());
|
|
623
|
+
|
|
624
|
+
const result = await executor.execute(
|
|
625
|
+
"unknown_tool",
|
|
626
|
+
{ test: true },
|
|
627
|
+
makeContext(events),
|
|
628
|
+
);
|
|
629
|
+
|
|
630
|
+
expect(result.isError).toBe(true);
|
|
631
|
+
const errorEvent = events.find((event) => event.type === "error");
|
|
632
|
+
if (errorEvent?.type !== "error") throw new Error("Expected error event");
|
|
633
|
+
expect(errorEvent.attribution).toBeNull();
|
|
634
|
+
});
|
|
635
|
+
|
|
636
|
+
test("stamps attribution on the aborted pre-execution gate error event", async () => {
|
|
637
|
+
const events: ToolLifecycleEvent[] = [];
|
|
638
|
+
const executor = new ToolExecutor(makePrompter());
|
|
639
|
+
const controller = new AbortController();
|
|
640
|
+
controller.abort();
|
|
641
|
+
|
|
642
|
+
const result = await executor.execute(
|
|
643
|
+
"file_read",
|
|
644
|
+
{ path: "README.md" },
|
|
645
|
+
makeContext(events, {
|
|
646
|
+
attribution: testAttribution,
|
|
647
|
+
signal: controller.signal,
|
|
648
|
+
}),
|
|
649
|
+
);
|
|
650
|
+
|
|
651
|
+
expect(result).toEqual({ content: "Cancelled", isError: true });
|
|
652
|
+
const errorEvent = events.find((event) => event.type === "error");
|
|
653
|
+
if (errorEvent?.type !== "error") throw new Error("Expected error event");
|
|
654
|
+
expect(errorEvent.errorMessage).toBe("Cancelled");
|
|
655
|
+
expect(errorEvent.attribution).toEqual(testAttribution);
|
|
656
|
+
});
|
|
657
|
+
|
|
658
|
+
// ── raw input byte sizing tests ───────────────────────────
|
|
659
|
+
|
|
660
|
+
test("stamps inputBytes from the raw input even when sanitization redacts fields", async () => {
|
|
661
|
+
const events: ToolLifecycleEvent[] = [];
|
|
662
|
+
const executor = new ToolExecutor(makePrompter());
|
|
663
|
+
|
|
664
|
+
const rawInput = { path: "README.md", token: "t-1" };
|
|
665
|
+
const rawSize = Buffer.byteLength(JSON.stringify(rawInput), "utf8");
|
|
666
|
+
|
|
667
|
+
await executor.execute("file_read", rawInput, makeContext(events));
|
|
668
|
+
|
|
669
|
+
const executed = events.find((event) => event.type === "executed");
|
|
670
|
+
if (executed?.type !== "executed")
|
|
671
|
+
throw new Error("Expected executed event");
|
|
672
|
+
// The event input is sanitized, but the size reflects the raw payload.
|
|
673
|
+
expect(executed.input.token).toBe("<redacted />");
|
|
674
|
+
expect(executed.inputBytes).toBe(rawSize);
|
|
675
|
+
expect(executed.inputBytes).not.toBe(
|
|
676
|
+
Buffer.byteLength(JSON.stringify(executed.input), "utf8"),
|
|
677
|
+
);
|
|
678
|
+
});
|
|
679
|
+
|
|
680
|
+
test("stamps inputBytes from the raw input on error events", async () => {
|
|
681
|
+
toolThrow = new Error("boom");
|
|
682
|
+
|
|
683
|
+
const events: ToolLifecycleEvent[] = [];
|
|
684
|
+
const executor = new ToolExecutor(makePrompter());
|
|
685
|
+
|
|
686
|
+
const rawInput = { path: "README.md", api_key: "k-1" };
|
|
687
|
+
|
|
688
|
+
await executor.execute("file_read", rawInput, makeContext(events));
|
|
689
|
+
|
|
690
|
+
const errorEvent = events.find((event) => event.type === "error");
|
|
691
|
+
if (errorEvent?.type !== "error") throw new Error("Expected error event");
|
|
692
|
+
expect(errorEvent.input.api_key).toBe("<redacted />");
|
|
693
|
+
expect(errorEvent.inputBytes).toBe(
|
|
694
|
+
Buffer.byteLength(JSON.stringify(rawInput), "utf8"),
|
|
695
|
+
);
|
|
696
|
+
});
|
|
697
|
+
|
|
698
|
+
test("stamps resultBytes from the raw content before sensitive-output sanitization", async () => {
|
|
699
|
+
// Directive stripping + placeholder substitution shrink the content the
|
|
700
|
+
// lifecycle event carries; the stamped size must reflect the raw output.
|
|
701
|
+
const rawContent =
|
|
702
|
+
'Your invite: <vellum-sensitive-output kind="invite_code" value="SECRET-CODE-123" /> use SECRET-CODE-123';
|
|
703
|
+
fakeToolResult = { content: rawContent, isError: false };
|
|
704
|
+
|
|
705
|
+
const events: ToolLifecycleEvent[] = [];
|
|
706
|
+
const executor = new ToolExecutor(makePrompter());
|
|
707
|
+
|
|
708
|
+
await executor.execute("file_read", { path: "a" }, makeContext(events));
|
|
709
|
+
|
|
710
|
+
const executed = events.find((event) => event.type === "executed");
|
|
711
|
+
if (executed?.type !== "executed")
|
|
712
|
+
throw new Error("Expected executed event");
|
|
713
|
+
// The event carries the sanitized content...
|
|
714
|
+
expect(executed.result.content).not.toContain("SECRET-CODE-123");
|
|
715
|
+
// ...but the stamped size is the raw pre-sanitization byte length.
|
|
716
|
+
expect(executed.resultBytes).toBe(Buffer.byteLength(rawContent, "utf8"));
|
|
717
|
+
expect(executed.resultBytes).not.toBe(
|
|
718
|
+
Buffer.byteLength(executed.result.content, "utf8"),
|
|
719
|
+
);
|
|
720
|
+
});
|
|
721
|
+
|
|
722
|
+
test("stamps resultBytes for non-sensitive results too (raw equals emitted content)", async () => {
|
|
723
|
+
const events: ToolLifecycleEvent[] = [];
|
|
724
|
+
const executor = new ToolExecutor(makePrompter());
|
|
725
|
+
|
|
726
|
+
await executor.execute("file_read", { path: "a" }, makeContext(events));
|
|
727
|
+
|
|
728
|
+
const executed = events.find((event) => event.type === "executed");
|
|
729
|
+
if (executed?.type !== "executed")
|
|
730
|
+
throw new Error("Expected executed event");
|
|
731
|
+
expect(executed.result.content).toBe("ok");
|
|
732
|
+
expect(executed.resultBytes).toBe(Buffer.byteLength("ok", "utf8"));
|
|
733
|
+
});
|
|
734
|
+
|
|
735
|
+
test("audit listener records result_bytes from the raw pre-sanitization output", async () => {
|
|
736
|
+
const { createToolAuditListener } =
|
|
737
|
+
await import("../events/tool-audit-listener.js");
|
|
738
|
+
const records: Array<{ resultBytes?: number | null; result: string }> = [];
|
|
739
|
+
const executor = new ToolExecutor(makePrompter());
|
|
740
|
+
|
|
741
|
+
const rawContent =
|
|
742
|
+
'Code: <vellum-sensitive-output kind="invite_code" value="SECRET-CODE-456" />SECRET-CODE-456';
|
|
743
|
+
fakeToolResult = { content: rawContent, isError: false };
|
|
744
|
+
|
|
745
|
+
await executor.execute(
|
|
746
|
+
"file_read",
|
|
747
|
+
{ path: "a" },
|
|
748
|
+
{
|
|
749
|
+
workingDir: "/tmp/project",
|
|
750
|
+
conversationId: "conversation-1",
|
|
751
|
+
trustClass: "guardian" as const,
|
|
752
|
+
onToolLifecycleEvent: createToolAuditListener((record) =>
|
|
753
|
+
records.push(record),
|
|
754
|
+
),
|
|
755
|
+
},
|
|
756
|
+
);
|
|
757
|
+
|
|
758
|
+
expect(records).toHaveLength(1);
|
|
759
|
+
expect(records[0].resultBytes).toBe(Buffer.byteLength(rawContent, "utf8"));
|
|
760
|
+
// The stored result column is still the sanitized payload.
|
|
761
|
+
expect(records[0].result).not.toContain("SECRET-CODE-456");
|
|
762
|
+
});
|
|
763
|
+
|
|
764
|
+
test("audit listener records arg_bytes equal to the raw serialized input size", async () => {
|
|
765
|
+
// End-to-end: executor sanitization must not shrink the recorded
|
|
766
|
+
// arg_bytes — the audit row sizes the raw pre-redaction input.
|
|
767
|
+
const { createToolAuditListener } =
|
|
768
|
+
await import("../events/tool-audit-listener.js");
|
|
769
|
+
const records: Array<{ argBytes?: number | null; input: string }> = [];
|
|
770
|
+
const executor = new ToolExecutor(makePrompter());
|
|
771
|
+
|
|
772
|
+
const rawInput = { path: "README.md", token: "t-1" };
|
|
773
|
+
|
|
774
|
+
await executor.execute("file_read", rawInput, {
|
|
775
|
+
workingDir: "/tmp/project",
|
|
776
|
+
conversationId: "conversation-1",
|
|
777
|
+
trustClass: "guardian" as const,
|
|
778
|
+
onToolLifecycleEvent: createToolAuditListener((record) =>
|
|
779
|
+
records.push(record),
|
|
780
|
+
),
|
|
781
|
+
});
|
|
782
|
+
|
|
783
|
+
expect(records).toHaveLength(1);
|
|
784
|
+
expect(records[0].argBytes).toBe(
|
|
785
|
+
Buffer.byteLength(JSON.stringify(rawInput), "utf8"),
|
|
786
|
+
);
|
|
787
|
+
// The stored input column is still the redacted payload.
|
|
788
|
+
expect(records[0].input).not.toContain("t-1");
|
|
789
|
+
});
|
|
790
|
+
|
|
518
791
|
test("skill tool with sandbox execution_target resolves to sandbox executionTarget", async () => {
|
|
519
792
|
const events: ToolLifecycleEvent[] = [];
|
|
520
793
|
const executor = new ToolExecutor(makePrompter());
|