@vellumai/assistant 0.8.8 → 0.8.9-staging.2
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/ARCHITECTURE.md +6 -6
- package/bun.lock +2 -2
- package/examples/plugins/echo/README.md +61 -60
- package/examples/plugins/echo/hooks/post-tool-use.ts +18 -0
- package/examples/plugins/echo/hooks/stop.ts +16 -0
- package/examples/plugins/echo/hooks/user-prompt-submit.ts +18 -0
- package/examples/plugins/echo/package.json +1 -2
- package/examples/plugins/echo/src/emit.ts +19 -0
- package/node_modules/@vellumai/skill-host-contracts/src/skill-host.ts +7 -6
- package/openapi.yaml +235 -6
- package/package.json +2 -2
- package/src/__tests__/agent-loop-callsite-precedence.test.ts +69 -14
- package/src/__tests__/agent-loop-exit-reason.test.ts +204 -144
- package/src/__tests__/agent-loop-mutable-latest-user-message.test.ts +50 -35
- package/src/__tests__/agent-loop-output-hooks.test.ts +357 -0
- package/src/__tests__/agent-loop-override-profile.test.ts +25 -6
- package/src/__tests__/agent-loop-provider-error-recording.test.ts +41 -21
- package/src/__tests__/agent-loop-thinking.test.ts +36 -20
- package/src/__tests__/agent-loop.test.ts +441 -96
- package/src/__tests__/agent-wake-disk-pressure-callsite.test.ts +14 -14
- package/src/__tests__/agent-wake-override-profile.test.ts +17 -21
- package/src/__tests__/anthropic-provider.test.ts +1 -1
- package/src/__tests__/app-builder-tool-scripts.test.ts +21 -0
- package/src/__tests__/app-control-flow.test.ts +1 -1
- package/src/__tests__/app-dir-path-guard.test.ts +1 -0
- package/src/__tests__/app-executors.test.ts +132 -0
- package/src/__tests__/approval-cascade.test.ts +5 -4
- package/src/__tests__/approval-routes-http.test.ts +4 -1
- package/src/__tests__/background-workers-disk-pressure.test.ts +1 -1
- package/src/__tests__/channel-approval-routes.test.ts +1 -1
- package/src/__tests__/channel-approvals.test.ts +1 -1
- package/src/__tests__/compaction-circuit.test.ts +258 -0
- package/src/__tests__/compaction-direct.test.ts +132 -0
- package/src/__tests__/compaction-events.test.ts +5 -5
- package/src/__tests__/compactor-web-search-strip.test.ts +213 -0
- package/src/__tests__/context-overflow-reducer.test.ts +1 -1
- package/src/__tests__/conversation-abort-tool-results.test.ts +6 -4
- package/src/__tests__/conversation-agent-loop-disk-pressure.test.ts +7 -10
- package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +78 -119
- package/src/__tests__/conversation-agent-loop-overflow.test.ts +142 -218
- package/src/__tests__/conversation-agent-loop.test.ts +297 -586
- package/src/__tests__/conversation-clean-command.test.ts +5 -2
- package/src/__tests__/conversation-confirmation-signals.test.ts +5 -4
- package/src/__tests__/conversation-crud-inference-profile.test.ts +7 -9
- package/src/__tests__/conversation-history-web-search.test.ts +1 -1
- package/src/__tests__/conversation-process-app-control-preactivation.test.ts +4 -4
- package/src/__tests__/conversation-process-callsite.test.ts +14 -14
- package/src/__tests__/conversation-provider-retry-repair.test.ts +70 -65
- package/src/__tests__/conversation-queue.test.ts +9 -9
- package/src/__tests__/conversation-runtime-assembly.test.ts +923 -231
- package/src/__tests__/conversation-runtime-workspace.test.ts +115 -20
- package/src/__tests__/conversation-slash-queue.test.ts +6 -4
- package/src/__tests__/conversation-slash-unknown.test.ts +6 -4
- package/src/__tests__/conversation-speed-override.test.ts +10 -9
- package/src/__tests__/conversation-starter-routes.test.ts +14 -6
- package/src/__tests__/conversation-workspace-cache-state.test.ts +23 -20
- package/src/__tests__/conversation-workspace-injection.test.ts +68 -6
- package/src/__tests__/conversation-workspace-tool-tracking.test.ts +14 -11
- package/src/__tests__/conversations-import-system-filter.test.ts +101 -0
- package/src/__tests__/credential-security-invariants.test.ts +0 -1
- package/src/__tests__/db-acp-history.test.ts +101 -0
- package/src/__tests__/dynamic-page-surface.test.ts +31 -0
- package/src/__tests__/empty-response-hook.test.ts +1 -1
- package/src/__tests__/file-write-tool.test.ts +63 -0
- package/src/__tests__/gateway-only-guard.test.ts +12 -2
- package/src/__tests__/guardian-grant-minting.test.ts +1 -1
- package/src/__tests__/guardian-routing-invariants.test.ts +2 -4
- package/src/__tests__/handlers-user-message-approval-consumption.test.ts +1 -1
- package/src/__tests__/heartbeat-disk-pressure.test.ts +1 -0
- package/src/__tests__/heartbeat-service.test.ts +1 -0
- package/src/__tests__/history-repair-hook.test.ts +1 -1
- package/src/__tests__/host-app-control-routes.test.ts +1 -1
- package/src/__tests__/host-cu-routes-targeted.test.ts +3 -3
- package/src/__tests__/inference-profile-reaper.test.ts +62 -0
- package/src/__tests__/inference-profile-session-handler.test.ts +86 -0
- package/src/__tests__/injector-background-turn.test.ts +13 -23
- package/src/__tests__/injector-chain.test.ts +268 -44
- package/src/__tests__/injector-disk-pressure.test.ts +210 -52
- package/src/__tests__/injector-document-comments.test.ts +97 -114
- package/src/__tests__/injector-pkb-v2-silenced.test.ts +2 -2
- package/src/__tests__/injector-v3-suppression.test.ts +4 -4
- package/src/__tests__/list-messages-client-message-id.test.ts +91 -0
- package/src/__tests__/list-messages-hidden-metadata.test.ts +38 -0
- package/src/__tests__/memory-retrieval-hook.test.ts +131 -26
- package/src/__tests__/memory-v2-static-injector.test.ts +86 -8
- package/src/__tests__/parallel-tool.benchmark.test.ts +35 -8
- package/src/__tests__/plugin-api-shim.test.ts +6 -9
- package/src/__tests__/plugin-bootstrap.test.ts +12 -23
- package/src/__tests__/plugin-registry.test.ts +3 -49
- package/src/__tests__/plugin-types.test.ts +0 -70
- package/src/__tests__/pre-model-call-sanitize.test.ts +109 -0
- package/src/__tests__/reaction-persistence.test.ts +1 -1
- package/src/__tests__/send-endpoint-busy.test.ts +4 -1
- package/src/__tests__/skill-feature-flags-integration.test.ts +33 -0
- package/src/__tests__/steer-tool-repair.test.ts +1 -1
- package/src/__tests__/subagent-call-site-routing.test.ts +1 -1
- package/src/__tests__/subagent-detail.test.ts +25 -7
- package/src/__tests__/subagent-fork-notifications.test.ts +1 -3
- package/src/__tests__/subagent-fork-spawn.test.ts +1 -1
- package/src/__tests__/subagent-manager-notify.test.ts +1 -3
- package/src/__tests__/subagent-notify-parent.test.ts +1 -3
- package/src/__tests__/subagent-spawn-tool-fork.test.ts +1 -1
- package/src/__tests__/title-generate-hook.test.ts +1 -1
- package/src/__tests__/tool-error-hook.test.ts +1 -1
- package/src/__tests__/tool-result-truncate-hook.test.ts +1 -1
- package/src/__tests__/user-plugin-loader.test.ts +54 -286
- package/src/acp/__tests__/agent-process.test.ts +161 -0
- package/src/acp/__tests__/client-handler.test.ts +40 -0
- package/src/acp/__tests__/helpers/acp-history-db.ts +82 -0
- package/src/acp/__tests__/helpers/exec-file-stub.ts +106 -0
- package/src/acp/__tests__/prepare-agent-env.test.ts +97 -0
- package/src/acp/__tests__/session-manager-persistence.test.ts +95 -28
- package/src/acp/__tests__/session-manager-resume.test.ts +888 -0
- package/src/acp/agent-process.ts +61 -1
- package/src/acp/auto-install.test.ts +280 -0
- package/src/acp/auto-install.ts +232 -0
- package/src/acp/client-handler.ts +31 -0
- package/src/acp/feature-gate.test.ts +48 -0
- package/src/acp/feature-gate.ts +34 -0
- package/src/acp/prepare-agent-env.ts +80 -27
- package/src/acp/resolve-agent.test.ts +225 -9
- package/src/acp/resolve-agent.ts +122 -17
- package/src/acp/resume-hint.ts +23 -0
- package/src/acp/session-manager.ts +507 -73
- package/src/agent/compaction-circuit.ts +60 -102
- package/src/agent/loop.ts +414 -248
- package/src/api/responses/conversation-message.ts +14 -1
- package/src/approvals/guardian-request-resolvers.ts +1 -1
- package/src/background-wake/next-wake.ts +1 -0
- package/src/cli/commands/db/__tests__/repair.test.ts +3 -1
- package/src/cli/commands/plugins.ts +43 -37
- package/src/cli/lib/__tests__/install-from-github.test.ts +429 -111
- package/src/cli/lib/__tests__/plugin-catalog-cache.test.ts +196 -0
- package/src/cli/lib/__tests__/plugin-details.test.ts +372 -0
- package/src/cli/lib/__tests__/plugin-marketplace.test.ts +220 -0
- package/src/cli/lib/__tests__/search-plugins.test.ts +226 -32
- package/src/cli/lib/install-from-github.ts +464 -55
- package/src/cli/lib/plugin-catalog-cache.ts +84 -0
- package/src/cli/lib/plugin-details.ts +409 -0
- package/src/cli/lib/plugin-marketplace.ts +197 -0
- package/src/cli/lib/search-plugins.ts +195 -29
- package/src/config/__tests__/feature-flag-registry-guard.test.ts +2 -2
- package/src/config/acp-defaults.test.ts +10 -0
- package/src/config/acp-defaults.ts +6 -0
- package/src/config/bundled-skills/acp/SKILL.md +85 -33
- package/src/config/bundled-skills/acp/TOOLS.json +4 -4
- package/src/config/bundled-skills/app-builder/SKILL.md +224 -381
- package/src/config/bundled-skills/app-builder/TOOLS.json +72 -2
- package/src/config/bundled-skills/app-builder/references/DESIGN_SYSTEM.md +48 -0
- package/src/config/bundled-skills/app-builder/references/RESPONSIVE.md +57 -0
- package/src/config/bundled-skills/app-builder/references/SLIDES.md +38 -0
- package/src/config/bundled-skills/app-builder/tools/app-list.ts +62 -0
- package/src/config/bundled-skills/app-builder/tools/app-update.ts +18 -0
- package/src/config/bundled-skills/document-editor/SKILL.md +28 -23
- package/src/config/bundled-skills/document-editor/TOOLS.json +1 -1
- package/src/config/bundled-tool-registry.ts +4 -0
- package/src/config/call-site-defaults.ts +0 -2
- package/src/config/feature-flag-registry.json +15 -6
- package/src/config/schemas/call-site-catalog.ts +0 -14
- package/src/config/schemas/heartbeat.ts +9 -0
- package/src/config/schemas/llm.ts +0 -2
- package/src/context/compactor.ts +22 -4
- package/src/context/strip-injections.ts +8 -2
- package/src/context/window-manager.ts +27 -13
- package/src/daemon/conversation-agent-loop-handlers.ts +10 -35
- package/src/daemon/conversation-agent-loop.ts +175 -1055
- package/src/daemon/conversation-lifecycle.ts +11 -255
- package/src/daemon/conversation-process.ts +8 -136
- package/src/daemon/conversation-registry.ts +159 -0
- package/src/daemon/conversation-runtime-assembly.ts +293 -392
- package/src/daemon/conversation-store.ts +9 -90
- package/src/daemon/conversation-surfaces.ts +24 -8
- package/src/daemon/conversation-workspace.ts +17 -0
- package/src/daemon/conversation.ts +404 -57
- package/src/daemon/disk-pressure-policy.ts +0 -1
- package/src/daemon/external-plugins-bootstrap.ts +14 -19
- package/src/daemon/handlers/conversations.ts +3 -1
- package/src/daemon/handlers/skills.ts +4 -1
- package/src/daemon/host-proxy-preactivation.ts +1 -3
- package/src/daemon/lifecycle.ts +21 -7
- package/src/daemon/server.ts +2 -0
- package/src/daemon/wake-conversation-ops.ts +269 -0
- package/src/embedded/plugin-api.ts +2 -2
- package/src/export/__tests__/transcript-formatter.test.ts +5 -0
- package/src/heartbeat/__tests__/heartbeat-service.test.ts +3 -0
- package/src/heartbeat/heartbeat-run-store.ts +23 -1
- package/src/heartbeat/heartbeat-service.ts +26 -0
- package/src/ipc/__tests__/browser-ipc.test.ts +1 -1
- package/src/ipc/__tests__/ui-request-route.test.ts +3 -3
- package/src/ipc/skill-routes/__tests__/memory.test.ts +15 -0
- package/src/ipc/skill-routes/memory.ts +4 -2
- package/src/memory/__tests__/jobs-worker-v2-schedule.test.ts +87 -0
- package/src/memory/conversation-crud.ts +29 -19
- package/src/memory/conversation-starter-checkpoints.ts +1 -0
- package/src/memory/db-init.ts +2 -0
- package/src/memory/job-handlers/conversation-starters.ts +13 -2
- package/src/memory/jobs/__tests__/embed-concept-page.test.ts +5 -4
- package/src/memory/jobs-worker.ts +25 -1
- package/src/memory/migrations/272-acp-session-history-cwd.ts +36 -0
- package/src/memory/migrations/index.ts +1 -0
- package/src/memory/schema/acp.ts +4 -0
- package/src/memory/v2/__tests__/consolidation-job.test.ts +3 -3
- package/src/memory/v2/consolidation-job.ts +13 -4
- package/src/plugin-api/constants.ts +4 -0
- package/src/plugin-api/index.ts +6 -5
- package/src/plugin-api/types.ts +75 -0
- package/src/plugins/defaults/compaction/compact.ts +59 -0
- package/src/{daemon → plugins/defaults/compaction}/context-overflow-reducer.ts +7 -7
- package/src/plugins/defaults/compaction/manager-store.ts +57 -0
- package/src/plugins/defaults/compaction/package.json +1 -2
- package/src/plugins/defaults/empty-response/package.json +0 -1
- package/src/plugins/defaults/history-repair/package.json +0 -1
- package/src/plugins/defaults/index.ts +135 -26
- package/src/plugins/defaults/memory-retrieval/hooks/post-compact.ts +100 -52
- package/src/plugins/defaults/memory-retrieval/hooks/user-prompt-submit-temp.ts +184 -74
- package/src/plugins/defaults/memory-retrieval/injector-chain.ts +2 -2
- package/src/plugins/defaults/{injectors/register.ts → memory-retrieval/injectors.ts} +148 -73
- package/src/plugins/defaults/memory-retrieval/unified-turn-context.ts +223 -0
- package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/assign.test.ts +4 -4
- package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/live-integration.test.ts +9 -6
- package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/maintain-job.test.ts +5 -5
- package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/orchestrate.test.ts +8 -5
- package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/reconcile.test.ts +2 -2
- package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/render-injection.test.ts +1 -1
- package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/router.test.ts +10 -5
- package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/selection-log-store.test.ts +8 -8
- package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/selector.test.ts +5 -5
- package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/shadow-plugin.test.ts +16 -17
- package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/types.test.ts +2 -2
- package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/assign.ts +9 -5
- package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/capabilities.ts +5 -2
- package/src/plugins/defaults/memory-v3-shadow/hooks/post-compact.ts +14 -0
- package/src/plugins/defaults/memory-v3-shadow/hooks/user-prompt-submit.ts +19 -0
- package/src/plugins/defaults/memory-v3-shadow/injector.ts +75 -0
- package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/maintain-job.ts +15 -8
- package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/orchestrate.ts +2 -2
- package/src/plugins/defaults/memory-v3-shadow/package.json +14 -0
- package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/page-content.ts +2 -2
- package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/provider-blocks.ts +1 -1
- package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/reconcile.ts +7 -3
- package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/render-injection.ts +1 -1
- package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/router.ts +5 -5
- package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/selection-log-store.ts +4 -4
- package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/selector.ts +7 -7
- package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/shadow-plugin.ts +32 -94
- package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/tree.ts +1 -1
- package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/types.ts +1 -1
- package/src/plugins/defaults/title-generate/package.json +0 -1
- package/src/plugins/defaults/tool-error/package.json +0 -1
- package/src/plugins/defaults/tool-result-truncate/package.json +0 -1
- package/src/plugins/pipeline.ts +6 -293
- package/src/plugins/registry.ts +9 -37
- package/src/plugins/types.ts +76 -381
- package/src/plugins/user-loader.ts +30 -127
- package/src/prompts/__tests__/system-prompt.test.ts +6 -0
- package/src/prompts/templates/BOOTSTRAP-ACTIVATION-RAIL.md +35 -3
- package/src/runtime/__tests__/agent-wake.test.ts +555 -691
- package/src/runtime/__tests__/interactive-ui.test.ts +1 -1
- package/src/runtime/agent-wake.ts +108 -209
- package/src/runtime/assistant-event-hub.ts +1 -1
- package/src/runtime/channel-approvals.ts +1 -1
- package/src/runtime/interactive-ui.ts +1 -1
- package/src/runtime/routes/__tests__/acp-routes.test.ts +315 -55
- package/src/runtime/routes/__tests__/conversation-list-routes.test.ts +1 -1
- package/src/runtime/routes/__tests__/plugins-routes.test.ts +423 -73
- package/src/runtime/routes/__tests__/surface-action-routes.test.ts +5 -4
- package/src/runtime/routes/__tests__/surface-content-routes.test.ts +4 -1
- package/src/runtime/routes/acp-routes.test.ts +89 -25
- package/src/runtime/routes/acp-routes.ts +81 -29
- package/src/runtime/routes/approval-routes.ts +1 -1
- package/src/runtime/routes/browser-routes.ts +1 -1
- package/src/runtime/routes/browser-tabs-routes.ts +6 -10
- package/src/runtime/routes/conversation-cli-routes.ts +1 -1
- package/src/runtime/routes/conversation-list-routes.ts +1 -1
- package/src/runtime/routes/conversation-query-routes.ts +1 -1
- package/src/runtime/routes/conversation-routes.ts +28 -2
- package/src/runtime/routes/conversation-starter-routes.ts +13 -7
- package/src/runtime/routes/conversations-import-routes.ts +24 -7
- package/src/runtime/routes/host-app-control-routes.ts +1 -1
- package/src/runtime/routes/host-cu-routes.ts +1 -1
- package/src/runtime/routes/identity-routes.ts +18 -3
- package/src/runtime/routes/inbound-message-handler.ts +1 -1
- package/src/runtime/routes/inference-profile-session-handler.ts +11 -0
- package/src/runtime/routes/inference-profile-session-reaper.ts +6 -0
- package/src/runtime/routes/memory-v3-routes.ts +16 -6
- package/src/runtime/routes/playground/helpers.ts +1 -1
- package/src/runtime/routes/plugins-routes.ts +337 -35
- package/src/runtime/routes/surface-conversation-resolver.ts +4 -3
- package/src/runtime/routes/work-items-routes.ts +2 -4
- package/src/runtime/services/conversation-serializer.ts +1 -1
- package/src/signals/cancel.ts +2 -4
- package/src/subagent/manager.ts +21 -5
- package/src/tools/acp/context.ts +20 -0
- package/src/tools/acp/list-agents.test.ts +8 -2
- package/src/tools/acp/spawn.test.ts +176 -195
- package/src/tools/acp/spawn.ts +37 -172
- package/src/tools/acp/steer.test.ts +105 -8
- package/src/tools/acp/steer.ts +48 -17
- package/src/tools/apps/executors.ts +166 -50
- package/src/tools/filesystem/write.ts +34 -0
- package/src/tools/subagent/spawn.ts +2 -4
- package/src/tools/ui-surface/definitions.ts +25 -5
- package/src/workspace/migrations/051-seed-conversation-summarization-callsite.ts +4 -5
- package/src/workspace/migrations/097-enable-adaptive-thinking-managed-profiles.ts +69 -45
- package/docs/plugins.md +0 -832
- package/examples/plugins/echo/register.ts +0 -143
- package/src/__tests__/circuit-breaker-pipeline.test.ts +0 -405
- package/src/__tests__/compaction-pipeline.test.ts +0 -210
- package/src/__tests__/compaction-timeout-recovery.test.ts +0 -251
- package/src/__tests__/overflow-reduce-pipeline.test.ts +0 -667
- package/src/__tests__/pipeline-runner.test.ts +0 -554
- package/src/__tests__/plugin-external-api.test.ts +0 -68
- package/src/daemon/wake-target-adapter.ts +0 -253
- package/src/plugins/defaults/circuit-breaker/middlewares/circuitBreaker.ts +0 -93
- package/src/plugins/defaults/circuit-breaker/package.json +0 -15
- package/src/plugins/defaults/circuit-breaker/register.ts +0 -39
- package/src/plugins/defaults/compaction/middlewares/compaction.ts +0 -25
- package/src/plugins/defaults/compaction/register.ts +0 -35
- package/src/plugins/defaults/compaction/terminal.ts +0 -73
- package/src/plugins/defaults/empty-response/register.ts +0 -23
- package/src/plugins/defaults/history-repair/register.ts +0 -24
- package/src/plugins/defaults/overflow-reduce/middlewares/overflowReduce.ts +0 -126
- package/src/plugins/defaults/overflow-reduce/package.json +0 -15
- package/src/plugins/defaults/overflow-reduce/register.ts +0 -42
- package/src/plugins/defaults/title-generate/register.ts +0 -35
- package/src/plugins/defaults/tool-error/register.ts +0 -23
- package/src/plugins/defaults/tool-result-truncate/register.ts +0 -24
- package/src/plugins/external-api.ts +0 -104
- package/src/proactive-artifact/aux-message-injector.ts +0 -97
- package/src/proactive-artifact/decision.test.ts +0 -226
- package/src/proactive-artifact/decision.ts +0 -165
- package/src/proactive-artifact/index.ts +0 -7
- package/src/proactive-artifact/job.test.ts +0 -962
- package/src/proactive-artifact/job.ts +0 -372
- package/src/proactive-artifact/message-copy.ts +0 -58
- package/src/proactive-artifact/trigger-state.test.ts +0 -286
- package/src/proactive-artifact/trigger-state.ts +0 -123
- /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/capabilities.test.ts +0 -0
- /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/core.test.ts +0 -0
- /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/fixtures/eval-turns.json +0 -0
- /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/fixtures/live-turns.json +0 -0
- /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/health.test.ts +0 -0
- /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/needle.test.ts +0 -0
- /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/provider-blocks.test.ts +0 -0
- /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/snapshot.test.ts +0 -0
- /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/tree.test.ts +0 -0
- /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/working-set-eviction.test.ts +0 -0
- /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/working-set-skeleton.test.ts +0 -0
- /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/core.ts +0 -0
- /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/README.md +0 -0
- /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/assignments.json +0 -0
- /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/core.json +0 -0
- /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/leaves/domain-a/topic-x.md +0 -0
- /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/leaves/domain-a/topic-y.md +0 -0
- /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/leaves/domain-b/topic-z.md +0 -0
- /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/health.ts +0 -0
- /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/llm-retry.ts +0 -0
- /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/needle.ts +0 -0
- /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/snapshot.ts +0 -0
- /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/working-set.ts +0 -0
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* This module contains the core agent loop orchestration: pre-flight
|
|
5
5
|
* setup, event handling, retry logic, history reconstruction, and
|
|
6
6
|
* completion event emission. The Conversation class delegates its
|
|
7
|
-
* runAgentLoop method here
|
|
7
|
+
* runAgentLoop method here, passing itself as the loop context.
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
import { v4 as uuid } from "uuid";
|
|
@@ -12,10 +12,8 @@ import { v4 as uuid } from "uuid";
|
|
|
12
12
|
import { optimizeImageForTransport } from "../agent/image-optimize.js";
|
|
13
13
|
import type {
|
|
14
14
|
AgentEvent,
|
|
15
|
-
AgentLoop,
|
|
16
15
|
AgentLoopExitReason,
|
|
17
16
|
CheckpointDecision,
|
|
18
|
-
MidLoopCompaction,
|
|
19
17
|
} from "../agent/loop.js";
|
|
20
18
|
import { createAssistantMessage } from "../agent/message-types.js";
|
|
21
19
|
import type {
|
|
@@ -24,7 +22,6 @@ import type {
|
|
|
24
22
|
TurnChannelContext,
|
|
25
23
|
TurnInterfaceContext,
|
|
26
24
|
} from "../channels/types.js";
|
|
27
|
-
import { isAssistantFeatureFlagEnabled } from "../config/assistant-feature-flags.js";
|
|
28
25
|
import {
|
|
29
26
|
contextWindowConfigFromEffective,
|
|
30
27
|
type EffectiveContextWindow,
|
|
@@ -44,11 +41,9 @@ import {
|
|
|
44
41
|
} from "../context/post-turn-tool-result-truncation.js";
|
|
45
42
|
import {
|
|
46
43
|
estimatePromptTokens,
|
|
47
|
-
estimatePromptTokensWithTools,
|
|
48
44
|
getCalibrationProviderKey,
|
|
49
45
|
} from "../context/token-estimator.js";
|
|
50
|
-
import type {
|
|
51
|
-
import type { ToolProfiler } from "../events/tool-profiling-listener.js";
|
|
46
|
+
import type { ContextWindowCompactOptions } from "../context/window-manager.js";
|
|
52
47
|
import { writeRelationshipState } from "../home/relationship-state-writer.js";
|
|
53
48
|
import {
|
|
54
49
|
clearSentryConversationContext,
|
|
@@ -62,66 +57,53 @@ import {
|
|
|
62
57
|
getConversation,
|
|
63
58
|
getConversationOriginChannel,
|
|
64
59
|
getConversationOriginInterface,
|
|
65
|
-
getConversationOverrideProfileFromRow,
|
|
66
60
|
getLastUserTimestampBefore,
|
|
67
61
|
getMessageById,
|
|
68
62
|
provenanceFromTrustContext,
|
|
63
|
+
resolveOverrideProfile,
|
|
69
64
|
updateConversationContextWindow,
|
|
70
65
|
updateConversationSlackContextWatermark,
|
|
71
|
-
updateMessageMetadata,
|
|
72
66
|
} from "../memory/conversation-crud.js";
|
|
73
67
|
import { getResolvedConversationDirPath } from "../memory/conversation-directories.js";
|
|
74
68
|
import { syncMessageToDisk } from "../memory/conversation-disk-view.js";
|
|
75
69
|
import { isReplaceableTitle } from "../memory/conversation-title-service.js";
|
|
76
|
-
import { isBackgroundConversationType } from "../memory/conversation-types.js";
|
|
77
70
|
import type { ConversationGraphMemory } from "../memory/graph/conversation-graph-memory.js";
|
|
78
71
|
import {
|
|
79
72
|
backfillMessageIdOnLogs,
|
|
80
73
|
recordSyntheticAgentErrorMessageLog,
|
|
81
74
|
} from "../memory/llm-request-log-store.js";
|
|
82
75
|
import { enqueueMemoryRetrospectiveOnCompaction } from "../memory/memory-retrospective-enqueue.js";
|
|
83
|
-
import type { PermissionPrompter } from "../permissions/prompter.js";
|
|
84
76
|
import { HOOKS } from "../plugin-api/constants.js";
|
|
85
77
|
import type { UserPromptSubmitContext } from "../plugin-api/types.js";
|
|
86
|
-
import {
|
|
78
|
+
import { defaultCompact } from "../plugins/defaults/compaction/compact.js";
|
|
79
|
+
import {
|
|
80
|
+
createInitialReducerState,
|
|
81
|
+
reduceContextOverflow,
|
|
82
|
+
type ReducerState,
|
|
83
|
+
} from "../plugins/defaults/compaction/context-overflow-reducer.js";
|
|
87
84
|
import { deepRepairHistory } from "../plugins/defaults/history-repair/terminal.js";
|
|
88
|
-
import postCompactReinject from "../plugins/defaults/memory-retrieval/hooks/post-compact.js";
|
|
89
85
|
import userPromptSubmitMemoryRetrieval, {
|
|
90
86
|
type MemoryRetrievalHookContext,
|
|
91
87
|
} from "../plugins/defaults/memory-retrieval/hooks/user-prompt-submit-temp.js";
|
|
92
|
-
import {
|
|
93
|
-
import { getMiddlewaresFor } from "../plugins/registry.js";
|
|
94
|
-
import type {
|
|
95
|
-
CompactionArgs,
|
|
96
|
-
CompactionResult,
|
|
97
|
-
OverflowReduceArgs,
|
|
98
|
-
OverflowReduceResult,
|
|
99
|
-
TurnContext as PluginTurnContext,
|
|
100
|
-
} from "../plugins/types.js";
|
|
101
|
-
import { PluginExecutionError, PluginTimeoutError } from "../plugins/types.js";
|
|
88
|
+
import { runHook } from "../plugins/pipeline.js";
|
|
102
89
|
import type { ContentBlock, Message } from "../providers/types.js";
|
|
103
90
|
import type { Provider } from "../providers/types.js";
|
|
104
|
-
import {
|
|
91
|
+
import {
|
|
92
|
+
isUntrustedTrustClass,
|
|
93
|
+
resolveActorTrust,
|
|
94
|
+
} from "../runtime/actor-trust-resolver.js";
|
|
105
95
|
import { broadcastMessage } from "../runtime/assistant-event-hub.js";
|
|
106
96
|
import { DAEMON_INTERNAL_ASSISTANT_ID } from "../runtime/assistant-scope.js";
|
|
107
97
|
import { publishConversationMessagesChanged } from "../runtime/sync/resource-sync-events.js";
|
|
108
|
-
import { getSubagentManager } from "../subagent/index.js";
|
|
109
98
|
import type { UsageActor } from "../usage/actors.js";
|
|
110
99
|
import { getLogger } from "../util/logger.js";
|
|
111
100
|
import { timeAgo } from "../util/time.js";
|
|
112
101
|
import { truncate } from "../util/truncate.js";
|
|
113
102
|
import { getWorkspaceGitService } from "../workspace/git-service.js";
|
|
114
103
|
import { commitTurnChanges } from "../workspace/turn-commit.js";
|
|
115
|
-
import {
|
|
116
|
-
type AssistantAttachmentDraft,
|
|
117
|
-
cleanAssistantContent,
|
|
118
|
-
} from "./assistant-attachments.js";
|
|
104
|
+
import { cleanAssistantContent } from "./assistant-attachments.js";
|
|
119
105
|
import { resolveOverflowAction } from "./context-overflow-policy.js";
|
|
120
|
-
import {
|
|
121
|
-
createInitialReducerState,
|
|
122
|
-
reduceContextOverflow,
|
|
123
|
-
type ReducerState,
|
|
124
|
-
} from "./context-overflow-reducer.js";
|
|
106
|
+
import type { Conversation } from "./conversation.js";
|
|
125
107
|
import {
|
|
126
108
|
createEventHandlerState,
|
|
127
109
|
dispatchAgentEvent,
|
|
@@ -140,35 +122,22 @@ import {
|
|
|
140
122
|
isUserCancellation,
|
|
141
123
|
} from "./conversation-error.js";
|
|
142
124
|
import { raceWithTimeout } from "./conversation-media-retry.js";
|
|
143
|
-
import type { MessageQueue } from "./conversation-queue-manager.js";
|
|
144
|
-
import type { QueueDrainReason } from "./conversation-queue-manager.js";
|
|
145
125
|
import type {
|
|
146
|
-
ChannelCapabilities,
|
|
147
126
|
InboundActorContext,
|
|
148
127
|
InjectionMode,
|
|
149
128
|
} from "./conversation-runtime-assembly.js";
|
|
150
129
|
import {
|
|
151
130
|
applyRuntimeInjections,
|
|
152
|
-
buildActiveDocuments,
|
|
153
|
-
buildActiveSurfaceContext,
|
|
154
|
-
buildSubagentStatusBlock,
|
|
155
|
-
buildUnifiedTurnContextBlock,
|
|
156
|
-
buildWorkspaceTopLevelContext,
|
|
157
131
|
getSlackCompactionWatermarkForPrefix,
|
|
158
132
|
inboundActorContextFromTrust,
|
|
159
133
|
inboundActorContextFromTrustContext,
|
|
160
|
-
loadSlackActiveThreadFocusBlock,
|
|
161
134
|
loadSlackChronologicalContext,
|
|
162
135
|
type SlackChronologicalContext,
|
|
163
136
|
stripInjectionsForCompaction,
|
|
164
137
|
} from "./conversation-runtime-assembly.js";
|
|
165
|
-
import type { SkillProjectionCache } from "./conversation-skill-tools.js";
|
|
166
138
|
import { markSurfaceCompleted } from "./conversation-surfaces.js";
|
|
167
139
|
import { recordUsage } from "./conversation-usage.js";
|
|
168
|
-
import {
|
|
169
|
-
formatTurnTimestamp,
|
|
170
|
-
resolveTurnTimezoneContext,
|
|
171
|
-
} from "./date-context.js";
|
|
140
|
+
import { resolveTurnTimezoneContext } from "./date-context.js";
|
|
172
141
|
import { getDiskPressureStatus } from "./disk-pressure-guard.js";
|
|
173
142
|
import { classifyDiskPressureTurnPolicy } from "./disk-pressure-policy.js";
|
|
174
143
|
import type {
|
|
@@ -177,15 +146,12 @@ import type {
|
|
|
177
146
|
SurfaceType,
|
|
178
147
|
UsageStats,
|
|
179
148
|
} from "./message-protocol.js";
|
|
180
|
-
import type { ConfirmationStateChanged } from "./message-types/messages.js";
|
|
181
149
|
import { parseActualTokensFromError } from "./parse-actual-tokens-from-error.js";
|
|
182
150
|
import {
|
|
183
151
|
persistUnsendableImageDowngrades,
|
|
184
152
|
UNSENDABLE_IMAGE_NOTE,
|
|
185
153
|
} from "./persist-unsendable-image.js";
|
|
186
|
-
import type { TraceEmitter } from "./trace-emitter.js";
|
|
187
154
|
import { resolveTrustClass, type TrustContext } from "./trust-context.js";
|
|
188
|
-
import { stripHistoricalWebSearchResults } from "./web-search-history.js";
|
|
189
155
|
|
|
190
156
|
const log = getLogger("conversation-agent-loop");
|
|
191
157
|
|
|
@@ -206,23 +172,14 @@ const TOOL_FRIENDLY_LABEL: Record<string, string> = {
|
|
|
206
172
|
skill_execute: "Run Skill Tool",
|
|
207
173
|
};
|
|
208
174
|
|
|
209
|
-
type GitServiceInitializer = {
|
|
210
|
-
ensureInitialized(): Promise<void>;
|
|
211
|
-
};
|
|
212
|
-
|
|
213
175
|
function formatDiskPressureBlockedMessage(): string {
|
|
214
176
|
return "Storage is critically low, so background processes are paused and remote messages are ignored until the guardian frees enough space. Remote senders should try again later.";
|
|
215
177
|
}
|
|
216
178
|
|
|
217
179
|
// ── Plugin pipeline helpers ──────────────────────────────────────────
|
|
218
|
-
//
|
|
219
|
-
// Canonical {@link PluginTurnContext} builder threaded into every
|
|
220
|
-
// `runPipeline` call inside `runAgentLoopImpl`. The orchestrator composes
|
|
221
|
-
// the context on demand at each call site from ambient state rather than
|
|
222
|
-
// carrying a persistent `TurnContext` instance across the turn.
|
|
223
180
|
|
|
224
181
|
/**
|
|
225
|
-
* Synthetic fallback trust context used when the orchestrator fires a
|
|
182
|
+
* Synthetic fallback trust context used when the orchestrator fires a hook
|
|
226
183
|
* before the per-turn trust snapshot has been captured (e.g. invocations that
|
|
227
184
|
* bypass `processMessage` / `drainQueue`). We bias to `unknown` rather than
|
|
228
185
|
* `guardian` so a missing snapshot cannot accidentally grant elevated trust
|
|
@@ -233,65 +190,23 @@ const FALLBACK_TURN_TRUST: TrustContext = {
|
|
|
233
190
|
trustClass: "unknown",
|
|
234
191
|
};
|
|
235
192
|
|
|
236
|
-
/**
|
|
237
|
-
* Build the {@link TurnContext} passed to {@link runPipeline}.
|
|
238
|
-
*
|
|
239
|
-
* Canonical source of truth for every pipeline call site inside the agent
|
|
240
|
-
* loop. Every `runPipeline` invocation in `runAgentLoopImpl` (and in the
|
|
241
|
-
* handlers that share its ambient state) must route through this helper
|
|
242
|
-
* rather than constructing a `TurnContext` literal inline — this keeps
|
|
243
|
-
* `turnIndex`, trust resolution, and the `contextWindowManager` attachment
|
|
244
|
-
* consistent across pipeline slots, which in turn keeps structured logs
|
|
245
|
-
* filtered by `conversationId`/`turnIndex` coherent across slots.
|
|
246
|
-
*
|
|
247
|
-
* Behavior:
|
|
248
|
-
* - `turnIndex` is always `ctx.turnCount` — the orchestrator-owned
|
|
249
|
-
* 0-based turn counter. Reading from a single source avoids the
|
|
250
|
-
* earlier inconsistency (`ctx.turnCount`, `ctx.messages.length - 1`,
|
|
251
|
-
* `ctx.messages.length`, and `0` were all used for the same turn).
|
|
252
|
-
* - Trust pulls from the per-turn snapshot first, then the conversation-
|
|
253
|
-
* level context, then {@link FALLBACK_TURN_TRUST}. The cascade matches
|
|
254
|
-
* the one inside the orchestrator's inline injection assembly so
|
|
255
|
-
* middleware reads the same trust class the runtime sees.
|
|
256
|
-
* - `contextWindowManager` is attached unconditionally. Pipelines that
|
|
257
|
-
* don't need it can ignore it; the default compaction plugin reads it
|
|
258
|
-
* via the typed optional field on `TurnContext`.
|
|
259
|
-
*/
|
|
260
|
-
function buildPluginTurnContext(
|
|
261
|
-
ctx: AgentLoopConversationContext,
|
|
262
|
-
requestId: string,
|
|
263
|
-
): PluginTurnContext {
|
|
264
|
-
const trust =
|
|
265
|
-
ctx.currentTurnTrustContext ?? ctx.trustContext ?? FALLBACK_TURN_TRUST;
|
|
266
|
-
return {
|
|
267
|
-
requestId,
|
|
268
|
-
conversationId: ctx.conversationId,
|
|
269
|
-
turnIndex: ctx.turnCount,
|
|
270
|
-
trust,
|
|
271
|
-
contextWindowManager: ctx.contextWindowManager,
|
|
272
|
-
callSite: ctx.currentCallSite,
|
|
273
|
-
};
|
|
274
|
-
}
|
|
275
|
-
|
|
276
193
|
/**
|
|
277
194
|
* Trust class of the actor whose turn is in progress, for the compactor's
|
|
278
195
|
* image manifest filter. Prefers the turn-start snapshot
|
|
279
|
-
* ({@link
|
|
196
|
+
* ({@link Conversation.currentTurnTrustContext}) over the live
|
|
280
197
|
* trust context so compaction running in a later tool iteration can't pick up
|
|
281
198
|
* a concurrent request's actor.
|
|
282
199
|
*/
|
|
283
200
|
function resolveTurnActorTrustClass(
|
|
284
|
-
ctx:
|
|
201
|
+
ctx: Conversation,
|
|
285
202
|
): TrustContext["trustClass"] | undefined {
|
|
286
203
|
return (ctx.currentTurnTrustContext ?? ctx.trustContext)?.trustClass;
|
|
287
204
|
}
|
|
288
205
|
|
|
289
|
-
// ── Context Interface ────────────────────────────────────────────────
|
|
290
|
-
|
|
291
206
|
/**
|
|
292
207
|
* Per-surface entry tracked on the current turn. Inline shape kept stable so
|
|
293
208
|
* routes and persistence helpers can consume it via a named import instead of
|
|
294
|
-
* `infer`-extracting from {@link
|
|
209
|
+
* `infer`-extracting from {@link Conversation}.
|
|
295
210
|
*/
|
|
296
211
|
export interface AssistantSurface {
|
|
297
212
|
surfaceId: string;
|
|
@@ -310,180 +225,10 @@ export interface AssistantSurface {
|
|
|
310
225
|
toolCallId?: string;
|
|
311
226
|
}
|
|
312
227
|
|
|
313
|
-
export interface AgentLoopConversationContext {
|
|
314
|
-
readonly conversationId: string;
|
|
315
|
-
messages: Message[];
|
|
316
|
-
isProcessing(): boolean;
|
|
317
|
-
setProcessing(value: boolean): void;
|
|
318
|
-
abortController: AbortController | null;
|
|
319
|
-
currentRequestId?: string;
|
|
320
|
-
/**
|
|
321
|
-
* The {@link LLMCallSite} of the in-flight turn, set at turn start from
|
|
322
|
-
* `options?.callSite ?? "mainAgent"`. Read by {@link buildPluginTurnContext}
|
|
323
|
-
* so pipeline/injector plugins can tell the main reply apart from
|
|
324
|
-
* background agent-loop work (compaction, subagents, …) on this same
|
|
325
|
-
* conversation. Per-turn mutable, mirroring {@link currentRequestId}.
|
|
326
|
-
*/
|
|
327
|
-
currentCallSite?: LLMCallSite;
|
|
328
|
-
|
|
329
|
-
readonly agentLoop: AgentLoop;
|
|
330
|
-
readonly provider: Provider;
|
|
331
|
-
readonly systemPrompt: string;
|
|
332
|
-
|
|
333
|
-
readonly contextWindowManager: ContextWindowManager;
|
|
334
|
-
contextCompactedMessageCount: number;
|
|
335
|
-
contextCompactedAt: number | null;
|
|
336
|
-
/**
|
|
337
|
-
* Set by `applyCompactionResult` when compaction strips runtime injections
|
|
338
|
-
* from the preserved tail. The next agent loop turn promotes this into a
|
|
339
|
-
* `compactedThisTurn` signal so NOW.md, PKB, and the v2 static block are
|
|
340
|
-
* re-injected on the first turn following `/compact` (which runs outside
|
|
341
|
-
* the agent loop and so has no other way to surface that compaction
|
|
342
|
-
* happened just before this turn).
|
|
343
|
-
*/
|
|
344
|
-
pendingPostCompactReinject: boolean;
|
|
345
|
-
|
|
346
|
-
readonly graphMemory: ConversationGraphMemory;
|
|
347
|
-
|
|
348
|
-
currentActiveSurfaceId?: string;
|
|
349
|
-
currentPage?: string;
|
|
350
|
-
readonly surfaceState: Map<
|
|
351
|
-
string,
|
|
352
|
-
{
|
|
353
|
-
surfaceType: SurfaceType;
|
|
354
|
-
data: SurfaceData;
|
|
355
|
-
title?: string;
|
|
356
|
-
actions?: Array<{
|
|
357
|
-
id: string;
|
|
358
|
-
label: string;
|
|
359
|
-
style?: string;
|
|
360
|
-
data?: Record<string, unknown>;
|
|
361
|
-
}>;
|
|
362
|
-
}
|
|
363
|
-
>;
|
|
364
|
-
pendingSurfaceActions: Map<string, { surfaceType: SurfaceType }>;
|
|
365
|
-
surfaceActionRequestIds: Set<string>;
|
|
366
|
-
approvedViaPromptThisTurn?: boolean;
|
|
367
|
-
currentTurnSurfaces: AssistantSurface[];
|
|
368
|
-
|
|
369
|
-
workingDir: string;
|
|
370
|
-
workspaceTopLevelContext: string | null;
|
|
371
|
-
workspaceTopLevelDirty: boolean;
|
|
372
|
-
channelCapabilities?: ChannelCapabilities;
|
|
373
|
-
/** Per-turn snapshot of trustContext, frozen at message-processing start. */
|
|
374
|
-
currentTurnTrustContext?: TrustContext;
|
|
375
|
-
/** Per-turn snapshot of channelCapabilities, frozen at message-processing start. */
|
|
376
|
-
currentTurnChannelCapabilities?: ChannelCapabilities;
|
|
377
|
-
/**
|
|
378
|
-
* Current inference-profile override for this turn. Read by
|
|
379
|
-
* `createToolExecutor` so `ToolContext.overrideProfile` carries the same
|
|
380
|
-
* profile the agent loop is sending to the provider. Refreshed between
|
|
381
|
-
* model calls so an explicitly confirmed profile session opened mid-turn
|
|
382
|
-
* is inherited by later tool executions and nested subagents.
|
|
383
|
-
*/
|
|
384
|
-
currentTurnOverrideProfile?: string;
|
|
385
|
-
/**
|
|
386
|
-
* Set by the `switch_inference_profile` tool when the model self-selects a
|
|
387
|
-
* different profile mid-turn. Read by `readCurrentOverrideProfile` in the
|
|
388
|
-
* agent loop so the next LLM call uses the switched profile. Reset at
|
|
389
|
-
* turn start.
|
|
390
|
-
*/
|
|
391
|
-
toolRoutedProfile?: string;
|
|
392
|
-
commandIntent?: { type: string; payload?: string; languageCode?: string };
|
|
393
|
-
trustContext?: TrustContext;
|
|
394
|
-
/** Task-run scope for the current turn. Cleared at turn end so queued/drained turns don't inherit it. */
|
|
395
|
-
taskRunId?: string;
|
|
396
|
-
assistantId?: string;
|
|
397
|
-
voiceCallControlPrompt?: string;
|
|
398
|
-
transportHints?: string[];
|
|
399
|
-
clientTimezone?: string;
|
|
400
|
-
|
|
401
|
-
readonly coreToolNames: Set<string>;
|
|
402
|
-
allowedToolNames?: Set<string>;
|
|
403
|
-
diskPressureCleanupModeActive?: boolean;
|
|
404
|
-
toolsDisabledDepth: number;
|
|
405
|
-
preactivatedSkillIds?: string[];
|
|
406
|
-
readonly skillProjectionState: Map<string, string>;
|
|
407
|
-
readonly skillProjectionCache: SkillProjectionCache;
|
|
408
|
-
|
|
409
|
-
readonly traceEmitter: TraceEmitter;
|
|
410
|
-
readonly profiler: ToolProfiler;
|
|
411
|
-
usageStats: UsageStats;
|
|
412
|
-
turnCount: number;
|
|
413
|
-
|
|
414
|
-
lastAssistantAttachments: AssistantAttachmentDraft[];
|
|
415
|
-
lastAttachmentWarnings: string[];
|
|
416
|
-
|
|
417
|
-
hasNoClient: boolean;
|
|
418
|
-
/** True when this conversation is itself a subagent (suppresses subagent status injection). */
|
|
419
|
-
isSubagent?: boolean;
|
|
420
|
-
headlessLock?: boolean;
|
|
421
|
-
readonly streamThinking: boolean;
|
|
422
|
-
readonly prompter: PermissionPrompter;
|
|
423
|
-
readonly queue: MessageQueue;
|
|
424
|
-
|
|
425
|
-
emitActivityState(
|
|
426
|
-
phase:
|
|
427
|
-
| "idle"
|
|
428
|
-
| "thinking"
|
|
429
|
-
| "streaming"
|
|
430
|
-
| "tool_running"
|
|
431
|
-
| "awaiting_confirmation",
|
|
432
|
-
reason:
|
|
433
|
-
| "message_dequeued"
|
|
434
|
-
| "thinking_delta"
|
|
435
|
-
| "first_text_delta"
|
|
436
|
-
| "tool_use_start"
|
|
437
|
-
| "preview_start"
|
|
438
|
-
| "tool_result_received"
|
|
439
|
-
| "confirmation_requested"
|
|
440
|
-
| "confirmation_resolved"
|
|
441
|
-
| "context_compacting"
|
|
442
|
-
| "message_complete"
|
|
443
|
-
| "generation_cancelled"
|
|
444
|
-
| "error_terminal",
|
|
445
|
-
options?: {
|
|
446
|
-
anchor?: "assistant_turn" | "user_turn" | "global";
|
|
447
|
-
requestId?: string;
|
|
448
|
-
statusText?: string;
|
|
449
|
-
},
|
|
450
|
-
): void;
|
|
451
|
-
emitConfirmationStateChanged(
|
|
452
|
-
params: ConfirmationStateChanged extends {
|
|
453
|
-
type: infer _;
|
|
454
|
-
}
|
|
455
|
-
? Omit<ConfirmationStateChanged, "type">
|
|
456
|
-
: never,
|
|
457
|
-
): void;
|
|
458
|
-
|
|
459
|
-
/**
|
|
460
|
-
* Optional callback invoked by the Conversation when a confirmation state changes.
|
|
461
|
-
* The agent loop registers this to track requestId → toolUseId mappings
|
|
462
|
-
* and record confirmation outcomes for persistence.
|
|
463
|
-
*/
|
|
464
|
-
onConfirmationOutcome?: (
|
|
465
|
-
requestId: string,
|
|
466
|
-
state: string,
|
|
467
|
-
toolUseId?: string,
|
|
468
|
-
) => void;
|
|
469
|
-
|
|
470
|
-
getWorkspaceGitService?: (workspaceDir: string) => GitServiceInitializer;
|
|
471
|
-
commitTurnChanges?: typeof commitTurnChanges;
|
|
472
|
-
|
|
473
|
-
refreshWorkspaceTopLevelContextIfNeeded(): void;
|
|
474
|
-
markWorkspaceTopLevelDirty(): void;
|
|
475
|
-
getQueueDepth(): number;
|
|
476
|
-
hasQueuedMessages(): boolean;
|
|
477
|
-
canHandoffAtCheckpoint(): boolean;
|
|
478
|
-
drainQueue(reason: QueueDrainReason): Promise<void>;
|
|
479
|
-
getTurnChannelContext(): TurnChannelContext | null;
|
|
480
|
-
getTurnInterfaceContext(): TurnInterfaceContext | null;
|
|
481
|
-
}
|
|
482
|
-
|
|
483
228
|
// ── runAgentLoop ─────────────────────────────────────────────────────
|
|
484
229
|
|
|
485
230
|
export async function runAgentLoopImpl(
|
|
486
|
-
ctx:
|
|
231
|
+
ctx: Conversation,
|
|
487
232
|
content: string,
|
|
488
233
|
userMessageId: string,
|
|
489
234
|
onEvent: (msg: ServerMessage) => void,
|
|
@@ -556,26 +301,22 @@ export async function runAgentLoopImpl(
|
|
|
556
301
|
// `resolveCallSiteConfig`, picking up any user overrides under
|
|
557
302
|
// `llm.callSites.mainAgent` (falling back to `llm.default` when absent).
|
|
558
303
|
const turnCallSite: LLMCallSite = options?.callSite ?? "mainAgent";
|
|
559
|
-
// Expose the turn's call site
|
|
560
|
-
//
|
|
304
|
+
// Expose the turn's call site on the live conversation so the runtime
|
|
305
|
+
// injection assembly self-resolves it for the turn's plugin contexts.
|
|
561
306
|
ctx.currentCallSite = turnCallSite;
|
|
562
307
|
|
|
563
|
-
// Read the conversation row once for both the override-profile derivation
|
|
564
|
-
// below and the title-replaceability check at turn start. Later reads in
|
|
565
|
-
// this function (post-turn truncation, disk sync, home-feed emission)
|
|
566
|
-
// intentionally re-read because state can change during the turn.
|
|
567
|
-
const turnStartConversation = getConversation(ctx.conversationId);
|
|
568
|
-
|
|
569
308
|
// Optional per-turn inference-profile override. Plumbed through to every
|
|
570
309
|
// LLM call the loop emits and inherited by any subagents spawned during
|
|
571
310
|
// this turn. Caller-supplied `options.overrideProfile` (e.g.
|
|
572
311
|
// SubagentManager forwarding the parent's pinned profile into the
|
|
573
|
-
// spawned subagent's background conversation) wins over the
|
|
574
|
-
// so the agent loop's
|
|
575
|
-
// explicitly inherited override.
|
|
312
|
+
// spawned subagent's background conversation) wins over the conversation's
|
|
313
|
+
// own override so the agent loop's background-skip rule doesn't zero out an
|
|
314
|
+
// explicitly inherited override. The override state is mirrored onto the
|
|
315
|
+
// live conversation (hydrated on load, kept current by the HTTP setters and
|
|
316
|
+
// the expiry reaper), so the derivation reads `ctx` rather than re-fetching
|
|
317
|
+
// the row.
|
|
576
318
|
const userExplicitOverride =
|
|
577
|
-
options?.overrideProfile ??
|
|
578
|
-
getConversationOverrideProfileFromRow(turnStartConversation);
|
|
319
|
+
options?.overrideProfile ?? resolveOverrideProfile(ctx);
|
|
579
320
|
|
|
580
321
|
const config = getConfig();
|
|
581
322
|
|
|
@@ -588,9 +329,7 @@ export async function runAgentLoopImpl(
|
|
|
588
329
|
|
|
589
330
|
const readCurrentOverrideProfile = (): string | undefined =>
|
|
590
331
|
options?.overrideProfile ??
|
|
591
|
-
|
|
592
|
-
getConversation(ctx.conversationId),
|
|
593
|
-
) ??
|
|
332
|
+
resolveOverrideProfile(ctx) ??
|
|
594
333
|
ctx.toolRoutedProfile;
|
|
595
334
|
|
|
596
335
|
const effectiveContextWindow = resolveEffectiveContextWindow({
|
|
@@ -608,11 +347,7 @@ export async function runAgentLoopImpl(
|
|
|
608
347
|
}).contextWindow,
|
|
609
348
|
currentEffectiveContextWindow,
|
|
610
349
|
);
|
|
611
|
-
|
|
612
|
-
ctx.contextWindowManager as ContextWindowManager & {
|
|
613
|
-
updateConfig?: (config: ContextWindowConfig) => void;
|
|
614
|
-
};
|
|
615
|
-
contextWindowManager.updateConfig?.(currentContextWindowConfig);
|
|
350
|
+
ctx.contextWindowManager.updateConfig(currentContextWindowConfig);
|
|
616
351
|
|
|
617
352
|
let appliedOverrideProfile = turnOverrideProfile;
|
|
618
353
|
let emittedToolRoutedProfile: string | undefined;
|
|
@@ -632,7 +367,7 @@ export async function runAgentLoopImpl(
|
|
|
632
367
|
}).contextWindow,
|
|
633
368
|
currentEffectiveContextWindow,
|
|
634
369
|
);
|
|
635
|
-
contextWindowManager.updateConfig
|
|
370
|
+
ctx.contextWindowManager.updateConfig(currentContextWindowConfig);
|
|
636
371
|
appliedOverrideProfile = currentOverrideProfile;
|
|
637
372
|
rlog.info(
|
|
638
373
|
{ overrideProfile: currentOverrideProfile ?? null },
|
|
@@ -751,11 +486,17 @@ export async function runAgentLoopImpl(
|
|
|
751
486
|
|
|
752
487
|
const isInteractiveResolved =
|
|
753
488
|
options?.isInteractive ?? (!ctx.hasNoClient && !ctx.headlessLock);
|
|
489
|
+
// Whether the in-flight turn has no human present to answer clarification
|
|
490
|
+
// questions. Derived from the loop's `isInteractive` option (which can fall
|
|
491
|
+
// back to mutable client/headless state that flips mid-turn), so it is
|
|
492
|
+
// resolved once here and threaded into every re-injection — including the
|
|
493
|
+
// post-compaction hook — rather than re-read per assembly call.
|
|
494
|
+
const isNonInteractive = !isInteractiveResolved;
|
|
754
495
|
const diskPressureDecision = classifyDiskPressureTurnPolicy(
|
|
755
496
|
getDiskPressureStatus(),
|
|
756
497
|
{
|
|
757
|
-
conversationType:
|
|
758
|
-
conversationSource:
|
|
498
|
+
conversationType: ctx.conversationType ?? null,
|
|
499
|
+
conversationSource: ctx.source ?? null,
|
|
759
500
|
callSite: turnCallSite,
|
|
760
501
|
isInteractive: isInteractiveResolved,
|
|
761
502
|
sourceChannel:
|
|
@@ -772,10 +513,6 @@ export async function runAgentLoopImpl(
|
|
|
772
513
|
: null,
|
|
773
514
|
},
|
|
774
515
|
);
|
|
775
|
-
const diskPressureContext =
|
|
776
|
-
diskPressureDecision.action === "allow-cleanup-mode"
|
|
777
|
-
? { cleanupModeActive: true }
|
|
778
|
-
: null;
|
|
779
516
|
ctx.diskPressureCleanupModeActive =
|
|
780
517
|
diskPressureDecision.action === "allow-cleanup-mode";
|
|
781
518
|
|
|
@@ -879,22 +616,7 @@ export async function runAgentLoopImpl(
|
|
|
879
616
|
}
|
|
880
617
|
|
|
881
618
|
const isFirstMessage = ctx.messages.length === 1;
|
|
882
|
-
// Promote a pending post-compaction re-inject signal (e.g. from `/compact`)
|
|
883
|
-
// into `compactedThisTurn` so NOW.md / PKB / v2 static blocks land on this
|
|
884
|
-
// turn even when no mid-turn compaction fires. Clear the flag immediately
|
|
885
|
-
// so this fires exactly once per `/compact` event.
|
|
886
|
-
const consumedPostCompactReinject = ctx.pendingPostCompactReinject;
|
|
887
|
-
ctx.pendingPostCompactReinject = false;
|
|
888
|
-
state.shouldInjectWorkspace = isFirstMessage || consumedPostCompactReinject;
|
|
889
|
-
let compactedThisTurn = consumedPostCompactReinject;
|
|
890
|
-
let slackCompactedThisTurn = false;
|
|
891
619
|
const isSlackConversation = ctx.channelCapabilities?.channel === "slack";
|
|
892
|
-
let currentSlackContextSummary =
|
|
893
|
-
turnStartConversation?.contextSummary ?? null;
|
|
894
|
-
let currentSlackContextCompactedMessageCount =
|
|
895
|
-
turnStartConversation?.contextCompactedMessageCount ?? 0;
|
|
896
|
-
let currentSlackContextCompactionWatermarkTs =
|
|
897
|
-
turnStartConversation?.slackContextCompactionWatermarkTs ?? null;
|
|
898
620
|
const loadCurrentSlackChronologicalContext =
|
|
899
621
|
(): SlackChronologicalContext | null => {
|
|
900
622
|
if (!isSlackConversation) return null;
|
|
@@ -903,18 +625,15 @@ export async function runAgentLoopImpl(
|
|
|
903
625
|
ctx.channelCapabilities!,
|
|
904
626
|
{
|
|
905
627
|
trustClass: ctx.trustContext?.trustClass,
|
|
906
|
-
contextSummary:
|
|
907
|
-
contextCompactedMessageCount:
|
|
908
|
-
currentSlackContextCompactedMessageCount,
|
|
628
|
+
contextSummary: ctx.contextSummary,
|
|
629
|
+
contextCompactedMessageCount: ctx.contextCompactedMessageCount,
|
|
909
630
|
slackContextCompactionWatermarkTs:
|
|
910
|
-
|
|
631
|
+
ctx.slackContextCompactionWatermarkTs,
|
|
911
632
|
},
|
|
912
633
|
);
|
|
913
634
|
};
|
|
914
635
|
let slackChronologicalContext: SlackChronologicalContext | null =
|
|
915
636
|
loadCurrentSlackChronologicalContext();
|
|
916
|
-
const messagesForStartOfTurnCompaction =
|
|
917
|
-
slackChronologicalContext?.messages ?? ctx.messages;
|
|
918
637
|
const getSlackProvenanceContextForCompactionBasis = (
|
|
919
638
|
messages: Message[],
|
|
920
639
|
compactedMessages: number,
|
|
@@ -1001,15 +720,6 @@ export async function runAgentLoopImpl(
|
|
|
1001
720
|
await applyCompactionResult(ctx, result, onEvent, reqId, {
|
|
1002
721
|
slackContextCompactionWatermarkTs: slackWatermarkTs,
|
|
1003
722
|
});
|
|
1004
|
-
currentSlackContextSummary = result.summaryText;
|
|
1005
|
-
currentSlackContextCompactedMessageCount =
|
|
1006
|
-
ctx.contextCompactedMessageCount;
|
|
1007
|
-
if (slackWatermarkTs) {
|
|
1008
|
-
currentSlackContextCompactionWatermarkTs = slackWatermarkTs;
|
|
1009
|
-
}
|
|
1010
|
-
if (isSlackConversation) {
|
|
1011
|
-
slackCompactedThisTurn = true;
|
|
1012
|
-
}
|
|
1013
723
|
slackChronologicalContext = projectSlackProvenanceAfterCompaction(
|
|
1014
724
|
provenanceContext,
|
|
1015
725
|
compactedBasis,
|
|
@@ -1017,88 +727,6 @@ export async function runAgentLoopImpl(
|
|
|
1017
727
|
);
|
|
1018
728
|
};
|
|
1019
729
|
|
|
1020
|
-
const compactCheck = ctx.contextWindowManager.shouldCompact(
|
|
1021
|
-
messagesForStartOfTurnCompaction,
|
|
1022
|
-
);
|
|
1023
|
-
// Skip auto-compaction while the circuit breaker is open. Force paths
|
|
1024
|
-
// and user-initiated /compact bypass this check.
|
|
1025
|
-
const autoCompactAllowed =
|
|
1026
|
-
!(await ctx.agentLoop.compactionCircuit.isOpen(ctx));
|
|
1027
|
-
if (compactCheck.needed && autoCompactAllowed) {
|
|
1028
|
-
ctx.emitActivityState("thinking", "context_compacting", {
|
|
1029
|
-
requestId: reqId,
|
|
1030
|
-
});
|
|
1031
|
-
}
|
|
1032
|
-
const compactionOptions = {
|
|
1033
|
-
precomputedEstimate: compactCheck.estimatedTokens,
|
|
1034
|
-
overrideProfile: resolveCurrentOverrideProfile() ?? null,
|
|
1035
|
-
actorTrustClass: resolveTurnActorTrustClass(ctx),
|
|
1036
|
-
};
|
|
1037
|
-
let compacted: Awaited<
|
|
1038
|
-
ReturnType<typeof ctx.contextWindowManager.maybeCompact>
|
|
1039
|
-
> | null = null;
|
|
1040
|
-
if (autoCompactAllowed) {
|
|
1041
|
-
try {
|
|
1042
|
-
compacted = (await runPipeline<CompactionArgs, CompactionResult>(
|
|
1043
|
-
"compaction",
|
|
1044
|
-
getMiddlewaresFor("compaction"),
|
|
1045
|
-
(args) =>
|
|
1046
|
-
defaultCompactionTerminal(args, buildPluginTurnContext(ctx, reqId)),
|
|
1047
|
-
{
|
|
1048
|
-
messages: messagesForStartOfTurnCompaction,
|
|
1049
|
-
signal: abortController.signal,
|
|
1050
|
-
options: compactionOptions,
|
|
1051
|
-
},
|
|
1052
|
-
buildPluginTurnContext(ctx, reqId),
|
|
1053
|
-
DEFAULT_TIMEOUTS.compaction,
|
|
1054
|
-
)) as Awaited<ReturnType<typeof ctx.contextWindowManager.maybeCompact>>;
|
|
1055
|
-
} catch (err) {
|
|
1056
|
-
if (err instanceof PluginTimeoutError) {
|
|
1057
|
-
// Pipeline exceeded its budget. Record the failure so the circuit
|
|
1058
|
-
// breaker tracks consecutive timeouts (it trips after three),
|
|
1059
|
-
// then degrade gracefully by skipping compaction this turn —
|
|
1060
|
-
// the turn proceeds with the un-compacted history rather than
|
|
1061
|
-
// hard-failing. The inner summary call has been aborted by the
|
|
1062
|
-
// runner's signal-linking, so updateSummary's local fallback
|
|
1063
|
-
// also ran before this catch block is reached.
|
|
1064
|
-
rlog.warn(
|
|
1065
|
-
{ err, phase: "start-of-turn-compaction" },
|
|
1066
|
-
"Compaction pipeline timed out — skipping compaction this turn",
|
|
1067
|
-
);
|
|
1068
|
-
await ctx.agentLoop.compactionCircuit.recordOutcome(
|
|
1069
|
-
ctx,
|
|
1070
|
-
true,
|
|
1071
|
-
onEvent,
|
|
1072
|
-
);
|
|
1073
|
-
compacted = null;
|
|
1074
|
-
} else {
|
|
1075
|
-
throw err;
|
|
1076
|
-
}
|
|
1077
|
-
}
|
|
1078
|
-
}
|
|
1079
|
-
// Only track circuit-breaker state when a summary LLM call actually ran.
|
|
1080
|
-
// `summaryFailed` is `undefined` on early returns (compaction disabled,
|
|
1081
|
-
// below threshold, no eligible messages, truncation-only
|
|
1082
|
-
// path) — treating those as "successful" compactions would silently reset
|
|
1083
|
-
// the 3-strike counter and break the invariant.
|
|
1084
|
-
if (compacted && compacted.summaryFailed !== undefined) {
|
|
1085
|
-
await ctx.agentLoop.compactionCircuit.recordOutcome(
|
|
1086
|
-
ctx,
|
|
1087
|
-
compacted.summaryFailed,
|
|
1088
|
-
onEvent,
|
|
1089
|
-
);
|
|
1090
|
-
}
|
|
1091
|
-
if (compacted?.compacted) {
|
|
1092
|
-
await applySuccessfulCompaction(
|
|
1093
|
-
compacted,
|
|
1094
|
-
messagesForStartOfTurnCompaction,
|
|
1095
|
-
);
|
|
1096
|
-
state.shouldInjectWorkspace = true;
|
|
1097
|
-
if (compacted.compactedPersistedMessages > 0) {
|
|
1098
|
-
compactedThisTurn = true;
|
|
1099
|
-
}
|
|
1100
|
-
}
|
|
1101
|
-
|
|
1102
730
|
// Register confirmation outcome tracker so the agent loop can link
|
|
1103
731
|
// confirmation decisions to tool_use_ids for persistence.
|
|
1104
732
|
ctx.onConfirmationOutcome = (requestId, confirmationState, toolUseId) => {
|
|
@@ -1165,18 +793,20 @@ export async function runAgentLoopImpl(
|
|
|
1165
793
|
}
|
|
1166
794
|
}
|
|
1167
795
|
|
|
1168
|
-
// Resolve the
|
|
1169
|
-
//
|
|
1170
|
-
//
|
|
1171
|
-
// assembly.
|
|
1172
|
-
const interfaceName =
|
|
1173
|
-
capturedTurnInterfaceContext.userMessageInterface ?? undefined;
|
|
1174
|
-
const channelName =
|
|
1175
|
-
capturedTurnChannelContext?.userMessageChannel ?? undefined;
|
|
796
|
+
// Resolve the guardian flag for this turn. It derives only from the
|
|
797
|
+
// resolved actor trust class — never from retrieval — so it settles before
|
|
798
|
+
// context assembly.
|
|
1176
799
|
const isGuardian =
|
|
1177
800
|
resolvedInboundActorContext?.trustClass === "guardian" ||
|
|
1178
801
|
!resolvedInboundActorContext;
|
|
1179
802
|
|
|
803
|
+
// Unified `<turn_context>` actor input, included only for non-guardian
|
|
804
|
+
// turns. Resolved once at turn start and threaded per call site (like
|
|
805
|
+
// `modelProfile`) so post-compaction re-injection receives it as an
|
|
806
|
+
// explicit hook input rather than re-deriving it from live state that can
|
|
807
|
+
// flip mid-turn.
|
|
808
|
+
const actorContext = isGuardian ? null : resolvedInboundActorContext;
|
|
809
|
+
|
|
1180
810
|
// Surface long gaps between user messages so the model can acknowledge
|
|
1181
811
|
// the absence naturally. Gated at >12h to avoid noisy injection during
|
|
1182
812
|
// normal back-and-forth turns.
|
|
@@ -1196,6 +826,19 @@ export async function runAgentLoopImpl(
|
|
|
1196
826
|
}
|
|
1197
827
|
}
|
|
1198
828
|
|
|
829
|
+
// Freeze the turn-start client timezone and long-absence gap on the
|
|
830
|
+
// conversation so `applyRuntimeInjections` sources them from live state —
|
|
831
|
+
// like the channel/voice/transport hints. Frozen here (rather than read
|
|
832
|
+
// live in assembly) because the live `ctx.clientTimezone` is overwritten
|
|
833
|
+
// when a newer message for the same conversation arrives mid-turn, which
|
|
834
|
+
// would otherwise leak a queued message's timezone into the in-flight turn.
|
|
835
|
+
// The `current_time` value is computed fresh at each injection point, so
|
|
836
|
+
// it is not part of this snapshot.
|
|
837
|
+
ctx.currentTurnTemporalSnapshot = {
|
|
838
|
+
clientTimezone: timezoneContext.clientTimezone,
|
|
839
|
+
timeSinceLastMessage,
|
|
840
|
+
};
|
|
841
|
+
|
|
1199
842
|
// Resolve the effective profile key for this turn and detect changes.
|
|
1200
843
|
// Only inject model_profile into the turn context when the profile
|
|
1201
844
|
// changed since the last turn (or on the first turn of a conversation)
|
|
@@ -1204,7 +847,7 @@ export async function runAgentLoopImpl(
|
|
|
1204
847
|
turnOverrideProfile ??
|
|
1205
848
|
config.llm.activeProfile ??
|
|
1206
849
|
resolveDefaultProfileKey("mainAgent", config.llm);
|
|
1207
|
-
const lastNotified =
|
|
850
|
+
const lastNotified = ctx.lastNotifiedInferenceProfile;
|
|
1208
851
|
let modelProfileStr: string | null = null;
|
|
1209
852
|
if (effectiveProfileKey != null && effectiveProfileKey !== lastNotified) {
|
|
1210
853
|
const profileEntry = config.llm.profiles?.[effectiveProfileKey];
|
|
@@ -1221,479 +864,42 @@ export async function runAgentLoopImpl(
|
|
|
1221
864
|
state.pendingNotifiedInferenceProfile = effectiveProfileKey;
|
|
1222
865
|
}
|
|
1223
866
|
|
|
1224
|
-
// Memory retrieval — fetches PKB
|
|
1225
|
-
// persists the retrieval's own side effects (injected-block
|
|
1226
|
-
// recall log, `memory_recalled` event)
|
|
1227
|
-
//
|
|
1228
|
-
//
|
|
1229
|
-
//
|
|
1230
|
-
//
|
|
1231
|
-
//
|
|
1232
|
-
//
|
|
867
|
+
// Memory retrieval + runtime injection — fetches PKB / NOW.md / memory-graph
|
|
868
|
+
// outputs, persists the retrieval's own side effects (injected-block
|
|
869
|
+
// metadata, recall log, `memory_recalled` event), and assembles the turn's
|
|
870
|
+
// runtime-injection blocks onto the history (persisting those blocks too).
|
|
871
|
+
// Runs at the early "prompt submitted, before context assembly" moment so
|
|
872
|
+
// its injected output is what the agent loop receives. It is shaped as the
|
|
873
|
+
// `user-prompt-submit-temp` hook handler but invoked directly for now,
|
|
874
|
+
// separate from the canonical late `user-prompt-submit` hook (history
|
|
875
|
+
// repair, title) that fires just before the loop.
|
|
876
|
+
// The injection inputs (`mode`, `isNonInteractive`, `modelProfile`,
|
|
877
|
+
// `actorContext`) are resolved once at turn start and threaded in so
|
|
878
|
+
// post-compaction re-injection reuses the same snapshot rather than live
|
|
879
|
+
// state that can flip mid-turn.
|
|
1233
880
|
const isTrustedActor = resolveTrustClass(ctx.trustContext) === "guardian";
|
|
881
|
+
let currentInjectionMode: InjectionMode = "full";
|
|
1234
882
|
const memoryCtx: MemoryRetrievalHookContext = {
|
|
1235
|
-
graphMemory: ctx.graphMemory,
|
|
1236
|
-
config: getConfig(),
|
|
1237
883
|
onEvent,
|
|
1238
|
-
isTrustedActor,
|
|
1239
884
|
conversationId: ctx.conversationId,
|
|
1240
885
|
userMessageId,
|
|
1241
886
|
logger: rlog,
|
|
1242
|
-
// An external cancel aborts `prepareMemory` instead of letting it run
|
|
1243
|
-
// to completion after the turn has already been torn down.
|
|
1244
|
-
signal: abortController.signal,
|
|
1245
887
|
latestMessages: ctx.messages,
|
|
888
|
+
requestId: reqId,
|
|
889
|
+
mode: currentInjectionMode,
|
|
890
|
+
isNonInteractive,
|
|
891
|
+
modelProfile: modelProfileStr,
|
|
892
|
+
actorContext,
|
|
1246
893
|
};
|
|
1247
894
|
await userPromptSubmitMemoryRetrieval(memoryCtx);
|
|
1248
895
|
|
|
1249
|
-
// The
|
|
1250
|
-
//
|
|
1251
|
-
// pair on the graph handle for the
|
|
1252
|
-
//
|
|
896
|
+
// The hook owns its side effects (injected-block metadata, recall log,
|
|
897
|
+
// `memory_recalled` event, and the runtime-injection metadata persist) and
|
|
898
|
+
// records the dense/sparse PKB query pair on the graph handle for the
|
|
899
|
+
// PKB-reminder injector to read back; the loop reuses the fully injected
|
|
900
|
+
// message list downstream.
|
|
1253
901
|
let runMessages = memoryCtx.latestMessages;
|
|
1254
902
|
|
|
1255
|
-
// Capture wall-clock "now" at its point of use, after the blocking memory
|
|
1256
|
-
// retrieval, so the injected `<turn_context>` timestamp reflects current
|
|
1257
|
-
// time rather than the moment the turn began.
|
|
1258
|
-
const timestamp = formatTurnTimestamp({
|
|
1259
|
-
timeZone: timezoneContext.effectiveTimezone,
|
|
1260
|
-
});
|
|
1261
|
-
|
|
1262
|
-
// Build unified turn context block that replaces the separate temporal,
|
|
1263
|
-
// channel, interface, and actor context blocks.
|
|
1264
|
-
const baseTurnContext = {
|
|
1265
|
-
timestamp,
|
|
1266
|
-
interfaceName,
|
|
1267
|
-
channelName,
|
|
1268
|
-
configuredUserTimezone: timezoneContext.configuredUserTimezone,
|
|
1269
|
-
clientTimezone: timezoneContext.clientTimezone,
|
|
1270
|
-
detectedTimezone: timezoneContext.detectedTimezone,
|
|
1271
|
-
timeSinceLastMessage,
|
|
1272
|
-
modelProfile: modelProfileStr,
|
|
1273
|
-
};
|
|
1274
|
-
const unifiedTurnContextStr = buildUnifiedTurnContextBlock(
|
|
1275
|
-
isGuardian
|
|
1276
|
-
? baseTurnContext
|
|
1277
|
-
: {
|
|
1278
|
-
...baseTurnContext,
|
|
1279
|
-
actorContext: resolvedInboundActorContext,
|
|
1280
|
-
},
|
|
1281
|
-
);
|
|
1282
|
-
|
|
1283
|
-
// The `remember` tool handles scratchpad-style memory writes directly to the graph.
|
|
1284
|
-
|
|
1285
|
-
// Subagent status injection — gives the parent LLM visibility into active/completed children.
|
|
1286
|
-
// Skipped when this conversation IS a subagent (no nesting) or has no children.
|
|
1287
|
-
const subagentStatusBlock = ctx.isSubagent
|
|
1288
|
-
? null
|
|
1289
|
-
: buildSubagentStatusBlock(
|
|
1290
|
-
getSubagentManager().getChildrenOf(ctx.conversationId),
|
|
1291
|
-
);
|
|
1292
|
-
|
|
1293
|
-
// For any Slack conversation (channels and DMs alike), build a
|
|
1294
|
-
// chronological transcript from the persisted message rows so the
|
|
1295
|
-
// model sees one channel-wide view instead of the gateway's per-turn
|
|
1296
|
-
// hints. DMs render as a flat sequence (no thread tags), channels
|
|
1297
|
-
// include sibling threads.
|
|
1298
|
-
const slackConversationForInjection = isSlackConversation
|
|
1299
|
-
? (getConversation(ctx.conversationId) ?? turnStartConversation)
|
|
1300
|
-
: turnStartConversation;
|
|
1301
|
-
if (isSlackConversation && !slackCompactedThisTurn) {
|
|
1302
|
-
slackChronologicalContext ??= loadSlackChronologicalContext(
|
|
1303
|
-
ctx.conversationId,
|
|
1304
|
-
ctx.channelCapabilities!,
|
|
1305
|
-
{
|
|
1306
|
-
trustClass: ctx.trustContext?.trustClass,
|
|
1307
|
-
contextSummary: slackConversationForInjection?.contextSummary,
|
|
1308
|
-
contextCompactedMessageCount:
|
|
1309
|
-
slackConversationForInjection?.contextCompactedMessageCount,
|
|
1310
|
-
slackContextCompactionWatermarkTs:
|
|
1311
|
-
slackConversationForInjection?.slackContextCompactionWatermarkTs,
|
|
1312
|
-
},
|
|
1313
|
-
);
|
|
1314
|
-
}
|
|
1315
|
-
const slackChronologicalMessages =
|
|
1316
|
-
slackChronologicalContext?.messages ?? null;
|
|
1317
|
-
|
|
1318
|
-
// Active-thread focus block: when the inbound user message belongs to
|
|
1319
|
-
// a Slack thread, append a non-persisted `<active_thread>` tail block
|
|
1320
|
-
// to the final user turn listing the thread's parent + replies. Helps
|
|
1321
|
-
// the model orient when the channel transcript is long and
|
|
1322
|
-
// interleaved. Replays strip the block via RUNTIME_INJECTION_PREFIXES.
|
|
1323
|
-
// DMs short-circuit to null inside `loadSlackActiveThreadFocusBlock`
|
|
1324
|
-
// since DMs do not have threads.
|
|
1325
|
-
const slackActiveThreadFocusBlock = isSlackConversation
|
|
1326
|
-
? loadSlackActiveThreadFocusBlock(
|
|
1327
|
-
ctx.conversationId,
|
|
1328
|
-
ctx.channelCapabilities!,
|
|
1329
|
-
{
|
|
1330
|
-
trustClass: ctx.trustContext?.trustClass,
|
|
1331
|
-
contextCompactedMessageCount:
|
|
1332
|
-
slackConversationForInjection?.contextCompactedMessageCount,
|
|
1333
|
-
slackContextCompactionWatermarkTs:
|
|
1334
|
-
slackConversationForInjection?.slackContextCompactionWatermarkTs,
|
|
1335
|
-
},
|
|
1336
|
-
)
|
|
1337
|
-
: null;
|
|
1338
|
-
|
|
1339
|
-
state.reducerCompacted = compactedThisTurn;
|
|
1340
|
-
|
|
1341
|
-
// memory-v3-live: when on, the provider anchors its long-TTL cache
|
|
1342
|
-
// breakpoint on the most recent STABLE user message, since the latest user
|
|
1343
|
-
// message now carries the volatile per-turn `<memory>` block the v3
|
|
1344
|
-
// injector emits. The matching v2-suppression strip is owned by
|
|
1345
|
-
// `applyRuntimeInjections`, which reads the same flag itself. Flag off →
|
|
1346
|
-
// bit-for-bit identical to today's v2 path.
|
|
1347
|
-
const memoryV3Live = isAssistantFeatureFlagEnabled(
|
|
1348
|
-
"memory-v3-live",
|
|
1349
|
-
getConfig(),
|
|
1350
|
-
);
|
|
1351
|
-
|
|
1352
|
-
// Shared injection options — reused whenever we need to re-inject after reduction.
|
|
1353
|
-
const injectionOpts = {
|
|
1354
|
-
diskPressureContext,
|
|
1355
|
-
// Resolved from the conversation's surface state here, where the
|
|
1356
|
-
// runtime injector is the only consumer of the active-surface block.
|
|
1357
|
-
activeSurface: buildActiveSurfaceContext({
|
|
1358
|
-
currentActiveSurfaceId: ctx.currentActiveSurfaceId,
|
|
1359
|
-
currentPage: ctx.currentPage,
|
|
1360
|
-
surfaceState: ctx.surfaceState,
|
|
1361
|
-
}),
|
|
1362
|
-
// Resolved here, where the runtime injector is the only consumer of the
|
|
1363
|
-
// active-documents block.
|
|
1364
|
-
activeDocuments: buildActiveDocuments(ctx.conversationId),
|
|
1365
|
-
workspaceTopLevelContext: buildWorkspaceTopLevelContext(
|
|
1366
|
-
ctx,
|
|
1367
|
-
state.shouldInjectWorkspace,
|
|
1368
|
-
),
|
|
1369
|
-
channelCapabilities: ctx.channelCapabilities ?? null,
|
|
1370
|
-
channelCommandContext: ctx.commandIntent ?? null,
|
|
1371
|
-
unifiedTurnContext: unifiedTurnContextStr,
|
|
1372
|
-
voiceCallControlPrompt: ctx.voiceCallControlPrompt ?? null,
|
|
1373
|
-
transportHints: ctx.transportHints ?? null,
|
|
1374
|
-
isNonInteractive: !isInteractiveResolved,
|
|
1375
|
-
isBackgroundConversation: isBackgroundConversationType(
|
|
1376
|
-
turnStartConversation?.conversationType,
|
|
1377
|
-
),
|
|
1378
|
-
subagentStatusBlock,
|
|
1379
|
-
slackChronologicalMessages,
|
|
1380
|
-
slackActiveThreadFocusBlock,
|
|
1381
|
-
} as const;
|
|
1382
|
-
|
|
1383
|
-
let currentInjectionMode: InjectionMode = "full";
|
|
1384
|
-
|
|
1385
|
-
// Canonical per-turn TurnContext forwarded to the injector chain. The
|
|
1386
|
-
// per-turn injection inputs are built inside `applyRuntimeInjections`
|
|
1387
|
-
// from the `injectionOpts` bag; we only need to hand in identity +
|
|
1388
|
-
// trust here so third-party injectors see the real turn metadata.
|
|
1389
|
-
const injectionTurnCtx = buildPluginTurnContext(ctx, reqId);
|
|
1390
|
-
|
|
1391
|
-
const injection = await applyRuntimeInjections(runMessages, {
|
|
1392
|
-
...injectionOpts,
|
|
1393
|
-
slackChronologicalMessages: state.reducerCompacted
|
|
1394
|
-
? null
|
|
1395
|
-
: injectionOpts.slackChronologicalMessages,
|
|
1396
|
-
mode: currentInjectionMode,
|
|
1397
|
-
turnContext: injectionTurnCtx,
|
|
1398
|
-
});
|
|
1399
|
-
runMessages = injection.messages;
|
|
1400
|
-
|
|
1401
|
-
// Persist injected blocks in message metadata so they survive conversation
|
|
1402
|
-
// reloads (eviction, restart, fork). loadFromDb re-injects from metadata.
|
|
1403
|
-
// Only the first call site persists — the overflow-recovery re-entry sites
|
|
1404
|
-
// send identical bytes and the tail row may not correspond to
|
|
1405
|
-
// `userMessageId`. All blocks are written in a single call to avoid
|
|
1406
|
-
// doubling SQLite SELECT+UPDATE work on every turn.
|
|
1407
|
-
if (
|
|
1408
|
-
injection.blocks.unifiedTurnContext ||
|
|
1409
|
-
injection.blocks.pkbSystemReminder ||
|
|
1410
|
-
injection.blocks.workspaceBlock ||
|
|
1411
|
-
injection.blocks.nowScratchpadBlock ||
|
|
1412
|
-
injection.blocks.pkbContextBlock ||
|
|
1413
|
-
injection.blocks.memoryV2StaticBlock
|
|
1414
|
-
) {
|
|
1415
|
-
try {
|
|
1416
|
-
const metadataUpdates: Record<string, unknown> = {};
|
|
1417
|
-
if (injection.blocks.unifiedTurnContext) {
|
|
1418
|
-
metadataUpdates.turnContextBlock =
|
|
1419
|
-
injection.blocks.unifiedTurnContext;
|
|
1420
|
-
}
|
|
1421
|
-
if (injection.blocks.pkbSystemReminder) {
|
|
1422
|
-
metadataUpdates.pkbSystemReminderBlock =
|
|
1423
|
-
injection.blocks.pkbSystemReminder;
|
|
1424
|
-
}
|
|
1425
|
-
if (injection.blocks.workspaceBlock) {
|
|
1426
|
-
metadataUpdates.workspaceBlock = injection.blocks.workspaceBlock;
|
|
1427
|
-
}
|
|
1428
|
-
if (injection.blocks.nowScratchpadBlock) {
|
|
1429
|
-
metadataUpdates.nowScratchpadBlock =
|
|
1430
|
-
injection.blocks.nowScratchpadBlock;
|
|
1431
|
-
}
|
|
1432
|
-
if (injection.blocks.pkbContextBlock) {
|
|
1433
|
-
metadataUpdates.pkbContextBlock = injection.blocks.pkbContextBlock;
|
|
1434
|
-
}
|
|
1435
|
-
if (injection.blocks.memoryV2StaticBlock) {
|
|
1436
|
-
metadataUpdates.memoryV2StaticBlock =
|
|
1437
|
-
injection.blocks.memoryV2StaticBlock;
|
|
1438
|
-
}
|
|
1439
|
-
updateMessageMetadata(userMessageId, metadataUpdates);
|
|
1440
|
-
} catch (err) {
|
|
1441
|
-
rlog.warn({ err }, "Failed to persist injection metadata (non-fatal)");
|
|
1442
|
-
}
|
|
1443
|
-
}
|
|
1444
|
-
|
|
1445
|
-
// ── Preflight budget evaluation ──────────────────────────────
|
|
1446
|
-
// After runtime injections are applied, estimate the prompt token count
|
|
1447
|
-
// and proactively invoke the reducer if already above budget. This avoids
|
|
1448
|
-
// a wasted provider round-trip that would just fail with context_too_large.
|
|
1449
|
-
const initialContextBudget = resolveCurrentContextBudget();
|
|
1450
|
-
const overflowRecovery = initialContextBudget.overflowRecovery;
|
|
1451
|
-
const preflightBudget = initialContextBudget.preflightBudget;
|
|
1452
|
-
let reducerState: ReducerState | undefined;
|
|
1453
|
-
|
|
1454
|
-
const toolTokenBudget = ctx.agentLoop.getToolTokenBudget(runMessages);
|
|
1455
|
-
// Canonical calibration key — used by the preflight estimate, the
|
|
1456
|
-
// overflow reducer config, and the convergence-path `estimatePromptTokens`
|
|
1457
|
-
// call. Matches the key recorded by `handleUsage` for wrapper providers
|
|
1458
|
-
// (OpenRouter routing to Anthropic → key is `"anthropic"`).
|
|
1459
|
-
const estimationProviderName = getCalibrationProviderKey(ctx.provider);
|
|
1460
|
-
|
|
1461
|
-
const preflightTokens = estimatePromptTokensWithTools(
|
|
1462
|
-
runMessages,
|
|
1463
|
-
ctx.systemPrompt,
|
|
1464
|
-
ctx.agentLoop.getResolvedTools(runMessages),
|
|
1465
|
-
estimationProviderName,
|
|
1466
|
-
);
|
|
1467
|
-
|
|
1468
|
-
if (overflowRecovery.enabled && preflightTokens > preflightBudget) {
|
|
1469
|
-
rlog.warn(
|
|
1470
|
-
{
|
|
1471
|
-
phase: "preflight",
|
|
1472
|
-
estimatedTokens: preflightTokens,
|
|
1473
|
-
budget: preflightBudget,
|
|
1474
|
-
},
|
|
1475
|
-
"Preflight budget exceeded — running overflow reducer before provider call",
|
|
1476
|
-
);
|
|
1477
|
-
|
|
1478
|
-
// Overflow reduction runs through the plugin pipeline. The default
|
|
1479
|
-
// middleware (`default-overflow-reduce`, registered at bootstrap)
|
|
1480
|
-
// contains the historical tier loop — forced compaction → tool-result
|
|
1481
|
-
// truncation → media stubbing → injection downgrade — plus the
|
|
1482
|
-
// re-inject/re-estimate convergence check. The callbacks below are
|
|
1483
|
-
// the orchestrator-specific side effects that the plugin coordinates
|
|
1484
|
-
// per iteration (activity emission, compaction application, runtime
|
|
1485
|
-
// injection reassembly, token re-estimation). Registered plugins that
|
|
1486
|
-
// wrap the `overflowReduce` slot see each iteration through their own
|
|
1487
|
-
// middleware `next` callback.
|
|
1488
|
-
const messagesForPreflightOverflowReduction =
|
|
1489
|
-
slackChronologicalContext?.messages ?? ctx.messages;
|
|
1490
|
-
const overflowArgs: OverflowReduceArgs = {
|
|
1491
|
-
messages: messagesForPreflightOverflowReduction,
|
|
1492
|
-
runMessages,
|
|
1493
|
-
systemPrompt: ctx.systemPrompt,
|
|
1494
|
-
providerName: estimationProviderName,
|
|
1495
|
-
contextWindow: resolveCurrentContextWindowConfig(),
|
|
1496
|
-
preflightBudget,
|
|
1497
|
-
toolTokenBudget,
|
|
1498
|
-
maxAttempts: resolveCurrentContextBudget().overflowRecovery.maxAttempts,
|
|
1499
|
-
abortSignal: abortController.signal,
|
|
1500
|
-
compactFn: async (msgs, signal, opts) => {
|
|
1501
|
-
// Route the reducer's forced-compaction tier through the
|
|
1502
|
-
// `compaction` pipeline so registered plugins observe these
|
|
1503
|
-
// invocations. Without this, custom compaction middleware only
|
|
1504
|
-
// sees the three orchestrator-owned call sites and misses the
|
|
1505
|
-
// reducer-initiated forced compactions entirely.
|
|
1506
|
-
//
|
|
1507
|
-
// Pipeline timeouts must be caught locally — a `PluginTimeoutError`
|
|
1508
|
-
// bubbling out of here would abort the overflow-reducer tier loop
|
|
1509
|
-
// entirely, skipping fallback tiers (tool-result truncation, media
|
|
1510
|
-
// stubbing, injection downgrade) and bypassing circuit-breaker
|
|
1511
|
-
// bookkeeping. On timeout, record the failure and return a
|
|
1512
|
-
// `compacted: false` result so the reducer falls through to the
|
|
1513
|
-
// next tier.
|
|
1514
|
-
try {
|
|
1515
|
-
return (await runPipeline<CompactionArgs, CompactionResult>(
|
|
1516
|
-
"compaction",
|
|
1517
|
-
getMiddlewaresFor("compaction"),
|
|
1518
|
-
(args) =>
|
|
1519
|
-
defaultCompactionTerminal(
|
|
1520
|
-
args,
|
|
1521
|
-
buildPluginTurnContext(ctx, reqId),
|
|
1522
|
-
),
|
|
1523
|
-
{
|
|
1524
|
-
messages: msgs,
|
|
1525
|
-
signal,
|
|
1526
|
-
options: {
|
|
1527
|
-
...(opts ?? {}),
|
|
1528
|
-
overrideProfile: resolveCurrentOverrideProfile() ?? null,
|
|
1529
|
-
actorTrustClass: resolveTurnActorTrustClass(ctx),
|
|
1530
|
-
},
|
|
1531
|
-
},
|
|
1532
|
-
buildPluginTurnContext(ctx, reqId),
|
|
1533
|
-
DEFAULT_TIMEOUTS.compaction,
|
|
1534
|
-
)) as Awaited<
|
|
1535
|
-
ReturnType<typeof ctx.contextWindowManager.maybeCompact>
|
|
1536
|
-
>;
|
|
1537
|
-
} catch (err) {
|
|
1538
|
-
if (err instanceof PluginTimeoutError) {
|
|
1539
|
-
rlog.warn(
|
|
1540
|
-
{ err, phase: "overflow-reducer-forced-compaction" },
|
|
1541
|
-
"Compaction pipeline timed out — falling through to next reducer tier",
|
|
1542
|
-
);
|
|
1543
|
-
await ctx.agentLoop.compactionCircuit.recordOutcome(
|
|
1544
|
-
ctx,
|
|
1545
|
-
true,
|
|
1546
|
-
onEvent,
|
|
1547
|
-
);
|
|
1548
|
-
return {
|
|
1549
|
-
messages: msgs,
|
|
1550
|
-
compacted: false,
|
|
1551
|
-
previousEstimatedInputTokens: 0,
|
|
1552
|
-
estimatedInputTokens: 0,
|
|
1553
|
-
maxInputTokens: 0,
|
|
1554
|
-
thresholdTokens: 0,
|
|
1555
|
-
compactedMessages: 0,
|
|
1556
|
-
compactedPersistedMessages: 0,
|
|
1557
|
-
summaryCalls: 0,
|
|
1558
|
-
summaryInputTokens: 0,
|
|
1559
|
-
summaryOutputTokens: 0,
|
|
1560
|
-
summaryModel: "",
|
|
1561
|
-
summaryText: "",
|
|
1562
|
-
reason: "compaction pipeline timed out",
|
|
1563
|
-
};
|
|
1564
|
-
}
|
|
1565
|
-
throw err;
|
|
1566
|
-
}
|
|
1567
|
-
},
|
|
1568
|
-
emitActivityState: () => {
|
|
1569
|
-
ctx.emitActivityState("thinking", "context_compacting", {
|
|
1570
|
-
requestId: reqId,
|
|
1571
|
-
});
|
|
1572
|
-
},
|
|
1573
|
-
onCompactionResult: async (result, compactedBasis) => {
|
|
1574
|
-
// Track circuit-breaker state whenever the reducer invoked
|
|
1575
|
-
// compaction. The reducer's forced_compaction tier uses
|
|
1576
|
-
// force:true, so it bypasses the open-circuit check, but we
|
|
1577
|
-
// still want failure tracking to detect a run of broken
|
|
1578
|
-
// summaries and clear the counter on success. Only track when
|
|
1579
|
-
// the summary LLM actually ran — `summaryFailed === undefined`
|
|
1580
|
-
// indicates an early return (no eligible messages,
|
|
1581
|
-
// truncation-only path, etc.) that shouldn't influence the
|
|
1582
|
-
// breaker.
|
|
1583
|
-
if (result.summaryFailed !== undefined) {
|
|
1584
|
-
await ctx.agentLoop.compactionCircuit.recordOutcome(
|
|
1585
|
-
ctx,
|
|
1586
|
-
result.summaryFailed,
|
|
1587
|
-
onEvent,
|
|
1588
|
-
);
|
|
1589
|
-
}
|
|
1590
|
-
if (result.compacted) {
|
|
1591
|
-
await applySuccessfulCompaction(result, compactedBasis);
|
|
1592
|
-
state.shouldInjectWorkspace = true;
|
|
1593
|
-
}
|
|
1594
|
-
},
|
|
1595
|
-
reinjectForMode: async (
|
|
1596
|
-
reducedMessages,
|
|
1597
|
-
mode,
|
|
1598
|
-
stepCompacted,
|
|
1599
|
-
accumulatedCompacted,
|
|
1600
|
-
) => {
|
|
1601
|
-
// Mirror the pre-PR-23 behavior: `ctx.messages` must track the
|
|
1602
|
-
// reducer's latest output before re-injection runs, because other
|
|
1603
|
-
// sites consulted through `injectionOpts` (`workspaceTopLevelContext`,
|
|
1604
|
-
// slack history, etc.) depend on it and `applyCompactionResult`
|
|
1605
|
-
// only updates `ctx.messages` on a compaction tier. Assigning here
|
|
1606
|
-
// keeps non-compaction tiers (tool-result truncation, media
|
|
1607
|
-
// stubbing, injection downgrade) observable to downstream
|
|
1608
|
-
// injection assembly on the same turn.
|
|
1609
|
-
ctx.messages = reducedMessages;
|
|
1610
|
-
|
|
1611
|
-
// When THIS iteration compacted, it stripped the existing
|
|
1612
|
-
// memory-static block — so we re-inject current content. A later
|
|
1613
|
-
// iteration that only truncates or downgrades must NOT re-force it,
|
|
1614
|
-
// or each round would grow the token count.
|
|
1615
|
-
// Gate: only the iteration that actually compacted re-injects.
|
|
1616
|
-
// (The `<knowledge_base>`, NOW.md, and v2 static `<info>` blocks
|
|
1617
|
-
// self-gate inside their injectors on whether they are already
|
|
1618
|
-
// present in `reducedMessages`.)
|
|
1619
|
-
const injection = await applyRuntimeInjections(reducedMessages, {
|
|
1620
|
-
...injectionOpts,
|
|
1621
|
-
workspaceTopLevelContext: buildWorkspaceTopLevelContext(
|
|
1622
|
-
ctx,
|
|
1623
|
-
state.shouldInjectWorkspace,
|
|
1624
|
-
),
|
|
1625
|
-
// Once ANY iteration has compacted `ctx.messages`, the captured
|
|
1626
|
-
// `slackChronologicalMessages` snapshot (built from the full
|
|
1627
|
-
// persisted transcript) would overwrite the compacted history
|
|
1628
|
-
// and undo compaction. Suppress the override from here on —
|
|
1629
|
-
// sticky across subsequent non-compacting iterations.
|
|
1630
|
-
slackChronologicalMessages: accumulatedCompacted
|
|
1631
|
-
? null
|
|
1632
|
-
: injectionOpts.slackChronologicalMessages,
|
|
1633
|
-
mode,
|
|
1634
|
-
turnContext: buildPluginTurnContext(ctx, reqId),
|
|
1635
|
-
});
|
|
1636
|
-
let next = injection.messages;
|
|
1637
|
-
if (isTrustedActor && mode !== "minimal") {
|
|
1638
|
-
const memResult = ctx.graphMemory.reinjectCachedMemory(next);
|
|
1639
|
-
next = memResult.runMessages;
|
|
1640
|
-
}
|
|
1641
|
-
return next;
|
|
1642
|
-
},
|
|
1643
|
-
estimatePostInjection: (runMsgs) =>
|
|
1644
|
-
estimatePromptTokens(runMsgs, ctx.systemPrompt, {
|
|
1645
|
-
providerName: estimationProviderName,
|
|
1646
|
-
toolTokenBudget,
|
|
1647
|
-
}),
|
|
1648
|
-
};
|
|
1649
|
-
|
|
1650
|
-
const overflowResult = await runPipeline<
|
|
1651
|
-
OverflowReduceArgs,
|
|
1652
|
-
OverflowReduceResult
|
|
1653
|
-
>(
|
|
1654
|
-
"overflowReduce",
|
|
1655
|
-
getMiddlewaresFor("overflowReduce"),
|
|
1656
|
-
// Terminal — only reached when every registered middleware calls
|
|
1657
|
-
// `next` and delegates past the innermost layer. The default plugin
|
|
1658
|
-
// is a terminal itself (it doesn't call `next`), so in practice
|
|
1659
|
-
// this fallback fires only when the default has been explicitly
|
|
1660
|
-
// deregistered (tests) and no user plugin replaces it. Strict-fail
|
|
1661
|
-
// semantics: throw so the missing terminal surfaces as a visible
|
|
1662
|
-
// error instead of silently returning the history untouched.
|
|
1663
|
-
async () => {
|
|
1664
|
-
throw new PluginExecutionError(
|
|
1665
|
-
"overflowReduce pipeline has no terminal handler — every reducer middleware called next() without providing a replacement",
|
|
1666
|
-
"overflowReduce",
|
|
1667
|
-
);
|
|
1668
|
-
},
|
|
1669
|
-
overflowArgs,
|
|
1670
|
-
buildPluginTurnContext(ctx, reqId),
|
|
1671
|
-
DEFAULT_TIMEOUTS.overflowReduce,
|
|
1672
|
-
);
|
|
1673
|
-
|
|
1674
|
-
ctx.messages = overflowResult.messages;
|
|
1675
|
-
runMessages = overflowResult.runMessages;
|
|
1676
|
-
currentInjectionMode = overflowResult.injectionMode;
|
|
1677
|
-
reducerState = overflowResult.reducerState;
|
|
1678
|
-
if (overflowResult.reducerCompacted) {
|
|
1679
|
-
state.reducerCompacted = true;
|
|
1680
|
-
}
|
|
1681
|
-
}
|
|
1682
|
-
|
|
1683
|
-
// Replace historical web_search_tool_result blocks with text summaries.
|
|
1684
|
-
// The opaque `encrypted_content` tokens Anthropic attaches to each result
|
|
1685
|
-
// expire / are route-scoped; replaying a stale token is rejected with
|
|
1686
|
-
// `Invalid encrypted_content in search_result block`. Titles + URLs
|
|
1687
|
-
// preserve enough context for the model on follow-up turns.
|
|
1688
|
-
const webSearchStrip = stripHistoricalWebSearchResults(runMessages);
|
|
1689
|
-
if (webSearchStrip.stats.blocksStripped > 0) {
|
|
1690
|
-
rlog.info(
|
|
1691
|
-
{ phase: "pre_run", ...webSearchStrip.stats },
|
|
1692
|
-
"Converted historical web_search_tool_result blocks to text summaries",
|
|
1693
|
-
);
|
|
1694
|
-
runMessages = webSearchStrip.messages;
|
|
1695
|
-
}
|
|
1696
|
-
|
|
1697
903
|
// user-prompt-submit hook: plugins may transform `runMessages` right
|
|
1698
904
|
// before the agent loop receives them. Fires once per user turn at the
|
|
1699
905
|
// primary `agentLoop.run` only — the re-entry / retry calls further down
|
|
@@ -1718,6 +924,16 @@ export async function runAgentLoopImpl(
|
|
|
1718
924
|
);
|
|
1719
925
|
runMessages = finalUserPromptCtx.latestMessages;
|
|
1720
926
|
|
|
927
|
+
// Reducer state, tool-token budget, and calibration provider key consumed
|
|
928
|
+
// by the post-rejection convergence loop further down. The tool-token
|
|
929
|
+
// budget is resolved once per turn (the resolved tool set is stable across
|
|
930
|
+
// the turn); the calibration key matches the key recorded by `handleUsage`
|
|
931
|
+
// for wrapper providers (OpenRouter routing to Anthropic → key is
|
|
932
|
+
// `"anthropic"`).
|
|
933
|
+
let reducerState: ReducerState | undefined;
|
|
934
|
+
const toolTokenBudget = ctx.agentLoop.getToolTokenBudget(runMessages);
|
|
935
|
+
const estimationProviderName = getCalibrationProviderKey(ctx.provider);
|
|
936
|
+
|
|
1721
937
|
const shouldGenerateTitle = isReplaceableTitle(
|
|
1722
938
|
getConversation(ctx.conversationId)?.title ?? null,
|
|
1723
939
|
);
|
|
@@ -1750,75 +966,45 @@ export async function runAgentLoopImpl(
|
|
|
1750
966
|
|
|
1751
967
|
rlog.info({ callSite: turnCallSite }, "Starting agent loop run");
|
|
1752
968
|
|
|
1753
|
-
//
|
|
1754
|
-
//
|
|
1755
|
-
//
|
|
1756
|
-
// the
|
|
1757
|
-
//
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
// trigger (its budget gate), the `compaction` pipeline call, the result
|
|
1762
|
-
// interpretation (circuit-breaker bookkeeping + the exhaustion decision),
|
|
1763
|
-
// and the inline continue; this callback bridges the injection state the
|
|
1764
|
-
// loop is intentionally blind to. Durable persistence is signalled via
|
|
1765
|
-
// events; re-injection stays orchestrator-supplied for now.
|
|
1766
|
-
const midLoopCompaction: MidLoopCompaction = {
|
|
1767
|
-
postCompactionHook: async ({ history, turnContext }) => {
|
|
1768
|
-
// stripInjectionsForCompaction() unconditionally removed the existing
|
|
1769
|
-
// memory-static block, so re-inject the current content regardless of
|
|
1770
|
-
// whether compaction actually ran. The `<knowledge_base>`, NOW.md, and
|
|
1771
|
-
// v2 static `<info>` blocks self-gate inside their injectors on block
|
|
1772
|
-
// presence.
|
|
1773
|
-
const injection = await postCompactReinject({
|
|
1774
|
-
...injectionOpts,
|
|
1775
|
-
workspaceTopLevelContext: buildWorkspaceTopLevelContext(
|
|
1776
|
-
ctx,
|
|
1777
|
-
state.shouldInjectWorkspace,
|
|
1778
|
-
),
|
|
1779
|
-
// Suppress the chronological-transcript snapshot once the reducer
|
|
1780
|
-
// has collapsed `ctx.messages`; the captured snapshot reflects the
|
|
1781
|
-
// full persisted transcript and would overwrite compaction.
|
|
1782
|
-
slackChronologicalMessages: state.reducerCompacted
|
|
1783
|
-
? null
|
|
1784
|
-
: injectionOpts.slackChronologicalMessages,
|
|
1785
|
-
mode: currentInjectionMode,
|
|
1786
|
-
turnContext,
|
|
1787
|
-
history,
|
|
1788
|
-
isTrustedActor,
|
|
1789
|
-
logger: rlog,
|
|
1790
|
-
});
|
|
1791
|
-
return injection.messages;
|
|
1792
|
-
},
|
|
1793
|
-
};
|
|
969
|
+
// Trust snapshot the loop forwards to its mid-loop in-place compaction
|
|
970
|
+
// (scoping the compactor's image manifest) and the post-compaction
|
|
971
|
+
// re-injection. Prefers the per-turn snapshot, then the conversation-level
|
|
972
|
+
// context, then the fallback — matching the trust the runtime injection
|
|
973
|
+
// assembly resolves for the same turn. The loop's other turn-identity
|
|
974
|
+
// fields self-resolve from its own conversation id.
|
|
975
|
+
const loopTrust =
|
|
976
|
+
ctx.currentTurnTrustContext ?? ctx.trustContext ?? FALLBACK_TURN_TRUST;
|
|
1794
977
|
|
|
1795
978
|
/**
|
|
1796
979
|
* Shared closure: runs the agent loop with the orchestrator's turn
|
|
1797
980
|
* context and maps the loop's returned checkpoint pause-reason into the
|
|
1798
981
|
* orchestrator's yield bookkeeping. Returns the updated history so call
|
|
1799
|
-
* sites consume it exactly as before. Pass `
|
|
1800
|
-
* primary run
|
|
1801
|
-
*
|
|
982
|
+
* sites consume it exactly as before. Pass `compactInPlace` only for the
|
|
983
|
+
* primary run: the loop then runs its budget gate before the first call
|
|
984
|
+
* (subsuming the proactive turn-start compaction) and compacts in place
|
|
985
|
+
* whenever the gate trips. Reruns omit it, skip the first-call gate, and
|
|
986
|
+
* keep yielding for budget.
|
|
1802
987
|
*/
|
|
1803
988
|
const runAgentLoop = async (
|
|
1804
989
|
msgs: Message[],
|
|
1805
|
-
|
|
990
|
+
compactInPlace = false,
|
|
1806
991
|
): Promise<Message[]> => {
|
|
1807
992
|
const { history, exitReason, appendedNewMessages, newMessages } =
|
|
1808
|
-
await ctx.agentLoop.run(
|
|
993
|
+
await ctx.agentLoop.run({
|
|
994
|
+
messages: msgs,
|
|
995
|
+
onEvent: eventHandler,
|
|
1809
996
|
signal: abortController.signal,
|
|
1810
997
|
requestId: reqId,
|
|
1811
998
|
onCheckpoint,
|
|
1812
999
|
callSite: turnCallSite,
|
|
1813
|
-
|
|
1000
|
+
trust: loopTrust,
|
|
1814
1001
|
overrideProfile: turnOverrideProfile,
|
|
1815
1002
|
resolveOverrideProfile: resolveCurrentOverrideProfile,
|
|
1816
1003
|
resolveContextWindow,
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
mutableLatestUserMessage: memoryV3Live,
|
|
1004
|
+
compactInPlace,
|
|
1005
|
+
isNonInteractive,
|
|
1006
|
+
modelProfile: modelProfileStr,
|
|
1007
|
+
actorContext,
|
|
1822
1008
|
});
|
|
1823
1009
|
lastRunAppendedNewMessages = appendedNewMessages;
|
|
1824
1010
|
lastRunNewMessages = newMessages;
|
|
@@ -1832,7 +1018,7 @@ export async function runAgentLoopImpl(
|
|
|
1832
1018
|
return history;
|
|
1833
1019
|
};
|
|
1834
1020
|
|
|
1835
|
-
let updatedHistory = await runAgentLoop(runMessages,
|
|
1021
|
+
let updatedHistory = await runAgentLoop(runMessages, true);
|
|
1836
1022
|
|
|
1837
1023
|
rlog.info(
|
|
1838
1024
|
{ resultMessageCount: updatedHistory.length },
|
|
@@ -1875,8 +1061,6 @@ export async function runAgentLoopImpl(
|
|
|
1875
1061
|
// intentionally deferred until there's a concrete plugin-level use case.
|
|
1876
1062
|
const retryRepair = deepRepairHistory(updatedHistory);
|
|
1877
1063
|
runMessages = retryRepair.messages;
|
|
1878
|
-
const retryStrip = stripHistoricalWebSearchResults(runMessages);
|
|
1879
|
-
runMessages = retryStrip.messages;
|
|
1880
1064
|
state.orderingErrorDetected = false;
|
|
1881
1065
|
state.deferredOrderingError = null;
|
|
1882
1066
|
|
|
@@ -2064,14 +1248,12 @@ export async function runAgentLoopImpl(
|
|
|
2064
1248
|
);
|
|
2065
1249
|
if (emergencyResult.summaryFailed !== undefined) {
|
|
2066
1250
|
await ctx.agentLoop.compactionCircuit.recordOutcome(
|
|
2067
|
-
ctx,
|
|
2068
1251
|
emergencyResult.summaryFailed,
|
|
2069
1252
|
onEvent,
|
|
2070
1253
|
);
|
|
2071
1254
|
}
|
|
2072
1255
|
if (emergencyResult.compacted) {
|
|
2073
1256
|
await applySuccessfulCompaction(emergencyResult, ctx.messages);
|
|
2074
|
-
state.shouldInjectWorkspace = true;
|
|
2075
1257
|
}
|
|
2076
1258
|
// Clear the overflow flag and re-run the agent loop with
|
|
2077
1259
|
// the compacted context.
|
|
@@ -2119,8 +1301,11 @@ export async function runAgentLoopImpl(
|
|
|
2119
1301
|
},
|
|
2120
1302
|
reducerState,
|
|
2121
1303
|
(msgs, signal, opts) =>
|
|
2122
|
-
|
|
2123
|
-
|
|
1304
|
+
defaultCompact({
|
|
1305
|
+
manager: ctx.contextWindowManager,
|
|
1306
|
+
messages: msgs,
|
|
1307
|
+
signal,
|
|
1308
|
+
...((opts ?? {}) as ContextWindowCompactOptions),
|
|
2124
1309
|
overrideProfile: resolveCurrentOverrideProfile() ?? null,
|
|
2125
1310
|
actorTrustClass: resolveTurnActorTrustClass(ctx),
|
|
2126
1311
|
}),
|
|
@@ -2140,7 +1325,6 @@ export async function runAgentLoopImpl(
|
|
|
2140
1325
|
step.compactionResult.summaryFailed !== undefined
|
|
2141
1326
|
) {
|
|
2142
1327
|
await ctx.agentLoop.compactionCircuit.recordOutcome(
|
|
2143
|
-
ctx,
|
|
2144
1328
|
step.compactionResult.summaryFailed,
|
|
2145
1329
|
onEvent,
|
|
2146
1330
|
);
|
|
@@ -2151,8 +1335,6 @@ export async function runAgentLoopImpl(
|
|
|
2151
1335
|
step.compactionResult,
|
|
2152
1336
|
convergenceCompactionBasis,
|
|
2153
1337
|
);
|
|
2154
|
-
state.shouldInjectWorkspace = true;
|
|
2155
|
-
state.reducerCompacted = true;
|
|
2156
1338
|
}
|
|
2157
1339
|
|
|
2158
1340
|
// Only re-inject the memory-static block when ctx.messages was
|
|
@@ -2161,29 +1343,17 @@ export async function runAgentLoopImpl(
|
|
|
2161
1343
|
// blocks self-gate inside their injectors on whether they are already
|
|
2162
1344
|
// present in `ctx.messages`.)
|
|
2163
1345
|
const injection = await applyRuntimeInjections(ctx.messages, {
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
state.shouldInjectWorkspace,
|
|
2168
|
-
),
|
|
2169
|
-
slackChronologicalMessages: state.reducerCompacted
|
|
2170
|
-
? null
|
|
2171
|
-
: injectionOpts.slackChronologicalMessages,
|
|
1346
|
+
isNonInteractive,
|
|
1347
|
+
modelProfile: modelProfileStr,
|
|
1348
|
+
actorContext,
|
|
2172
1349
|
mode: currentInjectionMode,
|
|
2173
|
-
|
|
1350
|
+
requestId: reqId,
|
|
1351
|
+
conversationId: ctx.conversationId,
|
|
2174
1352
|
});
|
|
2175
1353
|
runMessages = injection.messages;
|
|
2176
1354
|
if (isTrustedActor && currentInjectionMode !== "minimal") {
|
|
2177
1355
|
ctx.graphMemory.retrackCachedNodes();
|
|
2178
1356
|
}
|
|
2179
|
-
const convergenceStrip = stripHistoricalWebSearchResults(runMessages);
|
|
2180
|
-
if (convergenceStrip.stats.blocksStripped > 0) {
|
|
2181
|
-
rlog.info(
|
|
2182
|
-
{ phase: "convergence", ...convergenceStrip.stats },
|
|
2183
|
-
"Converted historical web_search_tool_result blocks to text summaries",
|
|
2184
|
-
);
|
|
2185
|
-
runMessages = convergenceStrip.messages;
|
|
2186
|
-
}
|
|
2187
1357
|
state.contextTooLargeDetected = false;
|
|
2188
1358
|
yieldedForBudget = false;
|
|
2189
1359
|
|
|
@@ -2219,7 +1389,7 @@ export async function runAgentLoopImpl(
|
|
|
2219
1389
|
// through to the final graceful-error fallback below.
|
|
2220
1390
|
if (state.contextTooLargeDetected) {
|
|
2221
1391
|
const action = resolveOverflowAction({
|
|
2222
|
-
overflowRecovery,
|
|
1392
|
+
overflowRecovery: convergenceBudget.overflowRecovery,
|
|
2223
1393
|
isInteractive: isInteractiveResolved,
|
|
2224
1394
|
});
|
|
2225
1395
|
|
|
@@ -2228,71 +1398,24 @@ export async function runAgentLoopImpl(
|
|
|
2228
1398
|
ctx.emitActivityState("thinking", "context_compacting", {
|
|
2229
1399
|
requestId: reqId,
|
|
2230
1400
|
});
|
|
2231
|
-
|
|
2232
|
-
|
|
2233
|
-
|
|
2234
|
-
|
|
2235
|
-
|
|
2236
|
-
|
|
2237
|
-
|
|
2238
|
-
|
|
2239
|
-
"compaction",
|
|
2240
|
-
getMiddlewaresFor("compaction"),
|
|
2241
|
-
(args) =>
|
|
2242
|
-
defaultCompactionTerminal(
|
|
2243
|
-
args,
|
|
2244
|
-
buildPluginTurnContext(ctx, reqId),
|
|
2245
|
-
),
|
|
2246
|
-
{
|
|
2247
|
-
messages: ctx.messages,
|
|
2248
|
-
signal: abortController.signal,
|
|
2249
|
-
options: {
|
|
2250
|
-
force: true,
|
|
2251
|
-
minKeepRecentUserTurns: 0,
|
|
2252
|
-
overrideProfile: resolveCurrentOverrideProfile() ?? null,
|
|
2253
|
-
},
|
|
2254
|
-
},
|
|
2255
|
-
buildPluginTurnContext(ctx, reqId),
|
|
2256
|
-
DEFAULT_TIMEOUTS.compaction,
|
|
2257
|
-
)) as Awaited<
|
|
2258
|
-
ReturnType<typeof ctx.contextWindowManager.maybeCompact>
|
|
2259
|
-
>;
|
|
2260
|
-
} catch (err) {
|
|
2261
|
-
if (err instanceof PluginTimeoutError) {
|
|
2262
|
-
// Emergency compaction timed out. Record the circuit-breaker
|
|
2263
|
-
// failure and fall through to the graceful-error path below
|
|
2264
|
-
// (the unsuccessful-compaction fallback) rather than hard-
|
|
2265
|
-
// failing the turn.
|
|
2266
|
-
rlog.warn(
|
|
2267
|
-
{ err, phase: "emergency-compaction" },
|
|
2268
|
-
"Emergency compaction pipeline timed out — continuing with overflow fallback",
|
|
2269
|
-
);
|
|
2270
|
-
await ctx.agentLoop.compactionCircuit.recordOutcome(
|
|
2271
|
-
ctx,
|
|
2272
|
-
true,
|
|
2273
|
-
onEvent,
|
|
2274
|
-
);
|
|
2275
|
-
emergencyCompact = null;
|
|
2276
|
-
} else {
|
|
2277
|
-
throw err;
|
|
2278
|
-
}
|
|
2279
|
-
}
|
|
1401
|
+
const emergencyCompact = await defaultCompact({
|
|
1402
|
+
manager: ctx.contextWindowManager,
|
|
1403
|
+
messages: ctx.messages,
|
|
1404
|
+
signal: abortController.signal,
|
|
1405
|
+
force: true,
|
|
1406
|
+
minKeepRecentUserTurns: 0,
|
|
1407
|
+
overrideProfile: resolveCurrentOverrideProfile() ?? null,
|
|
1408
|
+
});
|
|
2280
1409
|
// Only track when the summary LLM actually ran; `force: true`
|
|
2281
1410
|
// bypasses the auto-threshold gate but not the early-return paths.
|
|
2282
|
-
if (
|
|
2283
|
-
emergencyCompact &&
|
|
2284
|
-
emergencyCompact.summaryFailed !== undefined
|
|
2285
|
-
) {
|
|
1411
|
+
if (emergencyCompact.summaryFailed !== undefined) {
|
|
2286
1412
|
await ctx.agentLoop.compactionCircuit.recordOutcome(
|
|
2287
|
-
ctx,
|
|
2288
1413
|
emergencyCompact.summaryFailed,
|
|
2289
1414
|
onEvent,
|
|
2290
1415
|
);
|
|
2291
1416
|
}
|
|
2292
|
-
if (emergencyCompact
|
|
1417
|
+
if (emergencyCompact.compacted) {
|
|
2293
1418
|
await applySuccessfulCompaction(emergencyCompact, ctx.messages);
|
|
2294
|
-
state.reducerCompacted = true;
|
|
2295
|
-
state.shouldInjectWorkspace = true;
|
|
2296
1419
|
}
|
|
2297
1420
|
|
|
2298
1421
|
// Only re-inject the memory-static block when ctx.messages was
|
|
@@ -2301,29 +1424,17 @@ export async function runAgentLoopImpl(
|
|
|
2301
1424
|
// self-gate inside their injectors on whether they are already
|
|
2302
1425
|
// present in `ctx.messages`.)
|
|
2303
1426
|
const injection = await applyRuntimeInjections(ctx.messages, {
|
|
2304
|
-
|
|
2305
|
-
|
|
2306
|
-
|
|
2307
|
-
state.shouldInjectWorkspace,
|
|
2308
|
-
),
|
|
2309
|
-
slackChronologicalMessages: state.reducerCompacted
|
|
2310
|
-
? null
|
|
2311
|
-
: injectionOpts.slackChronologicalMessages,
|
|
1427
|
+
isNonInteractive,
|
|
1428
|
+
modelProfile: modelProfileStr,
|
|
1429
|
+
actorContext,
|
|
2312
1430
|
mode: currentInjectionMode,
|
|
2313
|
-
|
|
1431
|
+
requestId: reqId,
|
|
1432
|
+
conversationId: ctx.conversationId,
|
|
2314
1433
|
});
|
|
2315
1434
|
runMessages = injection.messages;
|
|
2316
1435
|
if (isTrustedActor && currentInjectionMode !== "minimal") {
|
|
2317
1436
|
ctx.graphMemory.retrackCachedNodes();
|
|
2318
1437
|
}
|
|
2319
|
-
const fallbackStrip = stripHistoricalWebSearchResults(runMessages);
|
|
2320
|
-
if (fallbackStrip.stats.blocksStripped > 0) {
|
|
2321
|
-
rlog.info(
|
|
2322
|
-
{ phase: "fail_gracefully_compact", ...fallbackStrip.stats },
|
|
2323
|
-
"Converted historical web_search_tool_result blocks to text summaries",
|
|
2324
|
-
);
|
|
2325
|
-
runMessages = fallbackStrip.messages;
|
|
2326
|
-
}
|
|
2327
1438
|
state.contextTooLargeDetected = false;
|
|
2328
1439
|
|
|
2329
1440
|
updatedHistory = await runAgentLoop(runMessages);
|
|
@@ -2906,10 +2017,7 @@ export async function runAgentLoopImpl(
|
|
|
2906
2017
|
// ── Helper ───────────────────────────────────────────────────────────
|
|
2907
2018
|
|
|
2908
2019
|
function emitUsage(
|
|
2909
|
-
ctx: Pick<
|
|
2910
|
-
AgentLoopConversationContext,
|
|
2911
|
-
"conversationId" | "provider" | "usageStats"
|
|
2912
|
-
>,
|
|
2020
|
+
ctx: Pick<Conversation, "conversationId" | "provider" | "usageStats">,
|
|
2913
2021
|
inputTokens: number,
|
|
2914
2022
|
outputTokens: number,
|
|
2915
2023
|
model: string,
|
|
@@ -2949,17 +2057,18 @@ function emitUsage(
|
|
|
2949
2057
|
}
|
|
2950
2058
|
|
|
2951
2059
|
/**
|
|
2952
|
-
* Minimal context shape consumed by `applyCompactionResult
|
|
2953
|
-
* `
|
|
2954
|
-
*
|
|
2955
|
-
*
|
|
2060
|
+
* Minimal context shape consumed by `applyCompactionResult`, satisfied by
|
|
2061
|
+
* `Conversation` via structural typing, so the helper can back both the 5
|
|
2062
|
+
* agent-loop auto-compaction sites and the single `forceCompact`
|
|
2063
|
+
* user-initiated site.
|
|
2956
2064
|
*/
|
|
2957
2065
|
export interface CompactionApplyContext {
|
|
2958
2066
|
readonly conversationId: string;
|
|
2959
2067
|
messages: Message[];
|
|
2960
2068
|
contextCompactedMessageCount: number;
|
|
2961
2069
|
contextCompactedAt: number | null;
|
|
2962
|
-
|
|
2070
|
+
contextSummary: string | null;
|
|
2071
|
+
slackContextCompactionWatermarkTs: string | null;
|
|
2963
2072
|
readonly graphMemory: ConversationGraphMemory;
|
|
2964
2073
|
readonly provider: Provider;
|
|
2965
2074
|
usageStats: UsageStats;
|
|
@@ -3006,13 +2115,22 @@ export async function applyCompactionResult(
|
|
|
3006
2115
|
} = {},
|
|
3007
2116
|
): Promise<void> {
|
|
3008
2117
|
ctx.messages = result.messages;
|
|
3009
|
-
|
|
2118
|
+
// Compaction operates on the in-context history. Untrusted actor views
|
|
2119
|
+
// render that history unsliced (boundary 0); trusted views start past the
|
|
2120
|
+
// already-compacted prefix (the mirrored DB count). Advance from that
|
|
2121
|
+
// in-context boundary rather than the raw mirror so the persisted count
|
|
2122
|
+
// stays consistent with what the new summary represents and never
|
|
2123
|
+
// double-counts an unsliced untrusted view.
|
|
2124
|
+
const inContextCompactedCount = isUntrustedTrustClass(
|
|
2125
|
+
ctx.trustContext?.trustClass,
|
|
2126
|
+
)
|
|
2127
|
+
? 0
|
|
2128
|
+
: ctx.contextCompactedMessageCount;
|
|
2129
|
+
ctx.contextCompactedMessageCount =
|
|
2130
|
+
inContextCompactedCount + result.compactedPersistedMessages;
|
|
2131
|
+
ctx.contextSummary = result.summaryText;
|
|
3010
2132
|
const compactedAt = Date.now();
|
|
3011
2133
|
ctx.contextCompactedAt = compactedAt;
|
|
3012
|
-
// Signal to the next agent loop turn that NOW.md / PKB / v2 static blocks
|
|
3013
|
-
// were stripped from the tail and need fresh re-injection. Consumed and
|
|
3014
|
-
// cleared at the top of the next `runAgentLoopImpl` run.
|
|
3015
|
-
ctx.pendingPostCompactReinject = true;
|
|
3016
2134
|
await ctx.graphMemory.onCompacted(result.compactedPersistedMessages);
|
|
3017
2135
|
updateConversationContextWindow(
|
|
3018
2136
|
ctx.conversationId,
|
|
@@ -3026,6 +2144,8 @@ export async function applyCompactionResult(
|
|
|
3026
2144
|
options.slackContextCompactionWatermarkTs,
|
|
3027
2145
|
compactedAt,
|
|
3028
2146
|
);
|
|
2147
|
+
ctx.slackContextCompactionWatermarkTs =
|
|
2148
|
+
options.slackContextCompactionWatermarkTs;
|
|
3029
2149
|
}
|
|
3030
2150
|
enqueueAutoAnalysisOnCompaction(
|
|
3031
2151
|
ctx.conversationId,
|