@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
|
@@ -32,15 +32,10 @@ import {
|
|
|
32
32
|
} from "../config/llm-resolver.js";
|
|
33
33
|
import { getConfig } from "../config/loader.js";
|
|
34
34
|
import type { LLMCallSite } from "../config/schemas/llm.js";
|
|
35
|
-
import type { ContextWindowConfig } from "../config/types.js";
|
|
36
35
|
import {
|
|
37
36
|
derefToolResultReReads,
|
|
38
37
|
postTurnTruncateToolResults,
|
|
39
38
|
} from "../context/post-turn-tool-result-truncation.js";
|
|
40
|
-
import {
|
|
41
|
-
estimatePromptTokens,
|
|
42
|
-
getCalibrationProviderKey,
|
|
43
|
-
} from "../context/token-estimator.js";
|
|
44
39
|
import { writeRelationshipState } from "../home/relationship-state-writer.js";
|
|
45
40
|
import {
|
|
46
41
|
clearSentryConversationContext,
|
|
@@ -72,17 +67,6 @@ import {
|
|
|
72
67
|
import { enqueueMemoryRetrospectiveOnCompaction } from "../memory/memory-retrospective-enqueue.js";
|
|
73
68
|
import { HOOKS } from "../plugin-api/constants.js";
|
|
74
69
|
import type { UserPromptSubmitContext } from "../plugin-api/types.js";
|
|
75
|
-
import { defaultCompact } from "../plugins/defaults/compaction/compact.js";
|
|
76
|
-
import {
|
|
77
|
-
createInitialReducerState,
|
|
78
|
-
reduceContextOverflow,
|
|
79
|
-
type ReducerState,
|
|
80
|
-
} from "../plugins/defaults/compaction/context-overflow-reducer.js";
|
|
81
|
-
import type { ContextWindowCompactOptions } from "../plugins/defaults/compaction/window-manager.js";
|
|
82
|
-
import { deepRepairHistory } from "../plugins/defaults/history-repair/terminal.js";
|
|
83
|
-
import userPromptSubmitMemoryRetrieval, {
|
|
84
|
-
type MemoryRetrievalHookContext,
|
|
85
|
-
} from "../plugins/defaults/memory-retrieval/hooks/user-prompt-submit-temp.js";
|
|
86
70
|
import { runHook } from "../plugins/pipeline.js";
|
|
87
71
|
import type { ContentBlock, Message } from "../providers/types.js";
|
|
88
72
|
import type { Provider } from "../providers/types.js";
|
|
@@ -98,7 +82,6 @@ import { truncate } from "../util/truncate.js";
|
|
|
98
82
|
import { getWorkspaceGitService } from "../workspace/git-service.js";
|
|
99
83
|
import { commitTurnChanges } from "../workspace/turn-commit.js";
|
|
100
84
|
import { cleanAssistantContent } from "./assistant-attachments.js";
|
|
101
|
-
import { resolveOverflowAction } from "./context-overflow-policy.js";
|
|
102
85
|
import type { Conversation } from "./conversation.js";
|
|
103
86
|
import {
|
|
104
87
|
createEventHandlerState,
|
|
@@ -118,14 +101,11 @@ import {
|
|
|
118
101
|
isUserCancellation,
|
|
119
102
|
} from "./conversation-error.js";
|
|
120
103
|
import { raceWithTimeout } from "./conversation-media-retry.js";
|
|
121
|
-
import type { InjectionMode } from "./conversation-runtime-assembly.js";
|
|
122
104
|
import {
|
|
123
|
-
applyRuntimeInjections,
|
|
124
105
|
getSlackCompactionWatermarkForPrefix,
|
|
125
106
|
loadSlackChronologicalContext,
|
|
126
107
|
resolveTurnInboundActorContext,
|
|
127
108
|
type SlackChronologicalContext,
|
|
128
|
-
stripInjectionsForCompaction,
|
|
129
109
|
} from "./conversation-runtime-assembly.js";
|
|
130
110
|
import { markSurfaceCompleted } from "./conversation-surfaces.js";
|
|
131
111
|
import { recordUsage } from "./conversation-usage.js";
|
|
@@ -138,12 +118,7 @@ import type {
|
|
|
138
118
|
SurfaceType,
|
|
139
119
|
UsageStats,
|
|
140
120
|
} from "./message-protocol.js";
|
|
141
|
-
import {
|
|
142
|
-
import {
|
|
143
|
-
oversizedImageReplacement,
|
|
144
|
-
persistUnsendableImageDowngrades,
|
|
145
|
-
} from "./persist-unsendable-image.js";
|
|
146
|
-
import { resolveTrustClass, type TrustContext } from "./trust-context.js";
|
|
121
|
+
import type { TrustContext } from "./trust-context.js";
|
|
147
122
|
|
|
148
123
|
const log = getLogger("conversation-agent-loop");
|
|
149
124
|
|
|
@@ -168,34 +143,6 @@ function formatDiskPressureBlockedMessage(): string {
|
|
|
168
143
|
return "Storage is critically low, so background processes are paused and remote messages are ignored until the guardian frees enough space. Remote senders should try again later.";
|
|
169
144
|
}
|
|
170
145
|
|
|
171
|
-
// ── Image-recovery helpers ───────────────────────────────────────────
|
|
172
|
-
|
|
173
|
-
/**
|
|
174
|
-
* True when a message's content holds an image the provider may have rejected
|
|
175
|
-
* for being oversized — either a top-level image block (user upload) or one
|
|
176
|
-
* nested inside a tool_result's contentBlocks (e.g. a browser screenshot).
|
|
177
|
-
*/
|
|
178
|
-
function messageHasImageBlock(content: ContentBlock[]): boolean {
|
|
179
|
-
return content.some(
|
|
180
|
-
(b) =>
|
|
181
|
-
b.type === "image" ||
|
|
182
|
-
(b.type === "tool_result" &&
|
|
183
|
-
(b.contentBlocks?.some((cb) => cb.type === "image") ?? false)),
|
|
184
|
-
);
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
/**
|
|
188
|
-
* Replace an oversized image with its downscaled form or an unsendable note,
|
|
189
|
-
* leaving still-sendable images untouched. Delegates to the shared
|
|
190
|
-
* {@link oversizedImageReplacement} so the in-memory recovery and the durable
|
|
191
|
-
* persist pass apply the identical provider-cap gate.
|
|
192
|
-
*/
|
|
193
|
-
function recoverImageBlock(
|
|
194
|
-
block: Extract<ContentBlock, { type: "image" }>,
|
|
195
|
-
): ContentBlock {
|
|
196
|
-
return oversizedImageReplacement(block) ?? block;
|
|
197
|
-
}
|
|
198
|
-
|
|
199
146
|
// ── Plugin pipeline helpers ──────────────────────────────────────────
|
|
200
147
|
|
|
201
148
|
/**
|
|
@@ -210,19 +157,6 @@ const FALLBACK_TURN_TRUST: TrustContext = {
|
|
|
210
157
|
trustClass: "unknown",
|
|
211
158
|
};
|
|
212
159
|
|
|
213
|
-
/**
|
|
214
|
-
* Trust class of the actor whose turn is in progress, for the compactor's
|
|
215
|
-
* image manifest filter. Prefers the turn-start snapshot
|
|
216
|
-
* ({@link Conversation.currentTurnTrustContext}) over the live
|
|
217
|
-
* trust context so compaction running in a later tool iteration can't pick up
|
|
218
|
-
* a concurrent request's actor.
|
|
219
|
-
*/
|
|
220
|
-
function resolveTurnActorTrustClass(
|
|
221
|
-
ctx: Conversation,
|
|
222
|
-
): TrustContext["trustClass"] | undefined {
|
|
223
|
-
return (ctx.currentTurnTrustContext ?? ctx.trustContext)?.trustClass;
|
|
224
|
-
}
|
|
225
|
-
|
|
226
160
|
/**
|
|
227
161
|
* Per-surface entry tracked on the current turn. Inline shape kept stable so
|
|
228
162
|
* routes and persistence helpers can consume it via a named import instead of
|
|
@@ -303,19 +237,21 @@ export async function runAgentLoopImpl(
|
|
|
303
237
|
requestId: reqId,
|
|
304
238
|
});
|
|
305
239
|
let yieldedForHandoff = false;
|
|
306
|
-
let yieldedForBudget = false;
|
|
307
|
-
// Whether the most recent agent-loop run produced at least one new assistant
|
|
308
|
-
// message — the loop's own forward-progress signal, used by the ordering
|
|
309
|
-
// retry gate and the overflow convergence fold.
|
|
310
|
-
let lastRunAppendedNewMessages = false;
|
|
311
240
|
// The messages the most recent agent-loop run appended on top of its base —
|
|
312
241
|
// the loop's own new-output boundary, persisted as this turn's new messages.
|
|
313
242
|
let lastRunNewMessages: Message[] = [];
|
|
314
|
-
|
|
315
|
-
//
|
|
316
|
-
//
|
|
317
|
-
//
|
|
318
|
-
|
|
243
|
+
// Terminal context-overflow outcome the agent loop emitted this turn (it
|
|
244
|
+
// drives recovery through the compaction reduction ladder and classifies the
|
|
245
|
+
// exit). The wrapper reads it to persist the matching user-facing notice
|
|
246
|
+
// after the tool-result flush; null when the turn did not end in overflow.
|
|
247
|
+
let overflowTerminalReason:
|
|
248
|
+
| "context_too_large"
|
|
249
|
+
| "budget_yield_unrecovered"
|
|
250
|
+
| null = null;
|
|
251
|
+
// Set when the loop ends the turn as `budget_yield_unrecovered`. SSE emission
|
|
252
|
+
// happens immediately at the detection site; assistant-row persistence is
|
|
253
|
+
// deferred until after the pendingToolResults flush so we don't orphan
|
|
254
|
+
// tool_use/tool_result pairs in the durable history.
|
|
319
255
|
let budgetYieldClassification: ReturnType<
|
|
320
256
|
typeof budgetYieldUnrecoveredClassification
|
|
321
257
|
> | null = null;
|
|
@@ -429,34 +365,12 @@ export async function runAgentLoopImpl(
|
|
|
429
365
|
refreshCurrentProfileState();
|
|
430
366
|
return currentEffectiveContextWindow.maxInputTokens;
|
|
431
367
|
};
|
|
432
|
-
const resolveCurrentContextWindowConfig = (): ContextWindowConfig => {
|
|
433
|
-
refreshCurrentProfileState();
|
|
434
|
-
return currentContextWindowConfig;
|
|
435
|
-
};
|
|
436
|
-
const resolveCurrentContextBudget = (): {
|
|
437
|
-
overflowRecovery: EffectiveContextWindow["overflowRecovery"];
|
|
438
|
-
providerMaxTokens: number;
|
|
439
|
-
preflightBudget: number;
|
|
440
|
-
} => {
|
|
441
|
-
refreshCurrentProfileState();
|
|
442
|
-
const overflowRecovery = currentEffectiveContextWindow.overflowRecovery;
|
|
443
|
-
const providerMaxTokens = currentEffectiveContextWindow.maxInputTokens;
|
|
444
|
-
const baseSafetyMargin = overflowRecovery.safetyMarginRatio;
|
|
445
|
-
const messageCount = ctx.messages.length;
|
|
446
|
-
const safetyMargin =
|
|
447
|
-
messageCount > 50 ? Math.max(baseSafetyMargin, 0.15) : baseSafetyMargin;
|
|
448
|
-
return {
|
|
449
|
-
overflowRecovery,
|
|
450
|
-
providerMaxTokens,
|
|
451
|
-
preflightBudget: Math.floor(providerMaxTokens * (1 - safetyMargin)),
|
|
452
|
-
};
|
|
453
|
-
};
|
|
454
368
|
/**
|
|
455
|
-
* The agent loop's window into the
|
|
456
|
-
*
|
|
457
|
-
*
|
|
458
|
-
*
|
|
459
|
-
*
|
|
369
|
+
* The agent loop's window into the wrapper's current effective context
|
|
370
|
+
* window. The loop reads `maxInputTokens` for tool-result truncation and
|
|
371
|
+
* `overflowRecovery` for its mid-loop budget gate, applying the long-history
|
|
372
|
+
* safety-margin bump itself off its own running history. Resolved fresh on
|
|
373
|
+
* each access so a mid-turn profile change is reflected.
|
|
460
374
|
*/
|
|
461
375
|
const resolveContextWindow = (): {
|
|
462
376
|
maxInputTokens: number;
|
|
@@ -801,14 +715,15 @@ export async function runAgentLoopImpl(
|
|
|
801
715
|
|
|
802
716
|
// Unified `<turn_context>` actor input for this turn (model-facing grounding
|
|
803
717
|
// metadata; the conversation runtime context remains the source for policy
|
|
804
|
-
// gating). Resolved once at turn start and
|
|
805
|
-
//
|
|
806
|
-
//
|
|
807
|
-
// mid-turn.
|
|
718
|
+
// gating). Resolved once at turn start and frozen onto the conversation so
|
|
719
|
+
// the post-compaction hook re-emits this same value during in-loop recovery
|
|
720
|
+
// instead of re-resolving against contact/member registry state that may
|
|
721
|
+
// have drifted mid-turn.
|
|
808
722
|
const actorContext = resolveTurnInboundActorContext(
|
|
809
723
|
ctx.trustContext,
|
|
810
724
|
ctx.assistantId,
|
|
811
725
|
);
|
|
726
|
+
ctx.currentTurnInboundActorContext = actorContext;
|
|
812
727
|
|
|
813
728
|
// Surface long gaps between user messages so the model can acknowledge
|
|
814
729
|
// the absence naturally. Gated at >12h to avoid noisy injection during
|
|
@@ -851,90 +766,55 @@ export async function runAgentLoopImpl(
|
|
|
851
766
|
config.llm.activeProfile ??
|
|
852
767
|
resolveDefaultProfileKey("mainAgent", config.llm);
|
|
853
768
|
const lastNotified = ctx.lastNotifiedInferenceProfile;
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
769
|
+
const modelProfileKey =
|
|
770
|
+
effectiveProfileKey != null && effectiveProfileKey !== lastNotified
|
|
771
|
+
? effectiveProfileKey
|
|
772
|
+
: null;
|
|
773
|
+
// The key is threaded as plain turn data to the user-prompt-submit and
|
|
774
|
+
// post-compaction hooks, which render the `Label (model)` line from it
|
|
775
|
+
// themselves.
|
|
776
|
+
if (modelProfileKey != null) {
|
|
862
777
|
// Record the notification for persistence on delivery rather than here:
|
|
863
778
|
// the model only "learns" the profile once it receives this turn
|
|
864
779
|
// context, signalled by the first `message_complete`. Persisting inline
|
|
865
780
|
// would mark the profile notified even if the turn is cancelled or fails
|
|
866
781
|
// before the model ever sees the notice.
|
|
867
|
-
state.pendingNotifiedInferenceProfile =
|
|
782
|
+
state.pendingNotifiedInferenceProfile = modelProfileKey;
|
|
868
783
|
}
|
|
869
784
|
|
|
870
|
-
//
|
|
871
|
-
//
|
|
872
|
-
//
|
|
873
|
-
//
|
|
874
|
-
//
|
|
875
|
-
//
|
|
876
|
-
//
|
|
877
|
-
//
|
|
878
|
-
//
|
|
879
|
-
//
|
|
880
|
-
//
|
|
881
|
-
//
|
|
882
|
-
|
|
883
|
-
let currentInjectionMode: InjectionMode = "full";
|
|
884
|
-
const memoryCtx: MemoryRetrievalHookContext = {
|
|
885
|
-
onEvent,
|
|
886
|
-
conversationId: ctx.conversationId,
|
|
887
|
-
userMessageId,
|
|
888
|
-
logger: rlog,
|
|
889
|
-
latestMessages: ctx.messages,
|
|
890
|
-
requestId: reqId,
|
|
891
|
-
isNonInteractive,
|
|
892
|
-
modelProfile: modelProfileStr,
|
|
893
|
-
};
|
|
894
|
-
await userPromptSubmitMemoryRetrieval(memoryCtx);
|
|
895
|
-
|
|
896
|
-
// The hook owns its side effects (injected-block metadata, recall log,
|
|
897
|
-
// `memory_recalled` event, and the runtime-injection metadata persist) and
|
|
898
|
-
// records the dense/sparse PKB query pair on the graph handle for the
|
|
899
|
-
// PKB-reminder injector to read back; the loop reuses the fully injected
|
|
900
|
-
// message list downstream.
|
|
901
|
-
let runMessages = memoryCtx.latestMessages;
|
|
902
|
-
|
|
903
|
-
// user-prompt-submit hook: plugins may transform `runMessages` right
|
|
904
|
-
// before the agent loop receives them. Fires once per user turn at the
|
|
905
|
-
// primary `agentLoop.run` only — the re-entry / retry calls further down
|
|
906
|
-
// in this function do not refire it (they're not new user submissions).
|
|
907
|
-
// Plugins may mutate `ctx.latestMessages` in place OR return a new
|
|
908
|
-
// context with a fresh array; `runHook` forwards whichever the chain
|
|
909
|
-
// settles on. Order is plugin registration order.
|
|
910
|
-
//
|
|
911
|
-
// Fires BEFORE the agent loop runs so the hook-emitted messages are part
|
|
912
|
-
// of the loop's input; the loop then reports its own appended output via
|
|
913
|
-
// `AgentLoopRunResult.newMessages`, which is what persistence consumes.
|
|
785
|
+
// user-prompt-submit hook chain. Fires once per user turn at the primary
|
|
786
|
+
// `agentLoop.run` (the re-entry / retry calls further down do not refire it
|
|
787
|
+
// — they're not new user submissions), before the loop runs so the
|
|
788
|
+
// hook-assembled messages are part of its input. Memory retrieval runs
|
|
789
|
+
// first — fetching PKB / NOW.md / memory-graph outputs, persisting its own
|
|
790
|
+
// side effects (injected-block metadata, recall log, `memory_recalled`
|
|
791
|
+
// event), and assembling the turn's runtime-injection blocks onto the
|
|
792
|
+
// history — followed by history repair and title generation, which see the
|
|
793
|
+
// fully injected history. Plugins may mutate `ctx.latestMessages` in place
|
|
794
|
+
// OR return a new context with a fresh array; `runHook` forwards whichever
|
|
795
|
+
// the chain settles on, in plugin registration order. The loop then reports
|
|
796
|
+
// its own appended output via `AgentLoopRunResult.newMessages`, which
|
|
797
|
+
// persistence consumes.
|
|
914
798
|
const userPromptCtx: UserPromptSubmitContext = {
|
|
915
799
|
conversationId: ctx.conversationId,
|
|
916
800
|
userMessageId,
|
|
917
801
|
requestId: reqId,
|
|
918
802
|
prompt: options?.titleText ?? content,
|
|
919
803
|
originalMessages: ctx.messages,
|
|
920
|
-
latestMessages:
|
|
804
|
+
latestMessages: ctx.messages,
|
|
921
805
|
logger: rlog,
|
|
806
|
+
modelProfileKey,
|
|
807
|
+
isNonInteractive,
|
|
922
808
|
};
|
|
923
809
|
const finalUserPromptCtx = await runHook(
|
|
924
810
|
HOOKS.USER_PROMPT_SUBMIT,
|
|
925
811
|
userPromptCtx,
|
|
926
812
|
);
|
|
927
|
-
runMessages = finalUserPromptCtx.latestMessages;
|
|
928
|
-
|
|
929
|
-
//
|
|
930
|
-
//
|
|
931
|
-
|
|
932
|
-
// the turn); the calibration key matches the key recorded by `handleUsage`
|
|
933
|
-
// for wrapper providers (OpenRouter routing to Anthropic → key is
|
|
934
|
-
// `"anthropic"`).
|
|
935
|
-
let reducerState: ReducerState | undefined;
|
|
936
|
-
const toolTokenBudget = ctx.agentLoop.getToolTokenBudget(runMessages);
|
|
937
|
-
const estimationProviderName = getCalibrationProviderKey(ctx.provider);
|
|
813
|
+
const runMessages = finalUserPromptCtx.latestMessages;
|
|
814
|
+
|
|
815
|
+
// Reset the manager's turn-scoped overflow-recovery ladder at the turn
|
|
816
|
+
// boundary so a new turn starts the ladder fresh from the emergency rung.
|
|
817
|
+
ctx.contextWindowManager.resetOverflowRecovery();
|
|
938
818
|
|
|
939
819
|
const shouldGenerateTitle = isReplaceableTitle(
|
|
940
820
|
getConversation(ctx.conversationId)?.title ?? null,
|
|
@@ -951,10 +831,32 @@ export async function runAgentLoopImpl(
|
|
|
951
831
|
turnInterfaceContext: capturedTurnInterfaceContext,
|
|
952
832
|
applyCompaction: applySuccessfulCompaction,
|
|
953
833
|
};
|
|
954
|
-
const eventHandler = (event: AgentEvent): Promise<void> =>
|
|
955
|
-
|
|
834
|
+
const eventHandler = (event: AgentEvent): Promise<void> => {
|
|
835
|
+
if (
|
|
836
|
+
event.type === "agent_loop_exit" &&
|
|
837
|
+
(event.reason === "context_too_large" ||
|
|
838
|
+
event.reason === "budget_yield_unrecovered")
|
|
839
|
+
) {
|
|
840
|
+
overflowTerminalReason = event.reason;
|
|
841
|
+
if (event.reason === "budget_yield_unrecovered") {
|
|
842
|
+
// The loop emits this terminal exit inline as it breaks. Stamping the
|
|
843
|
+
// exit reason now would land on the last real LLM call before the
|
|
844
|
+
// wrapper has recorded the synthetic yield row below — and the
|
|
845
|
+
// wrapper then stamps again after that row exists, double-stamping
|
|
846
|
+
// two real rows. Capture the reason here and let the wrapper drive a
|
|
847
|
+
// single stamp via `emitTerminalExit` once the synthetic row is in
|
|
848
|
+
// place, preserving the "latest LLM call carries the exit reason"
|
|
849
|
+
// invariant.
|
|
850
|
+
return Promise.resolve();
|
|
851
|
+
}
|
|
852
|
+
}
|
|
853
|
+
return dispatchAgentEvent(state, deps, event);
|
|
854
|
+
};
|
|
956
855
|
emitTerminalExit = async (reason: AgentLoopExitReason): Promise<void> => {
|
|
957
|
-
await
|
|
856
|
+
await dispatchAgentEvent(state, deps, {
|
|
857
|
+
type: "agent_loop_exit",
|
|
858
|
+
reason,
|
|
859
|
+
});
|
|
958
860
|
};
|
|
959
861
|
|
|
960
862
|
const onCheckpoint = async (): Promise<CheckpointDecision> => {
|
|
@@ -978,49 +880,41 @@ export async function runAgentLoopImpl(
|
|
|
978
880
|
ctx.currentTurnTrustContext ?? ctx.trustContext ?? FALLBACK_TURN_TRUST;
|
|
979
881
|
|
|
980
882
|
/**
|
|
981
|
-
* Shared closure: runs the agent loop with the
|
|
982
|
-
*
|
|
983
|
-
*
|
|
984
|
-
*
|
|
985
|
-
*
|
|
986
|
-
*
|
|
987
|
-
*
|
|
988
|
-
* keep yielding for budget.
|
|
883
|
+
* Shared closure: runs the agent loop with the wrapper's turn context and
|
|
884
|
+
* maps the loop's returned checkpoint pause-reason into the wrapper's yield
|
|
885
|
+
* bookkeeping. Returns the updated history so call sites consume it exactly
|
|
886
|
+
* as before. Pass `compactInPlace` only for the primary run: the loop then
|
|
887
|
+
* runs its budget gate before the first call (subsuming the proactive
|
|
888
|
+
* turn-start compaction) and compacts in place whenever the gate trips.
|
|
889
|
+
* Reruns omit it and skip the first-call gate.
|
|
989
890
|
*/
|
|
990
891
|
const runAgentLoop = async (
|
|
991
892
|
msgs: Message[],
|
|
992
893
|
compactInPlace = false,
|
|
993
894
|
): Promise<Message[]> => {
|
|
994
|
-
const { history, exitReason,
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
actorContext,
|
|
1010
|
-
});
|
|
1011
|
-
lastRunAppendedNewMessages = appendedNewMessages;
|
|
895
|
+
const { history, exitReason, newMessages } = await ctx.agentLoop.run({
|
|
896
|
+
messages: msgs,
|
|
897
|
+
onEvent: eventHandler,
|
|
898
|
+
signal: abortController.signal,
|
|
899
|
+
requestId: reqId,
|
|
900
|
+
onCheckpoint,
|
|
901
|
+
callSite: turnCallSite,
|
|
902
|
+
trust: loopTrust,
|
|
903
|
+
overrideProfile: turnOverrideProfile,
|
|
904
|
+
resolveOverrideProfile: resolveCurrentOverrideProfile,
|
|
905
|
+
resolveContextWindow,
|
|
906
|
+
compactInPlace,
|
|
907
|
+
isNonInteractive,
|
|
908
|
+
modelProfileKey,
|
|
909
|
+
});
|
|
1012
910
|
lastRunNewMessages = newMessages;
|
|
1013
911
|
if (exitReason === "handoff") {
|
|
1014
912
|
yieldedForHandoff = true;
|
|
1015
|
-
pendingCheckpointYield = "handoff";
|
|
1016
|
-
} else if (exitReason === "budget") {
|
|
1017
|
-
yieldedForBudget = true;
|
|
1018
|
-
pendingCheckpointYield = "budget";
|
|
1019
913
|
}
|
|
1020
914
|
return history;
|
|
1021
915
|
};
|
|
1022
916
|
|
|
1023
|
-
|
|
917
|
+
const updatedHistory = await runAgentLoop(runMessages, true);
|
|
1024
918
|
|
|
1025
919
|
rlog.info(
|
|
1026
920
|
{ resultMessageCount: updatedHistory.length },
|
|
@@ -1029,455 +923,34 @@ export async function runAgentLoopImpl(
|
|
|
1029
923
|
|
|
1030
924
|
if (yieldedForHandoff) {
|
|
1031
925
|
await emitTerminalExit?.("checkpoint_handoff");
|
|
1032
|
-
pendingCheckpointYield = null;
|
|
1033
|
-
}
|
|
1034
|
-
|
|
1035
|
-
// The loop compacts in place when its budget gate trips and only yields
|
|
1036
|
-
// `exitReason = "budget"` when that inline compaction timed out or
|
|
1037
|
-
// exhausted its retry budget (the `reinject` hook has already restored
|
|
1038
|
-
// runtime context for the productive case). Escalate to the convergence
|
|
1039
|
-
// loop's more aggressive reducer tiers so a half-finished turn doesn't
|
|
1040
|
-
// reach the user.
|
|
1041
|
-
if (yieldedForBudget && !abortController.signal.aborted) {
|
|
1042
|
-
rlog.warn(
|
|
1043
|
-
{ phase: "mid-loop-compact" },
|
|
1044
|
-
"Inline compaction could not get under budget — escalating to convergence loop",
|
|
1045
|
-
);
|
|
1046
|
-
state.contextTooLargeDetected = true;
|
|
1047
|
-
}
|
|
1048
|
-
|
|
1049
|
-
// One-shot ordering error retry
|
|
1050
|
-
if (state.orderingErrorDetected && !lastRunAppendedNewMessages) {
|
|
1051
|
-
rlog.warn(
|
|
1052
|
-
{ phase: "retry" },
|
|
1053
|
-
"Provider ordering error detected, attempting one-shot deep-repair retry",
|
|
1054
|
-
);
|
|
1055
|
-
// Design note: deep-repair intentionally stays a direct call rather
|
|
1056
|
-
// than running through the `user-prompt-submit` hook chain. Deep-repair
|
|
1057
|
-
// is a recovery-only path triggered by a provider ordering error — it
|
|
1058
|
-
// must be deterministic and unaffected by user hooks that might have
|
|
1059
|
-
// caused (or be unable to recover from) the original drift. Plugins can
|
|
1060
|
-
// already observe / transform the pre-run repair via the
|
|
1061
|
-
// `user-prompt-submit` hook (the default history-repair plugin runs
|
|
1062
|
-
// `repairHistory` there); widening that surface to deep-repair is
|
|
1063
|
-
// intentionally deferred until there's a concrete plugin-level use case.
|
|
1064
|
-
const retryRepair = deepRepairHistory(updatedHistory);
|
|
1065
|
-
runMessages = retryRepair.messages;
|
|
1066
|
-
state.orderingErrorDetected = false;
|
|
1067
|
-
state.deferredOrderingError = null;
|
|
1068
|
-
|
|
1069
|
-
updatedHistory = await runAgentLoop(runMessages);
|
|
1070
|
-
|
|
1071
|
-
if (state.orderingErrorDetected) {
|
|
1072
|
-
rlog.error(
|
|
1073
|
-
{ phase: "retry" },
|
|
1074
|
-
"Deep-repair retry also failed with ordering error. Consider starting a new conversation if this persists.",
|
|
1075
|
-
);
|
|
1076
|
-
}
|
|
1077
|
-
}
|
|
1078
|
-
|
|
1079
|
-
// ── Image-dimension overflow recovery ──────────────────────────
|
|
1080
|
-
// When the provider rejects because an image block exceeds its pixel
|
|
1081
|
-
// or payload cap, recover every oversized image in ctx.messages and
|
|
1082
|
-
// retry once. recoverImageBlock downscales an oversized image, or swaps
|
|
1083
|
-
// it for a text note when resize is a no-op (e.g. sips unavailable
|
|
1084
|
-
// off macOS), while leaving still-sendable images untouched. This covers
|
|
1085
|
-
// both top-level image blocks (user uploads) and images nested inside a
|
|
1086
|
-
// tool_result's contentBlocks (e.g. a browser screenshot), which is where
|
|
1087
|
-
// the rejected block usually lives.
|
|
1088
|
-
if (state.imageTooLargeDetected) {
|
|
1089
|
-
state.imageTooLargeDetected = false;
|
|
1090
|
-
rlog.warn(
|
|
1091
|
-
{ phase: "image-recovery" },
|
|
1092
|
-
"Image too large — recovering oversized image blocks and retrying",
|
|
1093
|
-
);
|
|
1094
|
-
ctx.messages = ctx.messages.map((msg) => {
|
|
1095
|
-
if (!Array.isArray(msg.content)) return msg;
|
|
1096
|
-
if (!messageHasImageBlock(msg.content)) return msg;
|
|
1097
|
-
return {
|
|
1098
|
-
...msg,
|
|
1099
|
-
content: msg.content.flatMap((b): ContentBlock[] => {
|
|
1100
|
-
if (b.type === "image") return [recoverImageBlock(b)];
|
|
1101
|
-
// Images returned by a tool (e.g. browser_screenshot) live in
|
|
1102
|
-
// the tool_result's contentBlocks, not as top-level blocks.
|
|
1103
|
-
// Recover them in place so the tool_use/tool_result pairing
|
|
1104
|
-
// stays intact rather than dropping the whole tool_result.
|
|
1105
|
-
if (b.type === "tool_result" && b.contentBlocks?.length) {
|
|
1106
|
-
return [
|
|
1107
|
-
{
|
|
1108
|
-
...b,
|
|
1109
|
-
contentBlocks: b.contentBlocks.map((cb) =>
|
|
1110
|
-
cb.type === "image" ? recoverImageBlock(cb) : cb,
|
|
1111
|
-
),
|
|
1112
|
-
},
|
|
1113
|
-
];
|
|
1114
|
-
}
|
|
1115
|
-
return [b];
|
|
1116
|
-
}),
|
|
1117
|
-
};
|
|
1118
|
-
});
|
|
1119
|
-
// The transform above only mutates ctx.messages for the current retry.
|
|
1120
|
-
// Persist the downgrade for images that can never be sent so the rejected
|
|
1121
|
-
// upload doesn't rehydrate from the DB and resurface on later turns. This
|
|
1122
|
-
// is cleanup for future turns, so a persistence failure must never abort
|
|
1123
|
-
// the retry that is about to run — log it and continue.
|
|
1124
|
-
try {
|
|
1125
|
-
const rewritten = persistUnsendableImageDowngrades(ctx.conversationId);
|
|
1126
|
-
if (rewritten > 0) {
|
|
1127
|
-
rlog.info(
|
|
1128
|
-
{ phase: "image-recovery", rewritten },
|
|
1129
|
-
"Persisted unsendable-image downgrades so they cannot resurface",
|
|
1130
|
-
);
|
|
1131
|
-
}
|
|
1132
|
-
} catch (err) {
|
|
1133
|
-
rlog.warn(
|
|
1134
|
-
{ phase: "image-recovery", err },
|
|
1135
|
-
"Failed to persist unsendable-image downgrade; continuing with in-memory recovery",
|
|
1136
|
-
);
|
|
1137
|
-
}
|
|
1138
|
-
runMessages = ctx.messages;
|
|
1139
|
-
updatedHistory = await runAgentLoop(runMessages);
|
|
1140
|
-
if (state.imageTooLargeDetected) {
|
|
1141
|
-
rlog.error(
|
|
1142
|
-
{ phase: "image-recovery" },
|
|
1143
|
-
"Image-recovery retry also failed — surfacing error to user",
|
|
1144
|
-
);
|
|
1145
|
-
const classified = classifyConversationError(
|
|
1146
|
-
new Error("Image dimensions too large"),
|
|
1147
|
-
{ phase: "agent_loop" },
|
|
1148
|
-
);
|
|
1149
|
-
deps.onEvent(
|
|
1150
|
-
buildConversationErrorMessage(deps.ctx.conversationId, classified),
|
|
1151
|
-
);
|
|
1152
|
-
state.providerErrorUserMessage = classified.userMessage;
|
|
1153
|
-
state.imageTooLargeDetected = false;
|
|
1154
|
-
}
|
|
1155
|
-
}
|
|
1156
|
-
|
|
1157
|
-
// ── Bounded context overflow convergence loop ──────────────────
|
|
1158
|
-
// When the provider rejects with context-too-large, iterate through
|
|
1159
|
-
// reducer tiers (forced compaction, tool-result truncation, media
|
|
1160
|
-
// stubbing, injection downgrade).
|
|
1161
|
-
//
|
|
1162
|
-
// When progress was made (agent added messages before hitting the
|
|
1163
|
-
// limit), incorporate those new messages into ctx.messages so the
|
|
1164
|
-
// convergence loop operates on the full (larger) history.
|
|
1165
|
-
if (state.contextTooLargeDetected) {
|
|
1166
|
-
if (lastRunAppendedNewMessages) {
|
|
1167
|
-
ctx.messages = stripInjectionsForCompaction(updatedHistory);
|
|
1168
|
-
markHistoryStrippedBestEffort(ctx.conversationId);
|
|
1169
|
-
}
|
|
1170
|
-
if (!reducerState) {
|
|
1171
|
-
reducerState = createInitialReducerState();
|
|
1172
|
-
}
|
|
1173
|
-
|
|
1174
|
-
// When the provider reveals the actual token count in its error
|
|
1175
|
-
// message (e.g. "242201 tokens > 200000"), use it to correct the
|
|
1176
|
-
// compaction target. The estimator may significantly underestimate
|
|
1177
|
-
// (e.g. estimated 185k but actual was 242k), so using the
|
|
1178
|
-
// uncorrected preflightBudget would still be too high. Passes the raw
|
|
1179
|
-
// error so ContextOverflowError.actualTokens can short-circuit the
|
|
1180
|
-
// string-regex path for proxy-rewrapped untyped errors.
|
|
1181
|
-
const actualTokens = parseActualTokensFromError(
|
|
1182
|
-
state.contextTooLargeError,
|
|
1183
|
-
);
|
|
1184
|
-
const estimatedTokensAtOverflow = estimatePromptTokens(
|
|
1185
|
-
ctx.messages,
|
|
1186
|
-
ctx.systemPrompt,
|
|
1187
|
-
{
|
|
1188
|
-
providerName: estimationProviderName,
|
|
1189
|
-
toolTokenBudget,
|
|
1190
|
-
},
|
|
1191
|
-
);
|
|
1192
|
-
const convergenceBudget = resolveCurrentContextBudget();
|
|
1193
|
-
let correctedTarget = convergenceBudget.preflightBudget;
|
|
1194
|
-
if (actualTokens && estimatedTokensAtOverflow > 0) {
|
|
1195
|
-
const estimationErrorRatio = actualTokens / estimatedTokensAtOverflow;
|
|
1196
|
-
if (estimationErrorRatio > 1.0) {
|
|
1197
|
-
correctedTarget = Math.floor(
|
|
1198
|
-
convergenceBudget.preflightBudget / estimationErrorRatio,
|
|
1199
|
-
);
|
|
1200
|
-
rlog.warn(
|
|
1201
|
-
{
|
|
1202
|
-
phase: "convergence",
|
|
1203
|
-
actualTokens,
|
|
1204
|
-
estimatedTokens: estimatedTokensAtOverflow,
|
|
1205
|
-
estimationErrorRatio: estimationErrorRatio.toFixed(2),
|
|
1206
|
-
preflightBudget: convergenceBudget.preflightBudget,
|
|
1207
|
-
correctedTarget,
|
|
1208
|
-
},
|
|
1209
|
-
"Adjusting compaction target based on observed estimation error",
|
|
1210
|
-
);
|
|
1211
|
-
}
|
|
1212
|
-
}
|
|
1213
|
-
|
|
1214
|
-
// ── Emergency mid-turn compaction ────────────────────────────
|
|
1215
|
-
// Before entering the reducer tier loop, attempt a targeted
|
|
1216
|
-
// emergency compaction: summarize everything before the last
|
|
1217
|
-
// tool_use + tool_result pair and let the agent continue with
|
|
1218
|
-
// [summary, last_tool_call, last_tool_result]. This preserves
|
|
1219
|
-
// the agent's most recent action context while aggressively
|
|
1220
|
-
// compressing history. Falls through to reducer tiers on failure.
|
|
1221
|
-
{
|
|
1222
|
-
try {
|
|
1223
|
-
const emergencyResult =
|
|
1224
|
-
await ctx.contextWindowManager.emergencyCompact(
|
|
1225
|
-
ctx.messages,
|
|
1226
|
-
{
|
|
1227
|
-
previousEstimatedInputTokens: estimatedTokensAtOverflow,
|
|
1228
|
-
overrideProfile: resolveCurrentOverrideProfile() ?? null,
|
|
1229
|
-
},
|
|
1230
|
-
abortController.signal,
|
|
1231
|
-
);
|
|
1232
|
-
if (emergencyResult.compacted) {
|
|
1233
|
-
rlog.info(
|
|
1234
|
-
{
|
|
1235
|
-
phase: "convergence",
|
|
1236
|
-
compactedMessages: emergencyResult.compactedMessages,
|
|
1237
|
-
summaryChars: emergencyResult.summaryText.length,
|
|
1238
|
-
},
|
|
1239
|
-
"Emergency mid-turn compaction succeeded — bypassing reducer tiers",
|
|
1240
|
-
);
|
|
1241
|
-
if (emergencyResult.summaryFailed !== undefined) {
|
|
1242
|
-
await ctx.agentLoop.compactionCircuit.recordOutcome(
|
|
1243
|
-
emergencyResult.summaryFailed,
|
|
1244
|
-
onEvent,
|
|
1245
|
-
);
|
|
1246
|
-
}
|
|
1247
|
-
if (emergencyResult.compacted) {
|
|
1248
|
-
await applySuccessfulCompaction(emergencyResult, ctx.messages);
|
|
1249
|
-
}
|
|
1250
|
-
// Clear the overflow flag and re-run the agent loop with
|
|
1251
|
-
// the compacted context.
|
|
1252
|
-
state.contextTooLargeDetected = false;
|
|
1253
|
-
}
|
|
1254
|
-
} catch (err) {
|
|
1255
|
-
rlog.warn(
|
|
1256
|
-
{ phase: "convergence", err },
|
|
1257
|
-
"Emergency mid-turn compaction failed; continuing to reducer tiers",
|
|
1258
|
-
);
|
|
1259
|
-
}
|
|
1260
|
-
// If emergency compaction failed, fall through to reducer tiers.
|
|
1261
|
-
}
|
|
1262
|
-
|
|
1263
|
-
let convergenceAttempts = 0;
|
|
1264
|
-
const maxAttempts = convergenceBudget.overflowRecovery.maxAttempts;
|
|
1265
|
-
|
|
1266
|
-
while (
|
|
1267
|
-
state.contextTooLargeDetected &&
|
|
1268
|
-
convergenceAttempts < maxAttempts &&
|
|
1269
|
-
!reducerState.exhausted
|
|
1270
|
-
) {
|
|
1271
|
-
convergenceAttempts++;
|
|
1272
|
-
rlog.warn(
|
|
1273
|
-
{
|
|
1274
|
-
phase: "convergence",
|
|
1275
|
-
attempt: convergenceAttempts,
|
|
1276
|
-
appliedTiers: reducerState.appliedTiers,
|
|
1277
|
-
},
|
|
1278
|
-
"Context too large — applying next reducer tier",
|
|
1279
|
-
);
|
|
1280
|
-
|
|
1281
|
-
ctx.emitActivityState("thinking", "context_compacting", {
|
|
1282
|
-
requestId: reqId,
|
|
1283
|
-
});
|
|
1284
|
-
const convergenceCompactionBasis = ctx.messages;
|
|
1285
|
-
const step = await reduceContextOverflow(
|
|
1286
|
-
convergenceCompactionBasis,
|
|
1287
|
-
{
|
|
1288
|
-
providerName: estimationProviderName,
|
|
1289
|
-
systemPrompt: ctx.systemPrompt,
|
|
1290
|
-
contextWindow: resolveCurrentContextWindowConfig(),
|
|
1291
|
-
targetTokens: correctedTarget,
|
|
1292
|
-
toolTokenBudget,
|
|
1293
|
-
},
|
|
1294
|
-
reducerState,
|
|
1295
|
-
(msgs, signal, opts) =>
|
|
1296
|
-
defaultCompact({
|
|
1297
|
-
conversationId: ctx.conversationId,
|
|
1298
|
-
messages: msgs,
|
|
1299
|
-
signal,
|
|
1300
|
-
...((opts ?? {}) as ContextWindowCompactOptions),
|
|
1301
|
-
overrideProfile: resolveCurrentOverrideProfile() ?? null,
|
|
1302
|
-
actorTrustClass: resolveTurnActorTrustClass(ctx),
|
|
1303
|
-
}),
|
|
1304
|
-
abortController.signal,
|
|
1305
|
-
);
|
|
1306
|
-
|
|
1307
|
-
reducerState = step.state;
|
|
1308
|
-
ctx.messages = step.messages;
|
|
1309
|
-
currentInjectionMode = step.state.injectionMode;
|
|
1310
|
-
|
|
1311
|
-
// See the preflight reducer call above for rationale. Only track when
|
|
1312
|
-
// the summary LLM actually ran — `summaryFailed === undefined`
|
|
1313
|
-
// indicates the reducer's forced compaction took an early-return path
|
|
1314
|
-
// without calling the summary LLM.
|
|
1315
|
-
if (
|
|
1316
|
-
step.compactionResult &&
|
|
1317
|
-
step.compactionResult.summaryFailed !== undefined
|
|
1318
|
-
) {
|
|
1319
|
-
await ctx.agentLoop.compactionCircuit.recordOutcome(
|
|
1320
|
-
step.compactionResult.summaryFailed,
|
|
1321
|
-
onEvent,
|
|
1322
|
-
);
|
|
1323
|
-
}
|
|
1324
|
-
|
|
1325
|
-
if (step.compactionResult?.compacted) {
|
|
1326
|
-
await applySuccessfulCompaction(
|
|
1327
|
-
step.compactionResult,
|
|
1328
|
-
convergenceCompactionBasis,
|
|
1329
|
-
);
|
|
1330
|
-
}
|
|
1331
|
-
|
|
1332
|
-
// Only re-inject the memory-static block when ctx.messages was
|
|
1333
|
-
// actually stripped; otherwise the existing block is still present and
|
|
1334
|
-
// re-injecting would duplicate it. (The `<knowledge_base>` and NOW.md
|
|
1335
|
-
// blocks self-gate inside their injectors on whether they are already
|
|
1336
|
-
// present in `ctx.messages`.)
|
|
1337
|
-
const injection = await applyRuntimeInjections(ctx.messages, {
|
|
1338
|
-
isNonInteractive,
|
|
1339
|
-
modelProfile: modelProfileStr,
|
|
1340
|
-
actorContext,
|
|
1341
|
-
mode: currentInjectionMode,
|
|
1342
|
-
requestId: reqId,
|
|
1343
|
-
conversationId: ctx.conversationId,
|
|
1344
|
-
});
|
|
1345
|
-
runMessages = injection.messages;
|
|
1346
|
-
if (isTrustedActor && currentInjectionMode !== "minimal") {
|
|
1347
|
-
ctx.graphMemory.retrackCachedNodes();
|
|
1348
|
-
}
|
|
1349
|
-
state.contextTooLargeDetected = false;
|
|
1350
|
-
yieldedForBudget = false;
|
|
1351
|
-
|
|
1352
|
-
updatedHistory = await runAgentLoop(runMessages);
|
|
1353
|
-
|
|
1354
|
-
// If the rerun still yields at checkpoint, the turn is still
|
|
1355
|
-
// incomplete — continue reducing through the remaining tiers
|
|
1356
|
-
// instead of silently dropping the incomplete state.
|
|
1357
|
-
if (yieldedForBudget && !abortController.signal.aborted) {
|
|
1358
|
-
rlog.warn(
|
|
1359
|
-
{
|
|
1360
|
-
phase: "convergence",
|
|
1361
|
-
attempt: convergenceAttempts,
|
|
1362
|
-
appliedTiers: reducerState.appliedTiers,
|
|
1363
|
-
},
|
|
1364
|
-
"Post-convergence rerun still yielded at checkpoint — continuing reduction",
|
|
1365
|
-
);
|
|
1366
|
-
state.contextTooLargeDetected = true;
|
|
1367
|
-
|
|
1368
|
-
// Fold rerun progress into ctx.messages so the next reducer
|
|
1369
|
-
// tier operates on up-to-date history instead of stale
|
|
1370
|
-
// pre-rerun messages.
|
|
1371
|
-
if (lastRunAppendedNewMessages) {
|
|
1372
|
-
ctx.messages = stripInjectionsForCompaction(updatedHistory);
|
|
1373
|
-
markHistoryStrippedBestEffort(ctx.conversationId);
|
|
1374
|
-
}
|
|
1375
|
-
}
|
|
1376
|
-
}
|
|
1377
|
-
|
|
1378
|
-
// All reducer tiers exhausted but provider still rejects —
|
|
1379
|
-
// consult the overflow policy for latest-turn compression.
|
|
1380
|
-
// The policy either auto-compresses the latest turn or falls
|
|
1381
|
-
// through to the final graceful-error fallback below.
|
|
1382
|
-
if (state.contextTooLargeDetected) {
|
|
1383
|
-
const action = resolveOverflowAction({
|
|
1384
|
-
overflowRecovery: convergenceBudget.overflowRecovery,
|
|
1385
|
-
isInteractive: isInteractiveResolved,
|
|
1386
|
-
});
|
|
1387
|
-
|
|
1388
|
-
if (action === "auto_compress_latest_turn") {
|
|
1389
|
-
// Auto-compress without asking — users opt out via the "drop" policy.
|
|
1390
|
-
ctx.emitActivityState("thinking", "context_compacting", {
|
|
1391
|
-
requestId: reqId,
|
|
1392
|
-
});
|
|
1393
|
-
const emergencyCompact = await defaultCompact({
|
|
1394
|
-
conversationId: ctx.conversationId,
|
|
1395
|
-
messages: ctx.messages,
|
|
1396
|
-
signal: abortController.signal,
|
|
1397
|
-
force: true,
|
|
1398
|
-
minKeepRecentUserTurns: 0,
|
|
1399
|
-
overrideProfile: resolveCurrentOverrideProfile() ?? null,
|
|
1400
|
-
});
|
|
1401
|
-
// Only track when the summary LLM actually ran; `force: true`
|
|
1402
|
-
// bypasses the auto-threshold gate but not the early-return paths.
|
|
1403
|
-
if (emergencyCompact.summaryFailed !== undefined) {
|
|
1404
|
-
await ctx.agentLoop.compactionCircuit.recordOutcome(
|
|
1405
|
-
emergencyCompact.summaryFailed,
|
|
1406
|
-
onEvent,
|
|
1407
|
-
);
|
|
1408
|
-
}
|
|
1409
|
-
if (emergencyCompact.compacted) {
|
|
1410
|
-
await applySuccessfulCompaction(emergencyCompact, ctx.messages);
|
|
1411
|
-
}
|
|
1412
|
-
|
|
1413
|
-
// Only re-inject the memory-static block when ctx.messages was
|
|
1414
|
-
// actually stripped; otherwise the existing block is still present.
|
|
1415
|
-
// (The `<knowledge_base>`, NOW.md, and v2 static `<info>` blocks
|
|
1416
|
-
// self-gate inside their injectors on whether they are already
|
|
1417
|
-
// present in `ctx.messages`.)
|
|
1418
|
-
const injection = await applyRuntimeInjections(ctx.messages, {
|
|
1419
|
-
isNonInteractive,
|
|
1420
|
-
modelProfile: modelProfileStr,
|
|
1421
|
-
actorContext,
|
|
1422
|
-
mode: currentInjectionMode,
|
|
1423
|
-
requestId: reqId,
|
|
1424
|
-
conversationId: ctx.conversationId,
|
|
1425
|
-
});
|
|
1426
|
-
runMessages = injection.messages;
|
|
1427
|
-
if (isTrustedActor && currentInjectionMode !== "minimal") {
|
|
1428
|
-
ctx.graphMemory.retrackCachedNodes();
|
|
1429
|
-
}
|
|
1430
|
-
state.contextTooLargeDetected = false;
|
|
1431
|
-
|
|
1432
|
-
updatedHistory = await runAgentLoop(runMessages);
|
|
1433
|
-
}
|
|
1434
|
-
// action === "fail_gracefully" falls through to the final error below
|
|
1435
|
-
}
|
|
1436
|
-
|
|
1437
|
-
// Final fallback: all recovery paths exhausted
|
|
1438
|
-
if (state.contextTooLargeDetected) {
|
|
1439
|
-
const classified = classifyConversationError(
|
|
1440
|
-
new Error("context_length_exceeded"),
|
|
1441
|
-
{ phase: "agent_loop" },
|
|
1442
|
-
);
|
|
1443
|
-
await emitTerminalExit?.("context_too_large");
|
|
1444
|
-
pendingCheckpointYield = null;
|
|
1445
|
-
onEvent(buildConversationErrorMessage(ctx.conversationId, classified));
|
|
1446
|
-
} else if (yieldedForBudget && !abortController.signal.aborted) {
|
|
1447
|
-
// The auto_compress_latest_turn rerun (action === "auto_compress_latest_turn"
|
|
1448
|
-
// above) reset `contextTooLargeDetected` to false before its final
|
|
1449
|
-
// `agentLoop.run`, so the context-too-large branch above won't fire
|
|
1450
|
-
// even when that rerun yields at the mid-loop budget checkpoint with
|
|
1451
|
-
// no further recovery layer to re-enter. Without surfacing this here,
|
|
1452
|
-
// the turn terminates silently — the inspector sees `agent_loop_exit_reason
|
|
1453
|
-
// = NULL` and the user sees no message at all (just a "ghost" turn).
|
|
1454
|
-
//
|
|
1455
|
-
// Unlike provider-error persistence at L3091 — which only fires when
|
|
1456
|
-
// the loop produced NO assistant output — budget_yield_unrecovered
|
|
1457
|
-
// typically yields AFTER one or more successful tool-use iterations,
|
|
1458
|
-
// so `hasAssistantResponse` is true and that path would skip us. We
|
|
1459
|
-
// capture the classification here so the live SSE event fires
|
|
1460
|
-
// immediately, and persist a dedicated notice row below — after the
|
|
1461
|
-
// pendingToolResults flush — so the transcript reads as: tool-use →
|
|
1462
|
-
// tool results → "I couldn't fit the next step…" notice. Persisting
|
|
1463
|
-
// earlier would orphan an assistant(tool_use) from its user(tool_result),
|
|
1464
|
-
// breaking provider adjacency on replay.
|
|
1465
|
-
budgetYieldClassification = budgetYieldUnrecoveredClassification();
|
|
1466
|
-
onEvent(
|
|
1467
|
-
buildConversationErrorMessage(
|
|
1468
|
-
ctx.conversationId,
|
|
1469
|
-
budgetYieldClassification,
|
|
1470
|
-
),
|
|
1471
|
-
);
|
|
1472
|
-
}
|
|
1473
926
|
}
|
|
1474
927
|
|
|
1475
|
-
|
|
928
|
+
// ── Context-overflow terminal notice ───────────────────────────
|
|
929
|
+
// The agent loop drives overflow recovery through the compaction plugin's
|
|
930
|
+
// reduction ladder and, when the ladder is spent and the provider still
|
|
931
|
+
// rejects, emits the terminal exit (`context_too_large` or
|
|
932
|
+
// `budget_yield_unrecovered`) itself. The wrapper only renders the matching
|
|
933
|
+
// user-facing notice. `budget_yield_unrecovered` defers its durable row to
|
|
934
|
+
// after the tool-result flush below (so the transcript reads tool-use →
|
|
935
|
+
// tool-results → notice), so it captures the classification here and emits
|
|
936
|
+
// the live SSE event; the durable write happens further down.
|
|
937
|
+
if (overflowTerminalReason === "context_too_large") {
|
|
1476
938
|
const classified = classifyConversationError(
|
|
1477
|
-
new Error(
|
|
939
|
+
new Error("context_length_exceeded"),
|
|
1478
940
|
{ phase: "agent_loop" },
|
|
1479
941
|
);
|
|
1480
942
|
onEvent(buildConversationErrorMessage(ctx.conversationId, classified));
|
|
943
|
+
} else if (
|
|
944
|
+
overflowTerminalReason === "budget_yield_unrecovered" &&
|
|
945
|
+
!abortController.signal.aborted
|
|
946
|
+
) {
|
|
947
|
+
budgetYieldClassification = budgetYieldUnrecoveredClassification();
|
|
948
|
+
onEvent(
|
|
949
|
+
buildConversationErrorMessage(
|
|
950
|
+
ctx.conversationId,
|
|
951
|
+
budgetYieldClassification,
|
|
952
|
+
),
|
|
953
|
+
);
|
|
1481
954
|
}
|
|
1482
955
|
|
|
1483
956
|
// Flush remaining tool results. On a normal turn these drain at the next
|
|
@@ -1803,10 +1276,6 @@ export async function runAgentLoopImpl(
|
|
|
1803
1276
|
|
|
1804
1277
|
// Re-check: the user may have cancelled during attachment resolution
|
|
1805
1278
|
if (abortController.signal.aborted) {
|
|
1806
|
-
if (pendingCheckpointYield === "budget") {
|
|
1807
|
-
await emitTerminalExit?.("aborted_after_checkpoint");
|
|
1808
|
-
pendingCheckpointYield = null;
|
|
1809
|
-
}
|
|
1810
1279
|
ctx.emitActivityState("idle", "generation_cancelled", {
|
|
1811
1280
|
anchor: "global",
|
|
1812
1281
|
requestId: reqId,
|
|
@@ -1885,10 +1354,6 @@ export async function runAgentLoopImpl(
|
|
|
1885
1354
|
aborted: abortController.signal.aborted,
|
|
1886
1355
|
};
|
|
1887
1356
|
if (isUserCancellation(err, errorCtx)) {
|
|
1888
|
-
if (pendingCheckpointYield === "budget") {
|
|
1889
|
-
await emitTerminalExit?.("aborted_after_checkpoint");
|
|
1890
|
-
pendingCheckpointYield = null;
|
|
1891
|
-
}
|
|
1892
1357
|
ctx.emitActivityState("idle", "generation_cancelled", {
|
|
1893
1358
|
anchor: "global",
|
|
1894
1359
|
requestId: reqId,
|