@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
|
@@ -17,6 +17,7 @@ import {
|
|
|
17
17
|
addMessage,
|
|
18
18
|
clearAll,
|
|
19
19
|
createConversation,
|
|
20
|
+
deleteConversation,
|
|
20
21
|
deleteLastExchange,
|
|
21
22
|
getConversation,
|
|
22
23
|
getMessages,
|
|
@@ -24,6 +25,7 @@ import {
|
|
|
24
25
|
import { isLastUserMessageToolResult } from "../memory/conversation-queries.js";
|
|
25
26
|
import { getDb } from "../memory/db-connection.js";
|
|
26
27
|
import { initializeDb } from "../memory/db-init.js";
|
|
28
|
+
import { skillLoadedEvents } from "../memory/schema.js";
|
|
27
29
|
// Initialize db once before all tests
|
|
28
30
|
initializeDb();
|
|
29
31
|
|
|
@@ -351,6 +353,78 @@ describe("attachment orphan cleanup", () => {
|
|
|
351
353
|
expect(linkCount.c).toBe(0);
|
|
352
354
|
});
|
|
353
355
|
|
|
356
|
+
test("clearAll removes skill_loaded_events", async () => {
|
|
357
|
+
// Privacy posture: Clear All wins over unflushed telemetry — the
|
|
358
|
+
// skill_loaded_events rows (conversation id, skill name, model
|
|
359
|
+
// attribution) must not survive the wipe and ship later.
|
|
360
|
+
getDb().delete(skillLoadedEvents).run();
|
|
361
|
+
const conv = createConversation("test");
|
|
362
|
+
getDb()
|
|
363
|
+
.insert(skillLoadedEvents)
|
|
364
|
+
.values([
|
|
365
|
+
{
|
|
366
|
+
id: "sle-clear-1",
|
|
367
|
+
createdAt: 1000,
|
|
368
|
+
conversationId: conv.id,
|
|
369
|
+
skillName: "web-research",
|
|
370
|
+
},
|
|
371
|
+
// Rows without a conversation are wiped too.
|
|
372
|
+
{ id: "sle-clear-2", createdAt: 2000, skillName: "tasks" },
|
|
373
|
+
])
|
|
374
|
+
.run();
|
|
375
|
+
|
|
376
|
+
await clearAll();
|
|
377
|
+
|
|
378
|
+
expect(getDb().select().from(skillLoadedEvents).all()).toHaveLength(0);
|
|
379
|
+
});
|
|
380
|
+
|
|
381
|
+
test("deleteConversation removes that conversation's skill_loaded_events", async () => {
|
|
382
|
+
getDb().delete(skillLoadedEvents).run();
|
|
383
|
+
const conv = createConversation("doomed");
|
|
384
|
+
await addMessage(conv.id, "user", "hello");
|
|
385
|
+
const other = createConversation("kept");
|
|
386
|
+
getDb()
|
|
387
|
+
.insert(skillLoadedEvents)
|
|
388
|
+
.values([
|
|
389
|
+
{
|
|
390
|
+
id: "sle-del-1",
|
|
391
|
+
createdAt: 1000,
|
|
392
|
+
conversationId: conv.id,
|
|
393
|
+
skillName: "web-research",
|
|
394
|
+
},
|
|
395
|
+
{
|
|
396
|
+
id: "sle-del-2",
|
|
397
|
+
createdAt: 2000,
|
|
398
|
+
conversationId: other.id,
|
|
399
|
+
skillName: "tasks",
|
|
400
|
+
},
|
|
401
|
+
])
|
|
402
|
+
.run();
|
|
403
|
+
|
|
404
|
+
deleteConversation(conv.id);
|
|
405
|
+
|
|
406
|
+
const remaining = getDb().select().from(skillLoadedEvents).all();
|
|
407
|
+
expect(remaining.map((r) => r.id)).toEqual(["sle-del-2"]);
|
|
408
|
+
});
|
|
409
|
+
|
|
410
|
+
test("deleteConversation removes skill_loaded_events when the conversation has no messages", () => {
|
|
411
|
+
getDb().delete(skillLoadedEvents).run();
|
|
412
|
+
const conv = createConversation("empty");
|
|
413
|
+
getDb()
|
|
414
|
+
.insert(skillLoadedEvents)
|
|
415
|
+
.values({
|
|
416
|
+
id: "sle-del-empty",
|
|
417
|
+
createdAt: 1000,
|
|
418
|
+
conversationId: conv.id,
|
|
419
|
+
skillName: "web-research",
|
|
420
|
+
})
|
|
421
|
+
.run();
|
|
422
|
+
|
|
423
|
+
deleteConversation(conv.id);
|
|
424
|
+
|
|
425
|
+
expect(getDb().select().from(skillLoadedEvents).all()).toHaveLength(0);
|
|
426
|
+
});
|
|
427
|
+
|
|
354
428
|
test("deleteLastExchange does not delete unlinked user uploads", async () => {
|
|
355
429
|
const conv = createConversation("test");
|
|
356
430
|
await addMessage(conv.id, "user", "hello");
|
|
@@ -511,6 +511,7 @@ describe("surfaceProxyResolver — app-control tool routing", () => {
|
|
|
511
511
|
_setActiveAppControlSession({
|
|
512
512
|
conversationId: "conv-1",
|
|
513
513
|
app: "com.example.editor",
|
|
514
|
+
targetClientId: "client-b",
|
|
514
515
|
});
|
|
515
516
|
|
|
516
517
|
const resultPromise = surfaceProxyResolver(ctx, "app_control_observe", {
|
|
@@ -595,6 +596,7 @@ describe("surfaceProxyResolver — app-control tool routing", () => {
|
|
|
595
596
|
_setActiveAppControlSession({
|
|
596
597
|
conversationId: "conv-1",
|
|
597
598
|
app: "com.example.editor",
|
|
599
|
+
targetClientId: "client-mine",
|
|
598
600
|
});
|
|
599
601
|
|
|
600
602
|
const resultPromise = surfaceProxyResolver(ctx, "app_control_observe", {
|
|
@@ -617,7 +619,7 @@ describe("surfaceProxyResolver — app-control tool routing", () => {
|
|
|
617
619
|
proxy.dispose();
|
|
618
620
|
});
|
|
619
621
|
|
|
620
|
-
test("single same-user client with no target
|
|
622
|
+
test("single same-user client with no target resolves to that client", async () => {
|
|
621
623
|
mockHubClients = [
|
|
622
624
|
{
|
|
623
625
|
clientId: "only-client",
|
|
@@ -630,6 +632,7 @@ describe("surfaceProxyResolver — app-control tool routing", () => {
|
|
|
630
632
|
_setActiveAppControlSession({
|
|
631
633
|
conversationId: "conv-1",
|
|
632
634
|
app: "com.example.editor",
|
|
635
|
+
targetClientId: "only-client",
|
|
633
636
|
});
|
|
634
637
|
|
|
635
638
|
const resultPromise = surfaceProxyResolver(ctx, "app_control_observe", {
|
|
@@ -0,0 +1,323 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for conversation model attribution threading in
|
|
3
|
+
* conversation-tool-setup.ts: `resolveConversationAttribution` must mirror
|
|
4
|
+
* the agent-loop usage path (the current turn's call site — defaulting to
|
|
5
|
+
* mainAgent — plus the per-turn override profile), `createToolExecutor`
|
|
6
|
+
* must stamp the snapshot onto the ToolContext handed to the executor, and
|
|
7
|
+
* attribution resolution failures must never break tool execution.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { afterEach, beforeEach, describe, expect, mock, test } from "bun:test";
|
|
11
|
+
|
|
12
|
+
import type { ToolSetupContext } from "../daemon/conversation-tool-setup.js";
|
|
13
|
+
import type { SurfaceData, SurfaceType } from "../daemon/message-protocol.js";
|
|
14
|
+
import type { PermissionPrompter } from "../permissions/prompter.js";
|
|
15
|
+
import type { SecretPrompter } from "../permissions/secret-prompter.js";
|
|
16
|
+
import type { ToolExecutor } from "../tools/executor.js";
|
|
17
|
+
import type { ToolContext, ToolExecutionResult } from "../tools/types.js";
|
|
18
|
+
|
|
19
|
+
// ---------------------------------------------------------------------------
|
|
20
|
+
// Module mocks (must precede the import of the module under test)
|
|
21
|
+
// ---------------------------------------------------------------------------
|
|
22
|
+
|
|
23
|
+
let mockLlmConfig: Record<string, unknown> = {};
|
|
24
|
+
let configThrows = false;
|
|
25
|
+
|
|
26
|
+
// Non-llm fields used by other conversation-tool-setup consumers (e.g.
|
|
27
|
+
// createResolveToolsCallback reads `tools.exclude`), so test files sharing
|
|
28
|
+
// this process keep working against the mocked loader.
|
|
29
|
+
const baseConfig = {
|
|
30
|
+
tools: { exclude: [] as string[] },
|
|
31
|
+
timeouts: {
|
|
32
|
+
shellDefaultTimeoutSec: 120,
|
|
33
|
+
shellMaxTimeoutSec: 600,
|
|
34
|
+
permissionTimeoutSec: 300,
|
|
35
|
+
toolExecutionTimeoutSec: 600,
|
|
36
|
+
},
|
|
37
|
+
services: {},
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
function mockGetConfig() {
|
|
41
|
+
if (configThrows) throw new Error("config unavailable");
|
|
42
|
+
return { ...baseConfig, llm: mockLlmConfig };
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
mock.module("../config/loader.js", () => ({
|
|
46
|
+
getConfig: mockGetConfig,
|
|
47
|
+
loadConfig: mockGetConfig,
|
|
48
|
+
invalidateConfigCache: () => {},
|
|
49
|
+
loadRawConfig: () => ({}),
|
|
50
|
+
saveRawConfig: () => {},
|
|
51
|
+
getNestedValue: () => undefined,
|
|
52
|
+
setNestedValue: () => {},
|
|
53
|
+
}));
|
|
54
|
+
|
|
55
|
+
mock.module("../runtime/assistant-event-hub.js", () => ({
|
|
56
|
+
broadcastMessage: mock(() => {}),
|
|
57
|
+
}));
|
|
58
|
+
|
|
59
|
+
mock.module("../daemon/conversation-surfaces.js", () => ({
|
|
60
|
+
refreshSurfacesForApp: mock(() => {}),
|
|
61
|
+
surfaceProxyResolver: mock(() =>
|
|
62
|
+
Promise.resolve({ content: "", isError: false }),
|
|
63
|
+
),
|
|
64
|
+
}));
|
|
65
|
+
|
|
66
|
+
mock.module("../services/published-app-updater.js", () => ({
|
|
67
|
+
updatePublishedAppDeployment: mock(() => Promise.resolve()),
|
|
68
|
+
}));
|
|
69
|
+
|
|
70
|
+
mock.module("../tools/browser/browser-screencast.js", () => ({
|
|
71
|
+
registerConversationSender: mock(() => {}),
|
|
72
|
+
}));
|
|
73
|
+
|
|
74
|
+
mock.module("../memory/app-store.js", () => ({
|
|
75
|
+
getApp: mock(() => null),
|
|
76
|
+
getAppDirPath: mock(() => "/tmp/test-apps/dummy"),
|
|
77
|
+
isMultifileApp: mock(() => false),
|
|
78
|
+
getAppsDir: mock(() => "/tmp/test-apps"),
|
|
79
|
+
resolveAppIdByDirName: mock(() => null),
|
|
80
|
+
resolveAppIdFromPath: mock(() => null),
|
|
81
|
+
}));
|
|
82
|
+
|
|
83
|
+
// ---------------------------------------------------------------------------
|
|
84
|
+
// Imports after mocks are in place
|
|
85
|
+
// ---------------------------------------------------------------------------
|
|
86
|
+
|
|
87
|
+
import { LLMSchema } from "../config/schemas/llm.js";
|
|
88
|
+
import {
|
|
89
|
+
createToolExecutor,
|
|
90
|
+
resolveConversationAttribution,
|
|
91
|
+
} from "../daemon/conversation-tool-setup.js";
|
|
92
|
+
|
|
93
|
+
// ---------------------------------------------------------------------------
|
|
94
|
+
// Helpers
|
|
95
|
+
// ---------------------------------------------------------------------------
|
|
96
|
+
|
|
97
|
+
function setLlmConfig(raw: unknown): void {
|
|
98
|
+
mockLlmConfig = LLMSchema.parse(raw) as Record<string, unknown>;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/** Build a minimal ToolSetupContext stub. */
|
|
102
|
+
function makeCtx(overrides: Partial<ToolSetupContext> = {}): ToolSetupContext {
|
|
103
|
+
return {
|
|
104
|
+
conversationId: "conv-test",
|
|
105
|
+
currentRequestId: "req-1",
|
|
106
|
+
workingDir: "/tmp/test",
|
|
107
|
+
abortController: null,
|
|
108
|
+
traceEmitter: { emit: () => {} },
|
|
109
|
+
sendToClient: mock(() => {}),
|
|
110
|
+
pendingSurfaceActions: new Map(),
|
|
111
|
+
lastSurfaceAction: new Map(),
|
|
112
|
+
surfaceState: new Map<
|
|
113
|
+
string,
|
|
114
|
+
{ surfaceType: SurfaceType; data: SurfaceData; title?: string }
|
|
115
|
+
>(),
|
|
116
|
+
surfaceUndoStacks: new Map(),
|
|
117
|
+
accumulatedSurfaceState: new Map(),
|
|
118
|
+
surfaceActionRequestIds: new Set<string>(),
|
|
119
|
+
currentTurnSurfaces: [],
|
|
120
|
+
isProcessing: () => false,
|
|
121
|
+
enqueueMessage: () => ({ queued: false, requestId: "r" }),
|
|
122
|
+
getQueueDepth: () => 0,
|
|
123
|
+
processMessage: async () => "",
|
|
124
|
+
withSurface: async <T>(_id: string, fn: () => T | Promise<T>) => fn(),
|
|
125
|
+
...overrides,
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/** Fake ToolExecutor that captures the context of each execute() call. */
|
|
130
|
+
function makeCapturingExecutor() {
|
|
131
|
+
const calls: Array<{ name: string; context: ToolContext }> = [];
|
|
132
|
+
const executor = {
|
|
133
|
+
execute: async (
|
|
134
|
+
name: string,
|
|
135
|
+
_input: Record<string, unknown>,
|
|
136
|
+
context: ToolContext,
|
|
137
|
+
): Promise<ToolExecutionResult> => {
|
|
138
|
+
calls.push({ name, context });
|
|
139
|
+
return { content: "ok", isError: false };
|
|
140
|
+
},
|
|
141
|
+
};
|
|
142
|
+
return { executor: executor as unknown as ToolExecutor, calls };
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const noopPrompter = {
|
|
146
|
+
prompt: mock(async () => ({ decision: "allow" as const })),
|
|
147
|
+
} as unknown as PermissionPrompter;
|
|
148
|
+
const noopSecretPrompter = {
|
|
149
|
+
prompt: mock(async () => ({ cancelled: true })),
|
|
150
|
+
} as unknown as SecretPrompter;
|
|
151
|
+
|
|
152
|
+
function makeToolFn(executor: ToolExecutor, ctx: ToolSetupContext) {
|
|
153
|
+
return createToolExecutor(
|
|
154
|
+
executor,
|
|
155
|
+
noopPrompter,
|
|
156
|
+
noopSecretPrompter,
|
|
157
|
+
ctx,
|
|
158
|
+
() => {},
|
|
159
|
+
);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// The module mock outlives this file when multiple test files share a
|
|
163
|
+
// process, so leave the config in a working (non-throwing) state.
|
|
164
|
+
afterEach(() => {
|
|
165
|
+
configThrows = false;
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
beforeEach(() => {
|
|
169
|
+
configThrows = false;
|
|
170
|
+
setLlmConfig({
|
|
171
|
+
default: { provider: "anthropic", model: "model-default" },
|
|
172
|
+
profiles: {
|
|
173
|
+
active: { provider: "openai", model: "model-active" },
|
|
174
|
+
pinned: { provider: "gemini", model: "model-pinned" },
|
|
175
|
+
},
|
|
176
|
+
activeProfile: "active",
|
|
177
|
+
});
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
// ---------------------------------------------------------------------------
|
|
181
|
+
// Tests
|
|
182
|
+
// ---------------------------------------------------------------------------
|
|
183
|
+
|
|
184
|
+
// Precedence semantics (override/active/call-site/default) are owned by
|
|
185
|
+
// resolveUsageAttribution and covered in usage-attribution.test.ts; the
|
|
186
|
+
// tests here only exercise what the wrapper adds on top.
|
|
187
|
+
describe("resolveConversationAttribution", () => {
|
|
188
|
+
test("falls back to the workspace active profile when no override is set", () => {
|
|
189
|
+
const snapshot = resolveConversationAttribution({
|
|
190
|
+
conversationId: "conv-test",
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
expect(snapshot).toMatchObject({
|
|
194
|
+
callSite: "mainAgent",
|
|
195
|
+
overrideProfile: null,
|
|
196
|
+
appliedProfile: "active",
|
|
197
|
+
profileSource: "active",
|
|
198
|
+
resolvedProvider: "openai",
|
|
199
|
+
resolvedModel: "model-active",
|
|
200
|
+
});
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
test("attributes non-main turns to the turn call site like the usage path (voice callAgent)", () => {
|
|
204
|
+
setLlmConfig({
|
|
205
|
+
default: { provider: "anthropic", model: "model-default" },
|
|
206
|
+
profiles: {
|
|
207
|
+
active: { provider: "openai", model: "model-active" },
|
|
208
|
+
pinned: { provider: "gemini", model: "model-pinned" },
|
|
209
|
+
},
|
|
210
|
+
activeProfile: "active",
|
|
211
|
+
callSites: { callAgent: { profile: "pinned" } },
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
const snapshot = resolveConversationAttribution({
|
|
215
|
+
conversationId: "conv-test",
|
|
216
|
+
currentCallSite: "callAgent",
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
// Matches resolveUsageAttribution semantics for non-main call sites:
|
|
220
|
+
// the call-site profile wins (profileSource "call_site"), not the
|
|
221
|
+
// workspace active profile the mainAgent path would report.
|
|
222
|
+
expect(snapshot).toMatchObject({
|
|
223
|
+
callSite: "callAgent",
|
|
224
|
+
activeProfile: "active",
|
|
225
|
+
callSiteProfile: "pinned",
|
|
226
|
+
appliedProfile: "pinned",
|
|
227
|
+
profileSource: "call_site",
|
|
228
|
+
resolvedProvider: "gemini",
|
|
229
|
+
resolvedModel: "model-pinned",
|
|
230
|
+
});
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
test("returns null instead of throwing when resolution fails", () => {
|
|
234
|
+
configThrows = true;
|
|
235
|
+
|
|
236
|
+
expect(
|
|
237
|
+
resolveConversationAttribution({ conversationId: "conv-test" }),
|
|
238
|
+
).toBeNull();
|
|
239
|
+
});
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
describe("createToolExecutor attribution threading", () => {
|
|
243
|
+
test("stamps the attribution snapshot onto the ToolContext for direct tool calls", async () => {
|
|
244
|
+
const { executor, calls } = makeCapturingExecutor();
|
|
245
|
+
const toolFn = makeToolFn(
|
|
246
|
+
executor,
|
|
247
|
+
makeCtx({ currentTurnOverrideProfile: "pinned" }),
|
|
248
|
+
);
|
|
249
|
+
|
|
250
|
+
const result = await toolFn("file_read", { path: "/tmp/a" });
|
|
251
|
+
|
|
252
|
+
expect(result).toMatchObject({ content: "ok", isError: false });
|
|
253
|
+
expect(calls).toHaveLength(1);
|
|
254
|
+
expect(calls[0].context.attribution).toMatchObject({
|
|
255
|
+
callSite: "mainAgent",
|
|
256
|
+
appliedProfile: "pinned",
|
|
257
|
+
profileSource: "conversation",
|
|
258
|
+
resolvedProvider: "gemini",
|
|
259
|
+
resolvedModel: "model-pinned",
|
|
260
|
+
});
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
test("threads a non-main turn call site (voice callAgent) into the snapshot", async () => {
|
|
264
|
+
setLlmConfig({
|
|
265
|
+
default: { provider: "anthropic", model: "model-default" },
|
|
266
|
+
profiles: {
|
|
267
|
+
active: { provider: "openai", model: "model-active" },
|
|
268
|
+
pinned: { provider: "gemini", model: "model-pinned" },
|
|
269
|
+
},
|
|
270
|
+
activeProfile: "active",
|
|
271
|
+
callSites: { callAgent: { profile: "pinned" } },
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
const { executor, calls } = makeCapturingExecutor();
|
|
275
|
+
const toolFn = makeToolFn(
|
|
276
|
+
executor,
|
|
277
|
+
makeCtx({ currentCallSite: "callAgent" }),
|
|
278
|
+
);
|
|
279
|
+
|
|
280
|
+
await toolFn("file_read", { path: "/tmp/a" });
|
|
281
|
+
|
|
282
|
+
expect(calls).toHaveLength(1);
|
|
283
|
+
expect(calls[0].context.attribution).toMatchObject({
|
|
284
|
+
callSite: "callAgent",
|
|
285
|
+
appliedProfile: "pinned",
|
|
286
|
+
profileSource: "call_site",
|
|
287
|
+
resolvedProvider: "gemini",
|
|
288
|
+
resolvedModel: "model-pinned",
|
|
289
|
+
});
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
test("stamps the attribution snapshot onto skill_execute dispatches", async () => {
|
|
293
|
+
const { executor, calls } = makeCapturingExecutor();
|
|
294
|
+
const toolFn = makeToolFn(executor, makeCtx());
|
|
295
|
+
|
|
296
|
+
await toolFn("skill_execute", {
|
|
297
|
+
tool: "file_read",
|
|
298
|
+
input: { path: "/tmp/a" },
|
|
299
|
+
activity: "testing",
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
expect(calls).toHaveLength(1);
|
|
303
|
+
expect(calls[0].name).toBe("file_read");
|
|
304
|
+
expect(calls[0].context.attribution).toMatchObject({
|
|
305
|
+
callSite: "mainAgent",
|
|
306
|
+
appliedProfile: "active",
|
|
307
|
+
profileSource: "active",
|
|
308
|
+
});
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
test("attribution resolution failure yields null and does not break tool execution", async () => {
|
|
312
|
+
configThrows = true;
|
|
313
|
+
|
|
314
|
+
const { executor, calls } = makeCapturingExecutor();
|
|
315
|
+
const toolFn = makeToolFn(executor, makeCtx());
|
|
316
|
+
|
|
317
|
+
const result = await toolFn("file_read", { path: "/tmp/a" });
|
|
318
|
+
|
|
319
|
+
expect(result).toMatchObject({ content: "ok", isError: false });
|
|
320
|
+
expect(calls).toHaveLength(1);
|
|
321
|
+
expect(calls[0].context.attribution).toBeNull();
|
|
322
|
+
});
|
|
323
|
+
});
|
|
@@ -154,4 +154,38 @@ describe("createResolveToolsCallback — toolsDisabledDepth", () => {
|
|
|
154
154
|
resolve(EMPTY_HISTORY);
|
|
155
155
|
expect(ctx.allowedToolNames).toEqual(new Set());
|
|
156
156
|
});
|
|
157
|
+
|
|
158
|
+
test("records the resolved set on lastResolvedToolNames for inventory queries", () => {
|
|
159
|
+
// GIVEN a normal resolver call
|
|
160
|
+
const toolDefs = [makeToolDef("tool_a"), makeToolDef("tool_b")];
|
|
161
|
+
const ctx = makeCtx();
|
|
162
|
+
const resolve = createResolveToolsCallback(toolDefs, ctx)!;
|
|
163
|
+
|
|
164
|
+
// WHEN tools resolve normally
|
|
165
|
+
resolve(EMPTY_HISTORY);
|
|
166
|
+
|
|
167
|
+
// THEN the durable snapshot mirrors the per-turn gate
|
|
168
|
+
expect(ctx.lastResolvedToolNames).toEqual(ctx.allowedToolNames!);
|
|
169
|
+
expect(ctx.lastResolvedToolNames?.has("tool_a")).toBe(true);
|
|
170
|
+
expect(ctx.lastResolvedToolNames?.has("tool_b")).toBe(true);
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
test("preserves lastResolvedToolNames when a later call disables tools", () => {
|
|
174
|
+
// GIVEN a conversation that resolved tools on a normal turn
|
|
175
|
+
const toolDefs = [makeToolDef("tool_a"), makeToolDef("tool_b")];
|
|
176
|
+
const ctx = makeCtx();
|
|
177
|
+
const resolve = createResolveToolsCallback(toolDefs, ctx)!;
|
|
178
|
+
resolve(EMPTY_HISTORY);
|
|
179
|
+
const snapshot = ctx.lastResolvedToolNames;
|
|
180
|
+
|
|
181
|
+
// WHEN a subsequent call disables tools (e.g. pointer generation, or the
|
|
182
|
+
// turn-teardown that clears the per-turn gate)
|
|
183
|
+
ctx.toolsDisabledDepth = 1;
|
|
184
|
+
resolve(EMPTY_HISTORY);
|
|
185
|
+
|
|
186
|
+
// THEN the per-turn gate is empty but the inventory snapshot survives
|
|
187
|
+
expect(ctx.allowedToolNames).toEqual(new Set());
|
|
188
|
+
expect(ctx.lastResolvedToolNames).toBe(snapshot);
|
|
189
|
+
expect(ctx.lastResolvedToolNames?.has("tool_a")).toBe(true);
|
|
190
|
+
});
|
|
157
191
|
});
|
|
@@ -190,6 +190,7 @@ mock.module("../plugins/defaults/compaction/window-manager.js", () => ({
|
|
|
190
190
|
async maybeCompact() {
|
|
191
191
|
return { compacted: false };
|
|
192
192
|
}
|
|
193
|
+
resetOverflowRecovery() {}
|
|
193
194
|
},
|
|
194
195
|
createContextSummaryMessage: () => ({
|
|
195
196
|
role: "user",
|
|
@@ -270,7 +271,6 @@ mock.module("../agent/loop.js", () => ({
|
|
|
270
271
|
return {
|
|
271
272
|
history: [...messages, assistantMessage],
|
|
272
273
|
exitReason: null,
|
|
273
|
-
appendedNewMessages: true,
|
|
274
274
|
newMessages: [assistantMessage],
|
|
275
275
|
};
|
|
276
276
|
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
|
|
3
|
+
import { computeCorrectedOverflowTarget } from "../plugins/defaults/compaction/corrected-target.js";
|
|
4
|
+
|
|
5
|
+
describe("computeCorrectedOverflowTarget", () => {
|
|
6
|
+
test("lowers the target proportionally when the estimator under-counted", () => {
|
|
7
|
+
/**
|
|
8
|
+
* When the provider's actual token count exceeds the estimator's count,
|
|
9
|
+
* the target is scaled down by the estimation-error ratio so a re-estimate
|
|
10
|
+
* of the reduced history lands under the provider's real ceiling.
|
|
11
|
+
*/
|
|
12
|
+
// GIVEN a preflight budget and an estimator that under-counted (185k vs 242k actual)
|
|
13
|
+
const preflightBudget = 162_000;
|
|
14
|
+
const actualTokens = 242_000;
|
|
15
|
+
const estimatedTokens = 185_000;
|
|
16
|
+
|
|
17
|
+
// WHEN computing the corrected target
|
|
18
|
+
const result = computeCorrectedOverflowTarget({
|
|
19
|
+
preflightBudget,
|
|
20
|
+
actualTokens,
|
|
21
|
+
estimatedTokens,
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
// THEN the target is the budget divided by the error ratio
|
|
25
|
+
const expectedRatio = actualTokens / estimatedTokens;
|
|
26
|
+
expect(result.targetTokens).toBe(
|
|
27
|
+
Math.floor(preflightBudget / expectedRatio),
|
|
28
|
+
);
|
|
29
|
+
// AND the ratio is surfaced for logging
|
|
30
|
+
expect(result.estimationErrorRatio).toBeCloseTo(expectedRatio, 10);
|
|
31
|
+
// AND the corrected target is below the nominal budget
|
|
32
|
+
expect(result.targetTokens).toBeLessThan(preflightBudget);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
test("returns the preflight budget unchanged when the estimator did not under-count", () => {
|
|
36
|
+
/**
|
|
37
|
+
* If the estimator matched or over-counted the provider, no correction is
|
|
38
|
+
* applied and the nominal preflight budget is the target.
|
|
39
|
+
*/
|
|
40
|
+
// GIVEN an actual count at or below the estimate
|
|
41
|
+
const preflightBudget = 162_000;
|
|
42
|
+
|
|
43
|
+
// WHEN computing the corrected target
|
|
44
|
+
const result = computeCorrectedOverflowTarget({
|
|
45
|
+
preflightBudget,
|
|
46
|
+
actualTokens: 180_000,
|
|
47
|
+
estimatedTokens: 185_000,
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
// THEN the target is the unchanged preflight budget and no ratio is reported
|
|
51
|
+
expect(result.targetTokens).toBe(preflightBudget);
|
|
52
|
+
expect(result.estimationErrorRatio).toBeNull();
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
test("falls back to the preflight budget when the actual count is unavailable", () => {
|
|
56
|
+
/**
|
|
57
|
+
* Proxy-rewrapped or unparseable rejections yield a null actual count; the
|
|
58
|
+
* correction is skipped rather than guessed.
|
|
59
|
+
*/
|
|
60
|
+
// GIVEN a null actual token count
|
|
61
|
+
const preflightBudget = 162_000;
|
|
62
|
+
|
|
63
|
+
// WHEN computing the corrected target
|
|
64
|
+
const result = computeCorrectedOverflowTarget({
|
|
65
|
+
preflightBudget,
|
|
66
|
+
actualTokens: null,
|
|
67
|
+
estimatedTokens: 185_000,
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
// THEN the nominal budget is used with no ratio
|
|
71
|
+
expect(result.targetTokens).toBe(preflightBudget);
|
|
72
|
+
expect(result.estimationErrorRatio).toBeNull();
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
test("falls back to the preflight budget when the estimate is zero", () => {
|
|
76
|
+
/**
|
|
77
|
+
* A zero estimate would make the ratio undefined; guard against it.
|
|
78
|
+
*/
|
|
79
|
+
// GIVEN a zero estimate
|
|
80
|
+
const preflightBudget = 162_000;
|
|
81
|
+
|
|
82
|
+
// WHEN computing the corrected target
|
|
83
|
+
const result = computeCorrectedOverflowTarget({
|
|
84
|
+
preflightBudget,
|
|
85
|
+
actualTokens: 242_000,
|
|
86
|
+
estimatedTokens: 0,
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
// THEN the nominal budget is used with no ratio
|
|
90
|
+
expect(result.targetTokens).toBe(preflightBudget);
|
|
91
|
+
expect(result.estimationErrorRatio).toBeNull();
|
|
92
|
+
});
|
|
93
|
+
});
|
|
@@ -157,7 +157,7 @@ describe("CES flags respect explicit false overrides", () => {
|
|
|
157
157
|
// ---------------------------------------------------------------------------
|
|
158
158
|
|
|
159
159
|
describe("CES flags do not affect unrelated flags", () => {
|
|
160
|
-
test("enabling all CES flags does not change declared
|
|
160
|
+
test("enabling all CES flags does not change other declared flags", () => {
|
|
161
161
|
const overrides: Record<string, boolean> = {};
|
|
162
162
|
for (const key of ALL_CES_FLAG_KEYS) {
|
|
163
163
|
overrides[key] = true;
|
|
@@ -165,10 +165,8 @@ describe("CES flags do not affect unrelated flags", () => {
|
|
|
165
165
|
setOverridesForTesting(overrides);
|
|
166
166
|
const config = makeConfig();
|
|
167
167
|
|
|
168
|
-
//
|
|
169
|
-
expect(isAssistantFeatureFlagEnabled("
|
|
170
|
-
true,
|
|
171
|
-
);
|
|
168
|
+
// auto-analyze defaults to false and should stay false.
|
|
169
|
+
expect(isAssistantFeatureFlagEnabled("auto-analyze", config)).toBe(false);
|
|
172
170
|
});
|
|
173
171
|
|
|
174
172
|
test("enabling all CES flags does not change unrelated fail-closed flags", () => {
|