@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
|
@@ -13,6 +13,7 @@ import type { LoopToolExecutor } from "../agent/loop.js";
|
|
|
13
13
|
import type { ServerMessage } from "../daemon/message-protocol.js";
|
|
14
14
|
import { resetPluginRegistryAndRegisterDefaults } from "../plugins/defaults/index.js";
|
|
15
15
|
import type { Message, Provider, ToolDefinition } from "../providers/types.js";
|
|
16
|
+
import { ContextOverflowError } from "../providers/types.js";
|
|
16
17
|
|
|
17
18
|
const conversationCrudRealSnapshot = {
|
|
18
19
|
...(createRequire(import.meta.url)(
|
|
@@ -94,7 +95,7 @@ mock.module("../config/loader.js", () => ({
|
|
|
94
95
|
// Token estimator returns a small value by default (well within budget)
|
|
95
96
|
// so preflight does not trigger unless the test overrides it. Both the
|
|
96
97
|
// calibrated entry point (`estimatePromptTokens`, which backs the preflight
|
|
97
|
-
// overflow gate and
|
|
98
|
+
// overflow gate and reactive overflow recovery) and the raw entry point
|
|
98
99
|
// (`estimatePromptTokensRaw`, used by the pre-send calibration capture) are
|
|
99
100
|
// stubbed so either call site can drive the test.
|
|
100
101
|
let mockEstimateTokens = 1000;
|
|
@@ -114,42 +115,106 @@ mock.module("../context/token-estimator.js", () => ({
|
|
|
114
115
|
let mockReducerStepFn:
|
|
115
116
|
| ((msgs: Message[], cfg: unknown, state: unknown) => unknown)
|
|
116
117
|
| null = null;
|
|
118
|
+
const makeInitialReducerState = () => ({
|
|
119
|
+
appliedTiers: [] as string[],
|
|
120
|
+
injectionMode: "full" as const,
|
|
121
|
+
exhausted: false,
|
|
122
|
+
});
|
|
123
|
+
const runMockReducer = async (
|
|
124
|
+
msgs: Message[],
|
|
125
|
+
cfg: unknown,
|
|
126
|
+
state: unknown,
|
|
127
|
+
) => {
|
|
128
|
+
if (mockReducerStepFn) return mockReducerStepFn(msgs, cfg, state);
|
|
129
|
+
return {
|
|
130
|
+
messages: msgs,
|
|
131
|
+
tier: "forced_compaction",
|
|
132
|
+
state: {
|
|
133
|
+
appliedTiers: [
|
|
134
|
+
"forced_compaction",
|
|
135
|
+
"tool_result_truncation",
|
|
136
|
+
"media_stubbing",
|
|
137
|
+
"injection_downgrade",
|
|
138
|
+
],
|
|
139
|
+
injectionMode: "full",
|
|
140
|
+
exhausted: true,
|
|
141
|
+
},
|
|
142
|
+
estimatedTokens: 1000,
|
|
143
|
+
};
|
|
144
|
+
};
|
|
117
145
|
mock.module(
|
|
118
146
|
"../plugins/defaults/compaction/context-overflow-reducer.js",
|
|
119
147
|
() => ({
|
|
120
|
-
createInitialReducerState:
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
148
|
+
createInitialReducerState: makeInitialReducerState,
|
|
149
|
+
reduceContextOverflow: runMockReducer,
|
|
150
|
+
}),
|
|
151
|
+
);
|
|
152
|
+
|
|
153
|
+
// Stand-in for `ContextWindowManager`'s turn-scoped overflow ladder. Threads
|
|
154
|
+
// reducer state across a turn's rungs and delegates each rung to the mocked
|
|
155
|
+
// reducer, mirroring `reduceOverflowOneRung` / `resetOverflowRecovery`.
|
|
156
|
+
// `recoverContextOverflow` adapts a rung into the `ContextWindowResult` the
|
|
157
|
+
// agent loop's compaction path consumes, mirroring the real manager's
|
|
158
|
+
// `overflowStepToResult` so the loop sees the rung's reduced history, injection
|
|
159
|
+
// mode, terminal auto-compress flag, and exhaustion.
|
|
160
|
+
function makeOverflowLadderStub(): {
|
|
161
|
+
resetOverflowRecovery: () => void;
|
|
162
|
+
reduceOverflowOneRung: (
|
|
163
|
+
msgs: Message[],
|
|
164
|
+
opts: unknown,
|
|
165
|
+
signal?: AbortSignal,
|
|
166
|
+
) => Promise<unknown>;
|
|
167
|
+
recoverContextOverflow: (
|
|
168
|
+
msgs: Message[],
|
|
169
|
+
opts: unknown,
|
|
170
|
+
signal?: AbortSignal,
|
|
171
|
+
) => Promise<unknown>;
|
|
172
|
+
} {
|
|
173
|
+
let state: unknown;
|
|
174
|
+
const reduceOverflowOneRung = async (msgs: Message[], opts: unknown) => {
|
|
175
|
+
if (!state) state = makeInitialReducerState();
|
|
176
|
+
const step = (await runMockReducer(msgs, opts, state)) as {
|
|
177
|
+
state: unknown;
|
|
178
|
+
};
|
|
179
|
+
state = step.state;
|
|
180
|
+
return step;
|
|
181
|
+
};
|
|
182
|
+
return {
|
|
183
|
+
resetOverflowRecovery: () => {
|
|
184
|
+
state = undefined;
|
|
185
|
+
},
|
|
186
|
+
reduceOverflowOneRung,
|
|
187
|
+
recoverContextOverflow: async (msgs: Message[], opts: unknown) => {
|
|
188
|
+
const step = (await reduceOverflowOneRung(msgs, opts)) as {
|
|
189
|
+
messages: Message[];
|
|
190
|
+
estimatedTokens?: number;
|
|
134
191
|
state: {
|
|
135
|
-
appliedTiers: [
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
192
|
+
appliedTiers: string[];
|
|
193
|
+
injectionMode: string;
|
|
194
|
+
exhausted: boolean;
|
|
195
|
+
};
|
|
196
|
+
compactionResult?: Record<string, unknown>;
|
|
197
|
+
};
|
|
198
|
+
const base = step.compactionResult ?? {
|
|
199
|
+
compacted: false,
|
|
200
|
+
messages: step.messages,
|
|
201
|
+
};
|
|
202
|
+
return {
|
|
203
|
+
...base,
|
|
204
|
+
messages: step.messages,
|
|
205
|
+
injectionMode: step.state.injectionMode,
|
|
206
|
+
autoCompressApplied: step.state.appliedTiers.includes(
|
|
207
|
+
"auto_compress_latest_turn",
|
|
208
|
+
),
|
|
209
|
+
exhausted: step.state.exhausted,
|
|
145
210
|
};
|
|
146
211
|
},
|
|
147
|
-
}
|
|
148
|
-
|
|
212
|
+
};
|
|
213
|
+
}
|
|
149
214
|
|
|
150
215
|
// Policy: default to fail_gracefully
|
|
151
216
|
let mockOverflowAction: string = "fail_gracefully";
|
|
152
|
-
mock.module("../
|
|
217
|
+
mock.module("../plugins/defaults/compaction/overflow-policy.js", () => ({
|
|
153
218
|
resolveOverflowAction: () => mockOverflowAction,
|
|
154
219
|
}));
|
|
155
220
|
|
|
@@ -431,6 +496,7 @@ mock.module("../plugins/defaults/history-repair/terminal.js", () => ({
|
|
|
431
496
|
},
|
|
432
497
|
}),
|
|
433
498
|
deepRepairHistory: (msgs: Message[]) => ({ messages: msgs, stats: {} }),
|
|
499
|
+
isRepairableOrderingError: () => false,
|
|
434
500
|
}));
|
|
435
501
|
|
|
436
502
|
const recordUsageMock = mock(() => {});
|
|
@@ -508,11 +574,6 @@ mock.module("../daemon/conversation-error.js", () => ({
|
|
|
508
574
|
isContextTooLarge: (msg: string) => /context.?length.?exceeded/i.test(msg),
|
|
509
575
|
}));
|
|
510
576
|
|
|
511
|
-
mock.module("../daemon/conversation-slash.js", () => ({
|
|
512
|
-
isProviderOrderingError: (msg: string) =>
|
|
513
|
-
/ordering|before.*after|messages.*order/i.test(msg),
|
|
514
|
-
}));
|
|
515
|
-
|
|
516
577
|
mock.module("../util/truncate.js", () => ({
|
|
517
578
|
truncate: (s: string) => s,
|
|
518
579
|
}));
|
|
@@ -716,6 +777,16 @@ function makeCtx(
|
|
|
716
777
|
|
|
717
778
|
...ctxOverrides,
|
|
718
779
|
} as unknown as Conversation;
|
|
780
|
+
// Reactive overflow recovery resolves the turn-scoped reduction ladder off
|
|
781
|
+
// the manager; give every fake manager the ladder methods unless a test
|
|
782
|
+
// supplied its own.
|
|
783
|
+
const manager = ctx.contextWindowManager as unknown as Record<
|
|
784
|
+
string,
|
|
785
|
+
unknown
|
|
786
|
+
>;
|
|
787
|
+
if (typeof manager.reduceOverflowOneRung !== "function") {
|
|
788
|
+
Object.assign(manager, makeOverflowLadderStub());
|
|
789
|
+
}
|
|
719
790
|
fakeContextWindowManagers.set(conversationId, ctx.contextWindowManager);
|
|
720
791
|
return ctx;
|
|
721
792
|
}
|
|
@@ -1470,509 +1541,6 @@ describe("session-agent-loop", () => {
|
|
|
1470
1541
|
});
|
|
1471
1542
|
});
|
|
1472
1543
|
|
|
1473
|
-
describe("context window exhaustion (context-too-large recovery)", () => {
|
|
1474
|
-
test("forwards cache-aware compaction usage to recordUsage", async () => {
|
|
1475
|
-
const events: ServerMessage[] = [];
|
|
1476
|
-
mockEstimateTokens = 120_000;
|
|
1477
|
-
|
|
1478
|
-
mockReducerStepFn = (msgs: Message[]) => ({
|
|
1479
|
-
messages: msgs,
|
|
1480
|
-
tier: "forced_compaction",
|
|
1481
|
-
state: {
|
|
1482
|
-
appliedTiers: ["forced_compaction"],
|
|
1483
|
-
injectionMode: "full",
|
|
1484
|
-
exhausted: false,
|
|
1485
|
-
},
|
|
1486
|
-
estimatedTokens: 5_000,
|
|
1487
|
-
compactionResult: {
|
|
1488
|
-
compacted: true,
|
|
1489
|
-
messages: msgs,
|
|
1490
|
-
compactedPersistedMessages: 5,
|
|
1491
|
-
summaryText: "Summary of prior conversation",
|
|
1492
|
-
previousEstimatedInputTokens: 90_000,
|
|
1493
|
-
estimatedInputTokens: 30_000,
|
|
1494
|
-
maxInputTokens: 100_000,
|
|
1495
|
-
thresholdTokens: 80_000,
|
|
1496
|
-
compactedMessages: 10,
|
|
1497
|
-
summaryCalls: 2,
|
|
1498
|
-
summaryInputTokens: 500,
|
|
1499
|
-
summaryOutputTokens: 200,
|
|
1500
|
-
summaryModel: "claude-opus-4-6",
|
|
1501
|
-
summaryCacheCreationInputTokens: 120,
|
|
1502
|
-
summaryCacheReadInputTokens: 340,
|
|
1503
|
-
summaryRawResponses: [
|
|
1504
|
-
{
|
|
1505
|
-
usage: {
|
|
1506
|
-
cache_creation: { ephemeral_5m_input_tokens: 120 },
|
|
1507
|
-
cache_read_input_tokens: 340,
|
|
1508
|
-
},
|
|
1509
|
-
},
|
|
1510
|
-
],
|
|
1511
|
-
},
|
|
1512
|
-
});
|
|
1513
|
-
|
|
1514
|
-
// The provider rejects the first call as too large; the convergence
|
|
1515
|
-
// reducer compacts and the rerun completes the turn, forwarding the
|
|
1516
|
-
// compaction's cache-aware usage to recordUsage.
|
|
1517
|
-
const { provider } = createMockProvider([
|
|
1518
|
-
new Error("context_length_exceeded"),
|
|
1519
|
-
textResponse("recovered"),
|
|
1520
|
-
]);
|
|
1521
|
-
const ctx = makeCtx({
|
|
1522
|
-
loopProvider: provider,
|
|
1523
|
-
contextWindowManager: {
|
|
1524
|
-
updateConfig: () => {},
|
|
1525
|
-
shouldCompact: () => ({ needed: false, estimatedTokens: 0 }),
|
|
1526
|
-
maybeCompact: async () => ({ compacted: false }),
|
|
1527
|
-
} as unknown as Conversation["contextWindowManager"],
|
|
1528
|
-
});
|
|
1529
|
-
await runAgentLoopImpl(ctx, "hello", "msg-1", (msg) => events.push(msg));
|
|
1530
|
-
|
|
1531
|
-
const compactorCall = recordUsageMock.mock.calls.find(
|
|
1532
|
-
(call) => (call as unknown[])[5] === "context_compactor",
|
|
1533
|
-
) as unknown[] | undefined;
|
|
1534
|
-
expect(compactorCall).toBeDefined();
|
|
1535
|
-
|
|
1536
|
-
const [
|
|
1537
|
-
usageCtx,
|
|
1538
|
-
inputTokens,
|
|
1539
|
-
outputTokens,
|
|
1540
|
-
model,
|
|
1541
|
-
_onEvent,
|
|
1542
|
-
actor,
|
|
1543
|
-
reqId,
|
|
1544
|
-
cacheCreationInputTokens,
|
|
1545
|
-
cacheReadInputTokens,
|
|
1546
|
-
rawResponse,
|
|
1547
|
-
] = compactorCall ?? [];
|
|
1548
|
-
|
|
1549
|
-
expect(usageCtx).toMatchObject({ conversationId: "test-conv" });
|
|
1550
|
-
expect(inputTokens).toBe(500);
|
|
1551
|
-
expect(outputTokens).toBe(200);
|
|
1552
|
-
expect(model).toBe("claude-opus-4-6");
|
|
1553
|
-
expect(actor).toBe("context_compactor");
|
|
1554
|
-
expect(reqId).toBe("test-req");
|
|
1555
|
-
expect(cacheCreationInputTokens).toBe(120);
|
|
1556
|
-
expect(cacheReadInputTokens).toBe(340);
|
|
1557
|
-
expect(rawResponse).toEqual({
|
|
1558
|
-
usage: {
|
|
1559
|
-
cache_creation: { ephemeral_5m_input_tokens: 120 },
|
|
1560
|
-
cache_read_input_tokens: 340,
|
|
1561
|
-
},
|
|
1562
|
-
});
|
|
1563
|
-
});
|
|
1564
|
-
|
|
1565
|
-
test("convergence loop applies reducer and retries when context-too-large is detected", async () => {
|
|
1566
|
-
const events: ServerMessage[] = [];
|
|
1567
|
-
let reducerCalled = false;
|
|
1568
|
-
|
|
1569
|
-
// Configure reducer to succeed on first call — return reduced messages
|
|
1570
|
-
// with a compaction result to trigger the context_compacted event.
|
|
1571
|
-
mockReducerStepFn = (msgs: Message[]) => {
|
|
1572
|
-
reducerCalled = true;
|
|
1573
|
-
return {
|
|
1574
|
-
messages: msgs,
|
|
1575
|
-
tier: "forced_compaction",
|
|
1576
|
-
state: {
|
|
1577
|
-
appliedTiers: ["forced_compaction"],
|
|
1578
|
-
injectionMode: "full",
|
|
1579
|
-
exhausted: false,
|
|
1580
|
-
},
|
|
1581
|
-
estimatedTokens: 30000,
|
|
1582
|
-
compactionResult: {
|
|
1583
|
-
compacted: true,
|
|
1584
|
-
messages: msgs,
|
|
1585
|
-
compactedPersistedMessages: 5,
|
|
1586
|
-
summaryText: "Summary of prior conversation",
|
|
1587
|
-
previousEstimatedInputTokens: 90000,
|
|
1588
|
-
estimatedInputTokens: 30000,
|
|
1589
|
-
maxInputTokens: 100000,
|
|
1590
|
-
thresholdTokens: 80000,
|
|
1591
|
-
compactedMessages: 10,
|
|
1592
|
-
summaryCalls: 1,
|
|
1593
|
-
summaryInputTokens: 500,
|
|
1594
|
-
summaryOutputTokens: 200,
|
|
1595
|
-
summaryModel: "mock-model",
|
|
1596
|
-
},
|
|
1597
|
-
};
|
|
1598
|
-
};
|
|
1599
|
-
|
|
1600
|
-
// The provider rejects the first call with a context-too-large error,
|
|
1601
|
-
// then succeeds once the orchestrator has reduced the context.
|
|
1602
|
-
const { provider, calls } = createMockProvider([
|
|
1603
|
-
new Error("context_length_exceeded"),
|
|
1604
|
-
textResponse("recovered"),
|
|
1605
|
-
]);
|
|
1606
|
-
|
|
1607
|
-
const ctx = makeCtx({
|
|
1608
|
-
loopProvider: provider,
|
|
1609
|
-
contextWindowManager: {
|
|
1610
|
-
updateConfig: () => {},
|
|
1611
|
-
shouldCompact: () => ({ needed: false, estimatedTokens: 0 }),
|
|
1612
|
-
maybeCompact: async () => ({ compacted: false }),
|
|
1613
|
-
} as unknown as Conversation["contextWindowManager"],
|
|
1614
|
-
});
|
|
1615
|
-
|
|
1616
|
-
await runAgentLoopImpl(ctx, "hello", "msg-1", (msg) => events.push(msg));
|
|
1617
|
-
|
|
1618
|
-
expect(reducerCalled).toBe(true);
|
|
1619
|
-
expect(calls.length).toBe(2);
|
|
1620
|
-
const compactEvent = events.find((e) => e.type === "context_compacted");
|
|
1621
|
-
expect(compactEvent).toBeDefined();
|
|
1622
|
-
});
|
|
1623
|
-
|
|
1624
|
-
test("emits conversation_error when context stays too large after all recovery attempts", async () => {
|
|
1625
|
-
const events: ServerMessage[] = [];
|
|
1626
|
-
|
|
1627
|
-
// The provider rejects every call with a context-too-large error, so the
|
|
1628
|
-
// orchestrator exhausts its recovery attempts.
|
|
1629
|
-
const ctx = makeCtx({
|
|
1630
|
-
providerResponses: [new Error("context_length_exceeded")],
|
|
1631
|
-
contextWindowManager: {
|
|
1632
|
-
updateConfig: () => {},
|
|
1633
|
-
shouldCompact: () => ({ needed: false, estimatedTokens: 0 }),
|
|
1634
|
-
// Compaction succeeds but context is still too large
|
|
1635
|
-
maybeCompact: async () => ({
|
|
1636
|
-
compacted: true,
|
|
1637
|
-
messages: [
|
|
1638
|
-
{ role: "user", content: [{ type: "text", text: "Hello" }] },
|
|
1639
|
-
] as Message[],
|
|
1640
|
-
compactedPersistedMessages: 5,
|
|
1641
|
-
summaryText: "Summary",
|
|
1642
|
-
previousEstimatedInputTokens: 90000,
|
|
1643
|
-
estimatedInputTokens: 85000,
|
|
1644
|
-
maxInputTokens: 100000,
|
|
1645
|
-
thresholdTokens: 80000,
|
|
1646
|
-
compactedMessages: 2,
|
|
1647
|
-
summaryCalls: 1,
|
|
1648
|
-
summaryInputTokens: 500,
|
|
1649
|
-
summaryOutputTokens: 200,
|
|
1650
|
-
summaryModel: "mock-model",
|
|
1651
|
-
}),
|
|
1652
|
-
} as unknown as Conversation["contextWindowManager"],
|
|
1653
|
-
});
|
|
1654
|
-
|
|
1655
|
-
await runAgentLoopImpl(ctx, "hello", "msg-1", (msg) => events.push(msg));
|
|
1656
|
-
|
|
1657
|
-
const conversationError = events.find(
|
|
1658
|
-
(e) => e.type === "conversation_error",
|
|
1659
|
-
);
|
|
1660
|
-
expect(conversationError).toBeDefined();
|
|
1661
|
-
});
|
|
1662
|
-
|
|
1663
|
-
test("bounded convergence loop applies reducer tiers and recovers", async () => {
|
|
1664
|
-
const events: ServerMessage[] = [];
|
|
1665
|
-
let reducerCalls = 0;
|
|
1666
|
-
|
|
1667
|
-
// Reducer: succeed on first call, returning reduced messages
|
|
1668
|
-
mockReducerStepFn = (msgs: Message[]) => {
|
|
1669
|
-
reducerCalls++;
|
|
1670
|
-
return {
|
|
1671
|
-
messages: msgs,
|
|
1672
|
-
tier: "forced_compaction",
|
|
1673
|
-
state: {
|
|
1674
|
-
appliedTiers: ["forced_compaction"],
|
|
1675
|
-
injectionMode: "full",
|
|
1676
|
-
exhausted: false,
|
|
1677
|
-
},
|
|
1678
|
-
estimatedTokens: 5000,
|
|
1679
|
-
};
|
|
1680
|
-
};
|
|
1681
|
-
|
|
1682
|
-
// The provider rejects the first call with a context-too-large error,
|
|
1683
|
-
// then succeeds once the orchestrator has reduced the context.
|
|
1684
|
-
const { provider, calls } = createMockProvider([
|
|
1685
|
-
new Error("context_length_exceeded"),
|
|
1686
|
-
textResponse("recovered via convergence"),
|
|
1687
|
-
]);
|
|
1688
|
-
|
|
1689
|
-
const ctx = makeCtx({
|
|
1690
|
-
loopProvider: provider,
|
|
1691
|
-
contextWindowManager: {
|
|
1692
|
-
updateConfig: () => {},
|
|
1693
|
-
shouldCompact: () => ({ needed: false, estimatedTokens: 0 }),
|
|
1694
|
-
maybeCompact: async () => ({ compacted: false }),
|
|
1695
|
-
} as unknown as Conversation["contextWindowManager"],
|
|
1696
|
-
});
|
|
1697
|
-
|
|
1698
|
-
await runAgentLoopImpl(ctx, "hello", "msg-1", (msg) => events.push(msg));
|
|
1699
|
-
|
|
1700
|
-
expect(reducerCalls).toBeGreaterThanOrEqual(1);
|
|
1701
|
-
expect(calls.length).toBe(2);
|
|
1702
|
-
const conversationError = events.find(
|
|
1703
|
-
(e) => e.type === "conversation_error",
|
|
1704
|
-
);
|
|
1705
|
-
expect(conversationError).toBeUndefined();
|
|
1706
|
-
const complete = events.find((e) => e.type === "message_complete");
|
|
1707
|
-
expect(complete).toBeDefined();
|
|
1708
|
-
});
|
|
1709
|
-
|
|
1710
|
-
test("non-interactive auto-compress continues without approval prompt", async () => {
|
|
1711
|
-
const events: ServerMessage[] = [];
|
|
1712
|
-
|
|
1713
|
-
// Reducer exhausts all tiers
|
|
1714
|
-
mockReducerStepFn = (msgs: Message[]) => ({
|
|
1715
|
-
messages: msgs,
|
|
1716
|
-
tier: "injection_downgrade",
|
|
1717
|
-
state: {
|
|
1718
|
-
appliedTiers: [
|
|
1719
|
-
"forced_compaction",
|
|
1720
|
-
"tool_result_truncation",
|
|
1721
|
-
"media_stubbing",
|
|
1722
|
-
"injection_downgrade",
|
|
1723
|
-
],
|
|
1724
|
-
injectionMode: "minimal",
|
|
1725
|
-
exhausted: true,
|
|
1726
|
-
},
|
|
1727
|
-
estimatedTokens: 120000,
|
|
1728
|
-
});
|
|
1729
|
-
|
|
1730
|
-
mockOverflowAction = "auto_compress_latest_turn";
|
|
1731
|
-
|
|
1732
|
-
// The provider rejects the first two calls with context-too-large errors,
|
|
1733
|
-
// then succeeds after the emergency auto-compress runs.
|
|
1734
|
-
const ctx = makeCtx({
|
|
1735
|
-
providerResponses: [
|
|
1736
|
-
new Error("context_length_exceeded"),
|
|
1737
|
-
new Error("context_length_exceeded"),
|
|
1738
|
-
textResponse("auto-recovered"),
|
|
1739
|
-
],
|
|
1740
|
-
hasNoClient: true,
|
|
1741
|
-
contextWindowManager: {
|
|
1742
|
-
updateConfig: () => {},
|
|
1743
|
-
shouldCompact: () => ({ needed: false, estimatedTokens: 0 }),
|
|
1744
|
-
maybeCompact: async () => ({
|
|
1745
|
-
compacted: true,
|
|
1746
|
-
messages: [
|
|
1747
|
-
{ role: "user", content: [{ type: "text", text: "Hello" }] },
|
|
1748
|
-
] as Message[],
|
|
1749
|
-
compactedPersistedMessages: 3,
|
|
1750
|
-
summaryText: "Compressed summary",
|
|
1751
|
-
previousEstimatedInputTokens: 120000,
|
|
1752
|
-
estimatedInputTokens: 30000,
|
|
1753
|
-
maxInputTokens: 100000,
|
|
1754
|
-
thresholdTokens: 80000,
|
|
1755
|
-
compactedMessages: 5,
|
|
1756
|
-
summaryCalls: 1,
|
|
1757
|
-
summaryInputTokens: 300,
|
|
1758
|
-
summaryOutputTokens: 100,
|
|
1759
|
-
summaryModel: "mock-model",
|
|
1760
|
-
}),
|
|
1761
|
-
} as unknown as Conversation["contextWindowManager"],
|
|
1762
|
-
});
|
|
1763
|
-
|
|
1764
|
-
await runAgentLoopImpl(ctx, "hello", "msg-1", (msg) => events.push(msg));
|
|
1765
|
-
|
|
1766
|
-
// Should not produce conversation_error since auto-compress recovered
|
|
1767
|
-
const conversationError = events.find(
|
|
1768
|
-
(e) => e.type === "conversation_error",
|
|
1769
|
-
);
|
|
1770
|
-
expect(conversationError).toBeUndefined();
|
|
1771
|
-
const complete = events.find((e) => e.type === "message_complete");
|
|
1772
|
-
expect(complete).toBeDefined();
|
|
1773
|
-
});
|
|
1774
|
-
|
|
1775
|
-
test("emits budget_yield_unrecovered when auto_compress rerun yields at mid-loop budget", async () => {
|
|
1776
|
-
// Regression test for the silent-stall failure mode:
|
|
1777
|
-
// when every recovery layer has been applied (tier reducer
|
|
1778
|
-
// exhausted + auto_compress_latest_turn emergency compaction)
|
|
1779
|
-
// and the final `agentLoop.run` STILL yields at the mid-loop
|
|
1780
|
-
// budget checkpoint, the orchestrator used to fall through to
|
|
1781
|
-
// post-turn cleanup with NO `agent_loop_exit_reason` emitted —
|
|
1782
|
-
// the turn just stopped mid-action and `llm_request_logs` showed
|
|
1783
|
-
// a NULL exit reason on the final row. We now emit
|
|
1784
|
-
// `budget_yield_unrecovered` so the inspector and dashboards can
|
|
1785
|
-
// attribute the silent stall.
|
|
1786
|
-
const events: ServerMessage[] = [];
|
|
1787
|
-
|
|
1788
|
-
// Reducer exhausts all 4 tiers on first call so the convergence
|
|
1789
|
-
// loop runs exactly one iteration before falling through to
|
|
1790
|
-
// the auto_compress_latest_turn branch.
|
|
1791
|
-
mockReducerStepFn = (msgs: Message[]) => ({
|
|
1792
|
-
messages: msgs,
|
|
1793
|
-
tier: "injection_downgrade",
|
|
1794
|
-
state: {
|
|
1795
|
-
appliedTiers: [
|
|
1796
|
-
"forced_compaction",
|
|
1797
|
-
"tool_result_truncation",
|
|
1798
|
-
"media_stubbing",
|
|
1799
|
-
"injection_downgrade",
|
|
1800
|
-
],
|
|
1801
|
-
injectionMode: "minimal",
|
|
1802
|
-
exhausted: true,
|
|
1803
|
-
},
|
|
1804
|
-
estimatedTokens: 120_000,
|
|
1805
|
-
});
|
|
1806
|
-
|
|
1807
|
-
mockOverflowAction = "auto_compress_latest_turn";
|
|
1808
|
-
|
|
1809
|
-
// Sits between the preflight budget (preflightBudget =
|
|
1810
|
-
// 100k * 0.95 = 95k — anything above triggers preflight reducer
|
|
1811
|
-
// *before* we get to the convergence/auto_compress path under
|
|
1812
|
-
// test) and the mid-loop threshold (preflightBudget * 0.85 =
|
|
1813
|
-
// ≈80.75k — anything above flips yieldedForBudget on a checkpoint
|
|
1814
|
-
// call). 90k satisfies both so the path reaches call 3.
|
|
1815
|
-
mockEstimateTokens = 90_000;
|
|
1816
|
-
|
|
1817
|
-
// Calls 1 (initial) and 2 (convergence rerun) reject with
|
|
1818
|
-
// context-too-large so `contextTooLargeDetected` stays true through the
|
|
1819
|
-
// convergence exit and the orchestrator enters the auto_compress branch.
|
|
1820
|
-
// Call 3 (the auto_compress rerun) is a tool turn: the loop runs it
|
|
1821
|
-
// without a compaction hook, so when its mid-loop budget gate trips on
|
|
1822
|
-
// the still-oversized estimate it yields `exitReason = "budget"` rather
|
|
1823
|
-
// than recovering — the silent-stall path under test.
|
|
1824
|
-
const ctx = makeCtx({
|
|
1825
|
-
providerResponses: [
|
|
1826
|
-
new Error("context_length_exceeded"),
|
|
1827
|
-
new Error("context_length_exceeded"),
|
|
1828
|
-
toolUseResponse("t1", "read_file", { path: "/a.txt" }),
|
|
1829
|
-
],
|
|
1830
|
-
loopTools: [
|
|
1831
|
-
{
|
|
1832
|
-
name: "read_file",
|
|
1833
|
-
description: "Read a file",
|
|
1834
|
-
input_schema: {
|
|
1835
|
-
type: "object",
|
|
1836
|
-
properties: { path: { type: "string" } },
|
|
1837
|
-
},
|
|
1838
|
-
},
|
|
1839
|
-
],
|
|
1840
|
-
toolExecutor: async () => ({ content: "data", isError: false }),
|
|
1841
|
-
hasNoClient: true,
|
|
1842
|
-
contextWindowManager: {
|
|
1843
|
-
updateConfig: () => {},
|
|
1844
|
-
shouldCompact: () => ({ needed: false, estimatedTokens: 0 }),
|
|
1845
|
-
maybeCompact: async () => ({
|
|
1846
|
-
compacted: true,
|
|
1847
|
-
messages: [
|
|
1848
|
-
{ role: "user", content: [{ type: "text", text: "Hello" }] },
|
|
1849
|
-
] as Message[],
|
|
1850
|
-
compactedPersistedMessages: 3,
|
|
1851
|
-
summaryText: "Compressed summary",
|
|
1852
|
-
previousEstimatedInputTokens: 120000,
|
|
1853
|
-
estimatedInputTokens: 30000,
|
|
1854
|
-
maxInputTokens: 100000,
|
|
1855
|
-
thresholdTokens: 80000,
|
|
1856
|
-
compactedMessages: 5,
|
|
1857
|
-
summaryCalls: 1,
|
|
1858
|
-
summaryInputTokens: 300,
|
|
1859
|
-
summaryOutputTokens: 100,
|
|
1860
|
-
summaryModel: "mock-model",
|
|
1861
|
-
}),
|
|
1862
|
-
} as unknown as Conversation["contextWindowManager"],
|
|
1863
|
-
});
|
|
1864
|
-
|
|
1865
|
-
await runAgentLoopImpl(ctx, "hello", "msg-1", (msg) => events.push(msg));
|
|
1866
|
-
|
|
1867
|
-
// Observability emit: exit reason was stamped onto the latest
|
|
1868
|
-
// llm_request_logs row.
|
|
1869
|
-
expect(setAgentLoopExitReasonOnLatestLogMock).toHaveBeenCalledWith(
|
|
1870
|
-
"test-conv",
|
|
1871
|
-
"budget_yield_unrecovered",
|
|
1872
|
-
);
|
|
1873
|
-
|
|
1874
|
-
// We did NOT also emit context_too_large — the auto_compress
|
|
1875
|
-
// branch resets `contextTooLargeDetected` before its rerun and
|
|
1876
|
-
// the rerun's yield-for-budget keeps it false, so the error
|
|
1877
|
-
// branch above stays skipped.
|
|
1878
|
-
expect(setAgentLoopExitReasonOnLatestLogMock).not.toHaveBeenCalledWith(
|
|
1879
|
-
"test-conv",
|
|
1880
|
-
"context_too_large",
|
|
1881
|
-
);
|
|
1882
|
-
|
|
1883
|
-
// User-facing emit: the classified BUDGET_YIELD_UNRECOVERED
|
|
1884
|
-
// error is sent to the client so the UI can render a notice
|
|
1885
|
-
// instead of leaving the turn looking like a silent ghost. The
|
|
1886
|
-
// assistant-side notice persistence is exercised in the overflow
|
|
1887
|
-
// suite (`conversation-agent-loop-overflow.test.ts`); this test
|
|
1888
|
-
// owns the observability + emit contract.
|
|
1889
|
-
const conversationError = events.find(
|
|
1890
|
-
(e) => e.type === "conversation_error",
|
|
1891
|
-
);
|
|
1892
|
-
expect(conversationError).toBeDefined();
|
|
1893
|
-
if (conversationError && "code" in conversationError) {
|
|
1894
|
-
expect(conversationError.code).toBe("BUDGET_YIELD_UNRECOVERED");
|
|
1895
|
-
expect(conversationError.retryable).toBe(true);
|
|
1896
|
-
expect(conversationError.errorCategory).toBe(
|
|
1897
|
-
"budget_yield_unrecovered",
|
|
1898
|
-
);
|
|
1899
|
-
} else {
|
|
1900
|
-
throw new Error("conversation_error missing `code` field");
|
|
1901
|
-
}
|
|
1902
|
-
});
|
|
1903
|
-
|
|
1904
|
-
test("recovery loop is bounded by maxAttempts", async () => {
|
|
1905
|
-
const events: ServerMessage[] = [];
|
|
1906
|
-
let reducerCalls = 0;
|
|
1907
|
-
|
|
1908
|
-
// Reducer never exhausts — always returns non-exhausted state
|
|
1909
|
-
// but context always stays too large
|
|
1910
|
-
mockReducerStepFn = (msgs: Message[]) => {
|
|
1911
|
-
reducerCalls++;
|
|
1912
|
-
return {
|
|
1913
|
-
messages: msgs,
|
|
1914
|
-
tier: "forced_compaction",
|
|
1915
|
-
state: {
|
|
1916
|
-
appliedTiers: ["forced_compaction"],
|
|
1917
|
-
injectionMode: "full",
|
|
1918
|
-
exhausted: false,
|
|
1919
|
-
},
|
|
1920
|
-
estimatedTokens: 120000,
|
|
1921
|
-
};
|
|
1922
|
-
};
|
|
1923
|
-
|
|
1924
|
-
// The provider rejects every call with a context-too-large error, so the
|
|
1925
|
-
// orchestrator keeps retrying until it hits the attempt ceiling.
|
|
1926
|
-
const ctx = makeCtx({
|
|
1927
|
-
providerResponses: [new Error("context_length_exceeded")],
|
|
1928
|
-
contextWindowManager: {
|
|
1929
|
-
updateConfig: () => {},
|
|
1930
|
-
shouldCompact: () => ({ needed: false, estimatedTokens: 0 }),
|
|
1931
|
-
maybeCompact: async () => ({ compacted: false }),
|
|
1932
|
-
} as unknown as Conversation["contextWindowManager"],
|
|
1933
|
-
});
|
|
1934
|
-
|
|
1935
|
-
await runAgentLoopImpl(ctx, "hello", "msg-1", (msg) => events.push(msg));
|
|
1936
|
-
|
|
1937
|
-
// maxAttempts is 3 — reducer should be called at most 3 times
|
|
1938
|
-
expect(reducerCalls).toBeLessThanOrEqual(3);
|
|
1939
|
-
});
|
|
1940
|
-
});
|
|
1941
|
-
|
|
1942
|
-
describe("provider ordering error retry", () => {
|
|
1943
|
-
test("retries with deep repair when ordering error is detected", async () => {
|
|
1944
|
-
const events: ServerMessage[] = [];
|
|
1945
|
-
|
|
1946
|
-
// The provider rejects the first call with an ordering error, then
|
|
1947
|
-
// succeeds once the orchestrator's deep repair re-sends the turn.
|
|
1948
|
-
const { provider, calls } = createMockProvider([
|
|
1949
|
-
new Error("messages ordering error"),
|
|
1950
|
-
textResponse("fixed"),
|
|
1951
|
-
]);
|
|
1952
|
-
|
|
1953
|
-
const ctx = makeCtx({ loopProvider: provider });
|
|
1954
|
-
await runAgentLoopImpl(ctx, "hello", "msg-1", (msg) => events.push(msg));
|
|
1955
|
-
|
|
1956
|
-
expect(calls.length).toBe(2);
|
|
1957
|
-
});
|
|
1958
|
-
|
|
1959
|
-
test("emits deferred ordering error when retry also fails", async () => {
|
|
1960
|
-
const events: ServerMessage[] = [];
|
|
1961
|
-
|
|
1962
|
-
// The provider rejects every call with an ordering error, so even the
|
|
1963
|
-
// deep-repair retry fails and the orchestrator surfaces the error.
|
|
1964
|
-
const ctx = makeCtx({
|
|
1965
|
-
providerResponses: [new Error("messages ordering error")],
|
|
1966
|
-
});
|
|
1967
|
-
await runAgentLoopImpl(ctx, "hello", "msg-1", (msg) => events.push(msg));
|
|
1968
|
-
|
|
1969
|
-
const conversationError = events.find(
|
|
1970
|
-
(e) => e.type === "conversation_error",
|
|
1971
|
-
);
|
|
1972
|
-
expect(conversationError).toBeDefined();
|
|
1973
|
-
});
|
|
1974
|
-
});
|
|
1975
|
-
|
|
1976
1544
|
describe("checkpoint handoff (infinite loop prevention)", () => {
|
|
1977
1545
|
test("yields at checkpoint when canHandoffAtCheckpoint returns true", async () => {
|
|
1978
1546
|
const events: ServerMessage[] = [];
|
|
@@ -2591,8 +2159,8 @@ describe("session-agent-loop", () => {
|
|
|
2591
2159
|
// Indexer/projector mocks default to no-op; no finalized row in this
|
|
2592
2160
|
// test, so `mockMessageById` stays null.
|
|
2593
2161
|
|
|
2594
|
-
// A single reducer
|
|
2595
|
-
//
|
|
2162
|
+
// A single reducer rung reduces the oversized context so the loop
|
|
2163
|
+
// re-enters after the first call's overflow rejection.
|
|
2596
2164
|
mockReducerStepFn = (msgs: Message[]) => ({
|
|
2597
2165
|
messages: msgs,
|
|
2598
2166
|
tier: "forced_compaction",
|
|
@@ -2606,11 +2174,16 @@ describe("session-agent-loop", () => {
|
|
|
2606
2174
|
|
|
2607
2175
|
// GIVEN a real loop whose first call rejects with context-too-large
|
|
2608
2176
|
// (reserving msg-strand-A but never finalizing it), then recovers via
|
|
2609
|
-
//
|
|
2610
|
-
// delete the stranded msg-strand-A before
|
|
2177
|
+
// the reactive overflow ladder on re-entry. The re-entry's
|
|
2178
|
+
// `llm_call_started` must delete the stranded msg-strand-A before
|
|
2179
|
+
// reserving msg-strand-B.
|
|
2611
2180
|
const ctx = makeCtx({
|
|
2612
2181
|
providerResponses: [
|
|
2613
|
-
new
|
|
2182
|
+
new ContextOverflowError(
|
|
2183
|
+
"context_length_exceeded: 250000 tokens > 200000 maximum",
|
|
2184
|
+
"mock-provider",
|
|
2185
|
+
{ actualTokens: 250_000, maxTokens: 200_000 },
|
|
2186
|
+
),
|
|
2614
2187
|
textResponse("retry succeeded"),
|
|
2615
2188
|
],
|
|
2616
2189
|
contextWindowManager: {
|
|
@@ -3570,7 +3143,7 @@ describe("session-agent-loop", () => {
|
|
|
3570
3143
|
});
|
|
3571
3144
|
|
|
3572
3145
|
describe("compaction-strip marker persistence", () => {
|
|
3573
|
-
test("records historyStrippedAt when
|
|
3146
|
+
test("records historyStrippedAt when overflow-recovery strip runs", async () => {
|
|
3574
3147
|
// Reducer: succeed on first call, returning reduced messages.
|
|
3575
3148
|
mockReducerStepFn = (msgs: Message[]) => ({
|
|
3576
3149
|
messages: msgs,
|
|
@@ -3583,15 +3156,18 @@ describe("session-agent-loop", () => {
|
|
|
3583
3156
|
estimatedTokens: 5000,
|
|
3584
3157
|
});
|
|
3585
3158
|
|
|
3586
|
-
// GIVEN a real loop that appends a tool turn
|
|
3587
|
-
//
|
|
3588
|
-
//
|
|
3589
|
-
//
|
|
3590
|
-
// recovers.
|
|
3159
|
+
// GIVEN a real loop that appends a tool turn and then rejects with a
|
|
3160
|
+
// context-too-large error on the following call — reactive overflow
|
|
3161
|
+
// recovery strips that appended history when it compacts before a final
|
|
3162
|
+
// call recovers.
|
|
3591
3163
|
const ctx = makeCtx({
|
|
3592
3164
|
providerResponses: [
|
|
3593
3165
|
toolUseResponse("t1", "file_read", {}),
|
|
3594
|
-
new
|
|
3166
|
+
new ContextOverflowError(
|
|
3167
|
+
"context_length_exceeded: 250000 tokens > 200000 maximum",
|
|
3168
|
+
"mock-provider",
|
|
3169
|
+
{ actualTokens: 250_000, maxTokens: 200_000 },
|
|
3170
|
+
),
|
|
3595
3171
|
textResponse("recovered"),
|
|
3596
3172
|
],
|
|
3597
3173
|
loopTools: [
|
|
@@ -3609,7 +3185,7 @@ describe("session-agent-loop", () => {
|
|
|
3609
3185
|
} as unknown as Conversation["contextWindowManager"],
|
|
3610
3186
|
});
|
|
3611
3187
|
|
|
3612
|
-
// WHEN the
|
|
3188
|
+
// WHEN the loop runs the turn to completion
|
|
3613
3189
|
await runAgentLoopImpl(ctx, "hello", "msg-1", () => {});
|
|
3614
3190
|
|
|
3615
3191
|
const stripCalls = setConversationHistoryStrippedAtMock.mock.calls.filter(
|
|
@@ -3636,12 +3212,16 @@ describe("session-agent-loop", () => {
|
|
|
3636
3212
|
|
|
3637
3213
|
// GIVEN a real loop that appends a tool turn and then rejects with a
|
|
3638
3214
|
// context-too-large error on the following call, driving the
|
|
3639
|
-
//
|
|
3215
|
+
// overflow-recovery strip whose marker-write helper is stubbed to throw,
|
|
3640
3216
|
// before a final call recovers.
|
|
3641
3217
|
const ctx = makeCtx({
|
|
3642
3218
|
providerResponses: [
|
|
3643
3219
|
toolUseResponse("t1", "file_read", {}),
|
|
3644
|
-
new
|
|
3220
|
+
new ContextOverflowError(
|
|
3221
|
+
"context_length_exceeded: 250000 tokens > 200000 maximum",
|
|
3222
|
+
"mock-provider",
|
|
3223
|
+
{ actualTokens: 250_000, maxTokens: 200_000 },
|
|
3224
|
+
),
|
|
3645
3225
|
textResponse("recovered"),
|
|
3646
3226
|
],
|
|
3647
3227
|
loopTools: [
|