@vellumai/assistant 0.6.4 → 0.6.6
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/.prettierignore +5 -0
- package/AGENTS.md +9 -1
- package/ARCHITECTURE.md +43 -49
- package/Dockerfile +17 -3
- package/README.md +3 -4
- package/__tests__/permissions/gateway-threshold-reader.test.ts +283 -0
- package/bun.lock +8 -3
- package/docs/architecture/integrations.md +33 -59
- package/docs/architecture/memory.md +25 -30
- package/docs/architecture/security.md +19 -18
- package/docs/browser-use-architecture-phase2.md +63 -20
- package/docs/error-handling.md +111 -0
- package/docs/plugins.md +761 -0
- package/docs/skills.md +10 -10
- package/docs/stt-provider-onboarding.md +2 -1
- package/examples/plugins/echo/README.md +132 -0
- package/examples/plugins/echo/package.json +17 -0
- package/examples/plugins/echo/register.ts +187 -0
- package/knip.json +9 -2
- package/node_modules/@vellumai/ces-contracts/package.json +2 -1
- package/node_modules/@vellumai/ces-contracts/src/__tests__/trust-rules.test.ts +471 -0
- package/node_modules/@vellumai/ces-contracts/src/trust-rules.ts +398 -4
- package/node_modules/@vellumai/credential-storage/bun.lock +2 -2
- package/node_modules/@vellumai/credential-storage/package.json +2 -2
- package/node_modules/@vellumai/credential-storage/src/oauth-runtime.ts +20 -2
- package/node_modules/@vellumai/egress-proxy/bun.lock +2 -2
- package/node_modules/@vellumai/egress-proxy/package.json +2 -2
- package/node_modules/@vellumai/egress-proxy/src/types.ts +19 -0
- package/openapi.yaml +334 -78
- package/package.json +6 -3
- package/scripts/generate-openapi.ts +50 -11
- package/src/__tests__/agent-loop-callsite-precedence.test.ts +318 -0
- package/src/__tests__/agent-loop-sentry-hygiene.test.ts +137 -0
- package/src/__tests__/agent-loop.test.ts +112 -1
- package/src/__tests__/anthropic-error-formatting.test.ts +98 -0
- package/src/__tests__/anthropic-provider.test.ts +171 -2
- package/src/__tests__/app-compiler.test.ts +57 -0
- package/src/__tests__/approval-cascade.test.ts +36 -10
- package/src/__tests__/approval-routes-http.test.ts +134 -10
- package/src/__tests__/assistant-attachments.test.ts +44 -0
- package/src/__tests__/assistant-feature-flags-integration.test.ts +29 -0
- package/src/__tests__/auto-analysis-end-to-end.test.ts +1 -0
- package/src/__tests__/avatar-generator.test.ts +4 -2
- package/src/__tests__/browser-fill-credential.test.ts +1 -1
- package/src/__tests__/browser-identifier-parity-guard.test.ts +53 -0
- package/src/__tests__/browser-skill-baseline-tool-payload.test.ts +23 -33
- package/src/__tests__/browser-skill-endstate.test.ts +51 -182
- package/src/__tests__/btw-routes.test.ts +47 -1
- package/src/__tests__/bundled-asset.test.ts +6 -6
- package/src/__tests__/call-controller.test.ts +1 -2
- package/src/__tests__/call-site-routing-provider.test.ts +214 -0
- package/src/__tests__/catalog-cache.test.ts +96 -4
- package/src/__tests__/channel-approval-routes.test.ts +4 -4
- package/src/__tests__/channel-reply-delivery.test.ts +300 -2
- package/src/__tests__/checker.test.ts +870 -655
- package/src/__tests__/circuit-breaker-pipeline.test.ts +406 -0
- package/src/__tests__/cli-command-risk-guard.test.ts +30 -33
- package/src/__tests__/compaction-events.test.ts +501 -0
- package/src/__tests__/compaction-pipeline.test.ts +210 -0
- package/src/__tests__/compaction-strip-metadata-clear.test.ts +181 -0
- package/src/__tests__/compaction-timeout-recovery.test.ts +262 -0
- package/src/__tests__/compaction.benchmark.test.ts +1 -1
- package/src/__tests__/config-analysis.test.ts +11 -28
- package/src/__tests__/config-loader-backfill.test.ts +174 -0
- package/src/__tests__/config-loader-corrupt.test.ts +183 -0
- package/src/__tests__/config-loader-quarantine-bulletin.test.ts +202 -0
- package/src/__tests__/config-model-image-provider.test.ts +110 -0
- package/src/__tests__/config-schema-cmd.test.ts +11 -5
- package/src/__tests__/config-schema.test.ts +440 -114
- package/src/__tests__/config-watcher-cleanup-throttle.test.ts +0 -4
- package/src/__tests__/config-watcher.test.ts +2 -2
- package/src/__tests__/contact-store-user-file.test.ts +72 -73
- package/src/__tests__/contacts-tools.test.ts +26 -0
- package/src/__tests__/contacts-write.test.ts +4 -4
- package/src/__tests__/context-overflow-policy.test.ts +7 -7
- package/src/__tests__/context-token-estimator.test.ts +191 -1
- package/src/__tests__/context-window-manager.test.ts +883 -4
- package/src/__tests__/conversation-abort-tool-results.test.ts +32 -15
- package/src/__tests__/conversation-agent-loop-overflow.test.ts +86 -46
- package/src/__tests__/conversation-agent-loop.test.ts +435 -216
- package/src/__tests__/conversation-attachments.test.ts +1 -1
- package/src/__tests__/conversation-confirmation-signals.test.ts +36 -10
- package/src/__tests__/conversation-error.test.ts +37 -6
- package/src/__tests__/conversation-history-web-search.test.ts +7 -0
- package/src/__tests__/conversation-init.benchmark.test.ts +34 -12
- package/src/__tests__/conversation-lifecycle.test.ts +336 -0
- package/src/__tests__/conversation-load-history-repair.test.ts +27 -10
- package/src/__tests__/conversation-pairing.test.ts +174 -10
- package/src/__tests__/conversation-pre-run-repair.test.ts +32 -15
- package/src/__tests__/conversation-process-callsite.test.ts +309 -0
- package/src/__tests__/conversation-provider-retry-repair.test.ts +44 -21
- package/src/__tests__/conversation-queue.test.ts +68 -38
- package/src/__tests__/conversation-routes-disk-view.test.ts +36 -7
- package/src/__tests__/conversation-routes-slash-commands.test.ts +31 -3
- package/src/__tests__/conversation-runtime-assembly.test.ts +2877 -152
- package/src/__tests__/conversation-runtime-workspace.test.ts +35 -50
- package/src/__tests__/conversation-seed-composer.test.ts +2 -2
- package/src/__tests__/conversation-skill-tools.test.ts +12 -146
- package/src/__tests__/conversation-slash-queue.test.ts +39 -19
- package/src/__tests__/conversation-slash-unknown.test.ts +53 -16
- package/src/__tests__/conversation-speed-override.test.ts +36 -12
- package/src/__tests__/conversation-surfaces-standalone-payloads.test.ts +1035 -0
- package/src/__tests__/conversation-surfaces-standalone.test.ts +630 -0
- package/src/__tests__/conversation-title-service.test.ts +118 -2
- package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +41 -2
- package/src/__tests__/conversation-tool-setup-batch-authorized.test.ts +1 -1
- package/src/__tests__/conversation-unread-route.test.ts +2 -2
- package/src/__tests__/conversation-usage.test.ts +4 -2
- package/src/__tests__/conversation-workspace-cache-state.test.ts +33 -9
- package/src/__tests__/conversation-workspace-injection.test.ts +46 -15
- package/src/__tests__/conversation-workspace-tool-tracking.test.ts +46 -15
- package/src/__tests__/credential-broker-browser-fill.test.ts +110 -0
- package/src/__tests__/credential-health-service.test.ts +78 -9
- package/src/__tests__/credential-security-invariants.test.ts +5 -2
- package/src/__tests__/credential-storage-oauth-compat.test.ts +18 -0
- package/src/__tests__/credential-storage-static-compat.test.ts +28 -0
- package/src/__tests__/credential-vault-unit.test.ts +135 -19
- package/src/__tests__/credentials-cli.test.ts +1 -9
- package/src/__tests__/cross-provider-web-search.test.ts +84 -0
- package/src/__tests__/daemon-server-persist-and-process-callsite.test.ts +92 -0
- package/src/__tests__/db-schedule-syntax-migration.test.ts +1 -0
- package/src/__tests__/delete-propagation.test.ts +437 -0
- package/src/__tests__/dm-backfill.test.ts +417 -0
- package/src/__tests__/dm-persistence.test.ts +227 -0
- package/src/__tests__/edit-propagation.test.ts +280 -0
- package/src/__tests__/empty-response-pipeline.test.ts +305 -0
- package/src/__tests__/ephemeral-permissions.test.ts +93 -3
- package/src/__tests__/estimator-calibration-integration.test.ts +208 -0
- package/src/__tests__/estimator-calibration.test.ts +213 -0
- package/src/__tests__/extension-id-sync-guard.test.ts +29 -10
- package/src/__tests__/file-write-tool.test.ts +151 -1
- package/src/__tests__/filing-service.test.ts +255 -0
- package/src/__tests__/first-greeting.test.ts +247 -5
- package/src/__tests__/gemini-provider.test.ts +0 -3
- package/src/__tests__/guardian-grant-minting.test.ts +8 -0
- package/src/__tests__/headless-browser-interactions.test.ts +1 -1
- package/src/__tests__/headless-browser-mode.test.ts +57 -0
- package/src/__tests__/heartbeat-service.test.ts +96 -15
- package/src/__tests__/history-repair-pipeline.test.ts +399 -0
- package/src/__tests__/host-browser-e2e-cloud.test.ts +307 -0
- package/src/__tests__/host-browser-e2e-self-hosted.test.ts +3 -3
- package/src/__tests__/host-proxy-interface.test.ts +36 -2
- package/src/__tests__/host-shell-tool.test.ts +124 -18
- package/src/__tests__/http-user-message-parity.test.ts +29 -1
- package/src/__tests__/image-credentials.test.ts +137 -0
- package/src/__tests__/image-service-dispatcher.test.ts +186 -0
- package/src/__tests__/inbound-slack-persistence.test.ts +340 -0
- package/src/__tests__/injector-chain.test.ts +526 -0
- package/src/__tests__/intent-routing.test.ts +1 -66
- package/src/__tests__/llm-call-pipeline.test.ts +285 -0
- package/src/__tests__/llm-catalog-parity.test.ts +174 -0
- package/src/__tests__/llm-context-normalization.test.ts +121 -0
- package/src/__tests__/llm-resolver.test.ts +214 -0
- package/src/__tests__/llm-schema.test.ts +223 -0
- package/src/__tests__/managed-proxy-context.test.ts +6 -2
- package/src/__tests__/media-generate-image.test.ts +119 -13
- package/src/__tests__/memory-retrieval-pipeline.test.ts +401 -0
- package/src/__tests__/memory-upsert-concurrency.test.ts +1 -0
- package/src/__tests__/messaging-skill-split.test.ts +3 -34
- package/src/__tests__/migration-import-from-url.test.ts +621 -0
- package/src/__tests__/model-intents.test.ts +11 -83
- package/src/__tests__/notification-broadcaster.test.ts +3 -3
- package/src/__tests__/notification-decision-fallback.test.ts +0 -10
- package/src/__tests__/notification-decision-identity.test.ts +0 -9
- package/src/__tests__/notification-decision-recipient-context.test.ts +0 -9
- package/src/__tests__/notification-decision-strategy.test.ts +0 -11
- package/src/__tests__/notification-schedule-notify-dedup.test.ts +108 -0
- package/src/__tests__/oauth-apps-routes.test.ts +1 -1
- package/src/__tests__/oauth-cli.test.ts +14 -12
- package/src/__tests__/oauth-connect-orchestrator.test.ts +4 -13
- package/src/__tests__/oauth-provider-serializer.test.ts +6 -4
- package/src/__tests__/oauth-provider-visibility.test.ts +3 -5
- package/src/__tests__/oauth-providers-routes.test.ts +3 -2
- package/src/__tests__/oauth-store.test.ts +46 -78
- package/src/__tests__/oauth2-gateway-transport.test.ts +8 -3
- package/src/__tests__/oauth2-refresh-retry.test.ts +279 -0
- package/src/__tests__/onboarding-template-contract.test.ts +16 -64
- package/src/__tests__/openai-image-service.test.ts +368 -0
- package/src/__tests__/openai-provider.test.ts +7 -0
- package/src/__tests__/openai-responses-provider.test.ts +396 -0
- package/src/__tests__/openrouter-provider-only.test.ts +135 -0
- package/src/__tests__/outbound-slack-persistence.test.ts +293 -0
- package/src/__tests__/overflow-reduce-pipeline.test.ts +676 -0
- package/src/__tests__/permission-checker-host-gate.test.ts +1 -25
- package/src/__tests__/permission-mode.test.ts +16 -0
- package/src/__tests__/permission-types.test.ts +0 -1
- package/src/__tests__/persist-onboarding-artifacts.test.ts +266 -0
- package/src/__tests__/persistence-pipeline.test.ts +377 -0
- package/src/__tests__/persona-resolver.test.ts +13 -13
- package/src/__tests__/pipeline-runner.test.ts +565 -0
- package/src/__tests__/pkb-autoinject.test.ts +37 -1
- package/src/__tests__/platform-bash-auto-approve.test.ts +1 -1
- package/src/__tests__/platform.test.ts +5 -2
- package/src/__tests__/plugin-bootstrap.test.ts +483 -0
- package/src/__tests__/plugin-registry.test.ts +273 -0
- package/src/__tests__/plugin-route-contribution.test.ts +288 -0
- package/src/__tests__/plugin-skill-contribution.test.ts +367 -0
- package/src/__tests__/plugin-tool-contribution.test.ts +286 -0
- package/src/__tests__/plugin-types.test.ts +320 -0
- package/src/__tests__/pricing.test.ts +93 -14
- package/src/__tests__/profiler-routes.test.ts +1 -1
- package/src/__tests__/provider-commit-message-generator.test.ts +14 -84
- package/src/__tests__/provider-env-vars-scope.test.ts +52 -0
- package/src/__tests__/provider-error-scenarios.test.ts +135 -6
- package/src/__tests__/provider-managed-proxy-integration.test.ts +42 -11
- package/src/__tests__/provider-registry-ollama.test.ts +1 -2
- package/src/__tests__/proxy-approval-callback.test.ts +69 -9
- package/src/__tests__/reaction-persistence.test.ts +561 -0
- package/src/__tests__/regenerate-fire-and-forget-trace.test.ts +1 -0
- package/src/__tests__/registry.test.ts +0 -2
- package/src/__tests__/relay-server.test.ts +1 -1
- package/src/__tests__/require-fresh-approval.test.ts +1 -1
- package/src/__tests__/retry-openrouter-only-normalization.test.ts +136 -0
- package/src/__tests__/retry-thinking-tool-choice.test.ts +226 -0
- package/src/__tests__/risk-classifier-parity.test.ts +230 -0
- package/src/__tests__/sanitize-config-for-transfer.test.ts +78 -1
- package/src/__tests__/schedule-routes.test.ts +131 -1
- package/src/__tests__/scheduler-recurrence.test.ts +14 -70
- package/src/__tests__/scheduler-reuse-conversation.test.ts +10 -50
- package/src/__tests__/secret-detection-handler.test.ts +0 -10
- package/src/__tests__/secret-ingress-http.test.ts +28 -0
- package/src/__tests__/secret-prompter-channel-fallback.test.ts +125 -0
- package/src/__tests__/secret-routes-managed-proxy.test.ts +2 -3
- package/src/__tests__/secret-scanner-executor.test.ts +1 -1
- package/src/__tests__/send-endpoint-busy.test.ts +29 -1
- package/src/__tests__/server-history-render.test.ts +31 -0
- package/src/__tests__/shell-identity.test.ts +0 -134
- package/src/__tests__/shell-parser-property.test.ts +13 -13
- package/src/__tests__/skill-cache-store.test.ts +182 -0
- package/src/__tests__/skills.test.ts +19 -33
- package/src/__tests__/slack-app-setup-skill-regression.test.ts +3 -1
- package/src/__tests__/slack-skill.test.ts +3 -8
- package/src/__tests__/starter-bundle.test.ts +35 -0
- package/src/__tests__/subagent-call-site-routing.test.ts +280 -0
- package/src/__tests__/suggestion-routes.test.ts +259 -3
- package/src/__tests__/system-prompt.test.ts +22 -35
- package/src/__tests__/task-memory-cleanup.test.ts +1 -0
- package/src/__tests__/task-runner.test.ts +3 -1
- package/src/__tests__/task-scheduler.test.ts +3 -15
- package/src/__tests__/tcc-sandbox-deny.test.ts +198 -0
- package/src/__tests__/terminal-tools.test.ts +8 -0
- package/src/__tests__/test-preload.ts +11 -0
- package/src/__tests__/test-support/browser-skill-harness.ts +2 -52
- package/src/__tests__/thread-backfill.test.ts +941 -0
- package/src/__tests__/title-generate-pipeline.test.ts +224 -0
- package/src/__tests__/token-estimate-pipeline.test.ts +431 -0
- package/src/__tests__/tool-error-pipeline.test.ts +244 -0
- package/src/__tests__/tool-execute-pipeline.test.ts +431 -0
- package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +2 -8
- package/src/__tests__/tool-executor-lifecycle-events.test.ts +2 -2
- package/src/__tests__/tool-executor-shell-integration.test.ts +7 -10
- package/src/__tests__/tool-executor.test.ts +201 -94
- package/src/__tests__/tool-result-truncate-pipeline.test.ts +356 -0
- package/src/__tests__/tool-result-truncation.test.ts +0 -110
- package/src/__tests__/trust-store.test.ts +442 -109
- package/src/__tests__/update-bulletin-job.test.ts +389 -0
- package/src/__tests__/usage-cache-backfill-migration.test.ts +3 -1
- package/src/__tests__/user-plugin-loader.test.ts +191 -0
- package/src/__tests__/verification-control-plane-policy.test.ts +1 -22
- package/src/__tests__/voice-session-bridge.test.ts +39 -0
- package/src/__tests__/volume-security-guard.test.ts +3 -2
- package/src/__tests__/web-search-history.test.ts +337 -0
- package/src/__tests__/workspace-migration-039-drop-legacy-llm-keys.test.ts +343 -0
- package/src/__tests__/workspace-migration-043-release-notes-latex-rendering.test.ts +202 -0
- package/src/__tests__/workspace-migration-045-release-notes-meet-avatar.test.ts +210 -0
- package/src/__tests__/workspace-migration-046-seed-conversation-starters-callsite.test.ts +185 -0
- package/src/__tests__/workspace-migration-049-release-notes-default-sonnet.test.ts +100 -0
- package/src/__tests__/workspace-migration-050-seed-main-agent-opus-callsite.test.ts +171 -0
- package/src/__tests__/workspace-migration-051-seed-conversation-summarization-callsite.test.ts +252 -0
- package/src/__tests__/workspace-migration-drop-user-md.test.ts +11 -11
- package/src/__tests__/workspace-migration-remove-hooks.test.ts +99 -0
- package/src/__tests__/workspace-migration-unify-llm-callsite-configs.test.ts +841 -0
- package/src/__tests__/workspace-policy.test.ts +22 -16
- package/src/acp/client-handler.ts +1 -2
- package/src/agent/loop.ts +545 -115
- package/src/approvals/__tests__/guardian-feed-event.test.ts +304 -0
- package/src/approvals/guardian-request-resolvers.ts +80 -0
- package/src/avatar/resvg-lazy.test.ts +136 -0
- package/src/avatar/resvg-lazy.ts +82 -9
- package/src/avatar/traits-png-sync.ts +21 -1
- package/src/backup/__tests__/backup-worker.test.ts +2 -13
- package/src/backup/backup-worker.ts +3 -15
- package/src/browser/__tests__/operations.test.ts +163 -0
- package/src/browser/identifiers.ts +51 -0
- package/src/browser/operations.ts +660 -0
- package/src/browser/types.ts +81 -0
- package/src/bundler/app-compiler.ts +84 -1
- package/src/calls/call-state.ts +2 -2
- package/src/calls/guardian-question-copy.ts +2 -2
- package/src/calls/telephony-stt-routing.ts +1 -1
- package/src/calls/voice-session-bridge.ts +1 -0
- package/src/channels/__tests__/types.test.ts +3 -3
- package/src/channels/types.ts +6 -4
- package/src/cli/AGENTS.md +1 -1
- package/src/cli/__tests__/notifications.test.ts +87 -211
- package/src/cli/commands/__tests__/attachment.test.ts +438 -0
- package/src/cli/commands/__tests__/backup.test.ts +1 -1
- package/src/cli/commands/__tests__/browser.test.ts +554 -0
- package/src/cli/commands/__tests__/cache.test.ts +623 -0
- package/src/cli/commands/__tests__/email-list.test.ts +6 -0
- package/src/cli/commands/__tests__/email-send.test.ts +93 -1
- package/src/cli/commands/__tests__/image-generation.test.ts +886 -0
- package/src/cli/commands/__tests__/inference-send.test.ts +463 -0
- package/src/cli/commands/__tests__/stt-transcribe.test.ts +454 -0
- package/src/cli/commands/__tests__/task.test.ts +913 -0
- package/src/cli/commands/__tests__/tts-synthesize.test.ts +606 -0
- package/src/cli/commands/__tests__/ui-confirm.test.ts +650 -0
- package/src/cli/commands/__tests__/ui.test.ts +1215 -0
- package/src/cli/commands/__tests__/watchers.test.ts +716 -0
- package/src/cli/commands/attachment.ts +182 -0
- package/src/cli/commands/backup.ts +2 -2
- package/src/cli/commands/browser.ts +350 -0
- package/src/cli/commands/cache.ts +341 -0
- package/src/cli/commands/clients.ts +138 -0
- package/src/cli/commands/completions.ts +2 -12
- package/src/cli/commands/config.ts +6 -6
- package/src/cli/commands/conversations-import.ts +347 -0
- package/src/cli/commands/conversations.ts +69 -8
- package/src/cli/commands/email.ts +234 -194
- package/src/cli/commands/image-generation.ts +299 -0
- package/src/cli/commands/inference.ts +200 -0
- package/src/cli/commands/memory.ts +127 -17
- package/src/cli/commands/notifications.ts +68 -103
- package/src/cli/commands/oauth/__tests__/providers-register.test.ts +1 -1
- package/src/cli/commands/oauth/__tests__/providers-update.test.ts +1 -1
- package/src/cli/commands/oauth/connect.ts +2 -2
- package/src/cli/commands/oauth/providers.ts +176 -8
- package/src/cli/commands/oauth/status.ts +46 -36
- package/src/cli/commands/platform/__tests__/callback-routes-list.test.ts +0 -1
- package/src/cli/commands/platform/__tests__/connect.test.ts +0 -1
- package/src/cli/commands/platform/__tests__/disconnect.test.ts +0 -1
- package/src/cli/commands/platform/__tests__/status.test.ts +0 -1
- package/src/cli/commands/skills.ts +3 -4
- package/src/cli/commands/stt.ts +339 -0
- package/src/cli/commands/task.ts +795 -0
- package/src/cli/commands/trust.ts +50 -19
- package/src/cli/commands/tts.ts +273 -0
- package/src/cli/commands/ui.ts +670 -0
- package/src/cli/commands/watchers.ts +509 -0
- package/src/cli/lib/daemon-credential-client.ts +0 -19
- package/src/cli/program.ts +39 -24
- package/src/cli.ts +0 -37
- package/src/config/__tests__/backup-schema.test.ts +7 -2
- package/src/config/bundled-skills/app-builder/SKILL.md +2 -2
- package/src/config/bundled-skills/app-builder/references/WIDGETS.md +10 -10
- package/src/config/bundled-skills/contacts/tools/contact-merge.ts +66 -87
- package/src/config/bundled-skills/contacts/tools/contact-search.ts +28 -51
- package/src/config/bundled-skills/contacts/tools/contact-upsert.ts +22 -40
- package/src/config/bundled-skills/image-studio/SKILL.md +2 -1
- package/src/config/bundled-skills/image-studio/TOOLS.json +2 -1
- package/src/config/bundled-skills/image-studio/tools/media-generate-image.ts +23 -39
- package/src/config/bundled-skills/media-processing/services/reduce.ts +1 -1
- package/src/config/bundled-skills/messaging/SKILL.md +5 -5
- package/src/config/bundled-skills/messaging/TOOLS.json +4 -0
- package/src/config/bundled-skills/messaging/tools/__tests__/messaging-feed-events.test.ts +207 -0
- package/src/config/bundled-skills/messaging/tools/messaging-archive-by-sender.ts +20 -1
- package/src/config/bundled-skills/messaging/tools/messaging-read.ts +15 -1
- package/src/config/bundled-skills/messaging/tools/messaging-search.ts +21 -1
- package/src/config/bundled-skills/messaging/tools/messaging-send.ts +69 -12
- package/src/config/bundled-skills/phone-calls/references/CONFIG.md +9 -8
- package/src/config/bundled-skills/schedule/SKILL.md +8 -3
- package/src/config/bundled-skills/schedule/TOOLS.json +15 -7
- package/src/config/bundled-skills/schedule/references/SCRIPT_MODE_PATTERNS.md +59 -0
- package/src/config/bundled-skills/settings/TOOLS.json +3 -3
- package/src/config/bundled-tool-registry.ts +0 -190
- package/src/config/env.ts +7 -2
- package/src/config/feature-flag-registry.json +42 -10
- package/src/config/llm-resolver.ts +128 -0
- package/src/config/loader.ts +194 -10
- package/src/config/raw-config-utils.ts +30 -2
- package/src/config/sanitize-for-transfer.ts +35 -0
- package/src/config/schema.ts +49 -41
- package/src/config/schemas/analysis.ts +3 -22
- package/src/config/schemas/backup.ts +1 -1
- package/src/config/schemas/calls.ts +0 -4
- package/src/config/schemas/conversations.ts +16 -0
- package/src/config/schemas/filing.ts +2 -7
- package/src/config/schemas/heartbeat.ts +0 -5
- package/src/config/schemas/inference.ts +3 -23
- package/src/config/schemas/llm.ts +317 -0
- package/src/config/schemas/memory-processing.ts +1 -9
- package/src/config/schemas/notifications.ts +4 -11
- package/src/config/schemas/platform.ts +3 -9
- package/src/config/schemas/security.ts +33 -0
- package/src/config/schemas/services.ts +9 -4
- package/src/config/schemas/stt.ts +1 -0
- package/src/config/schemas/tts.ts +64 -0
- package/src/config/schemas/updates.ts +1 -1
- package/src/config/schemas/workspace-git.ts +3 -40
- package/src/config/skill-state.ts +6 -2
- package/src/config/skills.ts +96 -7
- package/src/context/__tests__/compact-prompt.test.ts +63 -0
- package/src/context/__tests__/microcompact.test.ts +805 -0
- package/src/context/estimator-calibration.ts +136 -0
- package/src/context/microcompact.ts +443 -0
- package/src/context/prompts/compact.md +26 -0
- package/src/context/token-estimator.ts +61 -3
- package/src/context/tool-result-truncation.ts +3 -63
- package/src/context/window-manager.ts +417 -39
- package/src/credential-execution/approval-bridge.ts +0 -1
- package/src/credential-execution/executable-discovery.ts +19 -8
- package/src/credential-execution/process-manager.test.ts +109 -0
- package/src/credential-execution/process-manager.ts +65 -2
- package/src/credential-health/credential-health-service.ts +19 -6
- package/src/daemon/__tests__/conversation-feed-event.test.ts +317 -0
- package/src/daemon/__tests__/conversation-lifecycle-auto-analyze.test.ts +4 -12
- package/src/daemon/__tests__/conversation-tool-setup.test.ts +14 -15
- package/src/daemon/approval-generators.ts +29 -4
- package/src/daemon/assistant-attachments.ts +24 -13
- package/src/daemon/classifier.ts +2 -2
- package/src/daemon/config-watcher.ts +0 -3
- package/src/daemon/context-overflow-policy.ts +4 -13
- package/src/daemon/context-overflow-reducer.ts +4 -1
- package/src/daemon/conversation-agent-loop-handlers.ts +162 -34
- package/src/daemon/conversation-agent-loop.ts +1282 -599
- package/src/daemon/conversation-attachments.ts +2 -6
- package/src/daemon/conversation-error.ts +36 -1
- package/src/daemon/conversation-history.ts +10 -19
- package/src/daemon/conversation-lifecycle.ts +59 -17
- package/src/daemon/conversation-messaging.ts +73 -4
- package/src/daemon/conversation-notifiers.ts +2 -110
- package/src/daemon/conversation-process.ts +24 -11
- package/src/daemon/conversation-queue-manager.ts +3 -0
- package/src/daemon/conversation-runtime-assembly.ts +1063 -211
- package/src/daemon/conversation-slash.ts +2 -2
- package/src/daemon/conversation-surfaces.ts +389 -1
- package/src/daemon/conversation-tool-setup.ts +51 -9
- package/src/daemon/conversation-usage.ts +1 -1
- package/src/daemon/conversation.ts +197 -64
- package/src/daemon/external-plugins-bootstrap.ts +478 -0
- package/src/daemon/external-skills-bootstrap.ts +41 -0
- package/src/daemon/first-greeting.ts +191 -14
- package/src/daemon/guardian-action-generators.ts +34 -14
- package/src/daemon/handlers/config-model.test.ts +86 -0
- package/src/daemon/handlers/config-model.ts +65 -12
- package/src/daemon/handlers/conversations.ts +9 -2
- package/src/daemon/handlers/shared.ts +39 -11
- package/src/daemon/handlers/skills.ts +7 -3
- package/src/daemon/handlers/slack-channel-oauth-install.ts +197 -0
- package/src/daemon/lifecycle.ts +109 -82
- package/src/daemon/message-types/computer-use.ts +2 -34
- package/src/daemon/message-types/conversations.ts +63 -0
- package/src/daemon/message-types/messages.ts +21 -1
- package/src/daemon/message-types/trust.ts +0 -2
- package/src/daemon/parse-actual-tokens-from-error.test.ts +57 -1
- package/src/daemon/parse-actual-tokens-from-error.ts +66 -0
- package/src/daemon/pkb-context-tracker.test.ts +169 -0
- package/src/daemon/pkb-context-tracker.ts +125 -0
- package/src/daemon/pkb-reminder-builder.test.ts +70 -0
- package/src/daemon/pkb-reminder-builder.ts +31 -0
- package/src/daemon/providers-setup.ts +6 -0
- package/src/daemon/server.ts +122 -12
- package/src/daemon/shutdown-handlers.ts +2 -12
- package/src/daemon/tool-side-effects.ts +14 -65
- package/src/daemon/web-search-history.ts +126 -0
- package/src/events/domain-events.ts +0 -1
- package/src/filing/filing-service.ts +9 -10
- package/src/heartbeat/__tests__/heartbeat-feed-event.test.ts +160 -0
- package/src/heartbeat/heartbeat-service.ts +99 -28
- package/src/home/__tests__/feed-population-integration.test.ts +312 -0
- package/src/home/__tests__/feed-scheduler.test.ts +39 -11
- package/src/home/__tests__/rollup-producer.test.ts +44 -0
- package/src/home/assistant-feed-authoring.ts +4 -0
- package/src/home/emit-feed-event.ts +11 -0
- package/src/home/feed-scheduler.ts +20 -4
- package/src/home/feed-types.ts +97 -4
- package/src/home/relationship-state-writer.ts +2 -2
- package/src/home/rewrite-command-preview.ts +66 -0
- package/src/home/rollup-producer.ts +34 -5
- package/src/home/suggested-prompts.ts +101 -0
- package/src/ipc/__tests__/attachment-ipc.test.ts +213 -0
- package/src/ipc/__tests__/browser-ipc.test.ts +339 -0
- package/src/ipc/__tests__/cache-ipc.test.ts +266 -0
- package/src/ipc/__tests__/socket-path.test.ts +34 -0
- package/src/ipc/__tests__/task-ipc.test.ts +577 -0
- package/src/ipc/__tests__/ui-request-route.test.ts +495 -0
- package/src/ipc/__tests__/watcher-ipc.test.ts +295 -0
- package/src/ipc/cli-client.ts +2 -1
- package/src/ipc/cli-server.ts +26 -8
- package/src/ipc/gateway-client.ts +6 -3
- package/src/ipc/routes/attachment.ts +114 -0
- package/src/ipc/routes/browser-context.ts +63 -0
- package/src/ipc/routes/browser.ts +97 -0
- package/src/ipc/routes/cache.ts +96 -0
- package/src/ipc/routes/get-contact.ts +16 -0
- package/src/ipc/routes/index.ts +31 -1
- package/src/ipc/routes/list-clients.ts +31 -0
- package/src/ipc/routes/merge-contacts.ts +17 -0
- package/src/ipc/routes/notification.ts +133 -0
- package/src/ipc/routes/rename-conversation.ts +59 -0
- package/src/ipc/routes/search-contacts.ts +19 -0
- package/src/ipc/routes/task-queue.ts +226 -0
- package/src/ipc/routes/task.ts +173 -0
- package/src/ipc/routes/ui-request.ts +50 -0
- package/src/ipc/routes/upsert-contact.ts +25 -0
- package/src/ipc/routes/watcher.ts +203 -0
- package/src/ipc/socket-path.ts +76 -0
- package/src/media/app-icon-generator.ts +23 -46
- package/src/media/avatar-router.ts +26 -41
- package/src/media/gemini-image-service.ts +8 -41
- package/src/media/image-credentials.ts +73 -0
- package/src/media/image-service.ts +85 -0
- package/src/media/openai-image-service.ts +131 -0
- package/src/media/types.ts +46 -0
- package/src/memory/__tests__/conversation-analyze-job.test.ts +9 -8
- package/src/memory/__tests__/conversation-group-migration.test.ts +99 -0
- package/src/memory/admin.ts +18 -0
- package/src/memory/conversation-analyze-job.ts +14 -13
- package/src/memory/conversation-attention-store.ts +13 -6
- package/src/memory/conversation-crud.ts +133 -3
- package/src/memory/conversation-group-migration.ts +38 -6
- package/src/memory/conversation-queries.ts +57 -4
- package/src/memory/conversation-title-service.ts +32 -4
- package/src/memory/db-init.ts +10 -0
- package/src/memory/embedding-backend.ts +1 -1
- package/src/memory/embedding-gemini.test.ts +41 -2
- package/src/memory/embedding-gemini.ts +6 -1
- package/src/memory/graph/bootstrap.test.ts +282 -0
- package/src/memory/graph/bootstrap.ts +8 -5
- package/src/memory/graph/compaction.ts +299 -0
- package/src/memory/graph/consolidation.ts +4 -4
- package/src/memory/graph/conversation-graph-memory.ts +89 -29
- package/src/memory/graph/extraction.test.ts +272 -2
- package/src/memory/graph/extraction.ts +183 -53
- package/src/memory/graph/graph-search.test.ts +93 -0
- package/src/memory/graph/graph-search.ts +4 -1
- package/src/memory/graph/inspect.ts +2 -2
- package/src/memory/graph/narrative.ts +2 -2
- package/src/memory/graph/pattern-scan.ts +2 -2
- package/src/memory/graph/retriever.test.ts +459 -0
- package/src/memory/graph/retriever.ts +237 -48
- package/src/memory/graph/store.ts +41 -0
- package/src/memory/graph/tool-handlers.ts +27 -0
- package/src/memory/graph/tools.ts +6 -1
- package/src/memory/indexer.ts +5 -5
- package/src/memory/job-handlers/conversation-starters.ts +23 -20
- package/src/memory/job-handlers/summarization.ts +2 -2
- package/src/memory/job-utils.ts +7 -1
- package/src/memory/jobs/embed-pkb-file.test.ts +168 -0
- package/src/memory/jobs/embed-pkb-file.ts +54 -0
- package/src/memory/jobs-store.ts +44 -3
- package/src/memory/jobs-worker.ts +4 -0
- package/src/memory/migrations/041-approval-prompt-ts-tracker.ts +26 -0
- package/src/memory/migrations/140-backfill-usage-cache-accounting.ts +1 -1
- package/src/memory/migrations/149-oauth-tables.ts +1 -0
- package/src/memory/migrations/220-normalize-user-file-by-principal.ts +2 -2
- package/src/memory/migrations/222-strip-placeholder-sentinels-from-messages.ts +82 -0
- package/src/memory/migrations/223-schedule-script-column.ts +11 -0
- package/src/memory/migrations/224-oauth-providers-managed-service-is-paid.ts +24 -0
- package/src/memory/migrations/225-oauth-providers-available-scopes.ts +13 -0
- package/src/memory/migrations/index.ts +5 -0
- package/src/memory/pkb/pkb-index.test.ts +369 -0
- package/src/memory/pkb/pkb-index.ts +255 -0
- package/src/memory/pkb/pkb-reconcile.test.ts +252 -0
- package/src/memory/pkb/pkb-reconcile.ts +148 -0
- package/src/memory/pkb/pkb-search.test.ts +499 -0
- package/src/memory/pkb/pkb-search.ts +159 -0
- package/src/memory/pkb/types.ts +53 -0
- package/src/memory/qdrant-client.test.ts +60 -0
- package/src/memory/qdrant-client.ts +147 -1
- package/src/memory/schema/infrastructure.ts +1 -0
- package/src/memory/schema/oauth.ts +4 -1
- package/src/memory/slack-thread-store.ts +37 -0
- package/src/messaging/providers/gmail/adapter.ts +6 -16
- package/src/messaging/providers/gmail/client.ts +22 -0
- package/src/messaging/providers/gmail/types.ts +7 -0
- package/src/messaging/providers/slack/adapter.ts +14 -2
- package/src/messaging/providers/slack/backfill.test.ts +257 -0
- package/src/messaging/providers/slack/backfill.ts +101 -0
- package/src/messaging/providers/slack/message-metadata.test.ts +316 -0
- package/src/messaging/providers/slack/message-metadata.ts +123 -0
- package/src/messaging/providers/slack/render-transcript.test.ts +1421 -0
- package/src/messaging/providers/slack/render-transcript.ts +501 -0
- package/src/messaging/style-analyzer.ts +5 -2
- package/src/notifications/README.md +9 -5
- package/src/notifications/conversation-pairing.ts +78 -19
- package/src/notifications/copy-composer.ts +0 -5
- package/src/notifications/decision-engine.ts +3 -9
- package/src/notifications/emit-signal.ts +1 -1
- package/src/notifications/preference-extractor.ts +2 -6
- package/src/notifications/signal.ts +1 -2
- package/src/oauth/AGENTS.md +1 -1
- package/src/oauth/__tests__/identity-verifier.test.ts +2 -1
- package/src/oauth/connect-orchestrator.ts +8 -34
- package/src/oauth/connect-types.ts +6 -10
- package/src/oauth/manual-token-connection.ts +23 -0
- package/src/oauth/oauth-store.ts +31 -14
- package/src/oauth/platform-connection.test.ts +47 -0
- package/src/oauth/platform-connection.ts +15 -5
- package/src/oauth/provider-serializer.ts +6 -1
- package/src/oauth/seed-providers.ts +56 -106
- package/src/outbound-proxy/http-forwarder.ts +9 -0
- package/src/permissions/approval-policy.test.ts +1223 -0
- package/src/permissions/approval-policy.ts +309 -0
- package/src/permissions/arg-parser.test.ts +161 -0
- package/src/permissions/arg-parser.ts +141 -0
- package/src/permissions/bash-risk-classifier.test.ts +1620 -0
- package/src/permissions/bash-risk-classifier.ts +950 -0
- package/src/permissions/checker.ts +348 -711
- package/src/permissions/command-registry.test.ts +774 -0
- package/src/permissions/command-registry.ts +1005 -0
- package/src/permissions/defaults.ts +28 -79
- package/src/permissions/file-risk-classifier.test.ts +535 -0
- package/src/permissions/file-risk-classifier.ts +274 -0
- package/src/permissions/gateway-threshold-reader.ts +196 -0
- package/src/permissions/prompter.ts +4 -0
- package/src/permissions/risk-types.ts +262 -0
- package/src/permissions/schedule-risk-classifier.test.ts +129 -0
- package/src/permissions/schedule-risk-classifier.ts +85 -0
- package/src/permissions/secret-prompter.ts +53 -2
- package/src/permissions/shell-identity.ts +2 -42
- package/src/permissions/skill-risk-classifier.test.ts +311 -0
- package/src/permissions/skill-risk-classifier.ts +214 -0
- package/src/permissions/trust-client.ts +52 -25
- package/src/permissions/trust-store-interface.ts +1 -6
- package/src/permissions/trust-store.ts +161 -62
- package/src/permissions/types.ts +25 -14
- package/src/permissions/web-risk-classifier.test.ts +170 -0
- package/src/permissions/web-risk-classifier.ts +89 -0
- package/src/permissions/workspace-policy.ts +9 -19
- package/src/platform/client.ts +19 -1
- package/src/plugins/defaults/circuit-breaker.ts +146 -0
- package/src/plugins/defaults/compaction.ts +145 -0
- package/src/plugins/defaults/empty-response.ts +126 -0
- package/src/plugins/defaults/history-repair.ts +85 -0
- package/src/plugins/defaults/index.ts +116 -0
- package/src/plugins/defaults/injectors.ts +491 -0
- package/src/plugins/defaults/llm-call.ts +82 -0
- package/src/plugins/defaults/memory-retrieval.ts +226 -0
- package/src/plugins/defaults/overflow-reduce.ts +181 -0
- package/src/plugins/defaults/persistence.ts +129 -0
- package/src/plugins/defaults/title-generate.ts +95 -0
- package/src/plugins/defaults/token-estimate.ts +104 -0
- package/src/plugins/defaults/tool-error.ts +126 -0
- package/src/plugins/defaults/tool-execute.ts +89 -0
- package/src/plugins/defaults/tool-result-truncate.ts +88 -0
- package/src/plugins/pipeline.ts +316 -0
- package/src/plugins/plugin-skill-contributions.ts +292 -0
- package/src/plugins/registry.ts +241 -0
- package/src/plugins/types.ts +1134 -0
- package/src/plugins/user-loader.ts +177 -0
- package/src/prompts/persona-resolver.ts +3 -3
- package/src/prompts/system-prompt.ts +19 -20
- package/src/prompts/templates/BOOTSTRAP.md +27 -77
- package/src/prompts/templates/SOUL.md +2 -2
- package/src/prompts/update-bulletin-job.ts +190 -0
- package/src/providers/__tests__/context-overflow-error.test.ts +328 -0
- package/src/providers/__tests__/provider-env-vars.test.ts +102 -0
- package/src/providers/__tests__/retry-callsite.test.ts +424 -0
- package/src/providers/anthropic/client.ts +183 -14
- package/src/providers/call-site-routing.ts +71 -0
- package/src/providers/gemini/client.ts +65 -2
- package/src/providers/managed-proxy/constants.ts +2 -1
- package/src/providers/model-catalog.ts +524 -33
- package/src/providers/model-intents.ts +4 -4
- package/src/providers/openai/chat-completions-provider.ts +57 -1
- package/src/providers/openai/responses-provider.ts +86 -9
- package/src/providers/openrouter/client.ts +80 -9
- package/src/providers/provider-env-vars.ts +56 -0
- package/src/providers/provider-send-message.ts +22 -5
- package/src/providers/ratelimit.ts +4 -0
- package/src/providers/registry.ts +19 -8
- package/src/providers/retry.ts +174 -39
- package/src/providers/speech-to-text/__tests__/resolve.test.ts +55 -0
- package/src/providers/speech-to-text/deepgram-realtime.test.ts +61 -0
- package/src/providers/speech-to-text/deepgram-realtime.ts +57 -0
- package/src/providers/speech-to-text/google-gemini-live-stream.ts +4 -4
- package/src/providers/speech-to-text/provider-catalog.ts +17 -0
- package/src/providers/speech-to-text/resolve.ts +7 -0
- package/src/providers/speech-to-text/xai-realtime.test.ts +646 -0
- package/src/providers/speech-to-text/xai-realtime.ts +821 -0
- package/src/providers/speech-to-text/xai.test.ts +155 -0
- package/src/providers/speech-to-text/xai.ts +97 -0
- package/src/providers/types.ts +93 -3
- package/src/runtime/AGENTS.md +27 -18
- package/src/runtime/__tests__/agent-wake.test.ts +43 -2
- package/src/runtime/__tests__/browser-extension-pair-routes.test.ts +3 -3
- package/src/runtime/__tests__/client-registry.test.ts +293 -0
- package/src/runtime/__tests__/interactive-ui.test.ts +673 -0
- package/src/runtime/agent-wake.ts +63 -22
- package/src/runtime/auth/route-policy.ts +4 -0
- package/src/runtime/btw-sidechain.ts +13 -3
- package/src/runtime/channel-reply-delivery.ts +106 -2
- package/src/runtime/client-registry.ts +261 -0
- package/src/runtime/decision-token.ts +116 -0
- package/src/runtime/gateway-client.ts +2 -2
- package/src/runtime/http-router.ts +32 -0
- package/src/runtime/http-server.ts +129 -9
- package/src/runtime/http-types.ts +23 -3
- package/src/runtime/interactive-ui.ts +362 -0
- package/src/runtime/invite-instruction-generator.ts +2 -2
- package/src/runtime/migrations/__tests__/gcs-signed-url.test.ts +176 -0
- package/src/runtime/migrations/__tests__/vbundle-metadata-merge-integration.test.ts +390 -0
- package/src/runtime/migrations/__tests__/vbundle-metadata-merge.test.ts +221 -0
- package/src/runtime/migrations/__tests__/vbundle-streaming-importer.test.ts +1540 -0
- package/src/runtime/migrations/__tests__/vbundle-streaming-validator.test.ts +453 -0
- package/src/runtime/migrations/__tests__/vbundle-tar-stream.test.ts +222 -0
- package/src/runtime/migrations/gcs-signed-url.ts +162 -0
- package/src/runtime/migrations/vbundle-builder.ts +1 -22
- package/src/runtime/migrations/vbundle-importer.ts +154 -9
- package/src/runtime/migrations/vbundle-metadata-merge.ts +124 -0
- package/src/runtime/migrations/vbundle-streaming-importer.ts +2522 -0
- package/src/runtime/migrations/vbundle-streaming-validator.ts +244 -0
- package/src/runtime/migrations/vbundle-tar-stream.ts +217 -0
- package/src/runtime/migrations/vbundle-validator.ts +15 -6
- package/src/runtime/routes/__tests__/home-feed-routes.test.ts +111 -0
- package/src/runtime/routes/__tests__/migration-import-credential-filter.test.ts +114 -75
- package/src/runtime/routes/__tests__/migration-vellum-metadata-reconcile.test.ts +246 -0
- package/src/runtime/routes/approval-prompt-ts-tracker.ts +78 -0
- package/src/runtime/routes/approval-routes.ts +29 -17
- package/src/runtime/routes/approval-strategies/guardian-callback-strategy.ts +9 -0
- package/src/runtime/routes/avatar-routes.ts +20 -4
- package/src/runtime/routes/browser-extension-pair-routes.ts +27 -8
- package/src/runtime/routes/btw-routes.ts +1 -4
- package/src/runtime/routes/conversation-management-routes.ts +20 -2
- package/src/runtime/routes/conversation-routes.ts +351 -138
- package/src/runtime/routes/debug-routes.ts +1 -1
- package/src/runtime/routes/diagnostics-routes.ts +6 -4
- package/src/runtime/routes/events-routes.ts +16 -0
- package/src/runtime/routes/guardian-approval-interception.ts +33 -3
- package/src/runtime/routes/guardian-approval-prompt.ts +13 -3
- package/src/runtime/routes/home-feed-routes.ts +120 -2
- package/src/runtime/routes/inbound-message-handler.ts +987 -2
- package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +113 -2
- package/src/runtime/routes/inbound-stages/background-dispatch.ts +61 -3
- package/src/runtime/routes/inbound-stages/edit-intercept.ts +129 -6
- package/src/runtime/routes/integrations/slack/channel.ts +25 -3
- package/src/runtime/routes/llm-context-normalization.ts +23 -1
- package/src/runtime/routes/memory-item-routes.test.ts +1 -0
- package/src/runtime/routes/migration-routes.ts +720 -127
- package/src/runtime/routes/playground/__tests__/force-compact.test.ts +284 -0
- package/src/runtime/routes/playground/__tests__/guard.test.ts +80 -0
- package/src/runtime/routes/playground/__tests__/inject-failures.test.ts +294 -0
- package/src/runtime/routes/playground/__tests__/reset-circuit.test.ts +271 -0
- package/src/runtime/routes/playground/__tests__/seed-conversation.test.ts +202 -0
- package/src/runtime/routes/playground/__tests__/seeded-conversations.test.ts +309 -0
- package/src/runtime/routes/playground/__tests__/state.test.ts +224 -0
- package/src/runtime/routes/playground/conversation-not-found.ts +29 -0
- package/src/runtime/routes/playground/deps.ts +56 -0
- package/src/runtime/routes/playground/force-compact.ts +73 -0
- package/src/runtime/routes/playground/guard.ts +37 -0
- package/src/runtime/routes/playground/index.ts +28 -0
- package/src/runtime/routes/playground/inject-failures.ts +159 -0
- package/src/runtime/routes/playground/reset-circuit.ts +115 -0
- package/src/runtime/routes/playground/seed-conversation.ts +139 -0
- package/src/runtime/routes/playground/seeded-conversations.ts +78 -0
- package/src/runtime/routes/playground/state.ts +78 -0
- package/src/runtime/routes/schedule-routes.ts +89 -8
- package/src/runtime/routes/settings-routes.ts +4 -2
- package/src/runtime/routes/trust-rules-routes.ts +30 -14
- package/src/runtime/routes/work-items-routes.test.ts +1 -1
- package/src/runtime/routes/work-items-routes.ts +3 -2
- package/src/runtime/services/__tests__/analyze-conversation.test.ts +25 -43
- package/src/runtime/services/analyze-conversation.ts +12 -16
- package/src/runtime/skill-route-registry.ts +97 -15
- package/src/schedule/run-script.ts +68 -0
- package/src/schedule/schedule-store.ts +7 -1
- package/src/schedule/scheduler.ts +56 -8
- package/src/security/__tests__/provider-key-env-fallback.test.ts +119 -0
- package/src/security/__tests__/untrusted-content.test.ts +109 -0
- package/src/security/oauth2.ts +98 -35
- package/src/security/secure-keys.ts +7 -8
- package/src/security/token-manager.ts +27 -13
- package/src/security/untrusted-content.ts +102 -0
- package/src/skills/catalog-cache.ts +35 -9
- package/src/skills/catalog-install.ts +31 -3
- package/src/skills/skill-cache-store.ts +97 -0
- package/src/stt/__tests__/daemon-batch-transcriber.test.ts +76 -0
- package/src/stt/daemon-batch-transcriber.ts +33 -0
- package/src/stt/stt-stream-session.ts +8 -1
- package/src/stt/types.ts +5 -1
- package/src/subagent/manager.ts +41 -13
- package/src/tasks/ephemeral-permissions.ts +9 -4
- package/src/telemetry/usage-telemetry-reporter.ts +27 -5
- package/src/tools/browser/__tests__/browser-status.test.ts +234 -2
- package/src/tools/browser/browser-execution.ts +150 -54
- package/src/tools/browser/cdp-client/__tests__/extension-cdp-client.test.ts +230 -0
- package/src/tools/browser/cdp-client/__tests__/factory.test.ts +146 -3
- package/src/tools/browser/cdp-client/cdp-inspect/discovery.ts +22 -0
- package/src/tools/browser/cdp-client/extension-cdp-client.ts +54 -3
- package/src/tools/browser/cdp-client/factory.ts +15 -4
- package/src/tools/credentials/tool-policy.ts +39 -5
- package/src/tools/credentials/vault.ts +9 -4
- package/src/tools/executor.ts +129 -73
- package/src/tools/filesystem/write.ts +52 -0
- package/src/tools/host-terminal/host-shell.ts +45 -5
- package/src/tools/memory/register.test.ts +185 -0
- package/src/tools/memory/register.ts +3 -1
- package/src/tools/network/script-proxy/session-manager.ts +37 -1
- package/src/tools/network/web-fetch.ts +20 -10
- package/src/tools/network/web-search.ts +19 -4
- package/src/tools/permission-checker.ts +116 -46
- package/src/tools/policy-context.ts +29 -8
- package/src/tools/registry.ts +195 -6
- package/src/tools/schedule/create.ts +23 -8
- package/src/tools/schedule/update.ts +3 -1
- package/src/tools/secret-detection-handler.ts +0 -51
- package/src/tools/side-effects.ts +0 -11
- package/src/tools/skills/execute.ts +2 -2
- package/src/tools/skills/sandbox-runner.ts +5 -2
- package/src/tools/system/avatar-generator.ts +6 -2
- package/src/tools/terminal/backends/native.ts +51 -2
- package/src/tools/terminal/safe-env.ts +3 -2
- package/src/tools/terminal/shell.ts +1 -0
- package/src/tools/tool-manifest.ts +6 -21
- package/src/tools/types.ts +40 -5
- package/src/tools/verification-control-plane-policy.ts +1 -1
- package/src/tts/__tests__/provider-adapters.test.ts +240 -13
- package/src/tts/provider-catalog.ts +18 -0
- package/src/tts/providers/index.ts +2 -0
- package/src/tts/providers/xai-provider.ts +224 -0
- package/src/tts/types.ts +46 -0
- package/src/types/tar-stream.d.ts +66 -0
- package/src/util/json.ts +17 -0
- package/src/util/platform.ts +9 -4
- package/src/util/pricing.ts +41 -8
- package/src/watcher/engine.ts +1 -1
- package/src/watcher/providers/google-calendar.ts +134 -8
- package/src/watcher/providers/outlook-calendar.ts +42 -2
- package/src/workspace/git-service.ts +23 -4
- package/src/workspace/migrations/006-services-config.ts +2 -4
- package/src/workspace/migrations/022-move-hooks-to-workspace.ts +2 -3
- package/src/workspace/migrations/038-unify-llm-callsite-configs.ts +516 -0
- package/src/workspace/migrations/039-drop-legacy-llm-keys.ts +171 -0
- package/src/workspace/migrations/040-seed-latency-callsite-defaults.ts +154 -0
- package/src/workspace/migrations/041-backfill-google-gmail-settings-scope.ts +56 -0
- package/src/workspace/migrations/042-fix-backfill-google-gmail-settings-scope.ts +70 -0
- package/src/workspace/migrations/043-release-notes-latex-rendering.ts +75 -0
- package/src/workspace/migrations/044-bump-stale-provider-stream-timeout.ts +51 -0
- package/src/workspace/migrations/045-release-notes-meet-avatar.ts +130 -0
- package/src/workspace/migrations/046-seed-conversation-starters-callsite.ts +108 -0
- package/src/workspace/migrations/047-remove-watch-callsites.ts +54 -0
- package/src/workspace/migrations/048-remove-workspace-hooks.ts +81 -0
- package/src/workspace/migrations/049-release-notes-default-sonnet.ts +80 -0
- package/src/workspace/migrations/050-seed-main-agent-opus-callsite.ts +86 -0
- package/src/workspace/migrations/051-seed-conversation-summarization-callsite.ts +128 -0
- package/src/workspace/migrations/AGENTS.md +1 -1
- package/src/workspace/migrations/registry.ts +28 -0
- package/src/workspace/provider-commit-message-generator.ts +19 -38
- package/tsconfig.json +1 -1
- package/hook-templates/debug-prompt-logger/hook.json +0 -7
- package/hook-templates/debug-prompt-logger/run.sh +0 -66
- package/src/__tests__/context-overflow-approval.test.ts +0 -156
- package/src/__tests__/gmail-archive-fallback.test.ts +0 -193
- package/src/__tests__/gmail-archive-gate.test.ts +0 -246
- package/src/__tests__/gmail-preferences.test.ts +0 -117
- package/src/__tests__/hooks-blocking.test.ts +0 -178
- package/src/__tests__/hooks-cli.test.ts +0 -182
- package/src/__tests__/hooks-config.test.ts +0 -108
- package/src/__tests__/hooks-discovery.test.ts +0 -211
- package/src/__tests__/hooks-integration.test.ts +0 -196
- package/src/__tests__/hooks-manager.test.ts +0 -226
- package/src/__tests__/hooks-runner.test.ts +0 -175
- package/src/__tests__/hooks-settings.test.ts +0 -160
- package/src/__tests__/hooks-templates.test.ts +0 -169
- package/src/__tests__/hooks-ts-runner.test.ts +0 -170
- package/src/__tests__/hooks-watch.test.ts +0 -112
- package/src/__tests__/notification-schedule-dedup.test.ts +0 -213
- package/src/__tests__/oauth-scope-policy.test.ts +0 -180
- package/src/__tests__/outlook-attachments.test.ts +0 -301
- package/src/__tests__/outlook-automation-tools.test.ts +0 -425
- package/src/__tests__/outlook-categories.test.ts +0 -212
- package/src/__tests__/outlook-compose-tools.test.ts +0 -325
- package/src/__tests__/outlook-declutter-tools.test.ts +0 -585
- package/src/__tests__/outlook-follow-up.test.ts +0 -196
- package/src/__tests__/outlook-trash.test.ts +0 -77
- package/src/__tests__/outlook-unsubscribe.test.ts +0 -279
- package/src/__tests__/send-notification-tool.test.ts +0 -83
- package/src/__tests__/update-bulletin-format.test.ts +0 -181
- package/src/__tests__/update-bulletin-state.test.ts +0 -135
- package/src/__tests__/update-bulletin.test.ts +0 -478
- package/src/__tests__/update-template-contract.test.ts +0 -29
- package/src/cli/commands/doctor.ts +0 -341
- package/src/cli/commands/shotgun.ts +0 -266
- package/src/config/bundled-skills/browser/SKILL.md +0 -88
- package/src/config/bundled-skills/browser/TOOLS.json +0 -516
- package/src/config/bundled-skills/browser/tools/browser-attach.ts +0 -12
- package/src/config/bundled-skills/browser/tools/browser-click.ts +0 -12
- package/src/config/bundled-skills/browser/tools/browser-close.ts +0 -12
- package/src/config/bundled-skills/browser/tools/browser-detach.ts +0 -12
- package/src/config/bundled-skills/browser/tools/browser-extract.ts +0 -12
- package/src/config/bundled-skills/browser/tools/browser-fill-credential.ts +0 -12
- package/src/config/bundled-skills/browser/tools/browser-hover.ts +0 -12
- package/src/config/bundled-skills/browser/tools/browser-navigate.ts +0 -12
- package/src/config/bundled-skills/browser/tools/browser-press-key.ts +0 -12
- package/src/config/bundled-skills/browser/tools/browser-screenshot.ts +0 -12
- package/src/config/bundled-skills/browser/tools/browser-scroll.ts +0 -12
- package/src/config/bundled-skills/browser/tools/browser-select-option.ts +0 -12
- package/src/config/bundled-skills/browser/tools/browser-snapshot.ts +0 -12
- package/src/config/bundled-skills/browser/tools/browser-status.ts +0 -12
- package/src/config/bundled-skills/browser/tools/browser-type.ts +0 -12
- package/src/config/bundled-skills/browser/tools/browser-wait-for-download.ts +0 -49
- package/src/config/bundled-skills/browser/tools/browser-wait-for.ts +0 -12
- package/src/config/bundled-skills/chatgpt-import/SKILL.md +0 -27
- package/src/config/bundled-skills/chatgpt-import/TOOLS.json +0 -27
- package/src/config/bundled-skills/chatgpt-import/tools/chatgpt-import.ts +0 -378
- package/src/config/bundled-skills/conversations/SKILL.md +0 -20
- package/src/config/bundled-skills/conversations/TOOLS.json +0 -23
- package/src/config/bundled-skills/conversations/tools/rename-conversation.ts +0 -66
- package/src/config/bundled-skills/gmail/SKILL.md +0 -221
- package/src/config/bundled-skills/gmail/TOOLS.json +0 -588
- package/src/config/bundled-skills/gmail/tools/gmail-archive.ts +0 -256
- package/src/config/bundled-skills/gmail/tools/gmail-attachments.ts +0 -112
- package/src/config/bundled-skills/gmail/tools/gmail-draft.ts +0 -44
- package/src/config/bundled-skills/gmail/tools/gmail-filters.ts +0 -81
- package/src/config/bundled-skills/gmail/tools/gmail-follow-up.ts +0 -108
- package/src/config/bundled-skills/gmail/tools/gmail-forward.ts +0 -146
- package/src/config/bundled-skills/gmail/tools/gmail-label.ts +0 -53
- package/src/config/bundled-skills/gmail/tools/gmail-outreach-scan.ts +0 -347
- package/src/config/bundled-skills/gmail/tools/gmail-preferences-tool.ts +0 -59
- package/src/config/bundled-skills/gmail/tools/gmail-preferences.ts +0 -82
- package/src/config/bundled-skills/gmail/tools/gmail-send-draft.ts +0 -26
- package/src/config/bundled-skills/gmail/tools/gmail-sender-digest.ts +0 -347
- package/src/config/bundled-skills/gmail/tools/gmail-trash.ts +0 -29
- package/src/config/bundled-skills/gmail/tools/gmail-unsubscribe.ts +0 -122
- package/src/config/bundled-skills/gmail/tools/gmail-vacation.ts +0 -67
- package/src/config/bundled-skills/gmail/tools/scan-result-store.ts +0 -100
- package/src/config/bundled-skills/gmail/tools/shared.ts +0 -47
- package/src/config/bundled-skills/google-calendar/SKILL.md +0 -51
- package/src/config/bundled-skills/google-calendar/TOOLS.json +0 -226
- package/src/config/bundled-skills/google-calendar/calendar-client.ts +0 -223
- package/src/config/bundled-skills/google-calendar/tools/calendar-check-availability.ts +0 -27
- package/src/config/bundled-skills/google-calendar/tools/calendar-create-event.ts +0 -48
- package/src/config/bundled-skills/google-calendar/tools/calendar-get-event.ts +0 -19
- package/src/config/bundled-skills/google-calendar/tools/calendar-list-events.ts +0 -36
- package/src/config/bundled-skills/google-calendar/tools/calendar-rsvp.ts +0 -58
- package/src/config/bundled-skills/google-calendar/tools/shared.ts +0 -17
- package/src/config/bundled-skills/google-calendar/types.ts +0 -97
- package/src/config/bundled-skills/heartbeat/SKILL.md +0 -43
- package/src/config/bundled-skills/notifications/SKILL.md +0 -40
- package/src/config/bundled-skills/notifications/TOOLS.json +0 -80
- package/src/config/bundled-skills/notifications/tools/send-notification.ts +0 -152
- package/src/config/bundled-skills/notifications/tools/shared.ts +0 -13
- package/src/config/bundled-skills/outlook/SKILL.md +0 -196
- package/src/config/bundled-skills/outlook/TOOLS.json +0 -530
- package/src/config/bundled-skills/outlook/tools/outlook-attachments.ts +0 -85
- package/src/config/bundled-skills/outlook/tools/outlook-categories.ts +0 -77
- package/src/config/bundled-skills/outlook/tools/outlook-draft.ts +0 -84
- package/src/config/bundled-skills/outlook/tools/outlook-follow-up.ts +0 -94
- package/src/config/bundled-skills/outlook/tools/outlook-forward.ts +0 -49
- package/src/config/bundled-skills/outlook/tools/outlook-outreach-scan.ts +0 -237
- package/src/config/bundled-skills/outlook/tools/outlook-rules.ts +0 -161
- package/src/config/bundled-skills/outlook/tools/outlook-send-draft.ts +0 -32
- package/src/config/bundled-skills/outlook/tools/outlook-sender-digest.ts +0 -272
- package/src/config/bundled-skills/outlook/tools/outlook-trash.ts +0 -29
- package/src/config/bundled-skills/outlook/tools/outlook-unsubscribe.ts +0 -129
- package/src/config/bundled-skills/outlook/tools/outlook-vacation.ts +0 -87
- package/src/config/bundled-skills/outlook/tools/shared.ts +0 -20
- package/src/config/bundled-skills/outlook-calendar/SKILL.md +0 -51
- package/src/config/bundled-skills/outlook-calendar/TOOLS.json +0 -221
- package/src/config/bundled-skills/outlook-calendar/calendar-client.ts +0 -252
- package/src/config/bundled-skills/outlook-calendar/tools/outlook-calendar-check-availability.ts +0 -53
- package/src/config/bundled-skills/outlook-calendar/tools/outlook-calendar-create-event.ts +0 -74
- package/src/config/bundled-skills/outlook-calendar/tools/outlook-calendar-get-event.ts +0 -18
- package/src/config/bundled-skills/outlook-calendar/tools/outlook-calendar-list-events.ts +0 -46
- package/src/config/bundled-skills/outlook-calendar/tools/outlook-calendar-rsvp.ts +0 -36
- package/src/config/bundled-skills/outlook-calendar/tools/shared.ts +0 -17
- package/src/config/bundled-skills/outlook-calendar/types.ts +0 -120
- package/src/config/bundled-skills/screen-watch/SKILL.md +0 -27
- package/src/config/bundled-skills/screen-watch/TOOLS.json +0 -35
- package/src/config/bundled-skills/screen-watch/tools/start-screen-watch.ts +0 -12
- package/src/config/bundled-skills/skills-catalog/SKILL.md +0 -84
- package/src/config/bundled-skills/slack/SKILL.md +0 -108
- package/src/config/bundled-skills/tasks/SKILL.md +0 -37
- package/src/config/bundled-skills/tasks/TOOLS.json +0 -353
- package/src/config/bundled-skills/tasks/icon.svg +0 -34
- package/src/config/bundled-skills/tasks/tools/task-delete.ts +0 -12
- package/src/config/bundled-skills/tasks/tools/task-list-add.ts +0 -12
- package/src/config/bundled-skills/tasks/tools/task-list-remove.ts +0 -12
- package/src/config/bundled-skills/tasks/tools/task-list-show.ts +0 -12
- package/src/config/bundled-skills/tasks/tools/task-list-update.ts +0 -12
- package/src/config/bundled-skills/tasks/tools/task-list.ts +0 -12
- package/src/config/bundled-skills/tasks/tools/task-queue-run.ts +0 -12
- package/src/config/bundled-skills/tasks/tools/task-run.ts +0 -12
- package/src/config/bundled-skills/tasks/tools/task-save.ts +0 -12
- package/src/config/bundled-skills/watcher/SKILL.md +0 -31
- package/src/config/bundled-skills/watcher/TOOLS.json +0 -167
- package/src/config/bundled-skills/watcher/tools/watcher-create.ts +0 -12
- package/src/config/bundled-skills/watcher/tools/watcher-delete.ts +0 -12
- package/src/config/bundled-skills/watcher/tools/watcher-digest.ts +0 -12
- package/src/config/bundled-skills/watcher/tools/watcher-list.ts +0 -12
- package/src/config/bundled-skills/watcher/tools/watcher-update.ts +0 -12
- package/src/daemon/context-overflow-approval.ts +0 -52
- package/src/daemon/watch-handler.ts +0 -399
- package/src/hooks/cli.ts +0 -253
- package/src/hooks/config.ts +0 -100
- package/src/hooks/discovery.ts +0 -135
- package/src/hooks/manager.ts +0 -179
- package/src/hooks/runner.ts +0 -117
- package/src/hooks/templates.ts +0 -77
- package/src/hooks/types.ts +0 -75
- package/src/oauth/scope-policy.ts +0 -89
- package/src/prompts/templates/UPDATES.md +0 -50
- package/src/prompts/update-bulletin-format.ts +0 -85
- package/src/prompts/update-bulletin-state.ts +0 -58
- package/src/prompts/update-bulletin-template-path.ts +0 -13
- package/src/prompts/update-bulletin.ts +0 -139
- package/src/runtime/gateway-internal-client.ts +0 -94
- package/src/runtime/routes/watch-routes.ts +0 -156
- package/src/shared/provider-env-vars.ts +0 -19
- package/src/signals/shotgun.ts +0 -203
- package/src/tools/watch/screen-watch.ts +0 -144
- package/src/tools/watch/watch-state.ts +0 -142
- package/src/tools/watcher/create.ts +0 -86
- package/src/tools/watcher/delete.ts +0 -36
- package/src/tools/watcher/digest.ts +0 -54
- package/src/tools/watcher/list.ts +0 -83
- package/src/tools/watcher/update.ts +0 -71
|
@@ -7,6 +7,8 @@
|
|
|
7
7
|
* runAgentLoop method here via the AgentLoopConversationContext interface.
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
+
import { join } from "node:path";
|
|
11
|
+
|
|
10
12
|
import { v4 as uuid } from "uuid";
|
|
11
13
|
|
|
12
14
|
import type {
|
|
@@ -24,15 +26,19 @@ import type {
|
|
|
24
26
|
} from "../channels/types.js";
|
|
25
27
|
import { isAssistantFeatureFlagEnabled } from "../config/assistant-feature-flags.js";
|
|
26
28
|
import { getConfig } from "../config/loader.js";
|
|
29
|
+
import type { LLMCallSite } from "../config/schemas/llm.js";
|
|
27
30
|
import {
|
|
28
31
|
derefToolResultReReads,
|
|
29
32
|
postTurnTruncateToolResults,
|
|
30
33
|
} from "../context/post-turn-tool-result-truncation.js";
|
|
31
|
-
import {
|
|
34
|
+
import {
|
|
35
|
+
estimatePromptTokens,
|
|
36
|
+
getCalibrationProviderKey,
|
|
37
|
+
} from "../context/token-estimator.js";
|
|
32
38
|
import type { ContextWindowManager } from "../context/window-manager.js";
|
|
33
39
|
import type { ToolProfiler } from "../events/tool-profiling-listener.js";
|
|
40
|
+
import { emitFeedEvent } from "../home/emit-feed-event.js";
|
|
34
41
|
import { writeRelationshipState } from "../home/relationship-state-writer.js";
|
|
35
|
-
import { getHookManager } from "../hooks/manager.js";
|
|
36
42
|
import {
|
|
37
43
|
clearSentryConversationContext,
|
|
38
44
|
setSentryConversationContext,
|
|
@@ -41,8 +47,7 @@ import { commitAppTurnChanges } from "../memory/app-git-service.js";
|
|
|
41
47
|
import { getApp, listAppFiles, resolveAppDir } from "../memory/app-store.js";
|
|
42
48
|
import { enqueueAutoAnalysisOnCompaction } from "../memory/auto-analysis-enqueue.js";
|
|
43
49
|
import {
|
|
44
|
-
|
|
45
|
-
deleteMessageById,
|
|
50
|
+
clearStrippedInjectionMetadataForConversation,
|
|
46
51
|
getConversation,
|
|
47
52
|
getConversationOriginChannel,
|
|
48
53
|
getConversationOriginInterface,
|
|
@@ -50,27 +55,60 @@ import {
|
|
|
50
55
|
getMessageById,
|
|
51
56
|
provenanceFromTrustContext,
|
|
52
57
|
updateConversationContextWindow,
|
|
53
|
-
updateConversationTitle,
|
|
54
|
-
updateMessageMetadata,
|
|
55
58
|
} from "../memory/conversation-crud.js";
|
|
56
59
|
import { getResolvedConversationDirPath } from "../memory/conversation-directories.js";
|
|
57
60
|
import { syncMessageToDisk } from "../memory/conversation-disk-view.js";
|
|
58
61
|
import {
|
|
59
62
|
isReplaceableTitle,
|
|
60
|
-
queueGenerateConversationTitle,
|
|
61
63
|
queueRegenerateConversationTitle,
|
|
62
|
-
UNTITLED_FALLBACK,
|
|
63
64
|
} from "../memory/conversation-title-service.js";
|
|
64
65
|
import type { ConversationGraphMemory } from "../memory/graph/conversation-graph-memory.js";
|
|
65
66
|
import { recordMemoryRecallLog } from "../memory/memory-recall-log-store.js";
|
|
67
|
+
import { PKB_WORKSPACE_SCOPE } from "../memory/pkb/types.js";
|
|
66
68
|
import type { PermissionPrompter } from "../permissions/prompter.js";
|
|
67
|
-
import
|
|
69
|
+
import { defaultCompactionTerminal } from "../plugins/defaults/compaction.js";
|
|
70
|
+
import { defaultHistoryRepairTerminal } from "../plugins/defaults/history-repair.js";
|
|
71
|
+
import {
|
|
72
|
+
asDefaultGraphPayload,
|
|
73
|
+
type DefaultMemoryRetrievalDeps,
|
|
74
|
+
type GraphMemoryPayload,
|
|
75
|
+
runDefaultMemoryRetrieval,
|
|
76
|
+
} from "../plugins/defaults/memory-retrieval.js";
|
|
77
|
+
import { defaultPersistenceTerminal } from "../plugins/defaults/persistence.js";
|
|
78
|
+
import { defaultTitleGenerateTerminal } from "../plugins/defaults/title-generate.js";
|
|
79
|
+
import { defaultTokenEstimateTerminal } from "../plugins/defaults/token-estimate.js";
|
|
80
|
+
import { DEFAULT_TIMEOUTS, runPipeline } from "../plugins/pipeline.js";
|
|
81
|
+
import { getMiddlewaresFor } from "../plugins/registry.js";
|
|
82
|
+
import type {
|
|
83
|
+
CircuitBreakerArgs,
|
|
84
|
+
CircuitBreakerResult,
|
|
85
|
+
CompactionArgs,
|
|
86
|
+
CompactionResult,
|
|
87
|
+
EstimateArgs,
|
|
88
|
+
EstimateResult,
|
|
89
|
+
HistoryRepairArgs,
|
|
90
|
+
HistoryRepairResult,
|
|
91
|
+
MemoryArgs,
|
|
92
|
+
MemoryResult,
|
|
93
|
+
OverflowReduceArgs,
|
|
94
|
+
OverflowReduceResult,
|
|
95
|
+
PersistArgs,
|
|
96
|
+
PersistResult,
|
|
97
|
+
TurnContext as PluginTurnContext,
|
|
98
|
+
} from "../plugins/types.js";
|
|
99
|
+
import { PluginExecutionError, PluginTimeoutError } from "../plugins/types.js";
|
|
100
|
+
import type {
|
|
101
|
+
ContentBlock,
|
|
102
|
+
Message,
|
|
103
|
+
ToolDefinition,
|
|
104
|
+
} from "../providers/types.js";
|
|
68
105
|
import type { Provider } from "../providers/types.js";
|
|
69
106
|
import { resolveActorTrust } from "../runtime/actor-trust-resolver.js";
|
|
70
107
|
import { DAEMON_INTERNAL_ASSISTANT_ID } from "../runtime/assistant-scope.js";
|
|
71
108
|
import { getSubagentManager } from "../subagent/index.js";
|
|
72
109
|
import type { UsageActor } from "../usage/actors.js";
|
|
73
110
|
import { getLogger } from "../util/logger.js";
|
|
111
|
+
import { getWorkspaceDir } from "../util/platform.js";
|
|
74
112
|
import { timeAgo } from "../util/time.js";
|
|
75
113
|
import { truncate } from "../util/truncate.js";
|
|
76
114
|
import { getWorkspaceGitService } from "../workspace/git-service.js";
|
|
@@ -79,7 +117,6 @@ import {
|
|
|
79
117
|
type AssistantAttachmentDraft,
|
|
80
118
|
cleanAssistantContent,
|
|
81
119
|
} from "./assistant-attachments.js";
|
|
82
|
-
import { requestCompressionApproval } from "./context-overflow-approval.js";
|
|
83
120
|
import { resolveOverflowAction } from "./context-overflow-policy.js";
|
|
84
121
|
import {
|
|
85
122
|
createInitialReducerState,
|
|
@@ -115,10 +152,11 @@ import {
|
|
|
115
152
|
buildSubagentStatusBlock,
|
|
116
153
|
buildUnifiedTurnContextBlock,
|
|
117
154
|
findLastInjectedNowContent,
|
|
155
|
+
getPkbAutoInjectList,
|
|
118
156
|
inboundActorContextFromTrust,
|
|
119
157
|
inboundActorContextFromTrustContext,
|
|
120
|
-
|
|
121
|
-
|
|
158
|
+
loadSlackActiveThreadFocusBlock,
|
|
159
|
+
loadSlackChronologicalMessages,
|
|
122
160
|
stripInjectionsForCompaction,
|
|
123
161
|
} from "./conversation-runtime-assembly.js";
|
|
124
162
|
import type { SkillProjectionCache } from "./conversation-skill-tools.js";
|
|
@@ -126,7 +164,7 @@ import { markSurfaceCompleted } from "./conversation-surfaces.js";
|
|
|
126
164
|
import { resolveTrustClass } from "./conversation-tool-setup.js";
|
|
127
165
|
import { recordUsage } from "./conversation-usage.js";
|
|
128
166
|
import { formatTurnTimestamp } from "./date-context.js";
|
|
129
|
-
import { deepRepairHistory
|
|
167
|
+
import { deepRepairHistory } from "./history-repair.js";
|
|
130
168
|
import type {
|
|
131
169
|
DynamicPageSurfaceData,
|
|
132
170
|
ServerMessage,
|
|
@@ -135,44 +173,12 @@ import type {
|
|
|
135
173
|
UsageStats,
|
|
136
174
|
} from "./message-protocol.js";
|
|
137
175
|
import type { MemoryRecalled } from "./message-types/memory.js";
|
|
176
|
+
import { parseActualTokensFromError } from "./parse-actual-tokens-from-error.js";
|
|
138
177
|
import type { TraceEmitter } from "./trace-emitter.js";
|
|
178
|
+
import { stripHistoricalWebSearchResults } from "./web-search-history.js";
|
|
139
179
|
|
|
140
180
|
const log = getLogger("conversation-agent-loop");
|
|
141
181
|
|
|
142
|
-
/**
|
|
143
|
-
* Parse the actual token count reported by the provider in a context-too-large
|
|
144
|
-
* error message. Providers typically include the prompt size, e.g.:
|
|
145
|
-
* "prompt is too long: 242201 tokens > 200000 maximum"
|
|
146
|
-
* "too many input tokens: 242201 > 200000"
|
|
147
|
-
*
|
|
148
|
-
* Returns the actual token count or null if it cannot be parsed.
|
|
149
|
-
*/
|
|
150
|
-
export function parseActualTokensFromError(
|
|
151
|
-
errorMessage: string | null,
|
|
152
|
-
): number | null {
|
|
153
|
-
if (!errorMessage) return null;
|
|
154
|
-
|
|
155
|
-
// Match patterns like "242201 tokens > 200000" or "242201 > 200000 maximum"
|
|
156
|
-
const match = errorMessage.match(
|
|
157
|
-
/(\d[\d,]*)\s*tokens?\s*[>≥]|:\s*(\d[\d,]*)\s*[>≥]/i,
|
|
158
|
-
);
|
|
159
|
-
if (match) {
|
|
160
|
-
const raw = (match[1] || match[2]).replace(/,/g, "");
|
|
161
|
-
const parsed = parseInt(raw, 10);
|
|
162
|
-
if (!isNaN(parsed) && parsed > 0) return parsed;
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
// Fallback: match "too many input tokens: N > M"
|
|
166
|
-
const fallback = errorMessage.match(/(\d[\d,]*)\s*[>≥]\s*\d/);
|
|
167
|
-
if (fallback) {
|
|
168
|
-
const raw = fallback[1].replace(/,/g, "");
|
|
169
|
-
const parsed = parseInt(raw, 10);
|
|
170
|
-
if (!isNaN(parsed) && parsed > 0) return parsed;
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
return null;
|
|
174
|
-
}
|
|
175
|
-
|
|
176
182
|
/** Title-cased friendly labels for tool names, used in confirmation chips. */
|
|
177
183
|
const TOOL_FRIENDLY_LABEL: Record<string, string> = {
|
|
178
184
|
bash: "Run Command",
|
|
@@ -181,12 +187,6 @@ const TOOL_FRIENDLY_LABEL: Record<string, string> = {
|
|
|
181
187
|
file_read: "Read File",
|
|
182
188
|
file_write: "Write File",
|
|
183
189
|
file_edit: "Edit File",
|
|
184
|
-
browser_navigate: "Browser",
|
|
185
|
-
browser_click: "Browser",
|
|
186
|
-
browser_type: "Browser",
|
|
187
|
-
browser_screenshot: "Browser",
|
|
188
|
-
browser_scroll: "Browser",
|
|
189
|
-
browser_wait: "Browser",
|
|
190
190
|
app_create: "Create App",
|
|
191
191
|
app_refresh: "Refresh App",
|
|
192
192
|
skill_load: "Load Skill",
|
|
@@ -197,6 +197,212 @@ type GitServiceInitializer = {
|
|
|
197
197
|
ensureInitialized(): Promise<void>;
|
|
198
198
|
};
|
|
199
199
|
|
|
200
|
+
// ── Compaction circuit-breaker pipeline helpers ─────────────────────
|
|
201
|
+
//
|
|
202
|
+
// The circuit-breaker behavior (3 consecutive summary-LLM failures trips a
|
|
203
|
+
// 1-hour cooldown) is now implemented by the `circuitBreaker` plugin
|
|
204
|
+
// pipeline. The default plugin (`plugins/defaults/circuit-breaker.ts`)
|
|
205
|
+
// replicates the legacy threshold/cooldown constants and event-emission
|
|
206
|
+
// semantics exactly — it operates on the `consecutiveCompactionFailures` /
|
|
207
|
+
// `compactionCircuitOpenUntil` fields the conversation still owns so the
|
|
208
|
+
// dev-only playground routes (`POST /playground/reset-compaction-circuit`,
|
|
209
|
+
// `POST /playground/inject-compaction-failures`) continue to read and
|
|
210
|
+
// mutate those fields directly.
|
|
211
|
+
//
|
|
212
|
+
// The helpers below build the pipeline inputs and invoke the runner. They
|
|
213
|
+
// are the sole entry points the rest of the daemon uses to query or update
|
|
214
|
+
// the compaction circuit.
|
|
215
|
+
|
|
216
|
+
/** Circuit-breaker key for a specific conversation's compaction pipeline. */
|
|
217
|
+
function compactionCircuitKey(conversationId: string): string {
|
|
218
|
+
return `compaction:${conversationId}`;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Build the minimal {@link TurnContext} the pipeline runner requires. Called
|
|
223
|
+
* both from inside the agent loop (where turn identifiers are available) and
|
|
224
|
+
* from non-turn invocations like `Conversation.forceCompact` (which falls
|
|
225
|
+
* back to stable placeholders so the runner's log records still carry the
|
|
226
|
+
* conversation identifier).
|
|
227
|
+
*/
|
|
228
|
+
function buildCircuitTurnContext(ctx: {
|
|
229
|
+
readonly conversationId: string;
|
|
230
|
+
currentRequestId?: string;
|
|
231
|
+
currentTurnTrustContext?: TrustContext;
|
|
232
|
+
trustContext?: TrustContext;
|
|
233
|
+
turnCount: number;
|
|
234
|
+
}): PluginTurnContext {
|
|
235
|
+
const trust: TrustContext =
|
|
236
|
+
ctx.currentTurnTrustContext ?? ctx.trustContext ?? FALLBACK_TURN_TRUST;
|
|
237
|
+
return {
|
|
238
|
+
requestId: ctx.currentRequestId ?? "circuit-breaker",
|
|
239
|
+
conversationId: ctx.conversationId,
|
|
240
|
+
turnIndex: ctx.turnCount,
|
|
241
|
+
trust,
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* Run the `circuitBreaker` pipeline for the compaction circuit on this
|
|
247
|
+
* conversation. When `outcome` is provided, state is updated (and transition
|
|
248
|
+
* events emit via `onEvent`); when omitted the call is query-only.
|
|
249
|
+
*
|
|
250
|
+
* Returns the post-call decision from the pipeline. Callers gate auto-paths
|
|
251
|
+
* on `!result.open` and admit forced paths regardless of the decision.
|
|
252
|
+
*/
|
|
253
|
+
async function runCompactionCircuitPipeline(
|
|
254
|
+
ctx: {
|
|
255
|
+
readonly conversationId: string;
|
|
256
|
+
consecutiveCompactionFailures: number;
|
|
257
|
+
compactionCircuitOpenUntil: number | null;
|
|
258
|
+
currentRequestId?: string;
|
|
259
|
+
currentTurnTrustContext?: TrustContext;
|
|
260
|
+
trustContext?: TrustContext;
|
|
261
|
+
turnCount: number;
|
|
262
|
+
},
|
|
263
|
+
args: {
|
|
264
|
+
outcome?: "success" | "failure";
|
|
265
|
+
onEvent?: (msg: ServerMessage) => void;
|
|
266
|
+
},
|
|
267
|
+
): Promise<CircuitBreakerResult> {
|
|
268
|
+
const turnContext = buildCircuitTurnContext(ctx);
|
|
269
|
+
return runPipeline<CircuitBreakerArgs, CircuitBreakerResult>(
|
|
270
|
+
"circuitBreaker",
|
|
271
|
+
getMiddlewaresFor("circuitBreaker"),
|
|
272
|
+
async (terminalArgs) => {
|
|
273
|
+
// No plugin in the chain produced a decision. This should be
|
|
274
|
+
// unreachable in production because the default plugin registers a
|
|
275
|
+
// `circuitBreaker` middleware that always returns a decision, but we
|
|
276
|
+
// defensively derive the state here so test setups that intentionally
|
|
277
|
+
// omit the default plugin still get a sensible response.
|
|
278
|
+
const openUntil = terminalArgs.state.compactionCircuitOpenUntil;
|
|
279
|
+
const now = Date.now();
|
|
280
|
+
if (openUntil !== null && now < openUntil) {
|
|
281
|
+
return { open: true, cooldownRemainingMs: openUntil - now };
|
|
282
|
+
}
|
|
283
|
+
return { open: false };
|
|
284
|
+
},
|
|
285
|
+
{
|
|
286
|
+
key: compactionCircuitKey(ctx.conversationId),
|
|
287
|
+
// Pass the ctx directly as the mutable state container. The
|
|
288
|
+
// `CircuitBreakerArgs.state` shape deliberately matches the subset of
|
|
289
|
+
// fields the conversation owns so plugins mutate the same object the
|
|
290
|
+
// playground routes read and write.
|
|
291
|
+
state: ctx,
|
|
292
|
+
...(args.outcome !== undefined ? { outcome: args.outcome } : {}),
|
|
293
|
+
...(args.onEvent ? { onEvent: args.onEvent } : {}),
|
|
294
|
+
},
|
|
295
|
+
turnContext,
|
|
296
|
+
DEFAULT_TIMEOUTS.circuitBreaker,
|
|
297
|
+
);
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* Query-only: is the compaction circuit breaker currently open for this
|
|
302
|
+
* conversation? Thin wrapper around {@link runCompactionCircuitPipeline}
|
|
303
|
+
* with no outcome. Async because the pipeline runner is async, but the
|
|
304
|
+
* default plugin resolves synchronously on its microtask.
|
|
305
|
+
*/
|
|
306
|
+
export async function isCompactionCircuitOpen(ctx: {
|
|
307
|
+
readonly conversationId: string;
|
|
308
|
+
consecutiveCompactionFailures: number;
|
|
309
|
+
compactionCircuitOpenUntil: number | null;
|
|
310
|
+
currentRequestId?: string;
|
|
311
|
+
currentTurnTrustContext?: TrustContext;
|
|
312
|
+
trustContext?: TrustContext;
|
|
313
|
+
turnCount: number;
|
|
314
|
+
}): Promise<boolean> {
|
|
315
|
+
const decision = await runCompactionCircuitPipeline(ctx, {});
|
|
316
|
+
return decision.open;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
/**
|
|
320
|
+
* Update the compaction circuit breaker with the outcome of a `maybeCompact`
|
|
321
|
+
* call and emit any transition event. A `summaryFailed` value of `undefined`
|
|
322
|
+
* means the summary LLM never ran (early return) — callers must guard with
|
|
323
|
+
* `summaryFailed !== undefined` before invoking this helper so early-return
|
|
324
|
+
* paths don't silently reset the 3-strike counter.
|
|
325
|
+
*
|
|
326
|
+
* The default plugin handles threshold-based tripping and cooldown reset;
|
|
327
|
+
* see `plugins/defaults/circuit-breaker.ts` for the canonical semantics.
|
|
328
|
+
*/
|
|
329
|
+
export async function trackCompactionOutcome(
|
|
330
|
+
ctx: {
|
|
331
|
+
readonly conversationId: string;
|
|
332
|
+
consecutiveCompactionFailures: number;
|
|
333
|
+
compactionCircuitOpenUntil: number | null;
|
|
334
|
+
currentRequestId?: string;
|
|
335
|
+
currentTurnTrustContext?: TrustContext;
|
|
336
|
+
trustContext?: TrustContext;
|
|
337
|
+
turnCount: number;
|
|
338
|
+
},
|
|
339
|
+
summaryFailed: boolean,
|
|
340
|
+
onEvent: (msg: ServerMessage) => void,
|
|
341
|
+
): Promise<void> {
|
|
342
|
+
await runCompactionCircuitPipeline(ctx, {
|
|
343
|
+
outcome: summaryFailed ? "failure" : "success",
|
|
344
|
+
onEvent,
|
|
345
|
+
});
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
// ── Plugin pipeline helpers ──────────────────────────────────────────
|
|
349
|
+
//
|
|
350
|
+
// Canonical {@link PluginTurnContext} builder threaded into every
|
|
351
|
+
// `runPipeline` call inside `runAgentLoopImpl`. The orchestrator composes
|
|
352
|
+
// the context on demand at each call site from ambient state rather than
|
|
353
|
+
// carrying a persistent `TurnContext` instance across the turn.
|
|
354
|
+
|
|
355
|
+
/**
|
|
356
|
+
* Synthetic fallback trust context used when the orchestrator fires a pipeline
|
|
357
|
+
* before the per-turn trust snapshot has been captured (e.g. invocations that
|
|
358
|
+
* bypass `processMessage` / `drainQueue`). We bias to `unknown` rather than
|
|
359
|
+
* `guardian` so a missing snapshot cannot accidentally grant elevated trust
|
|
360
|
+
* to a custom plugin reading `ctx.trust`.
|
|
361
|
+
*/
|
|
362
|
+
export const FALLBACK_TURN_TRUST: TrustContext = {
|
|
363
|
+
sourceChannel: "vellum",
|
|
364
|
+
trustClass: "unknown",
|
|
365
|
+
};
|
|
366
|
+
|
|
367
|
+
/**
|
|
368
|
+
* Build the {@link TurnContext} passed to {@link runPipeline}.
|
|
369
|
+
*
|
|
370
|
+
* Canonical source of truth for every pipeline call site inside the agent
|
|
371
|
+
* loop. Every `runPipeline` invocation in `runAgentLoopImpl` (and in the
|
|
372
|
+
* handlers that share its ambient state) must route through this helper
|
|
373
|
+
* rather than constructing a `TurnContext` literal inline — this keeps
|
|
374
|
+
* `turnIndex`, trust resolution, and the `contextWindowManager` attachment
|
|
375
|
+
* consistent across pipeline slots, which in turn keeps structured logs
|
|
376
|
+
* filtered by `conversationId`/`turnIndex` coherent across slots.
|
|
377
|
+
*
|
|
378
|
+
* Behavior:
|
|
379
|
+
* - `turnIndex` is always `ctx.turnCount` — the orchestrator-owned
|
|
380
|
+
* 0-based turn counter. Reading from a single source avoids the
|
|
381
|
+
* earlier inconsistency (`ctx.turnCount`, `ctx.messages.length - 1`,
|
|
382
|
+
* `ctx.messages.length`, and `0` were all used for the same turn).
|
|
383
|
+
* - Trust pulls from the per-turn snapshot first, then the conversation-
|
|
384
|
+
* level context, then {@link FALLBACK_TURN_TRUST}. The cascade matches
|
|
385
|
+
* the one inside the orchestrator's inline injection assembly so
|
|
386
|
+
* middleware reads the same trust class the runtime sees.
|
|
387
|
+
* - `contextWindowManager` is attached unconditionally. Pipelines that
|
|
388
|
+
* don't need it can ignore it; the default compaction plugin reads it
|
|
389
|
+
* via the typed optional field on `TurnContext`.
|
|
390
|
+
*/
|
|
391
|
+
export function buildPluginTurnContext(
|
|
392
|
+
ctx: AgentLoopConversationContext,
|
|
393
|
+
requestId: string,
|
|
394
|
+
): PluginTurnContext {
|
|
395
|
+
const trust =
|
|
396
|
+
ctx.currentTurnTrustContext ?? ctx.trustContext ?? FALLBACK_TURN_TRUST;
|
|
397
|
+
return {
|
|
398
|
+
requestId,
|
|
399
|
+
conversationId: ctx.conversationId,
|
|
400
|
+
turnIndex: ctx.turnCount,
|
|
401
|
+
trust,
|
|
402
|
+
contextWindowManager: ctx.contextWindowManager,
|
|
403
|
+
};
|
|
404
|
+
}
|
|
405
|
+
|
|
200
406
|
// ── Context Interface ────────────────────────────────────────────────
|
|
201
407
|
|
|
202
408
|
export interface AgentLoopConversationContext {
|
|
@@ -213,6 +419,10 @@ export interface AgentLoopConversationContext {
|
|
|
213
419
|
readonly contextWindowManager: ContextWindowManager;
|
|
214
420
|
contextCompactedMessageCount: number;
|
|
215
421
|
contextCompactedAt: number | null;
|
|
422
|
+
/** Tracks consecutive compaction failures (summary LLM call threw). */
|
|
423
|
+
consecutiveCompactionFailures: number;
|
|
424
|
+
/** Timestamp (ms since epoch) until which the circuit breaker is open. */
|
|
425
|
+
compactionCircuitOpenUntil: number | null;
|
|
216
426
|
|
|
217
427
|
readonly memoryPolicy: { scopeId: string; includeDefaultFallback: boolean };
|
|
218
428
|
readonly graphMemory: ConversationGraphMemory;
|
|
@@ -235,6 +445,7 @@ export interface AgentLoopConversationContext {
|
|
|
235
445
|
>;
|
|
236
446
|
pendingSurfaceActions: Map<string, { surfaceType: SurfaceType }>;
|
|
237
447
|
surfaceActionRequestIds: Set<string>;
|
|
448
|
+
approvedViaPromptThisTurn?: boolean;
|
|
238
449
|
currentTurnSurfaces: Array<{
|
|
239
450
|
surfaceId: string;
|
|
240
451
|
surfaceType: SurfaceType;
|
|
@@ -352,10 +563,16 @@ export async function runAgentLoopImpl(
|
|
|
352
563
|
userMessageId: string,
|
|
353
564
|
onEvent: (msg: ServerMessage) => void,
|
|
354
565
|
options?: {
|
|
355
|
-
skipPreMessageRollback?: boolean;
|
|
356
566
|
isInteractive?: boolean;
|
|
357
567
|
isUserMessage?: boolean;
|
|
358
568
|
titleText?: string;
|
|
569
|
+
/**
|
|
570
|
+
* LLM call-site identifier threaded into the per-call provider config.
|
|
571
|
+
* Adapter callers (heartbeat, filing, scheduler, etc.) pass their own
|
|
572
|
+
* call-site id so the resolver picks `llm.callSites.<id>`. When unset,
|
|
573
|
+
* the agent loop defaults to `'mainAgent'` for user-initiated turns.
|
|
574
|
+
*/
|
|
575
|
+
callSite?: LLMCallSite;
|
|
359
576
|
},
|
|
360
577
|
): Promise<void> {
|
|
361
578
|
if (!ctx.abortController) {
|
|
@@ -379,6 +596,13 @@ export async function runAgentLoopImpl(
|
|
|
379
596
|
});
|
|
380
597
|
let yieldedForHandoff = false;
|
|
381
598
|
|
|
599
|
+
// Default user-initiated turns to the `mainAgent` call site. Other
|
|
600
|
+
// invocation contexts (heartbeat, filing, analyze, etc.) pass their own
|
|
601
|
+
// `callSite`. The provider layer resolves provider/model/maxTokens via
|
|
602
|
+
// `resolveCallSiteConfig`, picking up any user overrides under
|
|
603
|
+
// `llm.callSites.mainAgent` (falling back to `llm.default` when absent).
|
|
604
|
+
const turnCallSite: LLMCallSite = options?.callSite ?? "mainAgent";
|
|
605
|
+
|
|
382
606
|
// Capture the turn channel context *before* any awaits so a second
|
|
383
607
|
// message from a different channel can't overwrite it mid-flight.
|
|
384
608
|
// When context is unavailable (e.g. regenerate after daemon restart),
|
|
@@ -462,40 +686,10 @@ export async function runAgentLoopImpl(
|
|
|
462
686
|
}
|
|
463
687
|
}
|
|
464
688
|
|
|
465
|
-
const preMessageResult = await getHookManager().trigger("pre-message", {
|
|
466
|
-
conversationId: ctx.conversationId,
|
|
467
|
-
messagePreview: truncate(content, 200, ""),
|
|
468
|
-
});
|
|
469
|
-
|
|
470
|
-
if (preMessageResult.blocked) {
|
|
471
|
-
if (!options?.skipPreMessageRollback) {
|
|
472
|
-
ctx.messages.pop();
|
|
473
|
-
deleteMessageById(userMessageId);
|
|
474
|
-
}
|
|
475
|
-
// Replace loading placeholder so the conversation isn't stuck as "Generating title..."
|
|
476
|
-
const currentConv = getConversation(ctx.conversationId);
|
|
477
|
-
if (
|
|
478
|
-
isReplaceableTitle(currentConv?.title ?? null) &&
|
|
479
|
-
currentConv?.title !== UNTITLED_FALLBACK
|
|
480
|
-
) {
|
|
481
|
-
updateConversationTitle(ctx.conversationId, UNTITLED_FALLBACK);
|
|
482
|
-
onEvent({
|
|
483
|
-
type: "conversation_title_updated",
|
|
484
|
-
conversationId: ctx.conversationId,
|
|
485
|
-
title: UNTITLED_FALLBACK,
|
|
486
|
-
});
|
|
487
|
-
}
|
|
488
|
-
onEvent({
|
|
489
|
-
type: "error",
|
|
490
|
-
message: `Message blocked by hook "${preMessageResult.blockedBy}"`,
|
|
491
|
-
});
|
|
492
|
-
return;
|
|
493
|
-
}
|
|
494
|
-
|
|
495
689
|
// Generate title early — the user message alone is sufficient context.
|
|
496
|
-
// Firing
|
|
497
|
-
//
|
|
498
|
-
//
|
|
690
|
+
// Firing before the main LLM call removes the delay of waiting for the
|
|
691
|
+
// full assistant response. The second-pass regeneration at turn 3 will
|
|
692
|
+
// refine the title with more context.
|
|
499
693
|
// No abort signal — title generation should complete even if the user
|
|
500
694
|
// cancels the response, since the user message is already persisted.
|
|
501
695
|
// Deferred via setTimeout so the main agent loop LLM call enqueues
|
|
@@ -503,18 +697,38 @@ export async function runAgentLoopImpl(
|
|
|
503
697
|
if (
|
|
504
698
|
isReplaceableTitle(getConversation(ctx.conversationId)?.title ?? null)
|
|
505
699
|
) {
|
|
700
|
+
// TurnContext routed through the canonical builder so the pipeline's
|
|
701
|
+
// log record reports the same `conversationId`/`turnIndex` shape as
|
|
702
|
+
// every other slot in this turn. Title generation does not depend on
|
|
703
|
+
// the context-window manager attached by the builder, but sharing the
|
|
704
|
+
// builder keeps the invariant enforced in one place.
|
|
705
|
+
const titlePipelineCtx = buildPluginTurnContext(ctx, reqId);
|
|
706
|
+
const titleArgs = {
|
|
707
|
+
conversationId: ctx.conversationId,
|
|
708
|
+
provider: ctx.provider,
|
|
709
|
+
userMessage: options?.titleText ?? content,
|
|
710
|
+
onTitleUpdated: (title: string) => {
|
|
711
|
+
onEvent({
|
|
712
|
+
type: "conversation_title_updated",
|
|
713
|
+
conversationId: ctx.conversationId,
|
|
714
|
+
title,
|
|
715
|
+
});
|
|
716
|
+
},
|
|
717
|
+
};
|
|
506
718
|
setTimeout(() => {
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
719
|
+
runPipeline(
|
|
720
|
+
"titleGenerate",
|
|
721
|
+
getMiddlewaresFor("titleGenerate"),
|
|
722
|
+
defaultTitleGenerateTerminal,
|
|
723
|
+
titleArgs,
|
|
724
|
+
titlePipelineCtx,
|
|
725
|
+
DEFAULT_TIMEOUTS.titleGenerate,
|
|
726
|
+
).catch((err) => {
|
|
727
|
+
// Fire-and-forget — keep previous non-propagating semantics.
|
|
728
|
+
// queueGenerateConversationTitle already swallows internal
|
|
729
|
+
// errors; this catch covers pipeline-layer errors (timeouts,
|
|
730
|
+
// middleware throws) without surfacing them to the agent loop.
|
|
731
|
+
rlog.warn({ err }, "titleGenerate pipeline failed (non-fatal)");
|
|
518
732
|
});
|
|
519
733
|
}, 0);
|
|
520
734
|
}
|
|
@@ -524,7 +738,10 @@ export async function runAgentLoopImpl(
|
|
|
524
738
|
let compactedThisTurn = false;
|
|
525
739
|
|
|
526
740
|
const compactCheck = ctx.contextWindowManager.shouldCompact(ctx.messages);
|
|
527
|
-
|
|
741
|
+
// Skip auto-compaction while the circuit breaker is open. Force paths
|
|
742
|
+
// and user-initiated /compact bypass this check.
|
|
743
|
+
const autoCompactAllowed = !(await isCompactionCircuitOpen(ctx));
|
|
744
|
+
if (compactCheck.needed && autoCompactAllowed) {
|
|
528
745
|
ctx.emitActivityState(
|
|
529
746
|
"thinking",
|
|
530
747
|
"context_compacting",
|
|
@@ -532,57 +749,59 @@ export async function runAgentLoopImpl(
|
|
|
532
749
|
reqId,
|
|
533
750
|
);
|
|
534
751
|
}
|
|
535
|
-
|
|
536
|
-
ctx.
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
752
|
+
let compacted: Awaited<
|
|
753
|
+
ReturnType<typeof ctx.contextWindowManager.maybeCompact>
|
|
754
|
+
> | null = null;
|
|
755
|
+
if (autoCompactAllowed) {
|
|
756
|
+
try {
|
|
757
|
+
compacted = (await runPipeline<CompactionArgs, CompactionResult>(
|
|
758
|
+
"compaction",
|
|
759
|
+
getMiddlewaresFor("compaction"),
|
|
760
|
+
(args) =>
|
|
761
|
+
defaultCompactionTerminal(args, buildPluginTurnContext(ctx, reqId)),
|
|
762
|
+
{
|
|
763
|
+
messages: ctx.messages,
|
|
764
|
+
signal: abortController.signal,
|
|
765
|
+
options: {
|
|
766
|
+
lastCompactedAt: ctx.contextCompactedAt ?? undefined,
|
|
767
|
+
precomputedEstimate: compactCheck.estimatedTokens,
|
|
768
|
+
conversationOriginChannel:
|
|
769
|
+
getConversationOriginChannel(ctx.conversationId) ?? undefined,
|
|
770
|
+
},
|
|
771
|
+
},
|
|
772
|
+
buildPluginTurnContext(ctx, reqId),
|
|
773
|
+
DEFAULT_TIMEOUTS.compaction,
|
|
774
|
+
)) as Awaited<ReturnType<typeof ctx.contextWindowManager.maybeCompact>>;
|
|
775
|
+
} catch (err) {
|
|
776
|
+
if (err instanceof PluginTimeoutError) {
|
|
777
|
+
// Pipeline exceeded its budget. Record the failure so the circuit
|
|
778
|
+
// breaker tracks consecutive timeouts (it trips after three),
|
|
779
|
+
// then degrade gracefully by skipping compaction this turn —
|
|
780
|
+
// the turn proceeds with the un-compacted history rather than
|
|
781
|
+
// hard-failing. The inner summary call has been aborted by the
|
|
782
|
+
// runner's signal-linking, so updateSummary's local fallback
|
|
783
|
+
// also ran before this catch block is reached.
|
|
784
|
+
rlog.warn(
|
|
785
|
+
{ err, phase: "start-of-turn-compaction" },
|
|
786
|
+
"Compaction pipeline timed out — skipping compaction this turn",
|
|
787
|
+
);
|
|
788
|
+
await trackCompactionOutcome(ctx, true, onEvent);
|
|
789
|
+
compacted = null;
|
|
790
|
+
} else {
|
|
791
|
+
throw err;
|
|
792
|
+
}
|
|
793
|
+
}
|
|
794
|
+
}
|
|
795
|
+
// Only track circuit-breaker state when a summary LLM call actually ran.
|
|
796
|
+
// `summaryFailed` is `undefined` on early returns (compaction disabled,
|
|
797
|
+
// below threshold, cooldown active, no eligible messages, truncation-only
|
|
798
|
+
// path) — treating those as "successful" compactions would silently reset
|
|
799
|
+
// the 3-strike counter and break the invariant.
|
|
800
|
+
if (compacted && compacted.summaryFailed !== undefined) {
|
|
801
|
+
await trackCompactionOutcome(ctx, compacted.summaryFailed, onEvent);
|
|
802
|
+
}
|
|
803
|
+
if (compacted?.compacted) {
|
|
804
|
+
applyCompactionResult(ctx, compacted, onEvent, reqId);
|
|
586
805
|
shouldInjectWorkspace = true;
|
|
587
806
|
if (compacted.compactedPersistedMessages > 0) {
|
|
588
807
|
compactedThisTurn = true;
|
|
@@ -630,26 +849,98 @@ export async function runAgentLoopImpl(
|
|
|
630
849
|
|
|
631
850
|
let runMessages = ctx.messages;
|
|
632
851
|
|
|
633
|
-
// Memory
|
|
634
|
-
//
|
|
852
|
+
// Memory retrieval pipeline — fetches PKB, NOW.md, and memory-graph
|
|
853
|
+
// outputs through a single `memoryRetrieval` pipeline. Plugins may
|
|
854
|
+
// replace the terminal behavior by registering a middleware that
|
|
855
|
+
// short-circuits with its own `MemoryResult`; the default terminal
|
|
856
|
+
// below runs `runDefaultMemoryRetrieval` which reproduces the prior
|
|
857
|
+
// in-lined behavior (PKB/NOW reads + gated graph call).
|
|
635
858
|
const isTrustedActor = resolveTrustClass(ctx.trustContext) === "guardian";
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
859
|
+
// Canonical builder — pulls trust from per-turn snapshot, then
|
|
860
|
+
// conversation-level, then the synthetic fallback. Memory retrieval
|
|
861
|
+
// does not need the context-window handle the builder attaches, but
|
|
862
|
+
// keeping every call site on one helper is load-bearing for log
|
|
863
|
+
// coherence across pipeline slots.
|
|
864
|
+
const memoryPluginTurnCtx = buildPluginTurnContext(ctx, reqId);
|
|
865
|
+
const memoryArgs: MemoryArgs = {
|
|
866
|
+
conversationId: ctx.conversationId,
|
|
867
|
+
trustContext: ctx.trustContext,
|
|
868
|
+
turnIndex: ctx.turnCount,
|
|
869
|
+
// Pass the abort signal via `args` (not `deps`) so the pipeline
|
|
870
|
+
// runner's `linkAbortSignal` can swap it for a signal linked to the
|
|
871
|
+
// pipeline's internal controller — on a plugin-set timeout or
|
|
872
|
+
// external cancel, the linked signal aborts and `prepareMemory`
|
|
873
|
+
// stops mutating graph state / emitting events after the pipeline
|
|
874
|
+
// has already errored.
|
|
875
|
+
signal: abortController.signal,
|
|
876
|
+
};
|
|
877
|
+
const memoryDeps: DefaultMemoryRetrievalDeps = {
|
|
878
|
+
messages: ctx.messages,
|
|
879
|
+
graphMemory: ctx.graphMemory,
|
|
880
|
+
config: getConfig(),
|
|
881
|
+
onEvent,
|
|
882
|
+
isTrustedActor,
|
|
883
|
+
};
|
|
884
|
+
const memoryResult: MemoryResult = await runPipeline(
|
|
885
|
+
"memoryRetrieval",
|
|
886
|
+
getMiddlewaresFor("memoryRetrieval"),
|
|
887
|
+
(args) => runDefaultMemoryRetrieval(args, memoryDeps),
|
|
888
|
+
memoryArgs,
|
|
889
|
+
memoryPluginTurnCtx,
|
|
890
|
+
DEFAULT_TIMEOUTS.memoryRetrieval,
|
|
891
|
+
);
|
|
892
|
+
|
|
893
|
+
// Consume the memory-graph block when the default retriever emitted
|
|
894
|
+
// one. Custom plugins that substitute their own blocks without the
|
|
895
|
+
// default discriminator are expected to handle their own side effects
|
|
896
|
+
// (event emission, metric persistence) inside their middleware; this
|
|
897
|
+
// block short-circuits to the original no-op behavior in that case.
|
|
898
|
+
const defaultGraphPayload: GraphMemoryPayload | null =
|
|
899
|
+
asDefaultGraphPayload(memoryResult.memoryGraphBlocks);
|
|
900
|
+
let pkbQueryVector: number[] | undefined;
|
|
901
|
+
let pkbSparseVector:
|
|
902
|
+
| import("../memory/qdrant-client.js").QdrantSparseVector
|
|
903
|
+
| undefined;
|
|
904
|
+
if (defaultGraphPayload) {
|
|
905
|
+
const graphResult = defaultGraphPayload.result;
|
|
643
906
|
runMessages = graphResult.runMessages;
|
|
907
|
+
// Select dense+sparse as a matched pair so RRF fusion combines two
|
|
908
|
+
// signals aligned to the same query text:
|
|
909
|
+
// 1. Context-load with a user query: user-query dense + user-query
|
|
910
|
+
// sparse — the cleanest pairing.
|
|
911
|
+
// 2. Otherwise (context-load without a user query, or per-turn):
|
|
912
|
+
// whatever `queryVector` / `sparseVector` the retriever produced,
|
|
913
|
+
// which are themselves co-aligned (both summary-derived in
|
|
914
|
+
// context-load, both user-last-message-derived in per-turn).
|
|
915
|
+
// Never pair a user-query dense with a summary-aligned sparse.
|
|
916
|
+
if (graphResult.userQueryVector) {
|
|
917
|
+
pkbQueryVector = graphResult.userQueryVector;
|
|
918
|
+
pkbSparseVector = graphResult.userQuerySparseVector;
|
|
919
|
+
} else {
|
|
920
|
+
pkbQueryVector = graphResult.queryVector;
|
|
921
|
+
pkbSparseVector = graphResult.sparseVector;
|
|
922
|
+
}
|
|
644
923
|
|
|
645
924
|
// Persist the injected block text in message metadata so it survives
|
|
646
925
|
// conversation reloads (eviction, restart, fork). loadFromDb re-injects
|
|
647
|
-
// from metadata.
|
|
926
|
+
// from metadata. Routed through the `persistence` pipeline so plugins
|
|
927
|
+
// can observe or override metadata updates alongside add/delete.
|
|
648
928
|
if (graphResult.injectedBlockText) {
|
|
649
929
|
try {
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
930
|
+
await runPipeline<PersistArgs, PersistResult>(
|
|
931
|
+
"persistence",
|
|
932
|
+
getMiddlewaresFor("persistence"),
|
|
933
|
+
defaultPersistenceTerminal,
|
|
934
|
+
{
|
|
935
|
+
op: "update",
|
|
936
|
+
messageId: userMessageId,
|
|
937
|
+
updates: {
|
|
938
|
+
memoryInjectedBlock: graphResult.injectedBlockText,
|
|
939
|
+
},
|
|
940
|
+
},
|
|
941
|
+
buildPluginTurnContext(ctx, reqId),
|
|
942
|
+
DEFAULT_TIMEOUTS.persistence,
|
|
943
|
+
);
|
|
653
944
|
} catch (err) {
|
|
654
945
|
rlog.warn(
|
|
655
946
|
{ err },
|
|
@@ -831,14 +1122,31 @@ export async function runAgentLoopImpl(
|
|
|
831
1122
|
// Inject NOW.md and PKB content only on the first turn (or after
|
|
832
1123
|
// compaction re-strips them). Old injections persist in history and
|
|
833
1124
|
// are never stripped on normal turns — this preserves the cached prefix.
|
|
834
|
-
|
|
1125
|
+
// PKB/NOW content is sourced from the `memoryRetrieval` pipeline above
|
|
1126
|
+
// so plugins can override either source without touching the agent loop.
|
|
1127
|
+
const currentNowContent = memoryResult.nowContent;
|
|
835
1128
|
const shouldInjectNowAndPkb = isFirstMessage || compactedThisTurn;
|
|
836
1129
|
const nowScratchpad = shouldInjectNowAndPkb ? currentNowContent : null;
|
|
837
1130
|
|
|
838
|
-
const currentPkbContent =
|
|
1131
|
+
const currentPkbContent = memoryResult.pkbContent;
|
|
839
1132
|
const pkbContext = shouldInjectNowAndPkb ? currentPkbContent : null;
|
|
840
1133
|
const pkbActive = currentPkbContent !== null;
|
|
841
1134
|
|
|
1135
|
+
// PKB relevance-hint inputs. Resolved once per turn and reused across
|
|
1136
|
+
// re-injections so post-compaction rebuilds pick up fresh hints against
|
|
1137
|
+
// the updated conversation history.
|
|
1138
|
+
const pkbRoot = pkbActive ? join(getWorkspaceDir(), "pkb") : undefined;
|
|
1139
|
+
const pkbAutoInjectList = pkbRoot
|
|
1140
|
+
? getPkbAutoInjectList(pkbRoot)
|
|
1141
|
+
: undefined;
|
|
1142
|
+
// Pass `ctx` directly — `PkbContextConversation` is structural and
|
|
1143
|
+
// `getInContextPkbPaths` re-reads `conversation.messages` on each call,
|
|
1144
|
+
// so post-compaction re-injects see the updated history.
|
|
1145
|
+
const pkbConversation = pkbActive ? ctx : undefined;
|
|
1146
|
+
// PKB points live under a single workspace sentinel scope, not the
|
|
1147
|
+
// conversation's memoryPolicy.scopeId. See `PKB_WORKSPACE_SCOPE` for why.
|
|
1148
|
+
const pkbScopeId = pkbActive ? PKB_WORKSPACE_SCOPE : undefined;
|
|
1149
|
+
|
|
842
1150
|
// Subagent status injection — gives the parent LLM visibility into active/completed children.
|
|
843
1151
|
// Skipped when this conversation IS a subagent (no nesting) or has no children.
|
|
844
1152
|
const subagentStatusBlock = ctx.isSubagent
|
|
@@ -847,6 +1155,43 @@ export async function runAgentLoopImpl(
|
|
|
847
1155
|
getSubagentManager().getChildrenOf(ctx.conversationId),
|
|
848
1156
|
);
|
|
849
1157
|
|
|
1158
|
+
// For any Slack conversation (channels and DMs alike), build a
|
|
1159
|
+
// chronological transcript from the persisted message rows so the
|
|
1160
|
+
// model sees one channel-wide view instead of the gateway's per-turn
|
|
1161
|
+
// hints. DMs render as a flat sequence (no thread tags), channels
|
|
1162
|
+
// include sibling threads.
|
|
1163
|
+
const isSlackConversation = ctx.channelCapabilities?.channel === "slack";
|
|
1164
|
+
const slackChronologicalMessages = isSlackConversation
|
|
1165
|
+
? loadSlackChronologicalMessages(
|
|
1166
|
+
ctx.conversationId,
|
|
1167
|
+
ctx.channelCapabilities!,
|
|
1168
|
+
{ trustClass: ctx.trustContext?.trustClass },
|
|
1169
|
+
)
|
|
1170
|
+
: null;
|
|
1171
|
+
|
|
1172
|
+
// Active-thread focus block: when the inbound user message belongs to
|
|
1173
|
+
// a Slack thread, append a non-persisted `<active_thread>` tail block
|
|
1174
|
+
// to the final user turn listing the thread's parent + replies. Helps
|
|
1175
|
+
// the model orient when the channel transcript is long and
|
|
1176
|
+
// interleaved. Replays strip the block via RUNTIME_INJECTION_PREFIXES.
|
|
1177
|
+
// DMs short-circuit to null inside `loadSlackActiveThreadFocusBlock`
|
|
1178
|
+
// since DMs do not have threads.
|
|
1179
|
+
const slackActiveThreadFocusBlock = isSlackConversation
|
|
1180
|
+
? loadSlackActiveThreadFocusBlock(
|
|
1181
|
+
ctx.conversationId,
|
|
1182
|
+
ctx.channelCapabilities!,
|
|
1183
|
+
{ trustClass: ctx.trustContext?.trustClass },
|
|
1184
|
+
)
|
|
1185
|
+
: null;
|
|
1186
|
+
|
|
1187
|
+
// Guards the chronological-transcript override on re-injection after
|
|
1188
|
+
// the reducer compacts `ctx.messages`. The captured transcript is the
|
|
1189
|
+
// full persisted history; blindly replaying it on every re-inject would
|
|
1190
|
+
// overwrite the reducer's compacted messages and undo compaction. Flip
|
|
1191
|
+
// to `true` after any compaction so subsequent re-injections fall back
|
|
1192
|
+
// to the reduced `ctx.messages`.
|
|
1193
|
+
let reducerCompacted = compactedThisTurn;
|
|
1194
|
+
|
|
850
1195
|
// Shared injection options — reused whenever we need to re-inject after reduction.
|
|
851
1196
|
const injectionOpts = {
|
|
852
1197
|
activeSurface,
|
|
@@ -858,27 +1203,97 @@ export async function runAgentLoopImpl(
|
|
|
858
1203
|
unifiedTurnContext: unifiedTurnContextStr,
|
|
859
1204
|
pkbContext,
|
|
860
1205
|
pkbActive,
|
|
1206
|
+
pkbQueryVector,
|
|
1207
|
+
pkbSparseVector,
|
|
1208
|
+
pkbScopeId,
|
|
1209
|
+
pkbConversation,
|
|
1210
|
+
pkbAutoInjectList,
|
|
1211
|
+
pkbRoot,
|
|
1212
|
+
pkbWorkingDir: pkbActive ? ctx.workingDir : undefined,
|
|
861
1213
|
nowScratchpad,
|
|
862
1214
|
voiceCallControlPrompt: ctx.voiceCallControlPrompt ?? null,
|
|
863
1215
|
transportHints: ctx.transportHints ?? null,
|
|
864
1216
|
isNonInteractive: !isInteractiveResolved,
|
|
865
1217
|
subagentStatusBlock,
|
|
1218
|
+
slackChronologicalMessages,
|
|
1219
|
+
slackActiveThreadFocusBlock,
|
|
866
1220
|
} as const;
|
|
867
1221
|
|
|
868
1222
|
let currentInjectionMode: InjectionMode = "full";
|
|
869
1223
|
|
|
870
|
-
|
|
1224
|
+
// Canonical per-turn TurnContext forwarded to the injector chain. The
|
|
1225
|
+
// per-turn injection inputs are built inside `applyRuntimeInjections`
|
|
1226
|
+
// from the `injectionOpts` bag; we only need to hand in identity +
|
|
1227
|
+
// trust here so third-party injectors see the real turn metadata.
|
|
1228
|
+
const injectionTurnCtx = buildPluginTurnContext(ctx, reqId);
|
|
1229
|
+
|
|
1230
|
+
const injection = await applyRuntimeInjections(runMessages, {
|
|
871
1231
|
...injectionOpts,
|
|
1232
|
+
slackChronologicalMessages: reducerCompacted
|
|
1233
|
+
? null
|
|
1234
|
+
: injectionOpts.slackChronologicalMessages,
|
|
872
1235
|
mode: currentInjectionMode,
|
|
1236
|
+
turnContext: injectionTurnCtx,
|
|
873
1237
|
});
|
|
1238
|
+
runMessages = injection.messages;
|
|
1239
|
+
|
|
1240
|
+
// Persist injected blocks in message metadata so they survive conversation
|
|
1241
|
+
// reloads (eviction, restart, fork). loadFromDb re-injects from metadata.
|
|
1242
|
+
// Only the first call site persists — the overflow-recovery re-entry sites
|
|
1243
|
+
// send identical bytes and the tail row may not correspond to
|
|
1244
|
+
// `userMessageId`. All blocks are written in a single call to avoid
|
|
1245
|
+
// doubling SQLite SELECT+UPDATE work on every turn.
|
|
1246
|
+
if (
|
|
1247
|
+
injection.blocks.unifiedTurnContext ||
|
|
1248
|
+
injection.blocks.pkbSystemReminder ||
|
|
1249
|
+
injection.blocks.workspaceBlock ||
|
|
1250
|
+
injection.blocks.nowScratchpadBlock ||
|
|
1251
|
+
injection.blocks.pkbContextBlock
|
|
1252
|
+
) {
|
|
1253
|
+
try {
|
|
1254
|
+
const metadataUpdates: Record<string, unknown> = {};
|
|
1255
|
+
if (injection.blocks.unifiedTurnContext) {
|
|
1256
|
+
metadataUpdates.turnContextBlock =
|
|
1257
|
+
injection.blocks.unifiedTurnContext;
|
|
1258
|
+
}
|
|
1259
|
+
if (injection.blocks.pkbSystemReminder) {
|
|
1260
|
+
metadataUpdates.pkbSystemReminderBlock =
|
|
1261
|
+
injection.blocks.pkbSystemReminder;
|
|
1262
|
+
}
|
|
1263
|
+
if (injection.blocks.workspaceBlock) {
|
|
1264
|
+
metadataUpdates.workspaceBlock = injection.blocks.workspaceBlock;
|
|
1265
|
+
}
|
|
1266
|
+
if (injection.blocks.nowScratchpadBlock) {
|
|
1267
|
+
metadataUpdates.nowScratchpadBlock =
|
|
1268
|
+
injection.blocks.nowScratchpadBlock;
|
|
1269
|
+
}
|
|
1270
|
+
if (injection.blocks.pkbContextBlock) {
|
|
1271
|
+
metadataUpdates.pkbContextBlock = injection.blocks.pkbContextBlock;
|
|
1272
|
+
}
|
|
1273
|
+
await runPipeline<PersistArgs, PersistResult>(
|
|
1274
|
+
"persistence",
|
|
1275
|
+
getMiddlewaresFor("persistence"),
|
|
1276
|
+
defaultPersistenceTerminal,
|
|
1277
|
+
{
|
|
1278
|
+
op: "update",
|
|
1279
|
+
messageId: userMessageId,
|
|
1280
|
+
updates: metadataUpdates,
|
|
1281
|
+
},
|
|
1282
|
+
buildPluginTurnContext(ctx, reqId),
|
|
1283
|
+
DEFAULT_TIMEOUTS.persistence,
|
|
1284
|
+
);
|
|
1285
|
+
} catch (err) {
|
|
1286
|
+
rlog.warn({ err }, "Failed to persist injection metadata (non-fatal)");
|
|
1287
|
+
}
|
|
1288
|
+
}
|
|
874
1289
|
|
|
875
1290
|
// ── Preflight budget evaluation ──────────────────────────────
|
|
876
1291
|
// After runtime injections are applied, estimate the prompt token count
|
|
877
1292
|
// and proactively invoke the reducer if already above budget. This avoids
|
|
878
1293
|
// a wasted provider round-trip that would just fail with context_too_large.
|
|
879
1294
|
const config = getConfig();
|
|
880
|
-
const overflowRecovery = config.contextWindow.overflowRecovery;
|
|
881
|
-
const providerMaxTokens = config.contextWindow.maxInputTokens;
|
|
1295
|
+
const overflowRecovery = config.llm.default.contextWindow.overflowRecovery;
|
|
1296
|
+
const providerMaxTokens = config.llm.default.contextWindow.maxInputTokens;
|
|
882
1297
|
// Widen safety margin for large conversations where estimation error
|
|
883
1298
|
// compounds across many messages with tool results.
|
|
884
1299
|
const baseSafetyMargin = overflowRecovery.safetyMarginRatio;
|
|
@@ -889,11 +1304,51 @@ export async function runAgentLoopImpl(
|
|
|
889
1304
|
let reducerState: ReducerState | undefined;
|
|
890
1305
|
|
|
891
1306
|
const toolTokenBudget = ctx.agentLoop.getToolTokenBudget(runMessages);
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
)
|
|
1307
|
+
// Canonical calibration key — passed to the `tokenEstimate` pipeline for
|
|
1308
|
+
// every preflight/mid-loop estimate, the overflow reducer config, and the
|
|
1309
|
+
// convergence-path `estimatePromptTokens` call. Matches the key recorded
|
|
1310
|
+
// by `handleUsage` for wrapper providers (OpenRouter routing to
|
|
1311
|
+
// Anthropic → key is `"anthropic"`).
|
|
1312
|
+
const estimationProviderName = getCalibrationProviderKey(ctx.provider);
|
|
1313
|
+
|
|
1314
|
+
// Shared `TurnContext` for every `tokenEstimate` pipeline invocation in
|
|
1315
|
+
// this turn. The pipeline is the extension point for plugins that want
|
|
1316
|
+
// to substitute an alternate estimator (e.g. provider-native tokenization)
|
|
1317
|
+
// without touching orchestrator code.
|
|
1318
|
+
//
|
|
1319
|
+
// Routed through the canonical builder — `turnIndex` is `ctx.turnCount`,
|
|
1320
|
+
// trust cascades through per-turn/conversation-level/fallback, and the
|
|
1321
|
+
// context-window handle rides along so any middleware that wants to
|
|
1322
|
+
// reuse the manager (e.g. to compute compaction-aware estimates) can.
|
|
1323
|
+
const pipelineTurnCtx = buildPluginTurnContext(ctx, reqId);
|
|
1324
|
+
|
|
1325
|
+
const runTokenEstimatePipeline = (
|
|
1326
|
+
history: Message[],
|
|
1327
|
+
): Promise<EstimateResult> =>
|
|
1328
|
+
runPipeline<EstimateArgs, EstimateResult>(
|
|
1329
|
+
"tokenEstimate",
|
|
1330
|
+
getMiddlewaresFor("tokenEstimate"),
|
|
1331
|
+
defaultTokenEstimateTerminal,
|
|
1332
|
+
{
|
|
1333
|
+
// Shallow-frozen copies so a misbehaving middleware that mutates
|
|
1334
|
+
// `args.history` or `args.tools` in place (e.g. trims the array
|
|
1335
|
+
// before calling next) can't silently strip prompt context from
|
|
1336
|
+
// the orchestrator's live `runMessages` / resolved-tools arrays.
|
|
1337
|
+
// TypeScript `readonly` on `EstimateArgs` does not prevent
|
|
1338
|
+
// `push`/`splice` at runtime; the frozen wrapper throws in strict
|
|
1339
|
+
// mode and isolates any mutation attempts from the call-site state.
|
|
1340
|
+
history: Object.freeze([...history]) as Message[],
|
|
1341
|
+
systemPrompt: ctx.systemPrompt,
|
|
1342
|
+
tools: Object.freeze([
|
|
1343
|
+
...ctx.agentLoop.getResolvedTools(history),
|
|
1344
|
+
]) as ToolDefinition[],
|
|
1345
|
+
providerName: estimationProviderName,
|
|
1346
|
+
},
|
|
1347
|
+
pipelineTurnCtx,
|
|
1348
|
+
DEFAULT_TIMEOUTS.tokenEstimate,
|
|
1349
|
+
);
|
|
1350
|
+
|
|
1351
|
+
const preflightTokens = await runTokenEstimatePipeline(runMessages);
|
|
897
1352
|
|
|
898
1353
|
if (overflowRecovery.enabled && preflightTokens > preflightBudget) {
|
|
899
1354
|
rlog.warn(
|
|
@@ -905,127 +1360,198 @@ export async function runAgentLoopImpl(
|
|
|
905
1360
|
"Preflight budget exceeded — running overflow reducer before provider call",
|
|
906
1361
|
);
|
|
907
1362
|
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
)
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
previousEstimatedInputTokens:
|
|
958
|
-
step.compactionResult.previousEstimatedInputTokens,
|
|
959
|
-
estimatedInputTokens: step.compactionResult.estimatedInputTokens,
|
|
960
|
-
maxInputTokens: step.compactionResult.maxInputTokens,
|
|
961
|
-
thresholdTokens: step.compactionResult.thresholdTokens,
|
|
962
|
-
compactedMessages: step.compactionResult.compactedMessages,
|
|
963
|
-
summaryCalls: step.compactionResult.summaryCalls,
|
|
964
|
-
summaryInputTokens: step.compactionResult.summaryInputTokens,
|
|
965
|
-
summaryOutputTokens: step.compactionResult.summaryOutputTokens,
|
|
966
|
-
summaryModel: step.compactionResult.summaryModel,
|
|
967
|
-
});
|
|
968
|
-
emitUsage(
|
|
969
|
-
ctx,
|
|
970
|
-
step.compactionResult.summaryInputTokens,
|
|
971
|
-
step.compactionResult.summaryOutputTokens,
|
|
972
|
-
step.compactionResult.summaryModel,
|
|
973
|
-
onEvent,
|
|
974
|
-
"context_compactor",
|
|
1363
|
+
// Overflow reduction runs through the plugin pipeline. The default
|
|
1364
|
+
// middleware (`default-overflow-reduce`, registered at bootstrap)
|
|
1365
|
+
// contains the historical tier loop — forced compaction → tool-result
|
|
1366
|
+
// truncation → media stubbing → injection downgrade — plus the
|
|
1367
|
+
// re-inject/re-estimate convergence check. The callbacks below are
|
|
1368
|
+
// the orchestrator-specific side effects that the plugin coordinates
|
|
1369
|
+
// per iteration (activity emission, compaction application, runtime
|
|
1370
|
+
// injection reassembly, token re-estimation). Registered plugins that
|
|
1371
|
+
// wrap the `overflowReduce` slot see each iteration through their own
|
|
1372
|
+
// middleware `next` callback.
|
|
1373
|
+
const overflowArgs: OverflowReduceArgs = {
|
|
1374
|
+
messages: ctx.messages,
|
|
1375
|
+
runMessages,
|
|
1376
|
+
systemPrompt: ctx.systemPrompt,
|
|
1377
|
+
providerName: estimationProviderName,
|
|
1378
|
+
contextWindow: config.llm.default.contextWindow,
|
|
1379
|
+
preflightBudget,
|
|
1380
|
+
toolTokenBudget,
|
|
1381
|
+
maxAttempts: overflowRecovery.maxAttempts,
|
|
1382
|
+
abortSignal: abortController.signal,
|
|
1383
|
+
compactFn: async (msgs, signal, opts) =>
|
|
1384
|
+
// Route the reducer's forced-compaction tier through the
|
|
1385
|
+
// `compaction` pipeline so registered plugins observe these
|
|
1386
|
+
// invocations. Without this, custom compaction middleware only
|
|
1387
|
+
// sees the three orchestrator-owned call sites and misses the
|
|
1388
|
+
// reducer-initiated forced compactions entirely.
|
|
1389
|
+
(await runPipeline<CompactionArgs, CompactionResult>(
|
|
1390
|
+
"compaction",
|
|
1391
|
+
getMiddlewaresFor("compaction"),
|
|
1392
|
+
(args) =>
|
|
1393
|
+
defaultCompactionTerminal(
|
|
1394
|
+
args,
|
|
1395
|
+
buildPluginTurnContext(ctx, reqId),
|
|
1396
|
+
),
|
|
1397
|
+
{
|
|
1398
|
+
messages: msgs,
|
|
1399
|
+
signal,
|
|
1400
|
+
options: opts,
|
|
1401
|
+
},
|
|
1402
|
+
buildPluginTurnContext(ctx, reqId),
|
|
1403
|
+
DEFAULT_TIMEOUTS.compaction,
|
|
1404
|
+
)) as Awaited<
|
|
1405
|
+
ReturnType<typeof ctx.contextWindowManager.maybeCompact>
|
|
1406
|
+
>,
|
|
1407
|
+
emitActivityState: () => {
|
|
1408
|
+
ctx.emitActivityState(
|
|
1409
|
+
"thinking",
|
|
1410
|
+
"context_compacting",
|
|
1411
|
+
"assistant_turn",
|
|
975
1412
|
reqId,
|
|
976
|
-
step.compactionResult.summaryCacheCreationInputTokens ?? 0,
|
|
977
|
-
step.compactionResult.summaryCacheReadInputTokens ?? 0,
|
|
978
|
-
collapseRawResponses(step.compactionResult.summaryRawResponses),
|
|
979
|
-
);
|
|
980
|
-
ctx.graphMemory.onCompacted(
|
|
981
|
-
step.compactionResult.compactedPersistedMessages,
|
|
982
1413
|
);
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
1414
|
+
},
|
|
1415
|
+
onCompactionResult: async (result) => {
|
|
1416
|
+
// Track circuit-breaker state whenever the reducer invoked
|
|
1417
|
+
// compaction. The reducer's forced_compaction tier uses
|
|
1418
|
+
// force:true, so it bypasses the open-circuit check, but we
|
|
1419
|
+
// still want failure tracking to detect a run of broken
|
|
1420
|
+
// summaries and clear the counter on success. Only track when
|
|
1421
|
+
// the summary LLM actually ran — `summaryFailed === undefined`
|
|
1422
|
+
// indicates an early return (no eligible messages,
|
|
1423
|
+
// truncation-only path, etc.) that shouldn't influence the
|
|
1424
|
+
// breaker.
|
|
1425
|
+
if (result.summaryFailed !== undefined) {
|
|
1426
|
+
await trackCompactionOutcome(ctx, result.summaryFailed, onEvent);
|
|
1427
|
+
}
|
|
1428
|
+
if (result.compacted) {
|
|
1429
|
+
applyCompactionResult(ctx, result, onEvent, reqId);
|
|
1430
|
+
shouldInjectWorkspace = true;
|
|
1431
|
+
}
|
|
1432
|
+
},
|
|
1433
|
+
reinjectForMode: async (
|
|
1434
|
+
reducedMessages,
|
|
1435
|
+
mode,
|
|
1436
|
+
stepCompacted,
|
|
1437
|
+
accumulatedCompacted,
|
|
1438
|
+
) => {
|
|
1439
|
+
// Mirror the pre-PR-23 behavior: `ctx.messages` must track the
|
|
1440
|
+
// reducer's latest output before re-injection runs, because other
|
|
1441
|
+
// sites consulted through `injectionOpts` (`workspaceTopLevelContext`,
|
|
1442
|
+
// slack history, etc.) depend on it and `applyCompactionResult`
|
|
1443
|
+
// only updates `ctx.messages` on a compaction tier. Assigning here
|
|
1444
|
+
// keeps non-compaction tiers (tool-result truncation, media
|
|
1445
|
+
// stubbing, injection downgrade) observable to downstream
|
|
1446
|
+
// injection assembly on the same turn.
|
|
1447
|
+
ctx.messages = reducedMessages;
|
|
1448
|
+
|
|
1449
|
+
// When THIS iteration compacted, it stripped existing NOW.md /
|
|
1450
|
+
// PKB blocks — so we re-inject current content. A later iteration
|
|
1451
|
+
// that only truncates or downgrades must NOT re-force PKB/NOW,
|
|
1452
|
+
// or each round would grow the token count. Matches the
|
|
1453
|
+
// pre-PR-23 per-iteration `step.compactionResult?.compacted` gate.
|
|
1454
|
+
const injection = await applyRuntimeInjections(reducedMessages, {
|
|
1455
|
+
...injectionOpts,
|
|
1456
|
+
...(stepCompacted && { pkbContext: currentPkbContent }),
|
|
1457
|
+
...(stepCompacted && { nowScratchpad: currentNowContent }),
|
|
1458
|
+
workspaceTopLevelContext: shouldInjectWorkspace
|
|
1459
|
+
? ctx.workspaceTopLevelContext
|
|
1460
|
+
: null,
|
|
1461
|
+
// Once ANY iteration has compacted `ctx.messages`, the captured
|
|
1462
|
+
// `slackChronologicalMessages` snapshot (built from the full
|
|
1463
|
+
// persisted transcript) would overwrite the compacted history
|
|
1464
|
+
// and undo compaction. Suppress the override from here on —
|
|
1465
|
+
// sticky across subsequent non-compacting iterations.
|
|
1466
|
+
slackChronologicalMessages: accumulatedCompacted
|
|
1467
|
+
? null
|
|
1468
|
+
: injectionOpts.slackChronologicalMessages,
|
|
1469
|
+
mode,
|
|
1470
|
+
turnContext: buildPluginTurnContext(ctx, reqId),
|
|
1471
|
+
});
|
|
1472
|
+
let next = injection.messages;
|
|
1473
|
+
if (isTrustedActor && mode !== "minimal") {
|
|
1474
|
+
const memResult = ctx.graphMemory.reinjectCachedMemory(next);
|
|
1475
|
+
next = memResult.runMessages;
|
|
1476
|
+
}
|
|
1477
|
+
return next;
|
|
1478
|
+
},
|
|
1479
|
+
estimatePostInjection: (runMsgs) =>
|
|
1480
|
+
estimatePromptTokens(runMsgs, ctx.systemPrompt, {
|
|
1481
|
+
providerName: estimationProviderName,
|
|
1482
|
+
toolTokenBudget,
|
|
997
1483
|
}),
|
|
998
|
-
|
|
999
|
-
? ctx.workspaceTopLevelContext
|
|
1000
|
-
: null,
|
|
1001
|
-
mode: currentInjectionMode,
|
|
1002
|
-
});
|
|
1003
|
-
if (isTrustedActor && currentInjectionMode !== "minimal") {
|
|
1004
|
-
const memResult = ctx.graphMemory.reinjectCachedMemory(runMessages);
|
|
1005
|
-
runMessages = memResult.runMessages;
|
|
1006
|
-
}
|
|
1484
|
+
};
|
|
1007
1485
|
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1486
|
+
const overflowResult = await runPipeline<
|
|
1487
|
+
OverflowReduceArgs,
|
|
1488
|
+
OverflowReduceResult
|
|
1489
|
+
>(
|
|
1490
|
+
"overflowReduce",
|
|
1491
|
+
getMiddlewaresFor("overflowReduce"),
|
|
1492
|
+
// Terminal — only reached when every registered middleware calls
|
|
1493
|
+
// `next` and delegates past the innermost layer. The default plugin
|
|
1494
|
+
// is a terminal itself (it doesn't call `next`), so in practice
|
|
1495
|
+
// this fallback fires only when the default has been explicitly
|
|
1496
|
+
// deregistered (tests) and no user plugin replaces it. Strict-fail
|
|
1497
|
+
// semantics: throw so the missing terminal surfaces as a visible
|
|
1498
|
+
// error instead of silently returning the history untouched.
|
|
1499
|
+
async () => {
|
|
1500
|
+
throw new PluginExecutionError(
|
|
1501
|
+
"overflowReduce pipeline has no terminal handler — every reducer middleware called next() without providing a replacement",
|
|
1502
|
+
"overflowReduce",
|
|
1503
|
+
);
|
|
1504
|
+
},
|
|
1505
|
+
overflowArgs,
|
|
1506
|
+
buildPluginTurnContext(ctx, reqId),
|
|
1507
|
+
DEFAULT_TIMEOUTS.overflowReduce,
|
|
1508
|
+
);
|
|
1016
1509
|
|
|
1017
|
-
|
|
1510
|
+
ctx.messages = overflowResult.messages;
|
|
1511
|
+
runMessages = overflowResult.runMessages;
|
|
1512
|
+
currentInjectionMode = overflowResult.injectionMode;
|
|
1513
|
+
reducerState = overflowResult.reducerState;
|
|
1514
|
+
if (overflowResult.reducerCompacted) {
|
|
1515
|
+
reducerCompacted = true;
|
|
1018
1516
|
}
|
|
1019
1517
|
}
|
|
1020
1518
|
|
|
1021
|
-
// Pre-run repair
|
|
1519
|
+
// Pre-run repair — routed through the `historyRepair` plugin pipeline so
|
|
1520
|
+
// plugins can observe or override repair behavior. The default plugin's
|
|
1521
|
+
// middleware is a passthrough; the actual repair runs in the terminal
|
|
1522
|
+
// (`defaultHistoryRepairTerminal`).
|
|
1022
1523
|
let preRepairMessages = runMessages;
|
|
1023
|
-
|
|
1524
|
+
let preRunRepair: HistoryRepairResult | null = null;
|
|
1525
|
+
try {
|
|
1526
|
+
preRunRepair = await runPipeline<HistoryRepairArgs, HistoryRepairResult>(
|
|
1527
|
+
"historyRepair",
|
|
1528
|
+
getMiddlewaresFor("historyRepair"),
|
|
1529
|
+
async (args) => defaultHistoryRepairTerminal(args),
|
|
1530
|
+
{ history: runMessages, provider: ctx.provider.name },
|
|
1531
|
+
buildPluginTurnContext(ctx, reqId),
|
|
1532
|
+
DEFAULT_TIMEOUTS.historyRepair,
|
|
1533
|
+
);
|
|
1534
|
+
} catch (err) {
|
|
1535
|
+
if (err instanceof PluginTimeoutError) {
|
|
1536
|
+
// Pipeline exceeded its budget — likely a misbehaving third-party
|
|
1537
|
+
// middleware. Degrade gracefully by proceeding with the un-repaired
|
|
1538
|
+
// history rather than turn-fatal-erroring; un-repaired history is
|
|
1539
|
+
// strictly better than no turn at all, and the provider call itself
|
|
1540
|
+
// will still error visibly if the drift is unrecoverable.
|
|
1541
|
+
rlog.warn(
|
|
1542
|
+
{ err, phase: "pre_run" },
|
|
1543
|
+
"historyRepair pipeline timed out — proceeding with un-repaired history",
|
|
1544
|
+
);
|
|
1545
|
+
} else {
|
|
1546
|
+
throw err;
|
|
1547
|
+
}
|
|
1548
|
+
}
|
|
1024
1549
|
if (
|
|
1025
|
-
preRunRepair
|
|
1026
|
-
preRunRepair.stats.
|
|
1027
|
-
|
|
1028
|
-
|
|
1550
|
+
preRunRepair !== null &&
|
|
1551
|
+
(preRunRepair.stats.assistantToolResultsMigrated > 0 ||
|
|
1552
|
+
preRunRepair.stats.missingToolResultsInserted > 0 ||
|
|
1553
|
+
preRunRepair.stats.orphanToolResultsDowngraded > 0 ||
|
|
1554
|
+
preRunRepair.stats.consecutiveSameRoleMerged > 0)
|
|
1029
1555
|
) {
|
|
1030
1556
|
rlog.warn(
|
|
1031
1557
|
{ phase: "pre_run", ...preRunRepair.stats },
|
|
@@ -1034,6 +1560,20 @@ export async function runAgentLoopImpl(
|
|
|
1034
1560
|
runMessages = preRunRepair.messages;
|
|
1035
1561
|
}
|
|
1036
1562
|
|
|
1563
|
+
// Replace historical web_search_tool_result blocks with text summaries.
|
|
1564
|
+
// The opaque `encrypted_content` tokens Anthropic attaches to each result
|
|
1565
|
+
// expire / are route-scoped; replaying a stale token is rejected with
|
|
1566
|
+
// `Invalid encrypted_content in search_result block`. Titles + URLs
|
|
1567
|
+
// preserve enough context for the model on follow-up turns.
|
|
1568
|
+
const webSearchStrip = stripHistoricalWebSearchResults(runMessages);
|
|
1569
|
+
if (webSearchStrip.stats.blocksStripped > 0) {
|
|
1570
|
+
rlog.info(
|
|
1571
|
+
{ phase: "pre_run", ...webSearchStrip.stats },
|
|
1572
|
+
"Converted historical web_search_tool_result blocks to text summaries",
|
|
1573
|
+
);
|
|
1574
|
+
runMessages = webSearchStrip.messages;
|
|
1575
|
+
}
|
|
1576
|
+
|
|
1037
1577
|
let preRunHistoryLength = runMessages.length;
|
|
1038
1578
|
|
|
1039
1579
|
const shouldGenerateTitle = isReplaceableTitle(
|
|
@@ -1055,18 +1595,14 @@ export async function runAgentLoopImpl(
|
|
|
1055
1595
|
|
|
1056
1596
|
let yieldedForBudget = false;
|
|
1057
1597
|
|
|
1058
|
-
const onCheckpoint = (
|
|
1059
|
-
|
|
1598
|
+
const onCheckpoint = async (
|
|
1599
|
+
checkpoint: CheckpointInfo,
|
|
1600
|
+
): Promise<CheckpointDecision> => {
|
|
1060
1601
|
state.currentTurnToolNames = [];
|
|
1061
1602
|
|
|
1062
1603
|
if (ctx.canHandoffAtCheckpoint()) {
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
turnTools.every((n) => n.startsWith("browser_"));
|
|
1066
|
-
if (!inBrowserFlow) {
|
|
1067
|
-
yieldedForHandoff = true;
|
|
1068
|
-
return "yield";
|
|
1069
|
-
}
|
|
1604
|
+
yieldedForHandoff = true;
|
|
1605
|
+
return "yield";
|
|
1070
1606
|
}
|
|
1071
1607
|
|
|
1072
1608
|
// Mid-loop token budget check: estimate current context size and
|
|
@@ -1074,11 +1610,7 @@ export async function runAgentLoopImpl(
|
|
|
1074
1610
|
// conversation-agent-loop run compaction before the provider rejects.
|
|
1075
1611
|
if (overflowRecovery.enabled) {
|
|
1076
1612
|
const midLoopThreshold = preflightBudget * 0.85;
|
|
1077
|
-
const estimated =
|
|
1078
|
-
checkpoint.history,
|
|
1079
|
-
ctx.systemPrompt,
|
|
1080
|
-
{ providerName: ctx.provider.name, toolTokenBudget },
|
|
1081
|
-
);
|
|
1613
|
+
const estimated = await runTokenEstimatePipeline(checkpoint.history);
|
|
1082
1614
|
if (estimated > midLoopThreshold) {
|
|
1083
1615
|
rlog.warn(
|
|
1084
1616
|
{ phase: "mid-loop", estimated, threshold: midLoopThreshold },
|
|
@@ -1094,7 +1626,15 @@ export async function runAgentLoopImpl(
|
|
|
1094
1626
|
|
|
1095
1627
|
turnStarted = true;
|
|
1096
1628
|
|
|
1097
|
-
|
|
1629
|
+
rlog.info({ callSite: turnCallSite }, "Starting agent loop run");
|
|
1630
|
+
|
|
1631
|
+
// Thread the orchestrator's canonical per-turn context into the agent
|
|
1632
|
+
// loop so its internal pipeline invocations (llmCall, emptyResponse,
|
|
1633
|
+
// toolError, toolResultTruncate, toolExecute) see the real
|
|
1634
|
+
// conversation identity / trust / contextWindowManager instead of the
|
|
1635
|
+
// synthesized `"agent-loop"` placeholder. The loop clones this value
|
|
1636
|
+
// and overwrites `turnIndex` with its own tool-use iteration counter.
|
|
1637
|
+
const loopTurnCtx = buildPluginTurnContext(ctx, reqId);
|
|
1098
1638
|
|
|
1099
1639
|
let updatedHistory = await ctx.agentLoop.run(
|
|
1100
1640
|
runMessages,
|
|
@@ -1102,6 +1642,13 @@ export async function runAgentLoopImpl(
|
|
|
1102
1642
|
abortController.signal,
|
|
1103
1643
|
reqId,
|
|
1104
1644
|
onCheckpoint,
|
|
1645
|
+
turnCallSite,
|
|
1646
|
+
loopTurnCtx,
|
|
1647
|
+
);
|
|
1648
|
+
|
|
1649
|
+
rlog.info(
|
|
1650
|
+
{ resultMessageCount: updatedHistory.length },
|
|
1651
|
+
"Agent loop run completed",
|
|
1105
1652
|
);
|
|
1106
1653
|
|
|
1107
1654
|
// ── Proactive mid-loop compaction ───────────────────────────────
|
|
@@ -1129,6 +1676,14 @@ export async function runAgentLoopImpl(
|
|
|
1129
1676
|
// so we compact the "raw" persistent messages.
|
|
1130
1677
|
const rawHistory = stripInjectionsForCompaction(updatedHistory);
|
|
1131
1678
|
ctx.messages = rawHistory;
|
|
1679
|
+
try {
|
|
1680
|
+
clearStrippedInjectionMetadataForConversation(ctx.conversationId);
|
|
1681
|
+
} catch (err) {
|
|
1682
|
+
rlog.warn(
|
|
1683
|
+
{ err },
|
|
1684
|
+
"Failed to clear stripped-injection metadata after compaction strip (non-fatal)",
|
|
1685
|
+
);
|
|
1686
|
+
}
|
|
1132
1687
|
|
|
1133
1688
|
ctx.emitActivityState(
|
|
1134
1689
|
"thinking",
|
|
@@ -1137,56 +1692,61 @@ export async function runAgentLoopImpl(
|
|
|
1137
1692
|
reqId,
|
|
1138
1693
|
"Compacting context",
|
|
1139
1694
|
);
|
|
1140
|
-
|
|
1141
|
-
ctx.
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1695
|
+
let midLoopCompact: Awaited<
|
|
1696
|
+
ReturnType<typeof ctx.contextWindowManager.maybeCompact>
|
|
1697
|
+
>;
|
|
1698
|
+
try {
|
|
1699
|
+
midLoopCompact = (await runPipeline<CompactionArgs, CompactionResult>(
|
|
1700
|
+
"compaction",
|
|
1701
|
+
getMiddlewaresFor("compaction"),
|
|
1702
|
+
(args) =>
|
|
1703
|
+
defaultCompactionTerminal(args, buildPluginTurnContext(ctx, reqId)),
|
|
1704
|
+
{
|
|
1705
|
+
messages: ctx.messages,
|
|
1706
|
+
signal: abortController.signal,
|
|
1707
|
+
options: {
|
|
1708
|
+
lastCompactedAt: ctx.contextCompactedAt ?? undefined,
|
|
1709
|
+
force: true,
|
|
1710
|
+
targetInputTokensOverride: preflightBudget,
|
|
1711
|
+
conversationOriginChannel:
|
|
1712
|
+
getConversationOriginChannel(ctx.conversationId) ?? undefined,
|
|
1713
|
+
},
|
|
1714
|
+
},
|
|
1715
|
+
buildPluginTurnContext(ctx, reqId),
|
|
1716
|
+
DEFAULT_TIMEOUTS.compaction,
|
|
1717
|
+
)) as Awaited<ReturnType<typeof ctx.contextWindowManager.maybeCompact>>;
|
|
1718
|
+
} catch (err) {
|
|
1719
|
+
if (err instanceof PluginTimeoutError) {
|
|
1720
|
+
// Mid-loop compaction timed out. Record the failure for the
|
|
1721
|
+
// circuit breaker and escalate to the convergence loop's more
|
|
1722
|
+
// aggressive reducer tiers (tool-result truncation, media
|
|
1723
|
+
// stubbing, injection downgrade) by flipping the overflow flag
|
|
1724
|
+
// and breaking out of the mid-loop retry. The existing
|
|
1725
|
+
// "exhausted all attempts" block further down handles the
|
|
1726
|
+
// escalation.
|
|
1727
|
+
rlog.warn(
|
|
1728
|
+
{ err, phase: "mid-loop-compact" },
|
|
1729
|
+
"Compaction pipeline timed out — escalating to convergence loop",
|
|
1730
|
+
);
|
|
1731
|
+
await trackCompactionOutcome(ctx, true, onEvent);
|
|
1732
|
+
state.contextTooLargeDetected = true;
|
|
1733
|
+
break;
|
|
1734
|
+
}
|
|
1735
|
+
throw err;
|
|
1736
|
+
}
|
|
1737
|
+
// `force: true` bypasses the cooldown/threshold gates but early returns
|
|
1738
|
+
// for "no eligible messages" / "insufficient messages" still leave
|
|
1739
|
+
// `summaryFailed` undefined. Only track when the summary LLM actually ran.
|
|
1740
|
+
if (midLoopCompact.summaryFailed !== undefined) {
|
|
1741
|
+
await trackCompactionOutcome(
|
|
1178
1742
|
ctx,
|
|
1179
|
-
midLoopCompact.
|
|
1180
|
-
midLoopCompact.summaryOutputTokens,
|
|
1181
|
-
midLoopCompact.summaryModel,
|
|
1743
|
+
midLoopCompact.summaryFailed,
|
|
1182
1744
|
onEvent,
|
|
1183
|
-
"context_compactor",
|
|
1184
|
-
reqId,
|
|
1185
|
-
midLoopCompact.summaryCacheCreationInputTokens ?? 0,
|
|
1186
|
-
midLoopCompact.summaryCacheReadInputTokens ?? 0,
|
|
1187
|
-
collapseRawResponses(midLoopCompact.summaryRawResponses),
|
|
1188
1745
|
);
|
|
1189
|
-
|
|
1746
|
+
}
|
|
1747
|
+
if (midLoopCompact.compacted) {
|
|
1748
|
+
applyCompactionResult(ctx, midLoopCompact, onEvent, reqId);
|
|
1749
|
+
reducerCompacted = true;
|
|
1190
1750
|
shouldInjectWorkspace = true;
|
|
1191
1751
|
}
|
|
1192
1752
|
|
|
@@ -1194,18 +1754,34 @@ export async function runAgentLoopImpl(
|
|
|
1194
1754
|
// stripInjectionsForCompaction() unconditionally removed the existing
|
|
1195
1755
|
// NOW.md block from ctx.messages above, so we must always re-inject
|
|
1196
1756
|
// the current content regardless of whether compaction actually ran.
|
|
1197
|
-
|
|
1757
|
+
const injection = await applyRuntimeInjections(ctx.messages, {
|
|
1198
1758
|
...injectionOpts,
|
|
1199
1759
|
pkbContext: currentPkbContent,
|
|
1200
1760
|
nowScratchpad: currentNowContent,
|
|
1201
1761
|
workspaceTopLevelContext: shouldInjectWorkspace
|
|
1202
1762
|
? ctx.workspaceTopLevelContext
|
|
1203
1763
|
: null,
|
|
1764
|
+
// Suppress the chronological-transcript snapshot once the reducer
|
|
1765
|
+
// has collapsed `ctx.messages`; the captured snapshot reflects the
|
|
1766
|
+
// full persisted transcript and would overwrite compaction.
|
|
1767
|
+
slackChronologicalMessages: reducerCompacted
|
|
1768
|
+
? null
|
|
1769
|
+
: injectionOpts.slackChronologicalMessages,
|
|
1204
1770
|
mode: currentInjectionMode,
|
|
1771
|
+
turnContext: buildPluginTurnContext(ctx, reqId),
|
|
1205
1772
|
});
|
|
1773
|
+
runMessages = injection.messages;
|
|
1206
1774
|
if (isTrustedActor && currentInjectionMode !== "minimal") {
|
|
1207
1775
|
ctx.graphMemory.retrackCachedNodes();
|
|
1208
1776
|
}
|
|
1777
|
+
const midLoopCompactStrip = stripHistoricalWebSearchResults(runMessages);
|
|
1778
|
+
if (midLoopCompactStrip.stats.blocksStripped > 0) {
|
|
1779
|
+
rlog.info(
|
|
1780
|
+
{ phase: "mid-loop-compact", ...midLoopCompactStrip.stats },
|
|
1781
|
+
"Converted historical web_search_tool_result blocks to text summaries",
|
|
1782
|
+
);
|
|
1783
|
+
runMessages = midLoopCompactStrip.messages;
|
|
1784
|
+
}
|
|
1209
1785
|
preRepairMessages = runMessages;
|
|
1210
1786
|
preRunHistoryLength = runMessages.length;
|
|
1211
1787
|
|
|
@@ -1215,6 +1791,8 @@ export async function runAgentLoopImpl(
|
|
|
1215
1791
|
abortController.signal,
|
|
1216
1792
|
reqId,
|
|
1217
1793
|
onCheckpoint,
|
|
1794
|
+
turnCallSite,
|
|
1795
|
+
loopTurnCtx,
|
|
1218
1796
|
);
|
|
1219
1797
|
}
|
|
1220
1798
|
|
|
@@ -1244,9 +1822,20 @@ export async function runAgentLoopImpl(
|
|
|
1244
1822
|
{ phase: "retry" },
|
|
1245
1823
|
"Provider ordering error detected, attempting one-shot deep-repair retry",
|
|
1246
1824
|
);
|
|
1825
|
+
// Design note: deep-repair intentionally bypasses the `historyRepair`
|
|
1826
|
+
// plugin pipeline. Deep-repair is a recovery-only path triggered by a
|
|
1827
|
+
// provider ordering error — it must be deterministic and unaffected by
|
|
1828
|
+
// user middleware that might have caused (or be unable to recover from)
|
|
1829
|
+
// the original drift. Plugins can already observe / override the
|
|
1830
|
+
// pre-run repair via the `historyRepair` pipeline above; widening that
|
|
1831
|
+
// surface to deep-repair is intentionally deferred until there's a
|
|
1832
|
+
// concrete plugin-level use case. Do not route this call through
|
|
1833
|
+
// `runPipeline` without first revisiting that contract.
|
|
1247
1834
|
const retryRepair = deepRepairHistory(runMessages);
|
|
1248
1835
|
runMessages = retryRepair.messages;
|
|
1249
|
-
|
|
1836
|
+
const retryStrip = stripHistoricalWebSearchResults(runMessages);
|
|
1837
|
+
runMessages = retryStrip.messages;
|
|
1838
|
+
preRepairMessages = runMessages;
|
|
1250
1839
|
preRunHistoryLength = runMessages.length;
|
|
1251
1840
|
state.orderingErrorDetected = false;
|
|
1252
1841
|
state.deferredOrderingError = null;
|
|
@@ -1257,6 +1846,8 @@ export async function runAgentLoopImpl(
|
|
|
1257
1846
|
abortController.signal,
|
|
1258
1847
|
reqId,
|
|
1259
1848
|
onCheckpoint,
|
|
1849
|
+
turnCallSite,
|
|
1850
|
+
loopTurnCtx,
|
|
1260
1851
|
);
|
|
1261
1852
|
|
|
1262
1853
|
if (state.orderingErrorDetected) {
|
|
@@ -1270,8 +1861,7 @@ export async function runAgentLoopImpl(
|
|
|
1270
1861
|
// ── Bounded context overflow convergence loop ──────────────────
|
|
1271
1862
|
// When the provider rejects with context-too-large, iterate through
|
|
1272
1863
|
// reducer tiers (forced compaction, tool-result truncation, media
|
|
1273
|
-
// stubbing, injection downgrade)
|
|
1274
|
-
// interactive latest-turn compression.
|
|
1864
|
+
// stubbing, injection downgrade).
|
|
1275
1865
|
//
|
|
1276
1866
|
// When progress was made (agent added messages before hitting the
|
|
1277
1867
|
// limit), incorporate those new messages into ctx.messages so the
|
|
@@ -1286,6 +1876,14 @@ export async function runAgentLoopImpl(
|
|
|
1286
1876
|
|
|
1287
1877
|
if (updatedHistory.length > preRunHistoryLength) {
|
|
1288
1878
|
ctx.messages = stripInjectionsForCompaction(updatedHistory);
|
|
1879
|
+
try {
|
|
1880
|
+
clearStrippedInjectionMetadataForConversation(ctx.conversationId);
|
|
1881
|
+
} catch (err) {
|
|
1882
|
+
rlog.warn(
|
|
1883
|
+
{ err },
|
|
1884
|
+
"Failed to clear stripped-injection metadata after compaction strip (non-fatal)",
|
|
1885
|
+
);
|
|
1886
|
+
}
|
|
1289
1887
|
convergenceStripped = true;
|
|
1290
1888
|
preRepairMessages = updatedHistory;
|
|
1291
1889
|
preRunHistoryLength = updatedHistory.length;
|
|
@@ -1298,14 +1896,19 @@ export async function runAgentLoopImpl(
|
|
|
1298
1896
|
// message (e.g. "242201 tokens > 200000"), use it to correct the
|
|
1299
1897
|
// compaction target. The estimator may significantly underestimate
|
|
1300
1898
|
// (e.g. estimated 185k but actual was 242k), so using the
|
|
1301
|
-
// uncorrected preflightBudget would still be too high.
|
|
1899
|
+
// uncorrected preflightBudget would still be too high. Passes the raw
|
|
1900
|
+
// error so ContextOverflowError.actualTokens can short-circuit the
|
|
1901
|
+
// string-regex path for proxy-rewrapped untyped errors.
|
|
1302
1902
|
const actualTokens = parseActualTokensFromError(
|
|
1303
|
-
state.
|
|
1903
|
+
state.contextTooLargeError,
|
|
1304
1904
|
);
|
|
1305
1905
|
const estimatedTokensAtOverflow = estimatePromptTokens(
|
|
1306
1906
|
ctx.messages,
|
|
1307
1907
|
ctx.systemPrompt,
|
|
1308
|
-
{
|
|
1908
|
+
{
|
|
1909
|
+
providerName: estimationProviderName,
|
|
1910
|
+
toolTokenBudget,
|
|
1911
|
+
},
|
|
1309
1912
|
);
|
|
1310
1913
|
let correctedTarget = preflightBudget;
|
|
1311
1914
|
if (actualTokens && estimatedTokensAtOverflow > 0) {
|
|
@@ -1353,9 +1956,9 @@ export async function runAgentLoopImpl(
|
|
|
1353
1956
|
const step = await reduceContextOverflow(
|
|
1354
1957
|
ctx.messages,
|
|
1355
1958
|
{
|
|
1356
|
-
providerName:
|
|
1959
|
+
providerName: estimationProviderName,
|
|
1357
1960
|
systemPrompt: ctx.systemPrompt,
|
|
1358
|
-
contextWindow: config.contextWindow,
|
|
1961
|
+
contextWindow: config.llm.default.contextWindow,
|
|
1359
1962
|
targetTokens: correctedTarget,
|
|
1360
1963
|
toolTokenBudget,
|
|
1361
1964
|
},
|
|
@@ -1369,66 +1972,55 @@ export async function runAgentLoopImpl(
|
|
|
1369
1972
|
ctx.messages = step.messages;
|
|
1370
1973
|
currentInjectionMode = step.state.injectionMode;
|
|
1371
1974
|
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
// Fire auto-analysis on compaction — see forceCompact() for rationale.
|
|
1382
|
-
enqueueAutoAnalysisOnCompaction(
|
|
1383
|
-
ctx.conversationId,
|
|
1384
|
-
ctx.trustContext?.trustClass,
|
|
1385
|
-
);
|
|
1386
|
-
onEvent({
|
|
1387
|
-
type: "context_compacted",
|
|
1388
|
-
previousEstimatedInputTokens:
|
|
1389
|
-
step.compactionResult.previousEstimatedInputTokens,
|
|
1390
|
-
estimatedInputTokens: step.compactionResult.estimatedInputTokens,
|
|
1391
|
-
maxInputTokens: step.compactionResult.maxInputTokens,
|
|
1392
|
-
thresholdTokens: step.compactionResult.thresholdTokens,
|
|
1393
|
-
compactedMessages: step.compactionResult.compactedMessages,
|
|
1394
|
-
summaryCalls: step.compactionResult.summaryCalls,
|
|
1395
|
-
summaryInputTokens: step.compactionResult.summaryInputTokens,
|
|
1396
|
-
summaryOutputTokens: step.compactionResult.summaryOutputTokens,
|
|
1397
|
-
summaryModel: step.compactionResult.summaryModel,
|
|
1398
|
-
});
|
|
1399
|
-
emitUsage(
|
|
1975
|
+
// See the preflight reducer call above for rationale. Only track when
|
|
1976
|
+
// the summary LLM actually ran — `summaryFailed === undefined`
|
|
1977
|
+
// indicates the reducer's forced compaction took an early-return path
|
|
1978
|
+
// without calling the summary LLM.
|
|
1979
|
+
if (
|
|
1980
|
+
step.compactionResult &&
|
|
1981
|
+
step.compactionResult.summaryFailed !== undefined
|
|
1982
|
+
) {
|
|
1983
|
+
await trackCompactionOutcome(
|
|
1400
1984
|
ctx,
|
|
1401
|
-
step.compactionResult.
|
|
1402
|
-
step.compactionResult.summaryOutputTokens,
|
|
1403
|
-
step.compactionResult.summaryModel,
|
|
1985
|
+
step.compactionResult.summaryFailed,
|
|
1404
1986
|
onEvent,
|
|
1405
|
-
"context_compactor",
|
|
1406
|
-
reqId,
|
|
1407
|
-
step.compactionResult.summaryCacheCreationInputTokens ?? 0,
|
|
1408
|
-
step.compactionResult.summaryCacheReadInputTokens ?? 0,
|
|
1409
|
-
collapseRawResponses(step.compactionResult.summaryRawResponses),
|
|
1410
|
-
);
|
|
1411
|
-
ctx.graphMemory.onCompacted(
|
|
1412
|
-
step.compactionResult.compactedPersistedMessages,
|
|
1413
1987
|
);
|
|
1988
|
+
}
|
|
1989
|
+
|
|
1990
|
+
if (step.compactionResult?.compacted) {
|
|
1991
|
+
applyCompactionResult(ctx, step.compactionResult, onEvent, reqId);
|
|
1414
1992
|
shouldInjectWorkspace = true;
|
|
1993
|
+
reducerCompacted = true;
|
|
1415
1994
|
}
|
|
1416
1995
|
|
|
1417
1996
|
// Only re-inject NOW.md when ctx.messages was actually stripped;
|
|
1418
1997
|
// otherwise the existing NOW.md block is still present and
|
|
1419
1998
|
// re-injecting would duplicate it.
|
|
1420
|
-
|
|
1999
|
+
const injection = await applyRuntimeInjections(ctx.messages, {
|
|
1421
2000
|
...injectionOpts,
|
|
1422
2001
|
pkbContext: currentPkbContent,
|
|
1423
2002
|
nowScratchpad: convergenceStripped ? currentNowContent : null,
|
|
1424
2003
|
workspaceTopLevelContext: shouldInjectWorkspace
|
|
1425
2004
|
? ctx.workspaceTopLevelContext
|
|
1426
2005
|
: null,
|
|
2006
|
+
slackChronologicalMessages: reducerCompacted
|
|
2007
|
+
? null
|
|
2008
|
+
: injectionOpts.slackChronologicalMessages,
|
|
1427
2009
|
mode: currentInjectionMode,
|
|
2010
|
+
turnContext: buildPluginTurnContext(ctx, reqId),
|
|
1428
2011
|
});
|
|
2012
|
+
runMessages = injection.messages;
|
|
1429
2013
|
if (isTrustedActor && currentInjectionMode !== "minimal") {
|
|
1430
2014
|
ctx.graphMemory.retrackCachedNodes();
|
|
1431
2015
|
}
|
|
2016
|
+
const convergenceStrip = stripHistoricalWebSearchResults(runMessages);
|
|
2017
|
+
if (convergenceStrip.stats.blocksStripped > 0) {
|
|
2018
|
+
rlog.info(
|
|
2019
|
+
{ phase: "convergence", ...convergenceStrip.stats },
|
|
2020
|
+
"Converted historical web_search_tool_result blocks to text summaries",
|
|
2021
|
+
);
|
|
2022
|
+
runMessages = convergenceStrip.messages;
|
|
2023
|
+
}
|
|
1432
2024
|
preRepairMessages = runMessages;
|
|
1433
2025
|
preRunHistoryLength = runMessages.length;
|
|
1434
2026
|
state.contextTooLargeDetected = false;
|
|
@@ -1440,6 +2032,8 @@ export async function runAgentLoopImpl(
|
|
|
1440
2032
|
abortController.signal,
|
|
1441
2033
|
reqId,
|
|
1442
2034
|
onCheckpoint,
|
|
2035
|
+
turnCallSite,
|
|
2036
|
+
loopTurnCtx,
|
|
1443
2037
|
);
|
|
1444
2038
|
|
|
1445
2039
|
// If the rerun still yields at checkpoint, the turn is still
|
|
@@ -1461,6 +2055,14 @@ export async function runAgentLoopImpl(
|
|
|
1461
2055
|
// pre-rerun messages.
|
|
1462
2056
|
if (updatedHistory.length > preRunHistoryLength) {
|
|
1463
2057
|
ctx.messages = stripInjectionsForCompaction(updatedHistory);
|
|
2058
|
+
try {
|
|
2059
|
+
clearStrippedInjectionMetadataForConversation(ctx.conversationId);
|
|
2060
|
+
} catch (err) {
|
|
2061
|
+
rlog.warn(
|
|
2062
|
+
{ err },
|
|
2063
|
+
"Failed to clear stripped-injection metadata after compaction strip (non-fatal)",
|
|
2064
|
+
);
|
|
2065
|
+
}
|
|
1464
2066
|
convergenceStripped = true;
|
|
1465
2067
|
preRepairMessages = updatedHistory;
|
|
1466
2068
|
preRunHistoryLength = updatedHistory.length;
|
|
@@ -1470,215 +2072,113 @@ export async function runAgentLoopImpl(
|
|
|
1470
2072
|
|
|
1471
2073
|
// All reducer tiers exhausted but provider still rejects —
|
|
1472
2074
|
// consult the overflow policy for latest-turn compression.
|
|
1473
|
-
//
|
|
1474
|
-
//
|
|
2075
|
+
// The policy either auto-compresses the latest turn or falls
|
|
2076
|
+
// through to the final graceful-error fallback below.
|
|
1475
2077
|
if (state.contextTooLargeDetected) {
|
|
1476
2078
|
const action = resolveOverflowAction({
|
|
1477
2079
|
overflowRecovery,
|
|
1478
2080
|
isInteractive: isInteractiveResolved,
|
|
1479
2081
|
});
|
|
1480
2082
|
|
|
1481
|
-
if (action === "
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
2083
|
+
if (action === "auto_compress_latest_turn") {
|
|
2084
|
+
// Auto-compress without asking — users opt out via the "drop" policy.
|
|
2085
|
+
ctx.emitActivityState(
|
|
2086
|
+
"thinking",
|
|
2087
|
+
"context_compacting",
|
|
2088
|
+
"assistant_turn",
|
|
2089
|
+
reqId,
|
|
2090
|
+
);
|
|
2091
|
+
let emergencyCompact: Awaited<
|
|
2092
|
+
ReturnType<typeof ctx.contextWindowManager.maybeCompact>
|
|
2093
|
+
> | null = null;
|
|
2094
|
+
try {
|
|
2095
|
+
emergencyCompact = (await runPipeline<
|
|
2096
|
+
CompactionArgs,
|
|
2097
|
+
CompactionResult
|
|
2098
|
+
>(
|
|
2099
|
+
"compaction",
|
|
2100
|
+
getMiddlewaresFor("compaction"),
|
|
2101
|
+
(args) =>
|
|
2102
|
+
defaultCompactionTerminal(
|
|
2103
|
+
args,
|
|
2104
|
+
buildPluginTurnContext(ctx, reqId),
|
|
2105
|
+
),
|
|
2106
|
+
{
|
|
2107
|
+
messages: ctx.messages,
|
|
2108
|
+
signal: abortController.signal,
|
|
2109
|
+
options: {
|
|
1493
2110
|
lastCompactedAt: ctx.contextCompactedAt ?? undefined,
|
|
1494
2111
|
force: true,
|
|
1495
2112
|
minKeepRecentUserTurns: 0,
|
|
1496
2113
|
targetInputTokensOverride: correctedTarget,
|
|
1497
2114
|
},
|
|
2115
|
+
},
|
|
2116
|
+
buildPluginTurnContext(ctx, reqId),
|
|
2117
|
+
DEFAULT_TIMEOUTS.compaction,
|
|
2118
|
+
)) as Awaited<
|
|
2119
|
+
ReturnType<typeof ctx.contextWindowManager.maybeCompact>
|
|
2120
|
+
>;
|
|
2121
|
+
} catch (err) {
|
|
2122
|
+
if (err instanceof PluginTimeoutError) {
|
|
2123
|
+
// Emergency compaction timed out. Record the circuit-breaker
|
|
2124
|
+
// failure and fall through to the graceful-error path below
|
|
2125
|
+
// (the unsuccessful-compaction fallback) rather than hard-
|
|
2126
|
+
// failing the turn.
|
|
2127
|
+
rlog.warn(
|
|
2128
|
+
{ err, phase: "emergency-compaction" },
|
|
2129
|
+
"Emergency compaction pipeline timed out — continuing with overflow fallback",
|
|
1498
2130
|
);
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
ctx.contextCompactedAt = Date.now();
|
|
1504
|
-
updateConversationContextWindow(
|
|
1505
|
-
ctx.conversationId,
|
|
1506
|
-
emergencyCompact.summaryText,
|
|
1507
|
-
ctx.contextCompactedMessageCount,
|
|
1508
|
-
);
|
|
1509
|
-
// Fire auto-analysis on compaction — see forceCompact() for rationale.
|
|
1510
|
-
enqueueAutoAnalysisOnCompaction(
|
|
1511
|
-
ctx.conversationId,
|
|
1512
|
-
ctx.trustContext?.trustClass,
|
|
1513
|
-
);
|
|
1514
|
-
onEvent({
|
|
1515
|
-
type: "context_compacted",
|
|
1516
|
-
previousEstimatedInputTokens:
|
|
1517
|
-
emergencyCompact.previousEstimatedInputTokens,
|
|
1518
|
-
estimatedInputTokens: emergencyCompact.estimatedInputTokens,
|
|
1519
|
-
maxInputTokens: emergencyCompact.maxInputTokens,
|
|
1520
|
-
thresholdTokens: emergencyCompact.thresholdTokens,
|
|
1521
|
-
compactedMessages: emergencyCompact.compactedMessages,
|
|
1522
|
-
summaryCalls: emergencyCompact.summaryCalls,
|
|
1523
|
-
summaryInputTokens: emergencyCompact.summaryInputTokens,
|
|
1524
|
-
summaryOutputTokens: emergencyCompact.summaryOutputTokens,
|
|
1525
|
-
summaryModel: emergencyCompact.summaryModel,
|
|
1526
|
-
});
|
|
1527
|
-
emitUsage(
|
|
1528
|
-
ctx,
|
|
1529
|
-
emergencyCompact.summaryInputTokens,
|
|
1530
|
-
emergencyCompact.summaryOutputTokens,
|
|
1531
|
-
emergencyCompact.summaryModel,
|
|
1532
|
-
onEvent,
|
|
1533
|
-
"context_compactor",
|
|
1534
|
-
reqId,
|
|
1535
|
-
emergencyCompact.summaryCacheCreationInputTokens ?? 0,
|
|
1536
|
-
emergencyCompact.summaryCacheReadInputTokens ?? 0,
|
|
1537
|
-
collapseRawResponses(emergencyCompact.summaryRawResponses),
|
|
1538
|
-
);
|
|
1539
|
-
ctx.graphMemory.onCompacted(
|
|
1540
|
-
emergencyCompact.compactedPersistedMessages,
|
|
1541
|
-
);
|
|
1542
|
-
shouldInjectWorkspace = true;
|
|
2131
|
+
await trackCompactionOutcome(ctx, true, onEvent);
|
|
2132
|
+
emergencyCompact = null;
|
|
2133
|
+
} else {
|
|
2134
|
+
throw err;
|
|
1543
2135
|
}
|
|
1544
|
-
|
|
1545
|
-
// Only re-inject NOW.md when ctx.messages was actually stripped;
|
|
1546
|
-
// otherwise the existing block is still present.
|
|
1547
|
-
runMessages = applyRuntimeInjections(ctx.messages, {
|
|
1548
|
-
...injectionOpts,
|
|
1549
|
-
pkbContext: currentPkbContent,
|
|
1550
|
-
nowScratchpad: convergenceStripped ? currentNowContent : null,
|
|
1551
|
-
workspaceTopLevelContext: shouldInjectWorkspace
|
|
1552
|
-
? ctx.workspaceTopLevelContext
|
|
1553
|
-
: null,
|
|
1554
|
-
mode: currentInjectionMode,
|
|
1555
|
-
});
|
|
1556
|
-
if (isTrustedActor && currentInjectionMode !== "minimal") {
|
|
1557
|
-
ctx.graphMemory.retrackCachedNodes();
|
|
1558
|
-
}
|
|
1559
|
-
preRepairMessages = runMessages;
|
|
1560
|
-
preRunHistoryLength = runMessages.length;
|
|
1561
|
-
state.contextTooLargeDetected = false;
|
|
1562
|
-
|
|
1563
|
-
updatedHistory = await ctx.agentLoop.run(
|
|
1564
|
-
runMessages,
|
|
1565
|
-
eventHandler,
|
|
1566
|
-
abortController.signal,
|
|
1567
|
-
reqId,
|
|
1568
|
-
onCheckpoint,
|
|
1569
|
-
);
|
|
1570
|
-
} else {
|
|
1571
|
-
// User denied compression — emit a graceful assistant explanation
|
|
1572
|
-
// instead of a conversation_error, and end the turn cleanly.
|
|
1573
|
-
state.contextTooLargeDetected = false;
|
|
1574
|
-
const denyText =
|
|
1575
|
-
"The conversation has grown too long for the model to process, " +
|
|
1576
|
-
"and compression was declined. Please start a new conversation " +
|
|
1577
|
-
"or manually shorten the conversation to continue.";
|
|
1578
|
-
const loopChannelMeta = {
|
|
1579
|
-
...provenanceFromTrustContext(ctx.trustContext),
|
|
1580
|
-
userMessageChannel: capturedTurnChannelContext.userMessageChannel,
|
|
1581
|
-
assistantMessageChannel:
|
|
1582
|
-
capturedTurnChannelContext.assistantMessageChannel,
|
|
1583
|
-
userMessageInterface:
|
|
1584
|
-
capturedTurnInterfaceContext.userMessageInterface,
|
|
1585
|
-
assistantMessageInterface:
|
|
1586
|
-
capturedTurnInterfaceContext.assistantMessageInterface,
|
|
1587
|
-
};
|
|
1588
|
-
const denyMessage = createAssistantMessage(denyText);
|
|
1589
|
-
await addMessage(
|
|
1590
|
-
ctx.conversationId,
|
|
1591
|
-
"assistant",
|
|
1592
|
-
JSON.stringify(denyMessage.content),
|
|
1593
|
-
loopChannelMeta,
|
|
1594
|
-
);
|
|
1595
|
-
denyCompressionMessage = denyMessage;
|
|
1596
|
-
onEvent({
|
|
1597
|
-
type: "assistant_text_delta",
|
|
1598
|
-
text: denyText,
|
|
1599
|
-
conversationId: ctx.conversationId,
|
|
1600
|
-
});
|
|
1601
|
-
// Prevent the final error fallback from firing
|
|
1602
|
-
state.providerErrorUserMessage = null;
|
|
1603
2136
|
}
|
|
1604
|
-
|
|
1605
|
-
//
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
);
|
|
1612
|
-
const emergencyCompact = await ctx.contextWindowManager.maybeCompact(
|
|
1613
|
-
ctx.messages,
|
|
1614
|
-
abortController.signal,
|
|
1615
|
-
{
|
|
1616
|
-
lastCompactedAt: ctx.contextCompactedAt ?? undefined,
|
|
1617
|
-
force: true,
|
|
1618
|
-
minKeepRecentUserTurns: 0,
|
|
1619
|
-
targetInputTokensOverride: correctedTarget,
|
|
1620
|
-
},
|
|
1621
|
-
);
|
|
1622
|
-
if (emergencyCompact.compacted) {
|
|
1623
|
-
ctx.messages = emergencyCompact.messages;
|
|
1624
|
-
ctx.contextCompactedMessageCount +=
|
|
1625
|
-
emergencyCompact.compactedPersistedMessages;
|
|
1626
|
-
ctx.contextCompactedAt = Date.now();
|
|
1627
|
-
updateConversationContextWindow(
|
|
1628
|
-
ctx.conversationId,
|
|
1629
|
-
emergencyCompact.summaryText,
|
|
1630
|
-
ctx.contextCompactedMessageCount,
|
|
1631
|
-
);
|
|
1632
|
-
// Fire auto-analysis on compaction — see forceCompact() for rationale.
|
|
1633
|
-
enqueueAutoAnalysisOnCompaction(
|
|
1634
|
-
ctx.conversationId,
|
|
1635
|
-
ctx.trustContext?.trustClass,
|
|
1636
|
-
);
|
|
1637
|
-
onEvent({
|
|
1638
|
-
type: "context_compacted",
|
|
1639
|
-
previousEstimatedInputTokens:
|
|
1640
|
-
emergencyCompact.previousEstimatedInputTokens,
|
|
1641
|
-
estimatedInputTokens: emergencyCompact.estimatedInputTokens,
|
|
1642
|
-
maxInputTokens: emergencyCompact.maxInputTokens,
|
|
1643
|
-
thresholdTokens: emergencyCompact.thresholdTokens,
|
|
1644
|
-
compactedMessages: emergencyCompact.compactedMessages,
|
|
1645
|
-
summaryCalls: emergencyCompact.summaryCalls,
|
|
1646
|
-
summaryInputTokens: emergencyCompact.summaryInputTokens,
|
|
1647
|
-
summaryOutputTokens: emergencyCompact.summaryOutputTokens,
|
|
1648
|
-
summaryModel: emergencyCompact.summaryModel,
|
|
1649
|
-
});
|
|
1650
|
-
emitUsage(
|
|
2137
|
+
// Only track when the summary LLM actually ran; `force: true`
|
|
2138
|
+
// bypasses the cooldown but not the early-return paths.
|
|
2139
|
+
if (
|
|
2140
|
+
emergencyCompact &&
|
|
2141
|
+
emergencyCompact.summaryFailed !== undefined
|
|
2142
|
+
) {
|
|
2143
|
+
await trackCompactionOutcome(
|
|
1651
2144
|
ctx,
|
|
1652
|
-
emergencyCompact.
|
|
1653
|
-
emergencyCompact.summaryOutputTokens,
|
|
1654
|
-
emergencyCompact.summaryModel,
|
|
2145
|
+
emergencyCompact.summaryFailed,
|
|
1655
2146
|
onEvent,
|
|
1656
|
-
"context_compactor",
|
|
1657
|
-
reqId,
|
|
1658
|
-
emergencyCompact.summaryCacheCreationInputTokens ?? 0,
|
|
1659
|
-
emergencyCompact.summaryCacheReadInputTokens ?? 0,
|
|
1660
|
-
collapseRawResponses(emergencyCompact.summaryRawResponses),
|
|
1661
|
-
);
|
|
1662
|
-
ctx.graphMemory.onCompacted(
|
|
1663
|
-
emergencyCompact.compactedPersistedMessages,
|
|
1664
2147
|
);
|
|
2148
|
+
}
|
|
2149
|
+
if (emergencyCompact?.compacted) {
|
|
2150
|
+
applyCompactionResult(ctx, emergencyCompact, onEvent, reqId);
|
|
2151
|
+
reducerCompacted = true;
|
|
1665
2152
|
shouldInjectWorkspace = true;
|
|
1666
2153
|
}
|
|
1667
2154
|
|
|
1668
2155
|
// Only re-inject NOW.md when ctx.messages was actually stripped;
|
|
1669
2156
|
// otherwise the existing block is still present.
|
|
1670
|
-
|
|
2157
|
+
const injection = await applyRuntimeInjections(ctx.messages, {
|
|
1671
2158
|
...injectionOpts,
|
|
1672
2159
|
pkbContext: currentPkbContent,
|
|
1673
2160
|
nowScratchpad: convergenceStripped ? currentNowContent : null,
|
|
1674
2161
|
workspaceTopLevelContext: shouldInjectWorkspace
|
|
1675
2162
|
? ctx.workspaceTopLevelContext
|
|
1676
2163
|
: null,
|
|
2164
|
+
slackChronologicalMessages: reducerCompacted
|
|
2165
|
+
? null
|
|
2166
|
+
: injectionOpts.slackChronologicalMessages,
|
|
1677
2167
|
mode: currentInjectionMode,
|
|
2168
|
+
turnContext: buildPluginTurnContext(ctx, reqId),
|
|
1678
2169
|
});
|
|
2170
|
+
runMessages = injection.messages;
|
|
1679
2171
|
if (isTrustedActor && currentInjectionMode !== "minimal") {
|
|
1680
2172
|
ctx.graphMemory.retrackCachedNodes();
|
|
1681
2173
|
}
|
|
2174
|
+
const fallbackStrip = stripHistoricalWebSearchResults(runMessages);
|
|
2175
|
+
if (fallbackStrip.stats.blocksStripped > 0) {
|
|
2176
|
+
rlog.info(
|
|
2177
|
+
{ phase: "fail_gracefully_compact", ...fallbackStrip.stats },
|
|
2178
|
+
"Converted historical web_search_tool_result blocks to text summaries",
|
|
2179
|
+
);
|
|
2180
|
+
runMessages = fallbackStrip.messages;
|
|
2181
|
+
}
|
|
1682
2182
|
preRepairMessages = runMessages;
|
|
1683
2183
|
preRunHistoryLength = runMessages.length;
|
|
1684
2184
|
state.contextTooLargeDetected = false;
|
|
@@ -1689,6 +2189,8 @@ export async function runAgentLoopImpl(
|
|
|
1689
2189
|
abortController.signal,
|
|
1690
2190
|
reqId,
|
|
1691
2191
|
onCheckpoint,
|
|
2192
|
+
turnCallSite,
|
|
2193
|
+
loopTurnCtx,
|
|
1692
2194
|
);
|
|
1693
2195
|
}
|
|
1694
2196
|
// action === "fail_gracefully" falls through to the final error below
|
|
@@ -1753,11 +2255,19 @@ export async function runAgentLoopImpl(
|
|
|
1753
2255
|
assistantMessageInterface:
|
|
1754
2256
|
capturedTurnInterfaceContext.assistantMessageInterface,
|
|
1755
2257
|
};
|
|
1756
|
-
await
|
|
1757
|
-
|
|
1758
|
-
"
|
|
1759
|
-
|
|
1760
|
-
|
|
2258
|
+
await runPipeline<PersistArgs, PersistResult>(
|
|
2259
|
+
"persistence",
|
|
2260
|
+
getMiddlewaresFor("persistence"),
|
|
2261
|
+
defaultPersistenceTerminal,
|
|
2262
|
+
{
|
|
2263
|
+
op: "add",
|
|
2264
|
+
conversationId: ctx.conversationId,
|
|
2265
|
+
role: "user",
|
|
2266
|
+
content: JSON.stringify(toolResultBlocks),
|
|
2267
|
+
metadata: toolResultMetadata,
|
|
2268
|
+
},
|
|
2269
|
+
buildPluginTurnContext(ctx, reqId),
|
|
2270
|
+
DEFAULT_TIMEOUTS.persistence,
|
|
1761
2271
|
);
|
|
1762
2272
|
state.pendingToolResults.clear();
|
|
1763
2273
|
}
|
|
@@ -1770,10 +2280,6 @@ export async function runAgentLoopImpl(
|
|
|
1770
2280
|
return { ...msg, content: cleanedBlocks };
|
|
1771
2281
|
});
|
|
1772
2282
|
|
|
1773
|
-
if (denyCompressionMessage) {
|
|
1774
|
-
newMessages.push(denyCompressionMessage);
|
|
1775
|
-
}
|
|
1776
|
-
|
|
1777
2283
|
const hasAssistantResponse = newMessages.some(
|
|
1778
2284
|
(msg) => msg.role === "assistant",
|
|
1779
2285
|
);
|
|
@@ -1795,11 +2301,19 @@ export async function runAgentLoopImpl(
|
|
|
1795
2301
|
const errorAssistantMessage = createAssistantMessage(
|
|
1796
2302
|
state.providerErrorUserMessage,
|
|
1797
2303
|
);
|
|
1798
|
-
await
|
|
1799
|
-
|
|
1800
|
-
"
|
|
1801
|
-
|
|
1802
|
-
|
|
2304
|
+
await runPipeline<PersistArgs, PersistResult>(
|
|
2305
|
+
"persistence",
|
|
2306
|
+
getMiddlewaresFor("persistence"),
|
|
2307
|
+
defaultPersistenceTerminal,
|
|
2308
|
+
{
|
|
2309
|
+
op: "add",
|
|
2310
|
+
conversationId: ctx.conversationId,
|
|
2311
|
+
role: "assistant",
|
|
2312
|
+
content: JSON.stringify(errorAssistantMessage.content),
|
|
2313
|
+
metadata: errChannelMeta,
|
|
2314
|
+
},
|
|
2315
|
+
buildPluginTurnContext(ctx, reqId),
|
|
2316
|
+
DEFAULT_TIMEOUTS.persistence,
|
|
1803
2317
|
);
|
|
1804
2318
|
newMessages.push(errorAssistantMessage);
|
|
1805
2319
|
// Do NOT send assistant_text_delta here — handleProviderError already
|
|
@@ -1863,14 +2377,10 @@ export async function runAgentLoopImpl(
|
|
|
1863
2377
|
state.exchangeLlmCallCount,
|
|
1864
2378
|
{
|
|
1865
2379
|
tokens: state.lastCallInputTokens,
|
|
1866
|
-
maxTokens: config.contextWindow.maxInputTokens,
|
|
2380
|
+
maxTokens: config.llm.default.contextWindow.maxInputTokens,
|
|
1867
2381
|
},
|
|
1868
2382
|
);
|
|
1869
2383
|
|
|
1870
|
-
void getHookManager().trigger("post-message", {
|
|
1871
|
-
conversationId: ctx.conversationId,
|
|
1872
|
-
});
|
|
1873
|
-
|
|
1874
2384
|
const syncLastAssistantMessageToDisk = (): void => {
|
|
1875
2385
|
if (!state.lastAssistantMessageId) return;
|
|
1876
2386
|
const convForDisk = getConversation(ctx.conversationId);
|
|
@@ -1987,13 +2497,65 @@ export async function runAgentLoopImpl(
|
|
|
1987
2497
|
? { messageId: state.lastAssistantMessageId }
|
|
1988
2498
|
: {}),
|
|
1989
2499
|
});
|
|
2500
|
+
|
|
2501
|
+
// Emit a home-feed event for background/scheduled conversation completions.
|
|
2502
|
+
// Scoped to message_complete only (not cancelled/handoff), wrapped in
|
|
2503
|
+
// try-catch so malformed message content can never propagate errors.
|
|
2504
|
+
try {
|
|
2505
|
+
const conv = getConversation(ctx.conversationId);
|
|
2506
|
+
if (
|
|
2507
|
+
conv &&
|
|
2508
|
+
(conv.conversationType === "background" ||
|
|
2509
|
+
conv.conversationType === "scheduled")
|
|
2510
|
+
) {
|
|
2511
|
+
const lastMsg = state.lastAssistantMessageId
|
|
2512
|
+
? getMessageById(state.lastAssistantMessageId, ctx.conversationId)
|
|
2513
|
+
: undefined;
|
|
2514
|
+
let summary: string;
|
|
2515
|
+
if (lastMsg) {
|
|
2516
|
+
const parsed: unknown = JSON.parse(lastMsg.content);
|
|
2517
|
+
if (typeof parsed === "string") {
|
|
2518
|
+
summary = parsed.slice(0, 200);
|
|
2519
|
+
} else if (Array.isArray(parsed)) {
|
|
2520
|
+
const textBlock = parsed.find(
|
|
2521
|
+
(b: { type?: string }) => b.type === "text",
|
|
2522
|
+
);
|
|
2523
|
+
summary =
|
|
2524
|
+
typeof textBlock?.text === "string"
|
|
2525
|
+
? textBlock.text.slice(0, 200)
|
|
2526
|
+
: (conv.title ?? "Background task completed.");
|
|
2527
|
+
} else {
|
|
2528
|
+
summary = conv.title ?? "Background task completed.";
|
|
2529
|
+
}
|
|
2530
|
+
} else {
|
|
2531
|
+
summary = conv.title ?? "Background task completed.";
|
|
2532
|
+
}
|
|
2533
|
+
void emitFeedEvent({
|
|
2534
|
+
source: "assistant",
|
|
2535
|
+
title: conv.title ?? "Background Task",
|
|
2536
|
+
summary,
|
|
2537
|
+
dedupKey: `bg-conv:${ctx.conversationId}`,
|
|
2538
|
+
}).catch((err) => {
|
|
2539
|
+
log.warn(
|
|
2540
|
+
{ err, conversationId: ctx.conversationId },
|
|
2541
|
+
"Failed to emit background conversation feed event",
|
|
2542
|
+
);
|
|
2543
|
+
});
|
|
2544
|
+
}
|
|
2545
|
+
} catch (feedErr) {
|
|
2546
|
+
log.warn(
|
|
2547
|
+
{ err: feedErr, conversationId: ctx.conversationId },
|
|
2548
|
+
"Failed to build home-feed event for background conversation",
|
|
2549
|
+
);
|
|
2550
|
+
}
|
|
1990
2551
|
}
|
|
1991
2552
|
}
|
|
1992
2553
|
|
|
1993
2554
|
// Second title pass: after 3 completed turns, re-generate the title
|
|
1994
2555
|
// using the last 3 messages for better context. Only fires when the
|
|
1995
|
-
// current title was auto-generated (isAutoTitle = 1)
|
|
1996
|
-
|
|
2556
|
+
// current title was auto-generated (isAutoTitle = 1) and the user
|
|
2557
|
+
// has not opted out via `conversations.skipAutoRetitling`.
|
|
2558
|
+
if (ctx.turnCount === 2 && !getConfig().conversations.skipAutoRetitling) {
|
|
1997
2559
|
// turnCount is 0-indexed, incremented in finally; 2 = about to become 3rd turn
|
|
1998
2560
|
queueRegenerateConversationTitle({
|
|
1999
2561
|
conversationId: ctx.conversationId,
|
|
@@ -2046,12 +2608,6 @@ export async function runAgentLoopImpl(
|
|
|
2046
2608
|
});
|
|
2047
2609
|
onEvent({ type: "error", message: classified.userMessage });
|
|
2048
2610
|
onEvent(buildConversationErrorMessage(ctx.conversationId, classified));
|
|
2049
|
-
void getHookManager().trigger("on-error", {
|
|
2050
|
-
error: err instanceof Error ? err.name : "Error",
|
|
2051
|
-
message,
|
|
2052
|
-
stack: err instanceof Error ? err.stack : undefined,
|
|
2053
|
-
conversationId: ctx.conversationId,
|
|
2054
|
-
});
|
|
2055
2611
|
}
|
|
2056
2612
|
} finally {
|
|
2057
2613
|
if (turnStarted) {
|
|
@@ -2095,6 +2651,7 @@ export async function runAgentLoopImpl(
|
|
|
2095
2651
|
ctx.processing = false;
|
|
2096
2652
|
ctx.onConfirmationOutcome = undefined;
|
|
2097
2653
|
ctx.surfaceActionRequestIds.delete(ctx.currentRequestId ?? "");
|
|
2654
|
+
ctx.approvedViaPromptThisTurn = false;
|
|
2098
2655
|
ctx.currentRequestId = undefined;
|
|
2099
2656
|
ctx.currentActiveSurfaceId = undefined;
|
|
2100
2657
|
ctx.allowedToolNames = undefined;
|
|
@@ -2160,7 +2717,133 @@ function emitUsage(
|
|
|
2160
2717
|
);
|
|
2161
2718
|
}
|
|
2162
2719
|
|
|
2163
|
-
|
|
2720
|
+
/**
|
|
2721
|
+
* Minimal context shape consumed by `applyCompactionResult`. Both
|
|
2722
|
+
* `AgentLoopConversationContext` and `Conversation` satisfy this via structural
|
|
2723
|
+
* typing, so the helper can back both the 5 agent-loop auto-compaction sites
|
|
2724
|
+
* and the single `forceCompact` user-initiated site.
|
|
2725
|
+
*/
|
|
2726
|
+
export interface CompactionApplyContext {
|
|
2727
|
+
readonly conversationId: string;
|
|
2728
|
+
messages: Message[];
|
|
2729
|
+
contextCompactedMessageCount: number;
|
|
2730
|
+
contextCompactedAt: number | null;
|
|
2731
|
+
readonly graphMemory: ConversationGraphMemory;
|
|
2732
|
+
readonly provider: Provider;
|
|
2733
|
+
usageStats: UsageStats;
|
|
2734
|
+
trustContext?: TrustContext;
|
|
2735
|
+
}
|
|
2736
|
+
|
|
2737
|
+
/**
|
|
2738
|
+
* Applies a successful `ContextWindowResult` to a conversation: updates the
|
|
2739
|
+
* in-memory message buffer and compaction counters, notifies the graph memory
|
|
2740
|
+
* and conversation-summary store, enqueues auto-analysis, emits the
|
|
2741
|
+
* `context_compacted` event, and records a `context_compactor` usage event.
|
|
2742
|
+
*
|
|
2743
|
+
* The emitted `usage_update` intentionally omits `contextWindow` — the
|
|
2744
|
+
* `context_compacted` event already carries the fresh
|
|
2745
|
+
* `estimatedInputTokens` / `maxInputTokens` and is the single source of
|
|
2746
|
+
* truth for the UI indicator after compaction. Emitting both caused a
|
|
2747
|
+
* redundant SwiftUI invalidation on every compaction.
|
|
2748
|
+
*/
|
|
2749
|
+
export function applyCompactionResult(
|
|
2750
|
+
ctx: CompactionApplyContext,
|
|
2751
|
+
result: {
|
|
2752
|
+
messages: Message[];
|
|
2753
|
+
compactedPersistedMessages: number;
|
|
2754
|
+
previousEstimatedInputTokens: number;
|
|
2755
|
+
estimatedInputTokens: number;
|
|
2756
|
+
maxInputTokens: number;
|
|
2757
|
+
thresholdTokens: number;
|
|
2758
|
+
compactedMessages: number;
|
|
2759
|
+
summaryCalls: number;
|
|
2760
|
+
summaryInputTokens: number;
|
|
2761
|
+
summaryOutputTokens: number;
|
|
2762
|
+
summaryModel: string;
|
|
2763
|
+
summaryText: string;
|
|
2764
|
+
summaryCacheCreationInputTokens?: number;
|
|
2765
|
+
summaryCacheReadInputTokens?: number;
|
|
2766
|
+
summaryRawResponses?: unknown[];
|
|
2767
|
+
},
|
|
2768
|
+
onEvent: (msg: ServerMessage) => void,
|
|
2769
|
+
reqId: string | null,
|
|
2770
|
+
): void {
|
|
2771
|
+
ctx.messages = result.messages;
|
|
2772
|
+
ctx.contextCompactedMessageCount += result.compactedPersistedMessages;
|
|
2773
|
+
ctx.contextCompactedAt = Date.now();
|
|
2774
|
+
ctx.graphMemory.onCompacted(result.compactedPersistedMessages);
|
|
2775
|
+
updateConversationContextWindow(
|
|
2776
|
+
ctx.conversationId,
|
|
2777
|
+
result.summaryText,
|
|
2778
|
+
ctx.contextCompactedMessageCount,
|
|
2779
|
+
);
|
|
2780
|
+
enqueueAutoAnalysisOnCompaction(
|
|
2781
|
+
ctx.conversationId,
|
|
2782
|
+
ctx.trustContext?.trustClass,
|
|
2783
|
+
);
|
|
2784
|
+
const summarySignals = computeSummaryQualitySignals(result.summaryText);
|
|
2785
|
+
onEvent({
|
|
2786
|
+
type: "context_compacted",
|
|
2787
|
+
conversationId: ctx.conversationId,
|
|
2788
|
+
previousEstimatedInputTokens: result.previousEstimatedInputTokens,
|
|
2789
|
+
estimatedInputTokens: result.estimatedInputTokens,
|
|
2790
|
+
maxInputTokens: result.maxInputTokens,
|
|
2791
|
+
thresholdTokens: result.thresholdTokens,
|
|
2792
|
+
compactedMessages: result.compactedMessages,
|
|
2793
|
+
summaryCalls: result.summaryCalls,
|
|
2794
|
+
summaryInputTokens: result.summaryInputTokens,
|
|
2795
|
+
summaryOutputTokens: result.summaryOutputTokens,
|
|
2796
|
+
summaryModel: result.summaryModel,
|
|
2797
|
+
summaryCharCount: summarySignals.charCount,
|
|
2798
|
+
summaryHeaderCount: summarySignals.headerCount,
|
|
2799
|
+
summaryHadMemoryEcho: summarySignals.hadMemoryEcho,
|
|
2800
|
+
});
|
|
2801
|
+
emitUsage(
|
|
2802
|
+
ctx,
|
|
2803
|
+
result.summaryInputTokens,
|
|
2804
|
+
result.summaryOutputTokens,
|
|
2805
|
+
result.summaryModel,
|
|
2806
|
+
onEvent,
|
|
2807
|
+
"context_compactor",
|
|
2808
|
+
reqId,
|
|
2809
|
+
result.summaryCacheCreationInputTokens ?? 0,
|
|
2810
|
+
result.summaryCacheReadInputTokens ?? 0,
|
|
2811
|
+
collapseRawResponses(result.summaryRawResponses),
|
|
2812
|
+
undefined /* providerName */,
|
|
2813
|
+
1 /* llmCallCount */,
|
|
2814
|
+
);
|
|
2815
|
+
}
|
|
2816
|
+
|
|
2817
|
+
export function collapseRawResponses(
|
|
2818
|
+
rawResponses?: unknown[],
|
|
2819
|
+
): unknown | undefined {
|
|
2164
2820
|
if (!rawResponses || rawResponses.length === 0) return undefined;
|
|
2165
2821
|
return rawResponses.length === 1 ? rawResponses[0] : rawResponses;
|
|
2166
2822
|
}
|
|
2823
|
+
|
|
2824
|
+
/**
|
|
2825
|
+
* Matches any runtime-injection tag that should never appear inside a
|
|
2826
|
+
* generated summary. If the regex hits, either the compaction strip logic
|
|
2827
|
+
* failed to drop an injected block from the summarizer input, or the
|
|
2828
|
+
* summarizer invented tag-like text on its own — both are quality bugs
|
|
2829
|
+
* worth surfacing via telemetry.
|
|
2830
|
+
*/
|
|
2831
|
+
const SUMMARY_MEMORY_ECHO_PATTERN =
|
|
2832
|
+
/<(?:memory|memory_context|memory_image|turn_context|workspace|workspace_top_level|knowledge_base|pkb|system_reminder|now_scratchpad|NOW\.md|active_thread|active_subagents|active_workspace|active_dynamic_page|channel_capabilities|transport_hints|system_notice|non_interactive_context|temporal_context|guardian_context|inbound_actor_context|channel_turn_context|interface_turn_context|channel_command_context|voice_call_control)\b/i;
|
|
2833
|
+
|
|
2834
|
+
/**
|
|
2835
|
+
* Compute light-weight quality signals for a compaction summary. Emitted
|
|
2836
|
+
* on every `context_compacted` event so regressions (short outputs,
|
|
2837
|
+
* header collapse, memory-injection leakage) are visible without having
|
|
2838
|
+
* to read the summary text from the DB.
|
|
2839
|
+
*/
|
|
2840
|
+
export function computeSummaryQualitySignals(summaryText: string): {
|
|
2841
|
+
charCount: number;
|
|
2842
|
+
headerCount: number;
|
|
2843
|
+
hadMemoryEcho: boolean;
|
|
2844
|
+
} {
|
|
2845
|
+
const charCount = summaryText.length;
|
|
2846
|
+
const headerCount = (summaryText.match(/^## /gm) ?? []).length;
|
|
2847
|
+
const hadMemoryEcho = SUMMARY_MEMORY_ECHO_PATTERN.test(summaryText);
|
|
2848
|
+
return { charCount, headerCount, hadMemoryEcho };
|
|
2849
|
+
}
|