@vellumai/assistant 0.8.6 → 0.8.7-dev.202606052118.34cd356
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/AGENTS.md +4 -4
- package/Dockerfile +21 -4
- package/bun.lock +13 -4
- package/docker-entrypoint.sh +12 -8
- package/docker-init-apt-root.sh +3 -1
- package/docker-kata-apt-env.sh +3 -1
- package/docker-kata-runtime-family.sh +12 -0
- package/docs/architecture/memory.md +1 -1
- package/docs/plugins.md +110 -83
- package/examples/plugins/echo/README.md +13 -12
- package/examples/plugins/echo/register.ts +0 -54
- package/knip.json +1 -0
- package/node_modules/@vellumai/environments/bun.lock +24 -0
- package/node_modules/@vellumai/environments/package.json +18 -0
- package/node_modules/@vellumai/environments/src/__tests__/package-boundary.test.ts +95 -0
- package/node_modules/@vellumai/environments/src/index.ts +11 -0
- package/node_modules/@vellumai/environments/src/seeds.ts +73 -0
- package/node_modules/@vellumai/environments/src/types.ts +70 -0
- package/node_modules/@vellumai/environments/tsconfig.json +20 -0
- package/node_modules/@vellumai/skill-host-contracts/src/assistant-event.ts +11 -0
- package/node_modules/@vellumai/skill-host-contracts/src/client.ts +3 -4
- package/node_modules/@vellumai/skill-host-contracts/src/server-message.ts +3 -3
- package/node_modules/@vellumai/skill-host-contracts/src/skill-host.ts +13 -8
- package/openapi.yaml +6964 -539
- package/package.json +8 -4
- package/scripts/generate-openapi.ts +88 -54
- package/src/__tests__/agent-loop-callsite-precedence.test.ts +42 -80
- package/src/__tests__/agent-loop-exit-reason.test.ts +188 -45
- package/src/__tests__/agent-loop-mutable-latest-user-message.test.ts +141 -0
- package/src/__tests__/agent-loop-override-profile.test.ts +19 -32
- package/src/__tests__/agent-loop-provider-error-recording.test.ts +7 -5
- package/src/__tests__/agent-loop-thinking.test.ts +17 -12
- package/src/__tests__/agent-loop.test.ts +238 -422
- package/src/__tests__/agent-wake-disk-pressure-callsite.test.ts +6 -2
- package/src/__tests__/agent-wake-override-profile.test.ts +22 -40
- package/src/__tests__/annotate-activity-metadata.test.ts +262 -0
- package/src/__tests__/annotate-risk-options.test.ts +2 -3
- package/src/__tests__/anthropic-provider.test.ts +296 -57
- package/src/__tests__/app-builder-skill-instructions.test.ts +22 -0
- package/src/__tests__/app-control-flow.test.ts +6 -1
- package/src/__tests__/app-dir-path-guard.test.ts +1 -0
- package/src/__tests__/approval-cascade.test.ts +4 -11
- package/src/__tests__/approval-routes-http.test.ts +8 -3
- package/src/__tests__/assistant-event-hub.test.ts +25 -0
- package/src/__tests__/assistant-event.test.ts +15 -0
- package/src/__tests__/assistant-events-sse-shed.test.ts +8 -0
- package/src/__tests__/assistant-feature-flags-integration.test.ts +2 -2
- package/src/__tests__/assistant-stream-state.test.ts +645 -0
- package/src/__tests__/auth-fallback-events-store.test.ts +116 -0
- package/src/__tests__/avatar-e2e.test.ts +7 -37
- package/src/__tests__/avatar-generator.test.ts +12 -42
- package/src/__tests__/avatar-identity-sync.test.ts +28 -3
- package/src/__tests__/background-shell-bash.test.ts +3 -7
- package/src/__tests__/background-workers-disk-pressure.test.ts +6 -0
- package/src/__tests__/btw-routes.test.ts +69 -15
- package/src/__tests__/build-persisted-content.test.ts +184 -0
- package/src/__tests__/call-pointer-messages.test.ts +5 -3
- package/src/__tests__/call-site-routing-provider.test.ts +22 -40
- package/src/__tests__/catalog-files.test.ts +1 -0
- package/src/__tests__/channel-approval-routes.test.ts +49 -21
- package/src/__tests__/channel-approvals.test.ts +4 -2
- package/src/__tests__/channel-invite-transport.test.ts +1 -5
- package/src/__tests__/channel-readiness-routes.test.ts +0 -4
- package/src/__tests__/channel-readiness-slack-remote.test.ts +2 -7
- package/src/__tests__/channel-retry-sweep.test.ts +71 -79
- package/src/__tests__/clawhub-files.test.ts +1 -0
- 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 -17
- package/src/__tests__/compaction-trail-store.test.ts +1 -79
- package/src/__tests__/compaction.benchmark.test.ts +0 -30
- package/src/__tests__/compactor-image-manifest-trust.test.ts +112 -0
- package/src/__tests__/computer-use-tools.test.ts +2 -2
- package/src/__tests__/config-watcher.test.ts +28 -0
- package/src/__tests__/context-search-agent-runner.test.ts +6 -3
- package/src/__tests__/context-token-estimator.test.ts +34 -0
- package/src/__tests__/context-window-manager-compact-retry.test.ts +291 -0
- package/src/__tests__/conversation-abort-tool-results.test.ts +70 -25
- package/src/__tests__/conversation-agent-loop-disk-pressure.test.ts +9 -7
- package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +22 -34
- package/src/__tests__/conversation-agent-loop-overflow.test.ts +476 -963
- package/src/__tests__/conversation-agent-loop.test.ts +823 -1321
- package/src/__tests__/conversation-analysis-routes.test.ts +7 -3
- package/src/__tests__/conversation-app-control-lifecycle.test.ts +1 -1
- package/src/__tests__/conversation-clean-command.test.ts +5 -2
- package/src/__tests__/conversation-clear-safety.test.ts +20 -10
- package/src/__tests__/conversation-confirmation-signals.test.ts +15 -45
- package/src/__tests__/conversation-disk-view-integration.test.ts +2 -2
- package/src/__tests__/conversation-disk-view.test.ts +10 -17
- package/src/__tests__/conversation-fork-crud.test.ts +86 -172
- package/src/__tests__/conversation-fork-route.test.ts +16 -14
- package/src/__tests__/conversation-history-web-search.test.ts +11 -1
- package/src/__tests__/conversation-init.benchmark.test.ts +6 -6
- package/src/__tests__/conversation-lifecycle.test.ts +3 -2
- package/src/__tests__/conversation-load-history-repair.test.ts +3 -2
- package/src/__tests__/conversation-load-history-stripped.test.ts +1 -1
- package/src/__tests__/conversation-message-sync-tags.test.ts +3 -4
- package/src/__tests__/conversation-pairing.test.ts +10 -7
- package/src/__tests__/conversation-pre-run-repair.test.ts +1 -1
- package/src/__tests__/conversation-process-app-control-preactivation.test.ts +10 -0
- package/src/__tests__/conversation-process-callsite.test.ts +27 -30
- package/src/__tests__/conversation-provider-retry-repair.test.ts +80 -51
- package/src/__tests__/conversation-queue.test.ts +272 -164
- package/src/__tests__/conversation-routes-disk-view.test.ts +6 -2
- package/src/__tests__/conversation-routes-guardian-reply.test.ts +2 -2
- package/src/__tests__/conversation-routes-slash-commands.test.ts +8 -7
- package/src/__tests__/conversation-runtime-assembly.test.ts +317 -313
- package/src/__tests__/conversation-runtime-workspace.test.ts +114 -36
- package/src/__tests__/conversation-slash-commands.test.ts +8 -42
- package/src/__tests__/conversation-slash-queue.test.ts +42 -31
- package/src/__tests__/conversation-slash-unknown.test.ts +13 -15
- package/src/__tests__/conversation-speed-override.test.ts +8 -22
- package/src/__tests__/conversation-starter-routes.test.ts +14 -6
- package/src/__tests__/conversation-surfaces-action-delivery.test.ts +90 -15
- package/src/__tests__/conversation-surfaces-app-control.test.ts +32 -4
- package/src/__tests__/conversation-surfaces-state-update.test.ts +5 -2
- package/src/__tests__/conversation-surfaces-table-action.test.ts +6 -15
- package/src/__tests__/conversation-sync-tags.test.ts +27 -15
- package/src/__tests__/conversation-title-service.test.ts +135 -2
- package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +23 -11
- package/src/__tests__/conversation-unread-route.test.ts +14 -2
- package/src/__tests__/conversation-usage.test.ts +0 -2
- package/src/__tests__/conversation-wipe.test.ts +1 -1
- package/src/__tests__/conversation-workspace-cache-state.test.ts +20 -17
- package/src/__tests__/conversation-workspace-injection.test.ts +114 -23
- package/src/__tests__/conversation-workspace-tool-tracking.test.ts +34 -13
- package/src/__tests__/conversations-import-system-filter.test.ts +101 -0
- package/src/__tests__/credential-execution-tools.test.ts +1 -2
- package/src/__tests__/credential-security-invariants.test.ts +0 -1
- package/src/__tests__/cross-provider-web-search.test.ts +220 -3
- package/src/__tests__/cu-unified-flow.test.ts +26 -1
- package/src/__tests__/db-acp-history.test.ts +101 -0
- package/src/__tests__/db-schedule-syntax-migration.test.ts +16 -0
- package/src/__tests__/disk-pressure-guard.test.ts +66 -0
- package/src/__tests__/disk-pressure-routes.test.ts +9 -2
- package/src/__tests__/dm-persistence.test.ts +12 -3
- package/src/__tests__/dynamic-page-surface.test.ts +99 -0
- package/src/__tests__/edit-propagation.test.ts +1 -2
- package/src/__tests__/empty-response-hook.test.ts +304 -0
- package/src/__tests__/feature-flag-test-helpers.ts +2 -2
- package/src/__tests__/file-write-tool.test.ts +63 -0
- package/src/__tests__/filing-service.test.ts +2 -2
- package/src/__tests__/first-greeting.test.ts +55 -14
- package/src/__tests__/gemini-image-service.test.ts +13 -0
- package/src/__tests__/gemini-inline-media.test.ts +78 -0
- package/src/__tests__/gemini-provider.test.ts +351 -28
- package/src/__tests__/guardian-grant-minting.test.ts +1 -1
- package/src/__tests__/guardian-routing-invariants.test.ts +2 -4
- package/src/__tests__/guardian-routing-state.test.ts +60 -71
- package/src/__tests__/handlers-user-message-approval-consumption.test.ts +10 -8
- package/src/__tests__/heartbeat-disk-pressure.test.ts +2 -0
- package/src/__tests__/heartbeat-service.test.ts +3 -1
- package/src/__tests__/helpers/mock-provider.ts +110 -0
- package/src/__tests__/helpers/native-web-search-harness.ts +129 -0
- package/src/__tests__/history-repair-hook.test.ts +162 -0
- package/src/__tests__/history-repair-observability.test.ts +1 -1
- package/src/__tests__/history-repair.test.ts +2 -1
- package/src/__tests__/host-app-control-proxy.test.ts +2 -0
- package/src/__tests__/host-app-control-routes.test.ts +1 -1
- package/src/__tests__/host-cu-proxy.test.ts +2 -0
- package/src/__tests__/host-cu-routes-targeted.test.ts +3 -3
- package/src/__tests__/host-file-edit-tool.test.ts +4 -2
- package/src/__tests__/host-file-proxy.test.ts +31 -0
- package/src/__tests__/host-file-read-tool.test.ts +4 -2
- package/src/__tests__/host-file-write-tool.test.ts +9 -3
- package/src/__tests__/host-proxy-preactivation.test.ts +53 -14
- package/src/__tests__/host-shell-tool.test.ts +9 -4
- package/src/__tests__/http-user-message-parity.test.ts +2 -2
- package/src/__tests__/identity-intro-cache.test.ts +47 -114
- package/src/__tests__/identity-routes.test.ts +248 -7
- package/src/__tests__/inbound-slack-persistence.test.ts +12 -3
- package/src/__tests__/injector-background-turn.test.ts +3 -9
- package/src/__tests__/injector-chain.test.ts +139 -275
- package/src/__tests__/injector-disk-pressure.test.ts +75 -41
- package/src/__tests__/injector-document-comments.test.ts +3 -3
- package/src/__tests__/injector-pkb-v2-silenced.test.ts +30 -22
- package/src/__tests__/injector-v3-suppression.test.ts +214 -0
- package/src/__tests__/internal-telemetry-routes.test.ts +109 -0
- package/src/__tests__/list-messages-attachments.test.ts +7 -8
- package/src/__tests__/list-messages-hidden-metadata.test.ts +55 -15
- package/src/__tests__/list-messages-page-latest.test.ts +60 -1
- package/src/__tests__/list-messages-tool-merge.test.ts +56 -6
- package/src/__tests__/llm-request-log-turn-query.test.ts +42 -86
- package/src/__tests__/llm-resolver.test.ts +23 -47
- package/src/__tests__/llm-usage-store.test.ts +268 -1
- package/src/__tests__/log-export-routes.test.ts +59 -0
- package/src/__tests__/managed-skill-lifecycle.test.ts +1 -8
- package/src/__tests__/mcp-auth-routes.test.ts +15 -10
- package/src/__tests__/mcp-health-check.test.ts +18 -13
- package/src/__tests__/memory-retrieval-hook.test.ts +297 -0
- package/src/__tests__/memory-v2-static-injector.test.ts +103 -35
- package/src/__tests__/messaging-send-tool.test.ts +8 -4
- package/src/__tests__/migration-export-http.test.ts +12 -12
- package/src/__tests__/migration-import-commit-http.test.ts +8 -8
- package/src/__tests__/migration-import-preflight-http.test.ts +7 -7
- package/src/__tests__/migration-validate-http.test.ts +3 -3
- package/src/__tests__/native-web-search.test.ts +205 -20
- package/src/__tests__/notification-decision-identity.test.ts +9 -18
- package/src/__tests__/notification-decision-recipient-context.test.ts +3 -6
- package/src/__tests__/oauth-commands-routes.test.ts +1 -1
- package/src/__tests__/onboarding-template-contract.test.ts +12 -0
- package/src/__tests__/openai-image-service.test.ts +17 -0
- package/src/__tests__/openai-provider.test.ts +97 -71
- package/src/__tests__/openai-responses-provider.test.ts +21 -77
- package/src/__tests__/outbound-slack-persistence.test.ts +2 -1
- package/src/__tests__/{overflow-reduce-pipeline.test.ts → overflow-reduction-loop.test.ts} +64 -286
- package/src/__tests__/parallel-tool.benchmark.test.ts +24 -36
- package/src/__tests__/persist-unsendable-image.test.ts +215 -0
- package/src/__tests__/persistence-secret-redaction.test.ts +3 -1
- package/src/__tests__/pipeline-runner.test.ts +31 -43
- package/src/__tests__/pkb-autoinject.test.ts +2 -5
- package/src/__tests__/plugin-bootstrap.test.ts +62 -51
- package/src/__tests__/plugin-registry.test.ts +0 -27
- package/src/__tests__/plugin-route-contribution.test.ts +6 -16
- package/src/__tests__/plugin-skill-contribution.test.ts +7 -17
- package/src/__tests__/plugin-tool-contribution.test.ts +10 -26
- package/src/__tests__/plugin-types.test.ts +8 -173
- package/src/__tests__/prechat-onboarding-contract.test.ts +23 -0
- package/src/__tests__/process-message-background-slack.test.ts +17 -16
- package/src/__tests__/process-message-display-content.test.ts +36 -44
- package/src/__tests__/provider-commit-message-generator.test.ts +19 -14
- package/src/__tests__/provider-error-scenarios.test.ts +7 -6
- package/src/__tests__/provider-platform-proxy-integration.test.ts +3 -8
- package/src/__tests__/provider-send-message-override-profile.test.ts +9 -25
- package/src/__tests__/provider-streaming.benchmark.test.ts +12 -22
- package/src/__tests__/provider-usage-tracking.test.ts +0 -6
- package/src/__tests__/ratelimit.test.ts +9 -4
- package/src/__tests__/reaction-persistence.test.ts +1 -1
- package/src/__tests__/regenerate-fire-and-forget-trace.test.ts +5 -1
- package/src/__tests__/relay-server.test.ts +20 -13
- package/src/__tests__/resolve-trust-class.test.ts +4 -4
- package/src/__tests__/retry-openrouter-only-normalization.test.ts +5 -8
- package/src/__tests__/retry-thinking-tool-choice.test.ts +10 -13
- package/src/__tests__/retry-verbosity-normalization.test.ts +5 -8
- package/src/__tests__/runtime-events-sse-reconnect.test.ts +390 -0
- package/src/__tests__/schedule-routes.test.ts +683 -12
- package/src/__tests__/schedule-store.test.ts +108 -0
- package/src/__tests__/schedule-tools.test.ts +160 -0
- package/src/__tests__/secret-ingress-http.test.ts +2 -2
- package/src/__tests__/secret-prompt-log-hygiene.test.ts +11 -7
- package/src/__tests__/secret-prompter-channel-fallback.test.ts +11 -9
- package/src/__tests__/secret-response-routing.test.ts +13 -11
- package/src/__tests__/send-endpoint-busy.test.ts +6 -2
- package/src/__tests__/server-history-render.test.ts +314 -1
- package/src/__tests__/shell-observability.test.ts +249 -0
- package/src/__tests__/skill-feature-flags-integration.test.ts +44 -11
- package/src/__tests__/skill-feature-flags.test.ts +6 -6
- package/src/__tests__/skill-load-feature-flag.test.ts +10 -10
- package/src/__tests__/skills-files-catalog-fallback.test.ts +10 -0
- package/src/__tests__/skillssh-files.test.ts +1 -0
- package/src/__tests__/starter-task-flow.test.ts +6 -6
- package/src/__tests__/strip-memory-injections.test.ts +102 -14
- package/src/__tests__/subagent-call-site-routing.test.ts +3 -3
- 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__/suggestion-routes.test.ts +3 -3
- package/src/__tests__/sync-message-contract.test.ts +19 -16
- package/src/__tests__/system-prompt.test.ts +74 -0
- package/src/__tests__/task-scheduler.test.ts +162 -1
- package/src/__tests__/terminal-tools.test.ts +9 -25
- package/src/__tests__/thread-backfill.test.ts +4 -9
- package/src/__tests__/title-generate-hook.test.ts +319 -0
- package/src/__tests__/tool-error-hook.test.ts +278 -0
- package/src/__tests__/tool-preview-lifecycle.test.ts +481 -16
- package/src/__tests__/tool-result-metadata-plumbing.test.ts +1 -0
- package/src/__tests__/tool-result-truncate-hook.test.ts +127 -0
- package/src/__tests__/tool-result-truncation.test.ts +1 -1
- package/src/__tests__/tools-audio-read.test.ts +113 -0
- package/src/__tests__/turn-boundary-resolution.test.ts +44 -84
- package/src/__tests__/turn-events-store.test.ts +11 -7
- package/src/__tests__/ui-choice-copy-surfaces.test.ts +254 -0
- package/src/__tests__/ui-work-result-surface.test.ts +159 -0
- package/src/__tests__/usage-routes.test.ts +285 -1
- package/src/__tests__/user-plugin-loader.test.ts +2 -2
- package/src/__tests__/voice-scoped-grant-consumer.test.ts +8 -6
- package/src/__tests__/voice-session-bridge.test.ts +19 -10
- package/src/__tests__/web-search-backend-failure.test.ts +166 -0
- 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 +101 -0
- package/src/acp/__tests__/prepare-agent-env.test.ts +143 -31
- package/src/acp/__tests__/session-manager-persistence.test.ts +95 -28
- package/src/acp/__tests__/session-manager-resume.test.ts +695 -0
- package/src/acp/agent-process.ts +61 -1
- package/src/acp/auto-install.test.ts +125 -0
- package/src/acp/auto-install.ts +174 -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 +52 -11
- package/src/acp/resolve-agent.test.ts +147 -6
- package/src/acp/resolve-agent.ts +81 -7
- package/src/acp/resume-hint.ts +22 -0
- package/src/acp/session-manager.ts +487 -71
- package/src/agent/compaction-circuit.ts +98 -0
- package/src/agent/loop.ts +651 -450
- package/src/api/README.md +19 -17
- package/src/api/constants/tool-execution.ts +21 -0
- package/src/api/events/assistant-activity-state.ts +75 -0
- package/src/api/events/assistant-outbound-attachment.ts +25 -27
- package/src/api/events/assistant-text-delta.ts +6 -8
- package/src/api/events/assistant-thinking-delta.ts +33 -0
- package/src/api/events/assistant-turn-start.ts +5 -7
- package/src/api/events/avatar-updated.ts +24 -0
- package/src/api/events/compaction-circuit-closed.ts +26 -0
- package/src/api/events/compaction-circuit-open.ts +28 -0
- package/src/api/events/confirmation-request.ts +114 -0
- package/src/api/events/contact-request.ts +33 -0
- package/src/api/events/conversation-error.ts +77 -0
- package/src/api/events/conversation-list-invalidated.ts +38 -0
- package/src/api/events/conversation-title-updated.ts +24 -0
- package/src/api/events/disk-pressure-status-changed.ts +61 -0
- package/src/api/events/document-comment-created.ts +24 -28
- package/src/api/events/document-comment-deleted.ts +6 -8
- package/src/api/events/document-comment-reopened.ts +6 -8
- package/src/api/events/document-comment-resolved.ts +8 -10
- package/src/api/events/document-editor-update.ts +27 -0
- package/src/api/events/error.ts +32 -0
- package/src/api/events/generation-cancelled.ts +4 -6
- package/src/api/events/generation-handoff.ts +13 -15
- package/src/api/events/home-feed-updated.ts +26 -0
- package/src/api/events/identity-changed.ts +32 -0
- package/src/api/events/interaction-resolved.ts +50 -0
- package/src/api/events/message-complete.ts +10 -12
- package/src/api/events/message-dequeued.ts +21 -0
- package/src/api/events/message-queued-deleted.ts +23 -0
- package/src/api/events/message-queued.ts +22 -0
- package/src/api/events/message-request-complete.ts +29 -0
- package/src/api/events/navigate-settings.ts +20 -0
- package/src/api/events/notification-intent.ts +33 -0
- package/src/api/events/open-url.ts +6 -8
- package/src/api/events/question-request.ts +67 -0
- package/src/api/events/relationship-state-updated.ts +4 -6
- package/src/api/events/secret-request.ts +42 -0
- package/src/api/events/subagent-event.ts +79 -0
- package/src/api/events/subagent-spawned.ts +40 -0
- package/src/api/events/subagent-status-changed.ts +65 -0
- package/src/api/events/sync-changed.ts +29 -0
- package/src/api/events/tool-output-chunk.ts +45 -0
- package/src/api/events/tool-result.ts +129 -0
- package/src/api/events/tool-use-preview-start.ts +32 -0
- package/src/api/events/tool-use-start.ts +8 -10
- package/src/api/events/trace-event.ts +69 -0
- package/src/api/events/turn-profile-auto-routed.ts +28 -0
- package/src/api/events/ui-surface-complete.ts +30 -0
- package/src/api/events/ui-surface-dismiss.ts +22 -0
- package/src/api/events/ui-surface-show.ts +67 -0
- package/src/api/events/ui-surface-update.ts +26 -0
- package/src/api/events/usage-update.ts +34 -0
- package/src/api/events/user-message-echo.ts +35 -0
- package/src/api/index.ts +389 -0
- package/src/api/requests/dictation.ts +45 -0
- package/src/api/responses/conversation-message.ts +374 -0
- package/src/api/responses/disk-pressure-status.ts +26 -0
- package/src/api/responses/home.ts +217 -0
- package/src/api/responses/llm-context-response.ts +2 -0
- package/src/api/responses/memory-v3-selection-log.ts +50 -0
- package/src/api/responses/subagent-detail.ts +48 -0
- package/src/approvals/guardian-decision-primitive.ts +7 -15
- package/src/approvals/guardian-request-resolvers.ts +7 -10
- package/src/avatar/__tests__/avatar-manifest.test.ts +236 -0
- package/src/avatar/__tests__/avatar-store.test.ts +198 -0
- package/src/avatar/avatar-manifest.ts +195 -0
- package/src/avatar/avatar-store.ts +113 -0
- package/src/avatar/traits-png-sync.ts +8 -2
- package/src/background-wake/next-wake.test.ts +31 -1
- package/src/background-wake/next-wake.ts +5 -1
- package/src/calls/call-conversation-messages.ts +6 -4
- package/src/calls/guardian-action-sweep.ts +6 -4
- package/src/calls/relay-server.ts +12 -8
- package/src/calls/voice-session-bridge.ts +13 -27
- package/src/cli/commands/__tests__/memory-v3.test.ts +245 -0
- package/src/cli/commands/__tests__/notifications.test.ts +58 -14
- package/src/cli/commands/avatar.ts +17 -11
- package/src/cli/commands/conversations.ts +15 -1
- package/src/cli/commands/db/__tests__/repair.test.ts +540 -0
- package/src/cli/commands/db/__tests__/status.test.ts +253 -0
- package/src/cli/commands/db/format.ts +48 -0
- package/src/cli/commands/db/index.ts +29 -0
- package/src/cli/commands/db/repair-step-conversation-backfill.ts +345 -0
- package/src/cli/commands/db/repair-step-integrity.ts +146 -0
- package/src/cli/commands/db/repair-steps.ts +164 -0
- package/src/cli/commands/db/repair.ts +141 -0
- package/src/cli/commands/db/status.ts +366 -0
- package/src/cli/commands/memory-v3.ts +159 -445
- package/src/cli/commands/notifications.ts +112 -60
- package/src/cli/lib/cli-colors.ts +24 -6
- package/src/cli/program.ts +4 -5
- package/src/config/__tests__/feature-flag-registry-guard.test.ts +4 -4
- package/src/config/acp-defaults.test.ts +10 -0
- package/src/config/acp-defaults.ts +6 -0
- package/src/config/assistant-feature-flags.ts +24 -13
- package/src/config/bundled-skills/acp/SKILL.md +64 -30
- package/src/config/bundled-skills/acp/TOOLS.json +4 -4
- package/src/config/bundled-skills/app-builder/SKILL.md +224 -387
- package/src/config/bundled-skills/app-builder/TOOLS.json +29 -0
- 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/references/examples/README.md +17 -0
- package/src/config/bundled-skills/app-builder/references/examples/expense-tracker.md +515 -0
- package/src/config/bundled-skills/app-builder/references/examples/focus-timer.md +342 -0
- package/src/config/bundled-skills/app-builder/references/examples/habit-tracker.md +490 -0
- package/src/config/bundled-skills/app-builder/tools/app-list.ts +62 -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-skills/media-processing/services/reduce.ts +6 -9
- package/src/config/bundled-skills/messaging/SKILL.md +0 -7
- package/src/config/bundled-skills/messaging/tools/messaging-send.ts +7 -2
- package/src/config/bundled-skills/schedule/SKILL.md +1 -1
- package/src/config/bundled-skills/schedule/TOOLS.json +8 -0
- package/src/config/bundled-tool-registry.ts +2 -0
- package/src/config/call-site-defaults.ts +2 -7
- package/src/config/feature-flag-cache.ts +3 -3
- package/src/config/feature-flag-registry.json +68 -12
- package/src/config/schemas/__tests__/memory-v2.test.ts +2 -226
- package/src/config/schemas/__tests__/memory-v3.test.ts +25 -0
- package/src/config/schemas/call-site-catalog.ts +8 -15
- package/src/config/schemas/heartbeat.ts +9 -0
- package/src/config/schemas/llm.ts +3 -3
- package/src/config/schemas/memory-lifecycle.ts +24 -0
- package/src/config/schemas/memory-v2.ts +8 -253
- package/src/config/schemas/memory-v3.ts +47 -0
- package/src/config/schemas/memory.ts +6 -1
- package/src/config/schemas/platform.ts +8 -0
- package/src/config/schemas/timeouts.ts +3 -1
- package/src/config/seed-inference-profiles.ts +2 -2
- package/src/config/skills.ts +13 -0
- package/src/context/compactor.ts +55 -32
- package/src/context/strip-injections.ts +128 -0
- package/src/context/token-estimator.ts +42 -0
- package/src/context/tool-result-truncation.ts +1 -66
- package/src/context/window-manager.ts +141 -26
- package/src/credential-execution/executable-discovery.ts +16 -0
- package/src/daemon/__tests__/conversation-lifecycle-auto-analyze.test.ts +6 -0
- package/src/daemon/__tests__/conversation-surfaces-launch.test.ts +2 -2
- package/src/daemon/__tests__/inference-profile-notification.test.ts +153 -0
- package/src/daemon/__tests__/native-web-search-metadata.test.ts +10 -8
- package/src/daemon/__tests__/web-search-status-text.test.ts +10 -6
- package/src/daemon/approval-generators.ts +4 -4
- package/src/daemon/assistant-attachments.ts +1 -1
- package/src/daemon/config-watcher.ts +7 -1
- package/src/daemon/context-overflow-reducer.ts +0 -1
- package/src/daemon/conversation-agent-loop-handlers.ts +793 -215
- package/src/daemon/conversation-agent-loop.ts +487 -1478
- package/src/daemon/conversation-error.ts +7 -7
- package/src/daemon/conversation-history.ts +27 -10
- package/src/daemon/conversation-launch.ts +4 -8
- package/src/daemon/conversation-lifecycle.ts +13 -42
- package/src/daemon/conversation-messaging.ts +8 -9
- package/src/daemon/conversation-notifiers.ts +7 -5
- package/src/daemon/conversation-process.ts +109 -93
- package/src/daemon/conversation-registry.ts +159 -0
- package/src/daemon/conversation-runtime-assembly.ts +209 -382
- package/src/daemon/conversation-slash.ts +6 -25
- package/src/daemon/conversation-store.ts +15 -95
- package/src/daemon/conversation-surfaces.ts +277 -73
- package/src/daemon/conversation-tool-setup.ts +5 -29
- package/src/daemon/conversation-workspace.ts +17 -0
- package/src/daemon/conversation.ts +123 -146
- package/src/daemon/daemon-skill-host.ts +2 -6
- package/src/daemon/disk-pressure-guard.ts +35 -29
- package/src/daemon/external-plugins-bootstrap.ts +53 -32
- package/src/daemon/first-greeting.ts +26 -4
- package/src/daemon/guardian-action-generators.ts +2 -2
- package/src/daemon/handlers/config-a2a.ts +51 -36
- package/src/daemon/handlers/config-slack-channel.ts +20 -14
- package/src/daemon/handlers/config-telegram.ts +16 -2
- package/src/daemon/handlers/conversations.ts +9 -23
- package/src/daemon/handlers/shared.ts +158 -82
- package/src/daemon/handlers/skills.ts +53 -20
- package/src/daemon/host-app-control-proxy.ts +54 -1
- package/src/daemon/host-cu-proxy.ts +46 -22
- package/src/daemon/host-file-proxy.ts +25 -1
- package/src/daemon/host-proxy-preactivation.ts +25 -6
- package/src/daemon/lifecycle.ts +53 -55
- package/src/daemon/message-protocol.ts +2 -3
- package/src/daemon/message-provenance.ts +49 -0
- package/src/daemon/message-types/apps.ts +1 -29
- package/src/daemon/message-types/contacts.ts +3 -20
- package/src/daemon/message-types/conversations.ts +13 -111
- package/src/daemon/message-types/documents.ts +3 -9
- package/src/daemon/message-types/home.ts +4 -17
- package/src/daemon/message-types/integrations.ts +2 -6
- package/src/daemon/message-types/messages.ts +37 -400
- package/src/daemon/message-types/notifications.ts +2 -32
- package/src/daemon/message-types/settings.ts +3 -8
- package/src/daemon/message-types/skills.ts +4 -0
- package/src/daemon/message-types/surfaces.ts +138 -3
- package/src/daemon/message-types/sync.ts +12 -25
- package/src/daemon/message-types/workspace.ts +3 -11
- package/src/daemon/now-scratchpad.ts +21 -0
- package/src/daemon/orphan-reaper.test.ts +210 -0
- package/src/daemon/orphan-reaper.ts +240 -0
- package/src/daemon/overflow-reduction-loop.ts +230 -0
- package/src/daemon/persist-unsendable-image.ts +117 -0
- package/src/daemon/process-message.ts +50 -49
- package/src/daemon/server.ts +14 -0
- package/src/daemon/tool-side-effects.ts +10 -7
- package/src/daemon/trace-emitter.ts +6 -4
- package/src/daemon/trust-context.ts +32 -0
- package/src/daemon/wake-target-adapter.ts +14 -2
- package/src/heartbeat/__tests__/heartbeat-service.test.ts +6 -1
- package/src/heartbeat/heartbeat-run-store.ts +54 -1
- package/src/heartbeat/heartbeat-service.ts +42 -0
- package/src/home/feed-types.ts +36 -221
- package/src/home/home-greeting-cache.ts +24 -1
- package/src/ipc/__tests__/browser-ipc.test.ts +1 -1
- package/src/ipc/__tests__/email-ipc.test.ts +0 -9
- package/src/ipc/__tests__/ui-request-route.test.ts +3 -3
- package/src/ipc/gateway-client.test.ts +2 -2
- package/src/ipc/gateway-client.ts +3 -3
- package/src/ipc/routes/__tests__/route-adapter.test.ts +244 -0
- package/src/ipc/routes/route-adapter.ts +45 -6
- package/src/ipc/skill-routes/__tests__/memory.test.ts +33 -9
- package/src/ipc/skill-routes/__tests__/providers.test.ts +10 -10
- package/src/ipc/skill-routes/__tests__/registries.test.ts +28 -18
- package/src/ipc/skill-routes/memory.ts +29 -14
- package/src/ipc/skill-routes/providers.ts +5 -6
- package/src/ipc/skill-routes/registries.ts +13 -61
- package/src/live-voice/__tests__/live-voice-archive.test.ts +24 -11
- package/src/media/gemini-image-service.ts +15 -0
- package/src/media/openai-image-service.ts +14 -0
- package/src/media/types.ts +34 -0
- package/src/memory/__tests__/conversation-queries.test.ts +192 -8
- package/src/memory/__tests__/db-maintenance.test.ts +128 -0
- package/src/memory/__tests__/jobs-store-job-classes.test.ts +5 -4
- package/src/memory/__tests__/jobs-worker-v2-schedule.test.ts +56 -0
- package/src/memory/__tests__/memory-retrospective-job.test.ts +10 -6
- package/src/memory/__tests__/memory-v3-selections-migration.test.ts +103 -0
- package/src/memory/auth-fallback-events-store.ts +94 -0
- package/src/memory/context-search/agent-runner.ts +2 -4
- package/src/memory/conversation-crud.ts +39 -8
- package/src/memory/conversation-queries.ts +78 -22
- package/src/memory/conversation-starter-checkpoints.ts +1 -0
- package/src/memory/conversation-title-service.ts +65 -41
- package/src/memory/db-init.ts +14 -0
- package/src/memory/db-maintenance.ts +18 -2
- package/src/memory/graph/__tests__/conversation-graph-memory-registry.test.ts +119 -0
- package/src/memory/graph/consolidation.ts +8 -11
- package/src/memory/graph/conversation-graph-memory.ts +106 -8
- package/src/memory/graph/extraction.ts +6 -9
- package/src/memory/graph/narrative.ts +2 -2
- package/src/memory/graph/pattern-scan.ts +2 -2
- package/src/memory/graph/retriever.ts +20 -26
- package/src/memory/graph/tools.ts +4 -4
- package/src/memory/job-handlers/conversation-starters.ts +45 -34
- package/src/memory/job-handlers/summarization.ts +1 -2
- package/src/memory/jobs-store.ts +36 -1
- package/src/memory/jobs-worker.ts +82 -43
- package/src/memory/llm-request-log-source-clickhouse.ts +5 -31
- package/src/memory/llm-request-log-source-local.ts +0 -11
- package/src/memory/llm-request-log-source.ts +9 -25
- package/src/memory/llm-request-log-store.ts +0 -41
- package/src/memory/llm-usage-store.ts +234 -50
- package/src/memory/memory-marker.ts +17 -0
- package/src/memory/memory-retrospective-job.ts +6 -2
- package/src/memory/memory-v2-activation-log-store.ts +1 -83
- package/src/memory/migrations/222-strip-placeholder-sentinels-from-messages.ts +6 -5
- package/src/memory/migrations/267-llm-usage-events-add-assistant-version.ts +46 -0
- package/src/memory/migrations/268-add-memory-v3-selections.ts +28 -0
- package/src/memory/migrations/269-schedule-script-timeout.ts +11 -0
- package/src/memory/migrations/270-messages-role-created-at-index.ts +18 -0
- package/src/memory/migrations/270-schedule-source-conversation.ts +13 -0
- package/src/memory/migrations/271-create-auth-fallback-events.ts +21 -0
- package/src/memory/migrations/272-acp-session-history-cwd.ts +36 -0
- package/src/memory/migrations/__tests__/267-llm-usage-events-add-assistant-version.test.ts +117 -0
- package/src/memory/migrations/index.ts +7 -0
- package/src/memory/pkb/autoinject.ts +61 -0
- package/src/memory/pkb/context.ts +50 -0
- package/src/memory/pkb/types.ts +14 -0
- package/src/memory/schedule-attribution-sql.ts +104 -0
- package/src/memory/schema/acp.ts +4 -0
- package/src/memory/schema/infrastructure.ts +27 -0
- package/src/memory/usage-grouped-buckets.ts +6 -1
- package/src/memory/v2/__tests__/consolidation-job.test.ts +125 -1
- package/src/memory/v2/__tests__/migration.test.ts +11 -3
- package/src/memory/v2/__tests__/page-index.test.ts +37 -1
- package/src/memory/v2/__tests__/router.test.ts +14 -4
- package/src/memory/v2/__tests__/sweep-job.test.ts +6 -5
- package/src/memory/v2/backfill-jobs.ts +6 -0
- package/src/memory/v2/consolidation-job.ts +99 -10
- package/src/memory/v2/migration.ts +5 -3
- package/src/memory/v2/page-index.ts +11 -0
- package/src/memory/v2/router.ts +8 -11
- package/src/memory/v2/sweep-job.ts +8 -11
- package/src/memory/v2/types.ts +1 -0
- package/src/messaging/providers/slack/render-transcript.test.ts +1 -1
- package/src/messaging/providers/slack/render-transcript.ts +2 -2
- package/src/messaging/style-analyzer.ts +8 -11
- package/src/notifications/conversation-pairing.ts +8 -13
- package/src/notifications/decision-engine.ts +16 -16
- package/src/notifications/home-feed-side-effect.ts +12 -1
- package/src/notifications/preference-extractor.ts +11 -14
- package/src/permissions/prompter.ts +46 -36
- package/src/permissions/question-prompter.test.ts +35 -26
- package/src/permissions/question-prompter.ts +6 -10
- package/src/plugin-api/constants.ts +4 -0
- package/src/plugin-api/index.ts +10 -1
- package/src/plugin-api/types.ts +176 -4
- package/src/plugins/defaults/compaction/compact.ts +59 -0
- package/src/plugins/defaults/compaction/package.json +15 -0
- package/src/plugins/defaults/compaction/register.ts +24 -0
- package/src/plugins/defaults/empty-response/hooks/stop.ts +126 -0
- package/src/plugins/defaults/empty-response/package.json +15 -0
- package/src/plugins/defaults/empty-response/register.ts +23 -0
- package/src/plugins/defaults/history-repair/hooks/user-prompt-submit.ts +35 -0
- package/src/plugins/defaults/history-repair/package.json +15 -0
- package/src/plugins/defaults/history-repair/register.ts +24 -0
- package/src/{daemon/history-repair.ts → plugins/defaults/history-repair/terminal.ts} +48 -35
- package/src/plugins/defaults/index.ts +22 -49
- package/src/plugins/defaults/memory-retrieval/hooks/post-compact.ts +95 -0
- package/src/plugins/defaults/memory-retrieval/hooks/user-prompt-submit-temp.ts +216 -0
- package/src/plugins/defaults/memory-retrieval/injector-chain.ts +35 -0
- package/src/plugins/defaults/{injectors.ts → memory-retrieval/injectors.ts} +295 -112
- package/src/plugins/defaults/memory-v3-shadow/__tests__/assign.test.ts +242 -0
- package/src/plugins/defaults/memory-v3-shadow/__tests__/capabilities.test.ts +118 -0
- package/src/plugins/defaults/memory-v3-shadow/__tests__/core.test.ts +39 -0
- package/src/plugins/defaults/memory-v3-shadow/__tests__/fixtures/eval-turns.json +36 -0
- package/src/plugins/defaults/memory-v3-shadow/__tests__/fixtures/live-turns.json +37 -0
- package/src/plugins/defaults/memory-v3-shadow/__tests__/health.test.ts +219 -0
- package/src/plugins/defaults/memory-v3-shadow/__tests__/live-integration.test.ts +330 -0
- package/src/plugins/defaults/memory-v3-shadow/__tests__/maintain-job.test.ts +288 -0
- package/src/plugins/defaults/memory-v3-shadow/__tests__/needle.test.ts +107 -0
- package/src/plugins/defaults/memory-v3-shadow/__tests__/orchestrate.test.ts +436 -0
- package/src/plugins/defaults/memory-v3-shadow/__tests__/provider-blocks.test.ts +13 -0
- package/src/plugins/defaults/memory-v3-shadow/__tests__/reconcile.test.ts +274 -0
- package/src/plugins/defaults/memory-v3-shadow/__tests__/render-injection.test.ts +61 -0
- package/src/plugins/defaults/memory-v3-shadow/__tests__/router.test.ts +332 -0
- package/src/plugins/defaults/memory-v3-shadow/__tests__/selection-log-store.test.ts +179 -0
- package/src/plugins/defaults/memory-v3-shadow/__tests__/selector.test.ts +470 -0
- package/src/plugins/defaults/memory-v3-shadow/__tests__/shadow-plugin.test.ts +432 -0
- package/src/plugins/defaults/memory-v3-shadow/__tests__/snapshot.test.ts +168 -0
- package/src/plugins/defaults/memory-v3-shadow/__tests__/tree.test.ts +192 -0
- package/src/plugins/defaults/memory-v3-shadow/__tests__/types.test.ts +54 -0
- package/src/plugins/defaults/memory-v3-shadow/__tests__/working-set-eviction.test.ts +106 -0
- package/src/plugins/defaults/memory-v3-shadow/__tests__/working-set-skeleton.test.ts +44 -0
- package/src/plugins/defaults/memory-v3-shadow/assign.ts +268 -0
- package/src/plugins/defaults/memory-v3-shadow/capabilities.ts +124 -0
- package/src/plugins/defaults/memory-v3-shadow/core.ts +26 -0
- package/src/plugins/defaults/memory-v3-shadow/data/README.md +84 -0
- package/src/plugins/defaults/memory-v3-shadow/data/assignments.json +5 -0
- package/src/plugins/defaults/memory-v3-shadow/data/core.json +1 -0
- package/src/plugins/defaults/memory-v3-shadow/data/leaves/domain-a/topic-x.md +9 -0
- package/src/plugins/defaults/memory-v3-shadow/data/leaves/domain-a/topic-y.md +9 -0
- package/src/plugins/defaults/memory-v3-shadow/data/leaves/domain-b/topic-z.md +9 -0
- package/src/plugins/defaults/memory-v3-shadow/health.ts +0 -0
- 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/plugins/defaults/memory-v3-shadow/llm-retry.ts +32 -0
- package/src/plugins/defaults/memory-v3-shadow/maintain-job.ts +314 -0
- package/src/plugins/defaults/memory-v3-shadow/needle.ts +115 -0
- package/src/plugins/defaults/memory-v3-shadow/orchestrate.ts +126 -0
- package/src/plugins/defaults/memory-v3-shadow/package.json +15 -0
- package/src/plugins/defaults/memory-v3-shadow/page-content.ts +34 -0
- package/src/plugins/defaults/memory-v3-shadow/provider-blocks.ts +26 -0
- package/src/plugins/defaults/memory-v3-shadow/reconcile.ts +523 -0
- package/src/plugins/defaults/memory-v3-shadow/register.ts +26 -0
- package/src/plugins/defaults/memory-v3-shadow/render-injection.ts +32 -0
- package/src/plugins/defaults/memory-v3-shadow/router.ts +190 -0
- package/src/plugins/defaults/memory-v3-shadow/selection-log-store.ts +84 -0
- package/src/plugins/defaults/memory-v3-shadow/selector.ts +226 -0
- package/src/plugins/defaults/memory-v3-shadow/shadow-plugin.ts +349 -0
- package/src/plugins/defaults/memory-v3-shadow/snapshot.ts +209 -0
- package/src/plugins/defaults/memory-v3-shadow/tree.ts +174 -0
- package/src/plugins/defaults/memory-v3-shadow/types.ts +59 -0
- package/src/plugins/defaults/memory-v3-shadow/working-set.ts +88 -0
- package/src/plugins/defaults/title-generate/hooks/stop.ts +75 -0
- package/src/plugins/defaults/title-generate/hooks/user-prompt-submit.ts +35 -0
- package/src/plugins/defaults/title-generate/package.json +15 -0
- package/src/plugins/defaults/title-generate/register.ts +35 -0
- package/src/plugins/defaults/tool-error/hooks/post-tool-use.ts +118 -0
- package/src/plugins/defaults/tool-error/package.json +15 -0
- package/src/plugins/defaults/tool-error/register.ts +23 -0
- package/src/plugins/defaults/tool-result-truncate/hooks/post-tool-use.ts +32 -0
- package/src/plugins/defaults/tool-result-truncate/package.json +15 -0
- package/src/plugins/defaults/tool-result-truncate/register.ts +24 -0
- package/src/plugins/defaults/tool-result-truncate/terminal.ts +132 -0
- package/src/plugins/external-plugin-loader.ts +2 -2
- package/src/plugins/pipeline.ts +8 -35
- package/src/plugins/registry.ts +8 -25
- package/src/plugins/types.ts +62 -721
- package/src/plugins/user-loader.ts +4 -3
- package/src/proactive-artifact/aux-message-injector.ts +4 -5
- package/src/proactive-artifact/job.test.ts +28 -21
- package/src/proactive-artifact/job.ts +3 -1
- package/src/prompts/__tests__/system-prompt.test.ts +42 -0
- package/src/prompts/sections.ts +20 -7
- package/src/prompts/templates/BOOTSTRAP-ACTIVATION-RAIL.md +64 -0
- package/src/prompts/templates/BOOTSTRAP-CONTENT-AUTOMATION.md +2 -2
- package/src/prompts/templates/BOOTSTRAP.md +7 -3
- package/src/prompts/templates/system-sections.ts +21 -0
- package/src/providers/__tests__/retry-callsite.test.ts +25 -25
- package/src/providers/__tests__/satellite-connection-routing.test.ts +7 -21
- package/src/providers/anthropic/client.ts +61 -34
- package/src/providers/call-site-routing.ts +1 -9
- package/src/providers/gemini/client.ts +152 -34
- package/src/providers/gemini/inline-media.ts +74 -0
- package/src/providers/openai/__tests__/chat-completions-provider-reasoning.test.ts +112 -2
- package/src/providers/openai/chat-completions-provider.ts +45 -4
- package/src/providers/openai/responses-provider.ts +1 -4
- package/src/providers/openrouter/client.ts +2 -6
- package/src/providers/placeholder-sentinels.ts +35 -0
- package/src/providers/provider-send-message.ts +6 -6
- package/src/providers/ratelimit.ts +1 -9
- package/src/providers/retry.ts +0 -5
- package/src/providers/types.ts +11 -2
- package/src/providers/usage-tracking.ts +1 -9
- package/src/runtime/__tests__/agent-wake.test.ts +141 -32
- package/src/runtime/__tests__/background-job-runner.test.ts +1 -3
- package/src/runtime/__tests__/interactive-ui.test.ts +1 -1
- package/src/runtime/agent-wake.ts +95 -23
- package/src/runtime/assistant-event-hub.ts +38 -8
- package/src/runtime/assistant-stream-state.ts +368 -0
- package/src/runtime/auth/__tests__/guard-tests.test.ts +75 -109
- package/src/runtime/auth/__tests__/route-policy.test.ts +153 -170
- package/src/runtime/auth/route-policy.ts +42 -1079
- package/src/runtime/background-job-runner.ts +1 -4
- package/src/runtime/btw-sidechain.ts +3 -1
- package/src/runtime/channel-approvals.ts +4 -15
- package/src/runtime/channel-invite-transport.ts +5 -6
- package/src/runtime/channel-readiness-service.ts +2 -5
- package/src/runtime/channel-retry-sweep.ts +12 -16
- package/src/runtime/http-router.ts +35 -43
- package/src/runtime/http-types.ts +23 -71
- package/src/runtime/interactive-ui.ts +1 -1
- package/src/runtime/invite-instruction-generator.ts +3 -3
- package/src/runtime/pending-interactions.ts +3 -2
- package/src/runtime/routes/__tests__/acp-routes.test.ts +253 -55
- package/src/runtime/routes/__tests__/avatar-state-routes.test.ts +565 -0
- package/src/runtime/routes/__tests__/consolidation-routes.test.ts +265 -2
- package/src/runtime/routes/__tests__/content-source-routes.test.ts +4 -4
- package/src/runtime/routes/__tests__/conversation-compaction-routes.test.ts +62 -32
- package/src/runtime/routes/__tests__/conversation-list-routes.test.ts +237 -0
- package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +31 -1
- package/src/runtime/routes/__tests__/inference-provider-connection-routes.test.ts +13 -22
- package/src/runtime/routes/__tests__/memory-v2-routes.test.ts +6 -2
- package/src/runtime/routes/__tests__/memory-v2-simulate-route.test.ts +7 -2
- package/src/runtime/routes/__tests__/sanity-routes.test.ts +6 -6
- package/src/runtime/routes/__tests__/stt-routes.test.ts +3 -3
- package/src/runtime/routes/__tests__/suggest-trust-rule-routes.test.ts +5 -2
- 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/__tests__/tts-routes.test.ts +9 -5
- package/src/runtime/routes/acp-routes.test.ts +186 -100
- package/src/runtime/routes/acp-routes.ts +110 -35
- package/src/runtime/routes/app-management-routes.ts +93 -131
- package/src/runtime/routes/app-routes.ts +38 -20
- package/src/runtime/routes/approval-routes.ts +17 -5
- package/src/runtime/routes/attachment-routes.ts +51 -16
- package/src/runtime/routes/audio-routes.ts +1 -0
- package/src/runtime/routes/audit-routes.ts +5 -0
- package/src/runtime/routes/auth-routes.ts +5 -0
- package/src/runtime/routes/avatar-routes.ts +264 -59
- package/src/runtime/routes/background-tool-routes.ts +9 -0
- package/src/runtime/routes/background-wake-routes.ts +13 -3
- package/src/runtime/routes/backup-routes.ts +45 -0
- package/src/runtime/routes/bookmark-routes.ts +13 -0
- package/src/runtime/routes/brain-graph-routes.ts +9 -0
- package/src/runtime/routes/browser-routes.ts +6 -1
- package/src/runtime/routes/browser-tabs-routes.ts +11 -10
- package/src/runtime/routes/btw-routes.ts +34 -24
- package/src/runtime/routes/cache-routes.ts +13 -0
- package/src/runtime/routes/call-routes.ts +21 -10
- package/src/runtime/routes/channel-availability-routes.ts +5 -1
- package/src/runtime/routes/channel-readiness-routes.ts +37 -4
- package/src/runtime/routes/channel-route-definitions.ts +21 -0
- package/src/runtime/routes/channel-verification-routes.ts +21 -0
- package/src/runtime/routes/chatgpt-subscription-auth-routes.ts +9 -2
- package/src/runtime/routes/client-routes.ts +9 -0
- package/src/runtime/routes/consolidation-routes.ts +133 -25
- package/src/runtime/routes/contact-prompt-routes.ts +9 -0
- package/src/runtime/routes/contact-routes.ts +90 -23
- package/src/runtime/routes/content-source-routes.ts +5 -1
- package/src/runtime/routes/conversation-analysis-routes.ts +5 -1
- package/src/runtime/routes/conversation-attention-routes.ts +5 -0
- package/src/runtime/routes/conversation-cli-routes.ts +54 -7
- package/src/runtime/routes/conversation-compaction-routes.ts +54 -25
- package/src/runtime/routes/conversation-list-routes.ts +81 -12
- package/src/runtime/routes/conversation-management-routes.ts +57 -14
- package/src/runtime/routes/conversation-query-routes.ts +90 -41
- package/src/runtime/routes/conversation-routes.ts +446 -204
- package/src/runtime/routes/conversation-starter-routes.ts +35 -20
- package/src/runtime/routes/conversations-import-routes.ts +30 -8
- package/src/runtime/routes/credential-prompt-routes.ts +5 -0
- package/src/runtime/routes/credential-routes.ts +25 -6
- package/src/runtime/routes/debug-bash-routes.ts +5 -0
- package/src/runtime/routes/debug-routes.ts +11 -2
- package/src/runtime/routes/defer-routes.ts +13 -0
- package/src/runtime/routes/diagnostics-routes.ts +37 -46
- package/src/runtime/routes/disk-pressure-routes.ts +17 -31
- package/src/runtime/routes/document-comments-routes.ts +46 -27
- package/src/runtime/routes/documents-routes.ts +25 -10
- package/src/runtime/routes/domain-routes.ts +98 -51
- package/src/runtime/routes/email-routes.ts +33 -0
- package/src/runtime/routes/epoch-millis-range.ts +34 -0
- package/src/runtime/routes/events-routes.ts +107 -8
- package/src/runtime/routes/filing-routes.ts +9 -4
- package/src/runtime/routes/gateway-log-routes.ts +31 -4
- package/src/runtime/routes/global-search-routes.ts +53 -50
- package/src/runtime/routes/group-routes.ts +21 -5
- package/src/runtime/routes/guardian-action-routes.ts +9 -0
- package/src/runtime/routes/guardian-approval-interception.ts +0 -31
- package/src/runtime/routes/heartbeat-routes.ts +57 -21
- package/src/runtime/routes/home-feed-routes.ts +23 -19
- package/src/runtime/routes/home-state-routes.ts +8 -40
- package/src/runtime/routes/host-app-control-routes.ts +6 -1
- package/src/runtime/routes/host-bash-routes.ts +5 -0
- package/src/runtime/routes/host-browser-routes.ts +13 -0
- package/src/runtime/routes/host-cu-routes.ts +6 -1
- package/src/runtime/routes/host-file-routes.ts +26 -6
- package/src/runtime/routes/host-transfer-routes.ts +13 -2
- package/src/runtime/routes/http-adapter.ts +1 -2
- package/src/runtime/routes/identity-intro-cache.ts +28 -40
- package/src/runtime/routes/identity-routes.ts +236 -20
- package/src/runtime/routes/image-generation-routes.ts +45 -2
- package/src/runtime/routes/inbound-message-handler.ts +16 -12
- package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +0 -12
- package/src/runtime/routes/inbound-stages/background-dispatch.ts +15 -19
- package/src/runtime/routes/index.ts +2 -0
- package/src/runtime/routes/inference-profile-session-routes.ts +13 -3
- package/src/runtime/routes/inference-provider-connection-routes.ts +21 -5
- package/src/runtime/routes/inference-send-routes.ts +11 -11
- package/src/runtime/routes/integrations/a2a.ts +32 -7
- package/src/runtime/routes/integrations/slack/__tests__/channel.test.ts +16 -0
- package/src/runtime/routes/integrations/slack/channel.ts +23 -3
- package/src/runtime/routes/integrations/slack/share.ts +36 -8
- package/src/runtime/routes/integrations/telegram.ts +34 -9
- package/src/runtime/routes/integrations/twilio.ts +77 -7
- package/src/runtime/routes/integrations/vercel.ts +3 -3
- package/src/runtime/routes/internal-oauth-routes.ts +5 -0
- package/src/runtime/routes/internal-telemetry-routes.ts +88 -0
- package/src/runtime/routes/internal-twilio-routes.ts +13 -0
- package/src/runtime/routes/llm-call-sites-routes.ts +39 -4
- package/src/runtime/routes/log-export-routes.ts +36 -10
- package/src/runtime/routes/mcp-auth-routes.ts +25 -0
- package/src/runtime/routes/memory-item-routes.ts +21 -10
- package/src/runtime/routes/memory-v2-routes.ts +105 -44
- package/src/runtime/routes/memory-v3-routes.ts +306 -408
- package/src/runtime/routes/migration-rollback-routes.ts +5 -1
- package/src/runtime/routes/migration-routes.ts +29 -0
- package/src/runtime/routes/notification-routes.ts +17 -1
- package/src/runtime/routes/oauth-apps.ts +99 -23
- package/src/runtime/routes/oauth-commands-routes.ts +37 -14
- package/src/runtime/routes/oauth-connect-routes.ts +9 -0
- package/src/runtime/routes/oauth-lifecycle-routes.ts +5 -1
- package/src/runtime/routes/oauth-providers.ts +79 -15
- package/src/runtime/routes/platform-routes.ts +102 -5
- package/src/runtime/routes/playground/__tests__/force-compact.test.ts +9 -6
- package/src/runtime/routes/playground/__tests__/inject-failures.test.ts +37 -16
- package/src/runtime/routes/playground/__tests__/reset-circuit.test.ts +7 -3
- package/src/runtime/routes/playground/__tests__/state.test.ts +10 -3
- package/src/runtime/routes/playground/force-compact.ts +2 -2
- package/src/runtime/routes/playground/helpers.ts +1 -2
- package/src/runtime/routes/playground/inject-failures.ts +13 -8
- package/src/runtime/routes/playground/reset-circuit.ts +14 -9
- package/src/runtime/routes/playground/seed-conversation.ts +1 -1
- package/src/runtime/routes/playground/seeded-conversations.ts +3 -3
- package/src/runtime/routes/playground/state.ts +4 -3
- package/src/runtime/routes/plugins-routes.ts +22 -19
- package/src/runtime/routes/profiler-routes.ts +17 -4
- package/src/runtime/routes/ps-routes.ts +5 -0
- package/src/runtime/routes/publish-routes.ts +13 -3
- package/src/runtime/routes/question-routes.ts +5 -0
- package/src/runtime/routes/recording-routes.ts +25 -12
- package/src/runtime/routes/rename-conversation-routes.ts +10 -0
- package/src/runtime/routes/sanity-routes.ts +9 -2
- package/src/runtime/routes/schedule-routes.ts +288 -88
- package/src/runtime/routes/secret-routes.ts +31 -6
- package/src/runtime/routes/sequence-routes.ts +33 -0
- package/src/runtime/routes/settings-routes.ts +65 -19
- package/src/runtime/routes/skills-routes.ts +166 -73
- package/src/runtime/routes/slack-channel-routes.ts +5 -0
- package/src/runtime/routes/stt-routes.ts +13 -6
- package/src/runtime/routes/subagents-routes.ts +24 -18
- package/src/runtime/routes/suggest-trust-rule-routes.ts +7 -2
- package/src/runtime/routes/surface-action-routes.ts +9 -0
- package/src/runtime/routes/surface-content-routes.ts +10 -2
- package/src/runtime/routes/surface-conversation-resolver.ts +4 -3
- package/src/runtime/routes/task-routes.ts +37 -0
- package/src/runtime/routes/telemetry-routes.ts +9 -0
- package/src/runtime/routes/tool-call-confirmation-enrichment.test.ts +161 -0
- package/src/runtime/routes/tool-call-confirmation-enrichment.ts +107 -0
- package/src/runtime/routes/trace-event-routes.ts +42 -1
- package/src/runtime/routes/trust-rules-routes.ts +31 -2
- package/src/runtime/routes/tts-routes.ts +48 -6
- package/src/runtime/routes/types.ts +83 -16
- package/src/runtime/routes/ui-request-routes.ts +5 -0
- package/src/runtime/routes/upgrade-broadcast-routes.ts +5 -0
- package/src/runtime/routes/usage-routes.ts +118 -42
- package/src/runtime/routes/user-routes-cli.ts +9 -0
- package/src/runtime/routes/user-routes.ts +5 -1
- package/src/runtime/routes/wake-conversation-routes.ts +5 -0
- package/src/runtime/routes/watcher-routes.ts +21 -0
- package/src/runtime/routes/webhook-routes.ts +50 -2
- package/src/runtime/routes/wipe-conversation-routes.ts +5 -0
- package/src/runtime/routes/work-items-routes.ts +49 -23
- package/src/runtime/routes/workspace-commit-routes.ts +5 -0
- package/src/runtime/routes/workspace-routes.test.ts +42 -0
- package/src/runtime/routes/workspace-routes.ts +124 -9
- package/src/runtime/services/__tests__/analyze-conversation.test.ts +8 -4
- package/src/runtime/services/analyze-conversation.ts +5 -8
- package/src/runtime/services/conversation-serializer.ts +24 -2
- package/src/runtime/sync/resource-sync-events.ts +16 -2
- package/src/runtime/sync/sync-publisher.ts +2 -2
- package/src/schedule/run-script.ts +28 -3
- package/src/schedule/schedule-store.ts +28 -1
- package/src/schedule/schedule-usage-store.ts +83 -0
- package/src/schedule/scheduler.ts +15 -6
- package/src/signals/cancel.ts +2 -4
- package/src/signals/user-message.ts +5 -8
- package/src/skills/catalog-files.ts +4 -1
- package/src/skills/catalog-install.ts +3 -0
- package/src/skills/categories-cache.ts +118 -0
- package/src/skills/clawhub-files.ts +1 -0
- package/src/skills/skillssh-files.ts +1 -0
- package/src/subagent/manager.ts +20 -11
- package/src/telemetry/types.ts +55 -1
- package/src/telemetry/usage-telemetry-reporter.test.ts +250 -4
- package/src/telemetry/usage-telemetry-reporter.ts +88 -2
- package/src/tools/acp/context.ts +20 -0
- package/src/tools/acp/list-agents.test.ts +7 -1
- package/src/tools/acp/spawn.test.ts +198 -93
- package/src/tools/acp/spawn.ts +32 -70
- package/src/tools/acp/steer.test.ts +105 -8
- package/src/tools/acp/steer.ts +48 -17
- package/src/tools/apps/definitions.ts +8 -4
- package/src/tools/apps/executors.ts +13 -8
- package/src/tools/ask-question/ask-question-tool.test.ts +120 -105
- package/src/tools/ask-question/ask-question-tool.ts +85 -90
- package/src/tools/computer-use/definitions.ts +28 -24
- package/src/tools/credential-execution/make-authenticated-request.ts +56 -51
- package/src/tools/credential-execution/manage-secure-command-tool.ts +2 -2
- package/src/tools/credential-execution/run-authenticated-command.ts +82 -77
- package/src/tools/credentials/vault.ts +112 -111
- package/src/tools/execution-target.ts +1 -1
- package/src/tools/execution-timeout.ts +3 -4
- package/src/tools/executor.ts +1 -53
- package/src/tools/filesystem/edit.ts +45 -42
- package/src/tools/filesystem/list.ts +33 -30
- package/src/tools/filesystem/read.ts +54 -35
- package/src/tools/filesystem/write.ts +69 -32
- package/src/tools/host-filesystem/edit.ts +44 -42
- package/src/tools/host-filesystem/read.ts +49 -35
- package/src/tools/host-filesystem/transfer.ts +121 -108
- package/src/tools/host-filesystem/write.ts +33 -31
- package/src/tools/host-terminal/host-shell.ts +50 -48
- package/src/tools/memory/register.ts +23 -24
- package/src/tools/network/__tests__/web-search-metadata.test.ts +7 -1
- package/src/tools/network/__tests__/web-search.test.ts +11 -3
- package/src/tools/network/web-fetch.ts +49 -46
- package/src/tools/network/web-search-error.test.ts +248 -0
- package/src/tools/network/web-search-error.ts +267 -0
- package/src/tools/network/web-search.ts +223 -61
- package/src/tools/registry.ts +39 -16
- package/src/tools/schedule/create.ts +13 -0
- package/src/tools/schedule/update.ts +16 -0
- package/src/tools/shared/filesystem/audio-read.ts +122 -0
- package/src/tools/shared/filesystem/image-read.ts +1 -1
- package/src/tools/skills/execute.ts +34 -31
- package/src/tools/skills/load.ts +29 -23
- package/src/tools/subagent/notify-parent.ts +35 -32
- package/src/tools/subagent/spawn.ts +2 -4
- package/src/tools/system/avatar-generator.ts +13 -22
- package/src/tools/system/request-permission.ts +30 -27
- package/src/tools/terminal/safe-env.ts +10 -1
- package/src/tools/terminal/shell.ts +190 -61
- package/src/tools/tool-defaults.ts +20 -9
- package/src/tools/tool-manifest.ts +4 -4
- package/src/tools/types.ts +74 -23
- package/src/tools/ui-surface/definitions.ts +99 -10
- package/src/tts/__tests__/provider-catalog-consistency.test.ts +85 -1
- package/src/tts/provider-catalog.ts +76 -1
- package/src/usage/types.ts +10 -0
- package/src/util/errors.ts +2 -2
- package/src/util/map-limit.ts +27 -0
- package/src/util/mutex.ts +47 -0
- package/src/util/platform.ts +15 -12
- package/src/work-items/work-item-runner.ts +7 -2
- package/src/workspace/git-service.ts +1 -42
- package/src/workspace/migrations/028-recover-conversations-from-disk-view.ts +7 -20
- package/src/workspace/migrations/092-backfill-v3-leaves.ts +169 -0
- package/src/workspace/migrations/093-backfill-leaf-ids.ts +144 -0
- package/src/workspace/migrations/094-seed-avatar-manifest.ts +155 -0
- package/src/workspace/migrations/095-bump-heartbeat-interval-30m-to-60m.ts +51 -0
- package/src/workspace/migrations/096-reduce-quality-profile-effort.ts +72 -0
- package/src/workspace/migrations/097-enable-adaptive-thinking-managed-profiles.ts +117 -0
- package/src/workspace/migrations/__tests__/094-seed-avatar-manifest.test.ts +136 -0
- package/src/workspace/migrations/__tests__/backfill-leaf-ids.test.ts +175 -0
- package/src/workspace/migrations/__tests__/backfill-v3-leaves.test.ts +124 -0
- package/src/workspace/migrations/registry.ts +12 -0
- package/src/workspace/provider-commit-message-generator.ts +15 -17
- package/tsconfig.json +4 -1
- package/src/__tests__/bootstrap-turn-cleanup.test.ts +0 -44
- 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 -262
- package/src/__tests__/empty-response-pipeline.test.ts +0 -301
- package/src/__tests__/history-repair-pipeline.test.ts +0 -396
- package/src/__tests__/llm-call-pipeline.test.ts +0 -281
- package/src/__tests__/memory-retrieval-pipeline.test.ts +0 -418
- package/src/__tests__/persistence-pipeline.test.ts +0 -514
- package/src/__tests__/title-generate-pipeline.test.ts +0 -211
- package/src/__tests__/token-estimate-pipeline.test.ts +0 -481
- package/src/__tests__/tool-error-pipeline.test.ts +0 -241
- package/src/__tests__/tool-execute-pipeline.test.ts +0 -417
- package/src/__tests__/tool-result-truncate-pipeline.test.ts +0 -344
- package/src/cli/commands/__tests__/memory-v3-render.test.ts +0 -340
- package/src/cli/commands/memory-v3-render.ts +0 -491
- package/src/daemon/bootstrap-turn-cleanup.ts +0 -45
- package/src/daemon/message-types/disk-pressure.ts +0 -9
- package/src/email/feature-gate.ts +0 -23
- package/src/gallery/default-gallery.ts +0 -1359
- package/src/gallery/gallery-manifest.ts +0 -28
- package/src/memory/v3/__tests__/coactivation-store.test.ts +0 -422
- package/src/memory/v3/__tests__/consolidation-job.test.ts +0 -466
- package/src/memory/v3/__tests__/coretrieval-seed.test.ts +0 -270
- package/src/memory/v3/__tests__/edge-learning-job.test.ts +0 -324
- package/src/memory/v3/__tests__/edges.test.ts +0 -706
- package/src/memory/v3/__tests__/filter.test.ts +0 -560
- package/src/memory/v3/__tests__/gate.test.ts +0 -637
- package/src/memory/v3/__tests__/index-composition.test.ts +0 -291
- package/src/memory/v3/__tests__/loop.test.ts +0 -775
- package/src/memory/v3/__tests__/retriever.test.ts +0 -226
- package/src/memory/v3/__tests__/scouts.test.ts +0 -489
- package/src/memory/v3/__tests__/shadow-diff.test.ts +0 -225
- package/src/memory/v3/__tests__/shadow-middleware.test.ts +0 -398
- package/src/memory/v3/__tests__/system-prompts.test.ts +0 -154
- package/src/memory/v3/__tests__/traversal.test.ts +0 -508
- package/src/memory/v3/__tests__/tree-index.test.ts +0 -280
- package/src/memory/v3/__tests__/tree-store.test.ts +0 -529
- package/src/memory/v3/__tests__/tree-walk.test.ts +0 -784
- package/src/memory/v3/__tests__/validate.test.ts +0 -277
- package/src/memory/v3/auto-edges.ts +0 -223
- package/src/memory/v3/coactivation-store.ts +0 -124
- package/src/memory/v3/consolidation-job.ts +0 -323
- package/src/memory/v3/coretrieval-seed.ts +0 -240
- package/src/memory/v3/edge-learning-job.ts +0 -160
- package/src/memory/v3/edges.ts +0 -286
- package/src/memory/v3/filter.ts +0 -286
- package/src/memory/v3/gate.ts +0 -349
- package/src/memory/v3/index-composition.ts +0 -126
- package/src/memory/v3/llm-capture.ts +0 -46
- package/src/memory/v3/loop.ts +0 -430
- package/src/memory/v3/maintenance.ts +0 -144
- package/src/memory/v3/prompt-context.ts +0 -33
- package/src/memory/v3/prompts/consolidation.ts +0 -458
- package/src/memory/v3/prompts/system-prompts.ts +0 -196
- package/src/memory/v3/retriever.ts +0 -33
- package/src/memory/v3/scouts.ts +0 -431
- package/src/memory/v3/shadow-diff.ts +0 -287
- package/src/memory/v3/shadow-middleware.ts +0 -347
- package/src/memory/v3/traversal.ts +0 -211
- package/src/memory/v3/tree-index.ts +0 -237
- package/src/memory/v3/tree-store.ts +0 -394
- package/src/memory/v3/tree-walk.ts +0 -356
- package/src/memory/v3/types.ts +0 -65
- package/src/memory/v3/validate.ts +0 -323
- package/src/plugins/defaults/circuit-breaker.ts +0 -141
- package/src/plugins/defaults/compaction.ts +0 -141
- package/src/plugins/defaults/empty-response.ts +0 -124
- package/src/plugins/defaults/history-repair.ts +0 -83
- package/src/plugins/defaults/llm-call.ts +0 -77
- package/src/plugins/defaults/memory-retrieval.ts +0 -219
- package/src/plugins/defaults/overflow-reduce.ts +0 -185
- package/src/plugins/defaults/persistence.ts +0 -146
- package/src/plugins/defaults/title-generate.ts +0 -90
- package/src/plugins/defaults/token-estimate.ts +0 -101
- package/src/plugins/defaults/tool-error.ts +0 -119
- package/src/plugins/defaults/tool-execute.ts +0 -87
- package/src/plugins/defaults/tool-result-truncate.ts +0 -84
- package/src/runtime/routes/__tests__/memory-v3-simulate-params.test.ts +0 -35
- package/src/skills/category-inference.ts +0 -111
|
@@ -7,8 +7,6 @@
|
|
|
7
7
|
* runAgentLoop method here via the AgentLoopConversationContext interface.
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
import { join } from "node:path";
|
|
11
|
-
|
|
12
10
|
import { v4 as uuid } from "uuid";
|
|
13
11
|
|
|
14
12
|
import { optimizeImageForTransport } from "../agent/image-optimize.js";
|
|
@@ -17,7 +15,7 @@ import type {
|
|
|
17
15
|
AgentLoop,
|
|
18
16
|
AgentLoopExitReason,
|
|
19
17
|
CheckpointDecision,
|
|
20
|
-
|
|
18
|
+
MidLoopCompaction,
|
|
21
19
|
} from "../agent/loop.js";
|
|
22
20
|
import { createAssistantMessage } from "../agent/message-types.js";
|
|
23
21
|
import type {
|
|
@@ -26,6 +24,7 @@ import type {
|
|
|
26
24
|
TurnChannelContext,
|
|
27
25
|
TurnInterfaceContext,
|
|
28
26
|
} from "../channels/types.js";
|
|
27
|
+
import { isAssistantFeatureFlagEnabled } from "../config/assistant-feature-flags.js";
|
|
29
28
|
import {
|
|
30
29
|
contextWindowConfigFromEffective,
|
|
31
30
|
type EffectiveContextWindow,
|
|
@@ -45,10 +44,13 @@ import {
|
|
|
45
44
|
} from "../context/post-turn-tool-result-truncation.js";
|
|
46
45
|
import {
|
|
47
46
|
estimatePromptTokens,
|
|
47
|
+
estimatePromptTokensWithTools,
|
|
48
48
|
getCalibrationProviderKey,
|
|
49
49
|
} from "../context/token-estimator.js";
|
|
50
|
-
import type {
|
|
51
|
-
|
|
50
|
+
import type {
|
|
51
|
+
ContextWindowCompactOptions,
|
|
52
|
+
ContextWindowManager,
|
|
53
|
+
} from "../context/window-manager.js";
|
|
52
54
|
import type { ToolProfiler } from "../events/tool-profiling-listener.js";
|
|
53
55
|
import { writeRelationshipState } from "../home/relationship-state-writer.js";
|
|
54
56
|
import {
|
|
@@ -56,9 +58,9 @@ import {
|
|
|
56
58
|
setSentryConversationContext,
|
|
57
59
|
} from "../instrument.js";
|
|
58
60
|
import { commitAppTurnChanges } from "../memory/app-git-service.js";
|
|
59
|
-
import { getApp, listAppFiles, resolveAppDir } from "../memory/app-store.js";
|
|
60
61
|
import { enqueueAutoAnalysisOnCompaction } from "../memory/auto-analysis-enqueue.js";
|
|
61
62
|
import {
|
|
63
|
+
addMessage,
|
|
62
64
|
deleteMessageById,
|
|
63
65
|
getConversation,
|
|
64
66
|
getConversationOriginChannel,
|
|
@@ -67,86 +69,40 @@ import {
|
|
|
67
69
|
getLastUserTimestampBefore,
|
|
68
70
|
getMessageById,
|
|
69
71
|
provenanceFromTrustContext,
|
|
70
|
-
setConversationHistoryStrippedAt,
|
|
71
|
-
setLastNotifiedInferenceProfile,
|
|
72
72
|
updateConversationContextWindow,
|
|
73
73
|
updateConversationSlackContextWatermark,
|
|
74
|
+
updateMessageMetadata,
|
|
74
75
|
} from "../memory/conversation-crud.js";
|
|
75
76
|
import { getResolvedConversationDirPath } from "../memory/conversation-directories.js";
|
|
76
77
|
import { syncMessageToDisk } from "../memory/conversation-disk-view.js";
|
|
77
|
-
import {
|
|
78
|
-
isReplaceableTitle,
|
|
79
|
-
queueRegenerateConversationTitle,
|
|
80
|
-
} from "../memory/conversation-title-service.js";
|
|
78
|
+
import { isReplaceableTitle } from "../memory/conversation-title-service.js";
|
|
81
79
|
import { isBackgroundConversationType } from "../memory/conversation-types.js";
|
|
82
80
|
import type { ConversationGraphMemory } from "../memory/graph/conversation-graph-memory.js";
|
|
83
81
|
import {
|
|
84
82
|
backfillMessageIdOnLogs,
|
|
85
83
|
recordSyntheticAgentErrorMessageLog,
|
|
86
84
|
} from "../memory/llm-request-log-store.js";
|
|
87
|
-
import { recordMemoryRecallLog } from "../memory/memory-recall-log-store.js";
|
|
88
85
|
import { enqueueMemoryRetrospectiveOnCompaction } from "../memory/memory-retrospective-enqueue.js";
|
|
89
|
-
import { PKB_WORKSPACE_SCOPE } from "../memory/pkb/types.js";
|
|
90
|
-
import type { QdrantSparseVector } from "../memory/qdrant-client.js";
|
|
91
|
-
import {
|
|
92
|
-
readMemoryV2StaticContent,
|
|
93
|
-
shouldExposePersonalMemory,
|
|
94
|
-
} from "../memory/v2/static-context.js";
|
|
95
86
|
import type { PermissionPrompter } from "../permissions/prompter.js";
|
|
96
87
|
import { HOOKS } from "../plugin-api/constants.js";
|
|
97
88
|
import type { UserPromptSubmitContext } from "../plugin-api/types.js";
|
|
98
|
-
import {
|
|
99
|
-
import {
|
|
100
|
-
import
|
|
101
|
-
|
|
102
|
-
type
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
} from "../plugins/
|
|
106
|
-
import {
|
|
107
|
-
import { defaultTitleGenerateTerminal } from "../plugins/defaults/title-generate.js";
|
|
108
|
-
import { defaultTokenEstimateTerminal } from "../plugins/defaults/token-estimate.js";
|
|
109
|
-
import { DEFAULT_TIMEOUTS, runHook, runPipeline } from "../plugins/pipeline.js";
|
|
110
|
-
import { getMiddlewaresFor } from "../plugins/registry.js";
|
|
111
|
-
import type {
|
|
112
|
-
CircuitBreakerArgs,
|
|
113
|
-
CircuitBreakerResult,
|
|
114
|
-
CompactionArgs,
|
|
115
|
-
CompactionResult,
|
|
116
|
-
EstimateArgs,
|
|
117
|
-
EstimateResult,
|
|
118
|
-
HistoryRepairArgs,
|
|
119
|
-
HistoryRepairResult,
|
|
120
|
-
MemoryArgs,
|
|
121
|
-
MemoryResult,
|
|
122
|
-
OverflowReduceArgs,
|
|
123
|
-
OverflowReduceResult,
|
|
124
|
-
PersistAddResult,
|
|
125
|
-
PersistArgs,
|
|
126
|
-
PersistResult,
|
|
127
|
-
TurnContext as PluginTurnContext,
|
|
128
|
-
} from "../plugins/types.js";
|
|
129
|
-
import { PluginExecutionError, PluginTimeoutError } from "../plugins/types.js";
|
|
130
|
-
import {
|
|
131
|
-
hasProactiveArtifactCompleted,
|
|
132
|
-
runProactiveArtifactJob,
|
|
133
|
-
tryClaimProactiveArtifactTrigger,
|
|
134
|
-
} from "../proactive-artifact/index.js";
|
|
135
|
-
import type {
|
|
136
|
-
ContentBlock,
|
|
137
|
-
Message,
|
|
138
|
-
ToolDefinition,
|
|
139
|
-
} from "../providers/types.js";
|
|
89
|
+
import { defaultCompact } from "../plugins/defaults/compaction/compact.js";
|
|
90
|
+
import { deepRepairHistory } from "../plugins/defaults/history-repair/terminal.js";
|
|
91
|
+
import postCompactReinject from "../plugins/defaults/memory-retrieval/hooks/post-compact.js";
|
|
92
|
+
import userPromptSubmitMemoryRetrieval, {
|
|
93
|
+
type MemoryRetrievalHookContext,
|
|
94
|
+
} from "../plugins/defaults/memory-retrieval/hooks/user-prompt-submit-temp.js";
|
|
95
|
+
import { runHook } from "../plugins/pipeline.js";
|
|
96
|
+
import type { TurnContext as PluginTurnContext } from "../plugins/types.js";
|
|
97
|
+
import type { ContentBlock, Message } from "../providers/types.js";
|
|
140
98
|
import type { Provider } from "../providers/types.js";
|
|
141
99
|
import { resolveActorTrust } from "../runtime/actor-trust-resolver.js";
|
|
142
100
|
import { broadcastMessage } from "../runtime/assistant-event-hub.js";
|
|
143
101
|
import { DAEMON_INTERNAL_ASSISTANT_ID } from "../runtime/assistant-scope.js";
|
|
144
102
|
import { publishConversationMessagesChanged } from "../runtime/sync/resource-sync-events.js";
|
|
145
|
-
import { redactSecrets } from "../security/secret-scanner.js";
|
|
146
103
|
import { getSubagentManager } from "../subagent/index.js";
|
|
147
104
|
import type { UsageActor } from "../usage/actors.js";
|
|
148
105
|
import { getLogger } from "../util/logger.js";
|
|
149
|
-
import { getWorkspaceDir } from "../util/platform.js";
|
|
150
106
|
import { timeAgo } from "../util/time.js";
|
|
151
107
|
import { truncate } from "../util/truncate.js";
|
|
152
108
|
import { getWorkspaceGitService } from "../workspace/git-service.js";
|
|
@@ -155,7 +111,6 @@ import {
|
|
|
155
111
|
type AssistantAttachmentDraft,
|
|
156
112
|
cleanAssistantContent,
|
|
157
113
|
} from "./assistant-attachments.js";
|
|
158
|
-
import { cleanupBootstrapAfterTurnThreshold } from "./bootstrap-turn-cleanup.js";
|
|
159
114
|
import { resolveOverflowAction } from "./context-overflow-policy.js";
|
|
160
115
|
import {
|
|
161
116
|
createInitialReducerState,
|
|
@@ -166,6 +121,8 @@ import {
|
|
|
166
121
|
createEventHandlerState,
|
|
167
122
|
dispatchAgentEvent,
|
|
168
123
|
type EventHandlerDeps,
|
|
124
|
+
finalizePendingToolResultRow,
|
|
125
|
+
markHistoryStrippedBestEffort,
|
|
169
126
|
} from "./conversation-agent-loop-handlers.js";
|
|
170
127
|
import {
|
|
171
128
|
approveHostAttachmentRead,
|
|
@@ -181,7 +138,6 @@ import { raceWithTimeout } from "./conversation-media-retry.js";
|
|
|
181
138
|
import type { MessageQueue } from "./conversation-queue-manager.js";
|
|
182
139
|
import type { QueueDrainReason } from "./conversation-queue-manager.js";
|
|
183
140
|
import type {
|
|
184
|
-
ActiveSurfaceContext,
|
|
185
141
|
ChannelCapabilities,
|
|
186
142
|
InboundActorContext,
|
|
187
143
|
InjectionMode,
|
|
@@ -190,8 +146,6 @@ import {
|
|
|
190
146
|
applyRuntimeInjections,
|
|
191
147
|
buildSubagentStatusBlock,
|
|
192
148
|
buildUnifiedTurnContextBlock,
|
|
193
|
-
findLastInjectedNowContent,
|
|
194
|
-
getPkbAutoInjectList,
|
|
195
149
|
getSlackCompactionWatermarkForPrefix,
|
|
196
150
|
inboundActorContextFromTrust,
|
|
197
151
|
inboundActorContextFromTrustContext,
|
|
@@ -202,7 +156,6 @@ import {
|
|
|
202
156
|
} from "./conversation-runtime-assembly.js";
|
|
203
157
|
import type { SkillProjectionCache } from "./conversation-skill-tools.js";
|
|
204
158
|
import { markSurfaceCompleted } from "./conversation-surfaces.js";
|
|
205
|
-
import { resolveTrustClass } from "./conversation-tool-setup.js";
|
|
206
159
|
import { recordUsage } from "./conversation-usage.js";
|
|
207
160
|
import {
|
|
208
161
|
formatTurnTimestamp,
|
|
@@ -210,47 +163,28 @@ import {
|
|
|
210
163
|
} from "./date-context.js";
|
|
211
164
|
import { getDiskPressureStatus } from "./disk-pressure-guard.js";
|
|
212
165
|
import { classifyDiskPressureTurnPolicy } from "./disk-pressure-policy.js";
|
|
213
|
-
import { deepRepairHistory } from "./history-repair.js";
|
|
214
166
|
import type {
|
|
215
|
-
DynamicPageSurfaceData,
|
|
216
167
|
ServerMessage,
|
|
217
168
|
SurfaceData,
|
|
218
169
|
SurfaceType,
|
|
219
170
|
UsageStats,
|
|
220
171
|
} from "./message-protocol.js";
|
|
221
|
-
import type { MemoryRecalled } from "./message-types/memory.js";
|
|
222
172
|
import type { ConfirmationStateChanged } from "./message-types/messages.js";
|
|
223
|
-
import {
|
|
173
|
+
import {
|
|
174
|
+
type OverflowReduceArgs,
|
|
175
|
+
runOverflowReductionLoop,
|
|
176
|
+
} from "./overflow-reduction-loop.js";
|
|
224
177
|
import { parseActualTokensFromError } from "./parse-actual-tokens-from-error.js";
|
|
178
|
+
import {
|
|
179
|
+
persistUnsendableImageDowngrades,
|
|
180
|
+
UNSENDABLE_IMAGE_NOTE,
|
|
181
|
+
} from "./persist-unsendable-image.js";
|
|
225
182
|
import type { TraceEmitter } from "./trace-emitter.js";
|
|
226
|
-
import type
|
|
183
|
+
import { resolveTrustClass, type TrustContext } from "./trust-context.js";
|
|
227
184
|
import { stripHistoricalWebSearchResults } from "./web-search-history.js";
|
|
228
185
|
|
|
229
186
|
const log = getLogger("conversation-agent-loop");
|
|
230
187
|
|
|
231
|
-
/**
|
|
232
|
-
* Best-effort persistence of the history-stripped marker after an
|
|
233
|
-
* injection-strip event (compaction / overflow recovery). The marker is a
|
|
234
|
-
* durability hint, not turn-critical state — a transient SQLite write failure
|
|
235
|
-
* (SQLITE_BUSY, disk-full, read-only FS) must not abort the turn. Logs a
|
|
236
|
-
* warning and continues on failure, preserving the long-standing non-fatal
|
|
237
|
-
* contract for this metadata write.
|
|
238
|
-
*/
|
|
239
|
-
function markHistoryStrippedBestEffort(
|
|
240
|
-
conversationId: string,
|
|
241
|
-
strippedAt: number,
|
|
242
|
-
logger: ReturnType<typeof getLogger>,
|
|
243
|
-
): void {
|
|
244
|
-
try {
|
|
245
|
-
setConversationHistoryStrippedAt(conversationId, strippedAt);
|
|
246
|
-
} catch (err) {
|
|
247
|
-
logger.warn(
|
|
248
|
-
{ err },
|
|
249
|
-
"Failed to persist history-stripped marker after compaction strip (non-fatal)",
|
|
250
|
-
);
|
|
251
|
-
}
|
|
252
|
-
}
|
|
253
|
-
|
|
254
188
|
const DISK_PRESSURE_ERROR_CODE = "DISK_SPACE_CRITICAL" as const;
|
|
255
189
|
const DISK_PRESSURE_ERROR_CATEGORY = "disk_pressure";
|
|
256
190
|
|
|
@@ -276,163 +210,15 @@ function formatDiskPressureBlockedMessage(): string {
|
|
|
276
210
|
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.";
|
|
277
211
|
}
|
|
278
212
|
|
|
279
|
-
// ── Compaction circuit-breaker pipeline helpers ─────────────────────
|
|
280
|
-
//
|
|
281
|
-
// The circuit-breaker behavior (3 consecutive summary-LLM failures trips a
|
|
282
|
-
// 1-hour cooldown) is now implemented by the `circuitBreaker` plugin
|
|
283
|
-
// pipeline. The default plugin (`plugins/defaults/circuit-breaker.ts`)
|
|
284
|
-
// replicates the legacy threshold/cooldown constants and event-emission
|
|
285
|
-
// semantics exactly — it operates on the `consecutiveCompactionFailures` /
|
|
286
|
-
// `compactionCircuitOpenUntil` fields the conversation still owns so the
|
|
287
|
-
// dev-only playground routes (`POST /playground/reset-compaction-circuit`,
|
|
288
|
-
// `POST /playground/inject-compaction-failures`) continue to read and
|
|
289
|
-
// mutate those fields directly.
|
|
290
|
-
//
|
|
291
|
-
// The helpers below build the pipeline inputs and invoke the runner. They
|
|
292
|
-
// are the sole entry points the rest of the daemon uses to query or update
|
|
293
|
-
// the compaction circuit.
|
|
294
|
-
|
|
295
|
-
/** Circuit-breaker key for a specific conversation's compaction pipeline. */
|
|
296
|
-
function compactionCircuitKey(conversationId: string): string {
|
|
297
|
-
return `compaction:${conversationId}`;
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
/**
|
|
301
|
-
* Build the minimal {@link TurnContext} the pipeline runner requires. Called
|
|
302
|
-
* both from inside the agent loop (where turn identifiers are available) and
|
|
303
|
-
* from non-turn invocations like `Conversation.forceCompact` (which falls
|
|
304
|
-
* back to stable placeholders so the runner's log records still carry the
|
|
305
|
-
* conversation identifier).
|
|
306
|
-
*/
|
|
307
|
-
function buildCircuitTurnContext(ctx: {
|
|
308
|
-
readonly conversationId: string;
|
|
309
|
-
currentRequestId?: string;
|
|
310
|
-
currentTurnTrustContext?: TrustContext;
|
|
311
|
-
trustContext?: TrustContext;
|
|
312
|
-
turnCount: number;
|
|
313
|
-
}): PluginTurnContext {
|
|
314
|
-
const trust: TrustContext =
|
|
315
|
-
ctx.currentTurnTrustContext ?? ctx.trustContext ?? FALLBACK_TURN_TRUST;
|
|
316
|
-
return {
|
|
317
|
-
requestId: ctx.currentRequestId ?? "circuit-breaker",
|
|
318
|
-
conversationId: ctx.conversationId,
|
|
319
|
-
turnIndex: ctx.turnCount,
|
|
320
|
-
trust,
|
|
321
|
-
};
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
/**
|
|
325
|
-
* Run the `circuitBreaker` pipeline for the compaction circuit on this
|
|
326
|
-
* conversation. When `outcome` is provided, state is updated (and transition
|
|
327
|
-
* events emit via `onEvent`); when omitted the call is query-only.
|
|
328
|
-
*
|
|
329
|
-
* Returns the post-call decision from the pipeline. Callers gate auto-paths
|
|
330
|
-
* on `!result.open` and admit forced paths regardless of the decision.
|
|
331
|
-
*/
|
|
332
|
-
async function runCompactionCircuitPipeline(
|
|
333
|
-
ctx: {
|
|
334
|
-
readonly conversationId: string;
|
|
335
|
-
consecutiveCompactionFailures: number;
|
|
336
|
-
compactionCircuitOpenUntil: number | null;
|
|
337
|
-
currentRequestId?: string;
|
|
338
|
-
currentTurnTrustContext?: TrustContext;
|
|
339
|
-
trustContext?: TrustContext;
|
|
340
|
-
turnCount: number;
|
|
341
|
-
},
|
|
342
|
-
args: {
|
|
343
|
-
outcome?: "success" | "failure";
|
|
344
|
-
onEvent?: (msg: ServerMessage) => void;
|
|
345
|
-
},
|
|
346
|
-
): Promise<CircuitBreakerResult> {
|
|
347
|
-
const turnContext = buildCircuitTurnContext(ctx);
|
|
348
|
-
return runPipeline<CircuitBreakerArgs, CircuitBreakerResult>(
|
|
349
|
-
"circuitBreaker",
|
|
350
|
-
getMiddlewaresFor("circuitBreaker"),
|
|
351
|
-
async (terminalArgs) => {
|
|
352
|
-
// No plugin in the chain produced a decision. This should be
|
|
353
|
-
// unreachable in production because the default plugin registers a
|
|
354
|
-
// `circuitBreaker` middleware that always returns a decision, but we
|
|
355
|
-
// defensively derive the state here so test setups that intentionally
|
|
356
|
-
// omit the default plugin still get a sensible response.
|
|
357
|
-
const openUntil = terminalArgs.state.compactionCircuitOpenUntil;
|
|
358
|
-
const now = Date.now();
|
|
359
|
-
if (openUntil !== null && now < openUntil) {
|
|
360
|
-
return { open: true, cooldownRemainingMs: openUntil - now };
|
|
361
|
-
}
|
|
362
|
-
return { open: false };
|
|
363
|
-
},
|
|
364
|
-
{
|
|
365
|
-
key: compactionCircuitKey(ctx.conversationId),
|
|
366
|
-
// Pass the ctx directly as the mutable state container. The
|
|
367
|
-
// `CircuitBreakerArgs.state` shape deliberately matches the subset of
|
|
368
|
-
// fields the conversation owns so plugins mutate the same object the
|
|
369
|
-
// playground routes read and write.
|
|
370
|
-
state: ctx,
|
|
371
|
-
...(args.outcome !== undefined ? { outcome: args.outcome } : {}),
|
|
372
|
-
...(args.onEvent ? { onEvent: args.onEvent } : {}),
|
|
373
|
-
},
|
|
374
|
-
turnContext,
|
|
375
|
-
DEFAULT_TIMEOUTS.circuitBreaker,
|
|
376
|
-
);
|
|
377
|
-
}
|
|
378
|
-
|
|
379
|
-
/**
|
|
380
|
-
* Query-only: is the compaction circuit breaker currently open for this
|
|
381
|
-
* conversation? Thin wrapper around {@link runCompactionCircuitPipeline}
|
|
382
|
-
* with no outcome. Async because the pipeline runner is async, but the
|
|
383
|
-
* default plugin resolves synchronously on its microtask.
|
|
384
|
-
*/
|
|
385
|
-
async function isCompactionCircuitOpen(ctx: {
|
|
386
|
-
readonly conversationId: string;
|
|
387
|
-
consecutiveCompactionFailures: number;
|
|
388
|
-
compactionCircuitOpenUntil: number | null;
|
|
389
|
-
currentRequestId?: string;
|
|
390
|
-
currentTurnTrustContext?: TrustContext;
|
|
391
|
-
trustContext?: TrustContext;
|
|
392
|
-
turnCount: number;
|
|
393
|
-
}): Promise<boolean> {
|
|
394
|
-
const decision = await runCompactionCircuitPipeline(ctx, {});
|
|
395
|
-
return decision.open;
|
|
396
|
-
}
|
|
397
|
-
|
|
398
|
-
/**
|
|
399
|
-
* Update the compaction circuit breaker with the outcome of a `maybeCompact`
|
|
400
|
-
* call and emit any transition event. A `summaryFailed` value of `undefined`
|
|
401
|
-
* means the summary LLM never ran (early return) — callers must guard with
|
|
402
|
-
* `summaryFailed !== undefined` before invoking this helper so early-return
|
|
403
|
-
* paths don't silently reset the 3-strike counter.
|
|
404
|
-
*
|
|
405
|
-
* The default plugin handles threshold-based tripping and cooldown reset;
|
|
406
|
-
* see `plugins/defaults/circuit-breaker.ts` for the canonical semantics.
|
|
407
|
-
*/
|
|
408
|
-
export async function trackCompactionOutcome(
|
|
409
|
-
ctx: {
|
|
410
|
-
readonly conversationId: string;
|
|
411
|
-
consecutiveCompactionFailures: number;
|
|
412
|
-
compactionCircuitOpenUntil: number | null;
|
|
413
|
-
currentRequestId?: string;
|
|
414
|
-
currentTurnTrustContext?: TrustContext;
|
|
415
|
-
trustContext?: TrustContext;
|
|
416
|
-
turnCount: number;
|
|
417
|
-
},
|
|
418
|
-
summaryFailed: boolean,
|
|
419
|
-
onEvent: (msg: ServerMessage) => void,
|
|
420
|
-
): Promise<void> {
|
|
421
|
-
await runCompactionCircuitPipeline(ctx, {
|
|
422
|
-
outcome: summaryFailed ? "failure" : "success",
|
|
423
|
-
onEvent,
|
|
424
|
-
});
|
|
425
|
-
}
|
|
426
|
-
|
|
427
213
|
// ── Plugin pipeline helpers ──────────────────────────────────────────
|
|
428
214
|
//
|
|
429
215
|
// Canonical {@link PluginTurnContext} builder threaded into every
|
|
430
|
-
// `
|
|
216
|
+
// `runHook` call inside `runAgentLoopImpl`. The orchestrator composes
|
|
431
217
|
// the context on demand at each call site from ambient state rather than
|
|
432
218
|
// carrying a persistent `TurnContext` instance across the turn.
|
|
433
219
|
|
|
434
220
|
/**
|
|
435
|
-
* Synthetic fallback trust context used when the orchestrator fires a
|
|
221
|
+
* Synthetic fallback trust context used when the orchestrator fires a hook
|
|
436
222
|
* before the per-turn trust snapshot has been captured (e.g. invocations that
|
|
437
223
|
* bypass `processMessage` / `drainQueue`). We bias to `unknown` rather than
|
|
438
224
|
* `guardian` so a missing snapshot cannot accidentally grant elevated trust
|
|
@@ -444,14 +230,14 @@ const FALLBACK_TURN_TRUST: TrustContext = {
|
|
|
444
230
|
};
|
|
445
231
|
|
|
446
232
|
/**
|
|
447
|
-
* Build the {@link TurnContext} passed to {@link
|
|
233
|
+
* Build the {@link TurnContext} passed to {@link runHook}.
|
|
448
234
|
*
|
|
449
|
-
* Canonical source of truth for every
|
|
450
|
-
* loop. Every `
|
|
235
|
+
* Canonical source of truth for every hook call site inside the agent
|
|
236
|
+
* loop. Every `runHook` invocation in `runAgentLoopImpl` (and in the
|
|
451
237
|
* handlers that share its ambient state) must route through this helper
|
|
452
238
|
* rather than constructing a `TurnContext` literal inline — this keeps
|
|
453
239
|
* `turnIndex`, trust resolution, and the `contextWindowManager` attachment
|
|
454
|
-
* consistent across
|
|
240
|
+
* consistent across hooks, which in turn keeps structured logs
|
|
455
241
|
* filtered by `conversationId`/`turnIndex` coherent across slots.
|
|
456
242
|
*
|
|
457
243
|
* Behavior:
|
|
@@ -463,9 +249,9 @@ const FALLBACK_TURN_TRUST: TrustContext = {
|
|
|
463
249
|
* level context, then {@link FALLBACK_TURN_TRUST}. The cascade matches
|
|
464
250
|
* the one inside the orchestrator's inline injection assembly so
|
|
465
251
|
* middleware reads the same trust class the runtime sees.
|
|
466
|
-
* - `contextWindowManager` is attached unconditionally.
|
|
467
|
-
* don't need it can ignore it;
|
|
468
|
-
*
|
|
252
|
+
* - `contextWindowManager` is attached unconditionally. Hooks that
|
|
253
|
+
* don't need it can ignore it; it remains available via the typed
|
|
254
|
+
* optional field on `TurnContext`.
|
|
469
255
|
*/
|
|
470
256
|
function buildPluginTurnContext(
|
|
471
257
|
ctx: AgentLoopConversationContext,
|
|
@@ -479,17 +265,62 @@ function buildPluginTurnContext(
|
|
|
479
265
|
turnIndex: ctx.turnCount,
|
|
480
266
|
trust,
|
|
481
267
|
contextWindowManager: ctx.contextWindowManager,
|
|
268
|
+
callSite: ctx.currentCallSite,
|
|
482
269
|
};
|
|
483
270
|
}
|
|
484
271
|
|
|
272
|
+
/**
|
|
273
|
+
* Trust class of the actor whose turn is in progress, for the compactor's
|
|
274
|
+
* image manifest filter. Prefers the turn-start snapshot
|
|
275
|
+
* ({@link AgentLoopConversationContext.currentTurnTrustContext}) over the live
|
|
276
|
+
* trust context so compaction running in a later tool iteration can't pick up
|
|
277
|
+
* a concurrent request's actor.
|
|
278
|
+
*/
|
|
279
|
+
function resolveTurnActorTrustClass(
|
|
280
|
+
ctx: AgentLoopConversationContext,
|
|
281
|
+
): TrustContext["trustClass"] | undefined {
|
|
282
|
+
return (ctx.currentTurnTrustContext ?? ctx.trustContext)?.trustClass;
|
|
283
|
+
}
|
|
284
|
+
|
|
485
285
|
// ── Context Interface ────────────────────────────────────────────────
|
|
486
286
|
|
|
287
|
+
/**
|
|
288
|
+
* Per-surface entry tracked on the current turn. Inline shape kept stable so
|
|
289
|
+
* routes and persistence helpers can consume it via a named import instead of
|
|
290
|
+
* `infer`-extracting from {@link AgentLoopConversationContext}.
|
|
291
|
+
*/
|
|
292
|
+
export interface AssistantSurface {
|
|
293
|
+
surfaceId: string;
|
|
294
|
+
surfaceType: SurfaceType;
|
|
295
|
+
title?: string;
|
|
296
|
+
data: SurfaceData;
|
|
297
|
+
actions?: Array<{
|
|
298
|
+
id: string;
|
|
299
|
+
label: string;
|
|
300
|
+
style?: string;
|
|
301
|
+
data?: Record<string, unknown>;
|
|
302
|
+
}>;
|
|
303
|
+
display?: string;
|
|
304
|
+
persistent?: boolean;
|
|
305
|
+
/** Id of the tool call that produced this surface (the `ui_show` proxy tool). Persisted so app previews can gate on the tool result's arrival rather than whole-turn streaming state. */
|
|
306
|
+
toolCallId?: string;
|
|
307
|
+
}
|
|
308
|
+
|
|
487
309
|
export interface AgentLoopConversationContext {
|
|
488
310
|
readonly conversationId: string;
|
|
489
311
|
messages: Message[];
|
|
490
|
-
|
|
312
|
+
isProcessing(): boolean;
|
|
313
|
+
setProcessing(value: boolean): void;
|
|
491
314
|
abortController: AbortController | null;
|
|
492
315
|
currentRequestId?: string;
|
|
316
|
+
/**
|
|
317
|
+
* The {@link LLMCallSite} of the in-flight turn, set at turn start from
|
|
318
|
+
* `options?.callSite ?? "mainAgent"`. Read by {@link buildPluginTurnContext}
|
|
319
|
+
* so pipeline/injector plugins can tell the main reply apart from
|
|
320
|
+
* background agent-loop work (compaction, subagents, …) on this same
|
|
321
|
+
* conversation. Per-turn mutable, mirroring {@link currentRequestId}.
|
|
322
|
+
*/
|
|
323
|
+
currentCallSite?: LLMCallSite;
|
|
493
324
|
|
|
494
325
|
readonly agentLoop: AgentLoop;
|
|
495
326
|
readonly provider: Provider;
|
|
@@ -507,10 +338,6 @@ export interface AgentLoopConversationContext {
|
|
|
507
338
|
* happened just before this turn).
|
|
508
339
|
*/
|
|
509
340
|
pendingPostCompactReinject: boolean;
|
|
510
|
-
/** Tracks consecutive compaction failures (summary LLM call threw). */
|
|
511
|
-
consecutiveCompactionFailures: number;
|
|
512
|
-
/** Timestamp (ms since epoch) until which the circuit breaker is open. */
|
|
513
|
-
compactionCircuitOpenUntil: number | null;
|
|
514
341
|
|
|
515
342
|
readonly graphMemory: ConversationGraphMemory;
|
|
516
343
|
|
|
@@ -533,24 +360,9 @@ export interface AgentLoopConversationContext {
|
|
|
533
360
|
pendingSurfaceActions: Map<string, { surfaceType: SurfaceType }>;
|
|
534
361
|
surfaceActionRequestIds: Set<string>;
|
|
535
362
|
approvedViaPromptThisTurn?: boolean;
|
|
536
|
-
currentTurnSurfaces:
|
|
537
|
-
surfaceId: string;
|
|
538
|
-
surfaceType: SurfaceType;
|
|
539
|
-
title?: string;
|
|
540
|
-
data: SurfaceData;
|
|
541
|
-
actions?: Array<{
|
|
542
|
-
id: string;
|
|
543
|
-
label: string;
|
|
544
|
-
style?: string;
|
|
545
|
-
data?: Record<string, unknown>;
|
|
546
|
-
}>;
|
|
547
|
-
display?: string;
|
|
548
|
-
persistent?: boolean;
|
|
549
|
-
}>;
|
|
363
|
+
currentTurnSurfaces: AssistantSurface[];
|
|
550
364
|
|
|
551
365
|
workingDir: string;
|
|
552
|
-
workspaceTopLevelContext: string | null;
|
|
553
|
-
workspaceTopLevelDirty: boolean;
|
|
554
366
|
channelCapabilities?: ChannelCapabilities;
|
|
555
367
|
/** Per-turn snapshot of trustContext, frozen at message-processing start. */
|
|
556
368
|
currentTurnTrustContext?: TrustContext;
|
|
@@ -624,9 +436,11 @@ export interface AgentLoopConversationContext {
|
|
|
624
436
|
| "message_complete"
|
|
625
437
|
| "generation_cancelled"
|
|
626
438
|
| "error_terminal",
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
439
|
+
options?: {
|
|
440
|
+
anchor?: "assistant_turn" | "user_turn" | "global";
|
|
441
|
+
requestId?: string;
|
|
442
|
+
statusText?: string;
|
|
443
|
+
},
|
|
630
444
|
): void;
|
|
631
445
|
emitConfirmationStateChanged(
|
|
632
446
|
params: ConfirmationStateChanged extends {
|
|
@@ -644,14 +458,12 @@ export interface AgentLoopConversationContext {
|
|
|
644
458
|
onConfirmationOutcome?: (
|
|
645
459
|
requestId: string,
|
|
646
460
|
state: string,
|
|
647
|
-
toolName?: string,
|
|
648
461
|
toolUseId?: string,
|
|
649
462
|
) => void;
|
|
650
463
|
|
|
651
464
|
getWorkspaceGitService?: (workspaceDir: string) => GitServiceInitializer;
|
|
652
465
|
commitTurnChanges?: typeof commitTurnChanges;
|
|
653
466
|
|
|
654
|
-
refreshWorkspaceTopLevelContextIfNeeded(): void;
|
|
655
467
|
markWorkspaceTopLevelDirty(): void;
|
|
656
468
|
getQueueDepth(): number;
|
|
657
469
|
hasQueuedMessages(): boolean;
|
|
@@ -712,6 +524,13 @@ export async function runAgentLoopImpl(
|
|
|
712
524
|
});
|
|
713
525
|
let yieldedForHandoff = false;
|
|
714
526
|
let yieldedForBudget = false;
|
|
527
|
+
// Whether the most recent agent-loop run produced at least one new assistant
|
|
528
|
+
// message — the loop's own forward-progress signal, used by the ordering
|
|
529
|
+
// retry gate and the overflow convergence fold.
|
|
530
|
+
let lastRunAppendedNewMessages = false;
|
|
531
|
+
// The messages the most recent agent-loop run appended on top of its base —
|
|
532
|
+
// the loop's own new-output boundary, persisted as this turn's new messages.
|
|
533
|
+
let lastRunNewMessages: Message[] = [];
|
|
715
534
|
let pendingCheckpointYield: "budget" | "handoff" | null = null;
|
|
716
535
|
// Captured when the auto_compress_latest_turn rerun yields at the mid-loop
|
|
717
536
|
// budget checkpoint. SSE emission happens immediately at the detection site;
|
|
@@ -730,6 +549,9 @@ export async function runAgentLoopImpl(
|
|
|
730
549
|
// `resolveCallSiteConfig`, picking up any user overrides under
|
|
731
550
|
// `llm.callSites.mainAgent` (falling back to `llm.default` when absent).
|
|
732
551
|
const turnCallSite: LLMCallSite = options?.callSite ?? "mainAgent";
|
|
552
|
+
// Expose the turn's call site to plugin pipeline/injector contexts (read by
|
|
553
|
+
// buildPluginTurnContext) so plugins can scope behaviour to the main reply.
|
|
554
|
+
ctx.currentCallSite = turnCallSite;
|
|
733
555
|
|
|
734
556
|
// Read the conversation row once for both the override-profile derivation
|
|
735
557
|
// below and the title-replaceability check at turn start. Later reads in
|
|
@@ -859,6 +681,25 @@ export async function runAgentLoopImpl(
|
|
|
859
681
|
preflightBudget: Math.floor(providerMaxTokens * (1 - safetyMargin)),
|
|
860
682
|
};
|
|
861
683
|
};
|
|
684
|
+
/**
|
|
685
|
+
* The agent loop's window into the orchestrator's current effective
|
|
686
|
+
* context window. The loop reads `maxInputTokens` for tool-result
|
|
687
|
+
* truncation and `overflowRecovery` for its mid-loop budget gate, applying
|
|
688
|
+
* the long-history safety-margin bump itself off its own running history.
|
|
689
|
+
* Resolved fresh on each access so a mid-turn profile change is reflected.
|
|
690
|
+
*/
|
|
691
|
+
const resolveContextWindow = (): {
|
|
692
|
+
maxInputTokens: number;
|
|
693
|
+
overflowRecovery: { enabled: boolean; safetyMarginRatio: number };
|
|
694
|
+
} => {
|
|
695
|
+
refreshCurrentProfileState();
|
|
696
|
+
const { enabled, safetyMarginRatio } =
|
|
697
|
+
currentEffectiveContextWindow.overflowRecovery;
|
|
698
|
+
return {
|
|
699
|
+
maxInputTokens: currentEffectiveContextWindow.maxInputTokens,
|
|
700
|
+
overflowRecovery: { enabled, safetyMarginRatio },
|
|
701
|
+
};
|
|
702
|
+
};
|
|
862
703
|
|
|
863
704
|
// Initial value for `createToolExecutor` to read into
|
|
864
705
|
// `ToolContext.overrideProfile`. `resolveCurrentOverrideProfile` refreshes
|
|
@@ -924,10 +765,6 @@ export async function runAgentLoopImpl(
|
|
|
924
765
|
: null,
|
|
925
766
|
},
|
|
926
767
|
);
|
|
927
|
-
const diskPressureContext =
|
|
928
|
-
diskPressureDecision.action === "allow-cleanup-mode"
|
|
929
|
-
? { cleanupModeActive: true }
|
|
930
|
-
: null;
|
|
931
768
|
ctx.diskPressureCleanupModeActive =
|
|
932
769
|
diskPressureDecision.action === "allow-cleanup-mode";
|
|
933
770
|
|
|
@@ -968,7 +805,10 @@ export async function runAgentLoopImpl(
|
|
|
968
805
|
{ reason: diskPressureDecision.reason },
|
|
969
806
|
"Blocked turn during disk pressure cleanup mode",
|
|
970
807
|
);
|
|
971
|
-
ctx.emitActivityState("idle", "error_terminal",
|
|
808
|
+
ctx.emitActivityState("idle", "error_terminal", {
|
|
809
|
+
anchor: "global",
|
|
810
|
+
requestId: reqId,
|
|
811
|
+
});
|
|
972
812
|
ctx.traceEmitter.emit("request_error", message, {
|
|
973
813
|
requestId: reqId,
|
|
974
814
|
status: "error",
|
|
@@ -1027,55 +867,6 @@ export async function runAgentLoopImpl(
|
|
|
1027
867
|
}
|
|
1028
868
|
}
|
|
1029
869
|
|
|
1030
|
-
// Generate title early — the user message alone is sufficient context.
|
|
1031
|
-
// Firing before the main LLM call removes the delay of waiting for the
|
|
1032
|
-
// full assistant response. The second-pass regeneration at turn 3 will
|
|
1033
|
-
// refine the title with more context.
|
|
1034
|
-
// No abort signal — title generation should complete even if the user
|
|
1035
|
-
// cancels the response, since the user message is already persisted.
|
|
1036
|
-
// Deferred via setTimeout so the main agent loop LLM call enqueues
|
|
1037
|
-
// first, avoiding rate-limit slot contention on strict configs.
|
|
1038
|
-
if (isReplaceableTitle(turnStartConversation?.title ?? null)) {
|
|
1039
|
-
// TurnContext routed through the canonical builder so the pipeline's
|
|
1040
|
-
// log record reports the same `conversationId`/`turnIndex` shape as
|
|
1041
|
-
// every other slot in this turn. Title generation does not depend on
|
|
1042
|
-
// the context-window manager attached by the builder, but sharing the
|
|
1043
|
-
// builder keeps the invariant enforced in one place.
|
|
1044
|
-
const titlePipelineCtx = buildPluginTurnContext(ctx, reqId);
|
|
1045
|
-
const titleArgs = {
|
|
1046
|
-
conversationId: ctx.conversationId,
|
|
1047
|
-
provider: ctx.provider,
|
|
1048
|
-
userMessage: options?.titleText ?? content,
|
|
1049
|
-
onTitleUpdated: (title: string) => {
|
|
1050
|
-
onEvent({
|
|
1051
|
-
type: "conversation_title_updated",
|
|
1052
|
-
conversationId: ctx.conversationId,
|
|
1053
|
-
title,
|
|
1054
|
-
});
|
|
1055
|
-
onEvent({
|
|
1056
|
-
type: "sync_changed",
|
|
1057
|
-
tags: [conversationMetadataSyncTag(ctx.conversationId)],
|
|
1058
|
-
});
|
|
1059
|
-
},
|
|
1060
|
-
};
|
|
1061
|
-
setTimeout(() => {
|
|
1062
|
-
runPipeline(
|
|
1063
|
-
"titleGenerate",
|
|
1064
|
-
getMiddlewaresFor("titleGenerate"),
|
|
1065
|
-
defaultTitleGenerateTerminal,
|
|
1066
|
-
titleArgs,
|
|
1067
|
-
titlePipelineCtx,
|
|
1068
|
-
DEFAULT_TIMEOUTS.titleGenerate,
|
|
1069
|
-
).catch((err) => {
|
|
1070
|
-
// Fire-and-forget — keep previous non-propagating semantics.
|
|
1071
|
-
// queueGenerateConversationTitle already swallows internal
|
|
1072
|
-
// errors; this catch covers pipeline-layer errors (timeouts,
|
|
1073
|
-
// middleware throws) without surfacing them to the agent loop.
|
|
1074
|
-
rlog.warn({ err }, "titleGenerate pipeline failed (non-fatal)");
|
|
1075
|
-
});
|
|
1076
|
-
}, 0);
|
|
1077
|
-
}
|
|
1078
|
-
|
|
1079
870
|
const isFirstMessage = ctx.messages.length === 1;
|
|
1080
871
|
// Promote a pending post-compaction re-inject signal (e.g. from `/compact`)
|
|
1081
872
|
// into `compactedThisTurn` so NOW.md / PKB / v2 static blocks land on this
|
|
@@ -1083,7 +874,6 @@ export async function runAgentLoopImpl(
|
|
|
1083
874
|
// so this fires exactly once per `/compact` event.
|
|
1084
875
|
const consumedPostCompactReinject = ctx.pendingPostCompactReinject;
|
|
1085
876
|
ctx.pendingPostCompactReinject = false;
|
|
1086
|
-
let shouldInjectWorkspace = isFirstMessage || consumedPostCompactReinject;
|
|
1087
877
|
let compactedThisTurn = consumedPostCompactReinject;
|
|
1088
878
|
let slackCompactedThisTurn = false;
|
|
1089
879
|
const isSlackConversation = ctx.channelCapabilities?.channel === "slack";
|
|
@@ -1220,74 +1010,42 @@ export async function runAgentLoopImpl(
|
|
|
1220
1010
|
);
|
|
1221
1011
|
// Skip auto-compaction while the circuit breaker is open. Force paths
|
|
1222
1012
|
// and user-initiated /compact bypass this check.
|
|
1223
|
-
const autoCompactAllowed =
|
|
1013
|
+
const autoCompactAllowed =
|
|
1014
|
+
!(await ctx.agentLoop.compactionCircuit.isOpen());
|
|
1224
1015
|
if (compactCheck.needed && autoCompactAllowed) {
|
|
1225
|
-
ctx.emitActivityState(
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
"assistant_turn",
|
|
1229
|
-
reqId,
|
|
1230
|
-
);
|
|
1016
|
+
ctx.emitActivityState("thinking", "context_compacting", {
|
|
1017
|
+
requestId: reqId,
|
|
1018
|
+
});
|
|
1231
1019
|
}
|
|
1232
|
-
const compactionOptions = {
|
|
1233
|
-
lastCompactedAt: ctx.contextCompactedAt ?? undefined,
|
|
1234
|
-
precomputedEstimate: compactCheck.estimatedTokens,
|
|
1235
|
-
conversationOriginChannel:
|
|
1236
|
-
getConversationOriginChannel(ctx.conversationId) ?? undefined,
|
|
1237
|
-
overrideProfile: resolveCurrentOverrideProfile() ?? null,
|
|
1238
|
-
};
|
|
1239
1020
|
let compacted: Awaited<
|
|
1240
1021
|
ReturnType<typeof ctx.contextWindowManager.maybeCompact>
|
|
1241
1022
|
> | null = null;
|
|
1242
1023
|
if (autoCompactAllowed) {
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
signal: abortController.signal,
|
|
1252
|
-
options: compactionOptions,
|
|
1253
|
-
},
|
|
1254
|
-
buildPluginTurnContext(ctx, reqId),
|
|
1255
|
-
DEFAULT_TIMEOUTS.compaction,
|
|
1256
|
-
)) as Awaited<ReturnType<typeof ctx.contextWindowManager.maybeCompact>>;
|
|
1257
|
-
} catch (err) {
|
|
1258
|
-
if (err instanceof PluginTimeoutError) {
|
|
1259
|
-
// Pipeline exceeded its budget. Record the failure so the circuit
|
|
1260
|
-
// breaker tracks consecutive timeouts (it trips after three),
|
|
1261
|
-
// then degrade gracefully by skipping compaction this turn —
|
|
1262
|
-
// the turn proceeds with the un-compacted history rather than
|
|
1263
|
-
// hard-failing. The inner summary call has been aborted by the
|
|
1264
|
-
// runner's signal-linking, so updateSummary's local fallback
|
|
1265
|
-
// also ran before this catch block is reached.
|
|
1266
|
-
rlog.warn(
|
|
1267
|
-
{ err, phase: "start-of-turn-compaction" },
|
|
1268
|
-
"Compaction pipeline timed out — skipping compaction this turn",
|
|
1269
|
-
);
|
|
1270
|
-
await trackCompactionOutcome(ctx, true, onEvent);
|
|
1271
|
-
compacted = null;
|
|
1272
|
-
} else {
|
|
1273
|
-
throw err;
|
|
1274
|
-
}
|
|
1275
|
-
}
|
|
1024
|
+
compacted = await defaultCompact({
|
|
1025
|
+
manager: ctx.contextWindowManager,
|
|
1026
|
+
messages: messagesForStartOfTurnCompaction,
|
|
1027
|
+
signal: abortController.signal,
|
|
1028
|
+
precomputedEstimate: compactCheck.estimatedTokens,
|
|
1029
|
+
overrideProfile: resolveCurrentOverrideProfile() ?? null,
|
|
1030
|
+
actorTrustClass: resolveTurnActorTrustClass(ctx),
|
|
1031
|
+
});
|
|
1276
1032
|
}
|
|
1277
1033
|
// Only track circuit-breaker state when a summary LLM call actually ran.
|
|
1278
1034
|
// `summaryFailed` is `undefined` on early returns (compaction disabled,
|
|
1279
|
-
// below threshold,
|
|
1035
|
+
// below threshold, no eligible messages, truncation-only
|
|
1280
1036
|
// path) — treating those as "successful" compactions would silently reset
|
|
1281
1037
|
// the 3-strike counter and break the invariant.
|
|
1282
1038
|
if (compacted && compacted.summaryFailed !== undefined) {
|
|
1283
|
-
await
|
|
1039
|
+
await ctx.agentLoop.compactionCircuit.recordOutcome(
|
|
1040
|
+
compacted.summaryFailed,
|
|
1041
|
+
onEvent,
|
|
1042
|
+
);
|
|
1284
1043
|
}
|
|
1285
1044
|
if (compacted?.compacted) {
|
|
1286
1045
|
await applySuccessfulCompaction(
|
|
1287
1046
|
compacted,
|
|
1288
1047
|
messagesForStartOfTurnCompaction,
|
|
1289
1048
|
);
|
|
1290
|
-
shouldInjectWorkspace = true;
|
|
1291
1049
|
if (compacted.compactedPersistedMessages > 0) {
|
|
1292
1050
|
compactedThisTurn = true;
|
|
1293
1051
|
}
|
|
@@ -1295,12 +1053,7 @@ export async function runAgentLoopImpl(
|
|
|
1295
1053
|
|
|
1296
1054
|
// Register confirmation outcome tracker so the agent loop can link
|
|
1297
1055
|
// confirmation decisions to tool_use_ids for persistence.
|
|
1298
|
-
ctx.onConfirmationOutcome = (
|
|
1299
|
-
requestId,
|
|
1300
|
-
confirmationState,
|
|
1301
|
-
toolName,
|
|
1302
|
-
toolUseId,
|
|
1303
|
-
) => {
|
|
1056
|
+
ctx.onConfirmationOutcome = (requestId, confirmationState, toolUseId) => {
|
|
1304
1057
|
if (confirmationState === "pending") {
|
|
1305
1058
|
// Use the toolUseId passed from the prompter (which knows which tool
|
|
1306
1059
|
// requested confirmation) instead of the ambient state.currentToolUseId,
|
|
@@ -1317,7 +1070,7 @@ export async function runAgentLoopImpl(
|
|
|
1317
1070
|
const resolvedId =
|
|
1318
1071
|
state.requestIdToToolUseId.get(requestId) ?? toolUseId;
|
|
1319
1072
|
if (resolvedId) {
|
|
1320
|
-
const name = state.toolUseIdToName.get(resolvedId) ??
|
|
1073
|
+
const name = state.toolUseIdToName.get(resolvedId) ?? "";
|
|
1321
1074
|
// Build a friendly label from the tool name
|
|
1322
1075
|
const label =
|
|
1323
1076
|
TOOL_FRIENDLY_LABEL[name] ??
|
|
@@ -1330,213 +1083,10 @@ export async function runAgentLoopImpl(
|
|
|
1330
1083
|
}
|
|
1331
1084
|
};
|
|
1332
1085
|
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
//
|
|
1336
|
-
//
|
|
1337
|
-
// replace the terminal behavior by registering a middleware that
|
|
1338
|
-
// short-circuits with its own `MemoryResult`; the default terminal
|
|
1339
|
-
// below runs `runDefaultMemoryRetrieval` which reproduces the prior
|
|
1340
|
-
// in-lined behavior (PKB/NOW reads + gated graph call).
|
|
1341
|
-
const isTrustedActor = resolveTrustClass(ctx.trustContext) === "guardian";
|
|
1342
|
-
// Canonical builder — pulls trust from per-turn snapshot, then
|
|
1343
|
-
// conversation-level, then the synthetic fallback. Memory retrieval
|
|
1344
|
-
// does not need the context-window handle the builder attaches, but
|
|
1345
|
-
// keeping every call site on one helper is load-bearing for log
|
|
1346
|
-
// coherence across pipeline slots.
|
|
1347
|
-
const memoryPluginTurnCtx = buildPluginTurnContext(ctx, reqId);
|
|
1348
|
-
const memoryArgs: MemoryArgs = {
|
|
1349
|
-
conversationId: ctx.conversationId,
|
|
1350
|
-
trustContext: ctx.trustContext,
|
|
1351
|
-
turnIndex: ctx.turnCount,
|
|
1352
|
-
// Pass the abort signal via `args` (not `deps`) so the pipeline
|
|
1353
|
-
// runner's `linkAbortSignal` can swap it for a signal linked to the
|
|
1354
|
-
// pipeline's internal controller — on a plugin-set timeout or
|
|
1355
|
-
// external cancel, the linked signal aborts and `prepareMemory`
|
|
1356
|
-
// stops mutating graph state / emitting events after the pipeline
|
|
1357
|
-
// has already errored.
|
|
1358
|
-
signal: abortController.signal,
|
|
1359
|
-
};
|
|
1360
|
-
const memoryDeps: DefaultMemoryRetrievalDeps = {
|
|
1361
|
-
messages: ctx.messages,
|
|
1362
|
-
graphMemory: ctx.graphMemory,
|
|
1363
|
-
config: getConfig(),
|
|
1364
|
-
onEvent,
|
|
1365
|
-
isTrustedActor,
|
|
1366
|
-
};
|
|
1367
|
-
const memoryResult: MemoryResult = await runPipeline(
|
|
1368
|
-
"memoryRetrieval",
|
|
1369
|
-
getMiddlewaresFor("memoryRetrieval"),
|
|
1370
|
-
(args) => runDefaultMemoryRetrieval(args, memoryDeps),
|
|
1371
|
-
memoryArgs,
|
|
1372
|
-
memoryPluginTurnCtx,
|
|
1373
|
-
DEFAULT_TIMEOUTS.memoryRetrieval,
|
|
1374
|
-
);
|
|
1375
|
-
|
|
1376
|
-
// Consume the memory-graph block when the default retriever emitted
|
|
1377
|
-
// one. Custom plugins that substitute their own blocks without the
|
|
1378
|
-
// default discriminator are expected to handle their own side effects
|
|
1379
|
-
// (event emission, metric persistence) inside their middleware; this
|
|
1380
|
-
// block short-circuits to the original no-op behavior in that case.
|
|
1381
|
-
const defaultGraphPayload: GraphMemoryPayload | null =
|
|
1382
|
-
asDefaultGraphPayload(memoryResult.memoryGraphBlocks);
|
|
1383
|
-
let pkbQueryVector: number[] | undefined;
|
|
1384
|
-
let pkbSparseVector: QdrantSparseVector | undefined;
|
|
1385
|
-
if (defaultGraphPayload) {
|
|
1386
|
-
const graphResult = defaultGraphPayload.result;
|
|
1387
|
-
runMessages = graphResult.runMessages;
|
|
1388
|
-
// Select dense+sparse as a matched pair so RRF fusion combines two
|
|
1389
|
-
// signals aligned to the same query text:
|
|
1390
|
-
// 1. Context-load with a user query: user-query dense + user-query
|
|
1391
|
-
// sparse — the cleanest pairing.
|
|
1392
|
-
// 2. Otherwise (context-load without a user query, or per-turn):
|
|
1393
|
-
// whatever `queryVector` / `sparseVector` the retriever produced,
|
|
1394
|
-
// which are themselves co-aligned (both summary-derived in
|
|
1395
|
-
// context-load, both user-last-message-derived in per-turn).
|
|
1396
|
-
// Never pair a user-query dense with a summary-aligned sparse.
|
|
1397
|
-
if (graphResult.userQueryVector) {
|
|
1398
|
-
pkbQueryVector = graphResult.userQueryVector;
|
|
1399
|
-
pkbSparseVector = graphResult.userQuerySparseVector;
|
|
1400
|
-
} else {
|
|
1401
|
-
pkbQueryVector = graphResult.queryVector;
|
|
1402
|
-
pkbSparseVector = graphResult.sparseVector;
|
|
1403
|
-
}
|
|
1404
|
-
|
|
1405
|
-
// Persist the injected block text in message metadata so it survives
|
|
1406
|
-
// conversation reloads (eviction, restart, fork). loadFromDb re-injects
|
|
1407
|
-
// from metadata. Routed through the `persistence` pipeline so plugins
|
|
1408
|
-
// can observe or override metadata updates alongside add/delete.
|
|
1409
|
-
if (graphResult.injectedBlockText) {
|
|
1410
|
-
try {
|
|
1411
|
-
await runPipeline<PersistArgs, PersistResult>(
|
|
1412
|
-
"persistence",
|
|
1413
|
-
getMiddlewaresFor("persistence"),
|
|
1414
|
-
defaultPersistenceTerminal,
|
|
1415
|
-
{
|
|
1416
|
-
op: "update",
|
|
1417
|
-
messageId: userMessageId,
|
|
1418
|
-
updates: {
|
|
1419
|
-
memoryInjectedBlock: graphResult.injectedBlockText,
|
|
1420
|
-
},
|
|
1421
|
-
},
|
|
1422
|
-
buildPluginTurnContext(ctx, reqId),
|
|
1423
|
-
DEFAULT_TIMEOUTS.persistence,
|
|
1424
|
-
);
|
|
1425
|
-
} catch (err) {
|
|
1426
|
-
rlog.warn(
|
|
1427
|
-
{ err },
|
|
1428
|
-
"Failed to persist memory injection to metadata (non-fatal)",
|
|
1429
|
-
);
|
|
1430
|
-
}
|
|
1431
|
-
}
|
|
1432
|
-
|
|
1433
|
-
const m = graphResult.metrics;
|
|
1434
|
-
|
|
1435
|
-
try {
|
|
1436
|
-
recordMemoryRecallLog({
|
|
1437
|
-
conversationId: ctx.conversationId,
|
|
1438
|
-
enabled: true,
|
|
1439
|
-
degraded: false,
|
|
1440
|
-
provider: m?.embeddingProvider ?? undefined,
|
|
1441
|
-
model: m?.embeddingModel ?? undefined,
|
|
1442
|
-
semanticHits: m?.semanticHits ?? 0,
|
|
1443
|
-
mergedCount: m?.mergedCount ?? 0,
|
|
1444
|
-
selectedCount: m?.selectedCount ?? 0,
|
|
1445
|
-
tier1Count: m?.tier1Count ?? 0,
|
|
1446
|
-
tier2Count: m?.tier2Count ?? 0,
|
|
1447
|
-
hybridSearchLatencyMs: m?.hybridSearchLatencyMs ?? 0,
|
|
1448
|
-
sparseVectorUsed: m?.sparseVectorUsed ?? false,
|
|
1449
|
-
injectedTokens: graphResult.injectedTokens,
|
|
1450
|
-
latencyMs: graphResult.latencyMs,
|
|
1451
|
-
topCandidatesJson: (m?.topCandidates ?? []).map((c) => ({
|
|
1452
|
-
key: c.nodeId,
|
|
1453
|
-
type: c.type,
|
|
1454
|
-
kind: "graph",
|
|
1455
|
-
finalScore: c.score,
|
|
1456
|
-
semantic: c.semanticSimilarity,
|
|
1457
|
-
recency: c.recencyBoost,
|
|
1458
|
-
})),
|
|
1459
|
-
injectedText: graphResult.injectedBlockText ?? undefined,
|
|
1460
|
-
reason: `graph:${graphResult.mode}`,
|
|
1461
|
-
queryContext: m?.queryContext ?? undefined,
|
|
1462
|
-
});
|
|
1463
|
-
} catch (err) {
|
|
1464
|
-
log.warn({ err }, "Failed to persist memory recall log (non-fatal)");
|
|
1465
|
-
}
|
|
1466
|
-
|
|
1467
|
-
if (m) {
|
|
1468
|
-
const memoryRecalledEvent: MemoryRecalled = {
|
|
1469
|
-
type: "memory_recalled",
|
|
1470
|
-
provider: m.embeddingProvider ?? "unknown",
|
|
1471
|
-
model: m.embeddingModel ?? "unknown",
|
|
1472
|
-
semanticHits: m.semanticHits,
|
|
1473
|
-
mergedCount: m.mergedCount,
|
|
1474
|
-
selectedCount: m.selectedCount,
|
|
1475
|
-
tier1Count: m.tier1Count,
|
|
1476
|
-
tier2Count: m.tier2Count,
|
|
1477
|
-
hybridSearchLatencyMs: m.hybridSearchLatencyMs,
|
|
1478
|
-
sparseVectorUsed: m.sparseVectorUsed,
|
|
1479
|
-
injectedTokens: graphResult.injectedTokens,
|
|
1480
|
-
latencyMs: graphResult.latencyMs,
|
|
1481
|
-
topCandidates: m.topCandidates.map((c) => ({
|
|
1482
|
-
key: c.nodeId,
|
|
1483
|
-
type: c.type,
|
|
1484
|
-
kind: "graph",
|
|
1485
|
-
finalScore: c.score,
|
|
1486
|
-
semantic: c.semanticSimilarity,
|
|
1487
|
-
recency: c.recencyBoost,
|
|
1488
|
-
})),
|
|
1489
|
-
};
|
|
1490
|
-
onEvent(memoryRecalledEvent);
|
|
1491
|
-
}
|
|
1492
|
-
}
|
|
1493
|
-
|
|
1494
|
-
// Build active surface context
|
|
1495
|
-
let activeSurface: ActiveSurfaceContext | null = null;
|
|
1496
|
-
if (ctx.currentActiveSurfaceId) {
|
|
1497
|
-
const stored = ctx.surfaceState.get(ctx.currentActiveSurfaceId);
|
|
1498
|
-
if (stored && stored.surfaceType === "dynamic_page") {
|
|
1499
|
-
const data = stored.data as DynamicPageSurfaceData;
|
|
1500
|
-
activeSurface = {
|
|
1501
|
-
surfaceId: ctx.currentActiveSurfaceId,
|
|
1502
|
-
html: data.html,
|
|
1503
|
-
currentPage: ctx.currentPage,
|
|
1504
|
-
};
|
|
1505
|
-
if (data.appId) {
|
|
1506
|
-
const app = getApp(data.appId);
|
|
1507
|
-
if (app) {
|
|
1508
|
-
activeSurface.appId = app.id;
|
|
1509
|
-
activeSurface.appName = app.name;
|
|
1510
|
-
activeSurface.appDirName = resolveAppDir(app.id).dirName;
|
|
1511
|
-
activeSurface.appSchemaJson = app.schemaJson;
|
|
1512
|
-
activeSurface.appFiles = listAppFiles(app.id);
|
|
1513
|
-
if (app.pages && Object.keys(app.pages).length > 0) {
|
|
1514
|
-
activeSurface.appPages = app.pages;
|
|
1515
|
-
}
|
|
1516
|
-
}
|
|
1517
|
-
}
|
|
1518
|
-
}
|
|
1519
|
-
}
|
|
1520
|
-
|
|
1521
|
-
// Query active documents for this conversation so the injector chain
|
|
1522
|
-
// can surface them to the assistant (prevents duplicate document_create
|
|
1523
|
-
// calls when existing documents should be targeted with document_update).
|
|
1524
|
-
const conversationDocs = getDocumentsForConversation(ctx.conversationId);
|
|
1525
|
-
const activeDocuments =
|
|
1526
|
-
conversationDocs.length > 0
|
|
1527
|
-
? conversationDocs.map((d) => ({
|
|
1528
|
-
surfaceId: d.surfaceId,
|
|
1529
|
-
title: d.title,
|
|
1530
|
-
wordCount: d.wordCount,
|
|
1531
|
-
updatedAt: d.updatedAt,
|
|
1532
|
-
}))
|
|
1533
|
-
: null;
|
|
1534
|
-
|
|
1535
|
-
ctx.refreshWorkspaceTopLevelContextIfNeeded();
|
|
1536
|
-
|
|
1537
|
-
// Compute fresh turn timestamp for date grounding.
|
|
1538
|
-
// Absolute "now" is always anchored to assistant host clock, while local
|
|
1539
|
-
// date semantics prefer configured user timezone, then device timezones.
|
|
1086
|
+
// Resolve the turn's timezone cascade up front. It depends only on config
|
|
1087
|
+
// and the inbound request — never on retrieval output — so it can be
|
|
1088
|
+
// settled before context assembly. Local date semantics prefer the
|
|
1089
|
+
// configured user timezone, then device timezones, then the host clock.
|
|
1540
1090
|
const hostTimeZone = Intl.DateTimeFormat().resolvedOptions().timeZone;
|
|
1541
1091
|
const timezoneContext = resolveTurnTimezoneContext({
|
|
1542
1092
|
configuredUserTimeZone: config.ui.userTimezone ?? null,
|
|
@@ -1544,9 +1094,6 @@ export async function runAgentLoopImpl(
|
|
|
1544
1094
|
detectedTimezone: config.ui.detectedTimezone ?? null,
|
|
1545
1095
|
hostTimeZone,
|
|
1546
1096
|
});
|
|
1547
|
-
const timestamp = formatTurnTimestamp({
|
|
1548
|
-
timeZone: timezoneContext.effectiveTimezone,
|
|
1549
|
-
});
|
|
1550
1097
|
|
|
1551
1098
|
// Resolve the inbound actor context for the unified <turn_context> block.
|
|
1552
1099
|
// When the conversation carries enough identity info, use the unified
|
|
@@ -1570,8 +1117,10 @@ export async function runAgentLoopImpl(
|
|
|
1570
1117
|
}
|
|
1571
1118
|
}
|
|
1572
1119
|
|
|
1573
|
-
//
|
|
1574
|
-
//
|
|
1120
|
+
// Resolve the channel/interface labels and the guardian flag for this
|
|
1121
|
+
// turn. These derive only from the captured turn context and the resolved
|
|
1122
|
+
// actor trust class — never from retrieval — so they settle before context
|
|
1123
|
+
// assembly.
|
|
1575
1124
|
const interfaceName =
|
|
1576
1125
|
capturedTurnInterfaceContext.userMessageInterface ?? undefined;
|
|
1577
1126
|
const channelName =
|
|
@@ -1616,9 +1165,54 @@ export async function runAgentLoopImpl(
|
|
|
1616
1165
|
});
|
|
1617
1166
|
const label = profileEntry?.label ?? effectiveProfileKey;
|
|
1618
1167
|
modelProfileStr = resolved.model ? `${label} (${resolved.model})` : label;
|
|
1619
|
-
|
|
1168
|
+
// Record the notification for persistence on delivery rather than here:
|
|
1169
|
+
// the model only "learns" the profile once it receives this turn
|
|
1170
|
+
// context, signalled by the first `message_complete`. Persisting inline
|
|
1171
|
+
// would mark the profile notified even if the turn is cancelled or fails
|
|
1172
|
+
// before the model ever sees the notice.
|
|
1173
|
+
state.pendingNotifiedInferenceProfile = effectiveProfileKey;
|
|
1620
1174
|
}
|
|
1621
1175
|
|
|
1176
|
+
// Memory retrieval — fetches PKB, NOW.md, and memory-graph outputs and
|
|
1177
|
+
// persists the retrieval's own side effects (injected-block metadata,
|
|
1178
|
+
// recall log, `memory_recalled` event). Runs at the early "prompt
|
|
1179
|
+
// submitted, before context assembly" moment because its outputs feed the
|
|
1180
|
+
// injection and overflow-reduction transforms below. It is shaped as the
|
|
1181
|
+
// `user-prompt-submit-temp` hook handler but invoked directly for now: it
|
|
1182
|
+
// must run early, while the canonical late `user-prompt-submit` hook
|
|
1183
|
+
// (history repair, title) runs after those transforms, so the two cannot
|
|
1184
|
+
// share a fire site until compaction is cleared from the gap between them.
|
|
1185
|
+
const isTrustedActor = resolveTrustClass(ctx.trustContext) === "guardian";
|
|
1186
|
+
const memoryCtx: MemoryRetrievalHookContext = {
|
|
1187
|
+
graphMemory: ctx.graphMemory,
|
|
1188
|
+
config: getConfig(),
|
|
1189
|
+
onEvent,
|
|
1190
|
+
isTrustedActor,
|
|
1191
|
+
conversationId: ctx.conversationId,
|
|
1192
|
+
userMessageId,
|
|
1193
|
+
logger: rlog,
|
|
1194
|
+
// An external cancel aborts `prepareMemory` instead of letting it run
|
|
1195
|
+
// to completion after the turn has already been torn down.
|
|
1196
|
+
signal: abortController.signal,
|
|
1197
|
+
latestMessages: ctx.messages,
|
|
1198
|
+
};
|
|
1199
|
+
await userPromptSubmitMemoryRetrieval(memoryCtx);
|
|
1200
|
+
|
|
1201
|
+
// The retriever owns its side effects (injected-block metadata, recall
|
|
1202
|
+
// log, `memory_recalled` event) and records the dense/sparse PKB query
|
|
1203
|
+
// pair on the graph handle for the PKB-reminder injector to read back; the
|
|
1204
|
+
// loop only reuses the injected message list downstream.
|
|
1205
|
+
let runMessages = memoryCtx.latestMessages;
|
|
1206
|
+
|
|
1207
|
+
// Capture wall-clock "now" at its point of use, after the blocking memory
|
|
1208
|
+
// retrieval, so the injected `<turn_context>` timestamp reflects current
|
|
1209
|
+
// time rather than the moment the turn began.
|
|
1210
|
+
const timestamp = formatTurnTimestamp({
|
|
1211
|
+
timeZone: timezoneContext.effectiveTimezone,
|
|
1212
|
+
});
|
|
1213
|
+
|
|
1214
|
+
// Build unified turn context block that replaces the separate temporal,
|
|
1215
|
+
// channel, interface, and actor context blocks.
|
|
1622
1216
|
const baseTurnContext = {
|
|
1623
1217
|
timestamp,
|
|
1624
1218
|
interfaceName,
|
|
@@ -1640,64 +1234,6 @@ export async function runAgentLoopImpl(
|
|
|
1640
1234
|
|
|
1641
1235
|
// The `remember` tool handles scratchpad-style memory writes directly to the graph.
|
|
1642
1236
|
|
|
1643
|
-
// Personal-memory trust gate: PKB, NOW.md, and v2 static blocks all
|
|
1644
|
-
// hold private user content. Block exposure to non-guardian actors
|
|
1645
|
-
// arriving over a remote channel; internal/local flows pass through.
|
|
1646
|
-
// See `shouldExposePersonalMemory` for the threat model.
|
|
1647
|
-
const personalMemoryAllowed = shouldExposePersonalMemory({
|
|
1648
|
-
sourceChannel: ctx.trustContext?.sourceChannel,
|
|
1649
|
-
isTrustedActor,
|
|
1650
|
-
});
|
|
1651
|
-
|
|
1652
|
-
// Inject NOW.md and PKB content only on the first turn (or after
|
|
1653
|
-
// compaction re-strips them). Old injections persist in history and
|
|
1654
|
-
// are never stripped on normal turns — this preserves the cached prefix.
|
|
1655
|
-
// PKB/NOW content is sourced from the `memoryRetrieval` pipeline above
|
|
1656
|
-
// so plugins can override either source without touching the agent loop.
|
|
1657
|
-
// NOW.md injection can be disabled via `memory.retrieval.scratchpadInjection.enabled`.
|
|
1658
|
-
const scratchpadInjectionEnabled =
|
|
1659
|
-
getConfig().memory.retrieval.scratchpadInjection.enabled;
|
|
1660
|
-
const currentNowContent =
|
|
1661
|
-
personalMemoryAllowed && scratchpadInjectionEnabled
|
|
1662
|
-
? memoryResult.nowContent
|
|
1663
|
-
: null;
|
|
1664
|
-
const shouldInjectNowAndPkb = isFirstMessage || compactedThisTurn;
|
|
1665
|
-
const nowScratchpad = shouldInjectNowAndPkb ? currentNowContent : null;
|
|
1666
|
-
|
|
1667
|
-
const currentPkbContent = personalMemoryAllowed
|
|
1668
|
-
? memoryResult.pkbContent
|
|
1669
|
-
: null;
|
|
1670
|
-
const pkbContext = shouldInjectNowAndPkb ? currentPkbContent : null;
|
|
1671
|
-
const pkbActive = currentPkbContent !== null;
|
|
1672
|
-
|
|
1673
|
-
// V2 static memory block (essentials/threads/recent/buffer).
|
|
1674
|
-
// `currentMemoryV2Static` is the trust-gated content reused by every
|
|
1675
|
-
// re-injection path — it stays non-null on non-full-mode turns so
|
|
1676
|
-
// that mid-turn reducer compaction (which strips the prior `<info>`
|
|
1677
|
-
// block) can restore the freshest content. `memoryV2Static` is the
|
|
1678
|
-
// first-turn / post-compaction cadence-gated value for initial
|
|
1679
|
-
// injection only. `readMemoryV2StaticContent` self-gates on the v2
|
|
1680
|
-
// flag + config and returns null when v2 is off.
|
|
1681
|
-
const currentMemoryV2Static = personalMemoryAllowed
|
|
1682
|
-
? readMemoryV2StaticContent()
|
|
1683
|
-
: null;
|
|
1684
|
-
const memoryV2Static = shouldInjectNowAndPkb ? currentMemoryV2Static : null;
|
|
1685
|
-
|
|
1686
|
-
// PKB relevance-hint inputs. Resolved once per turn and reused across
|
|
1687
|
-
// re-injections so post-compaction rebuilds pick up fresh hints against
|
|
1688
|
-
// the updated conversation history.
|
|
1689
|
-
const pkbRoot = pkbActive ? join(getWorkspaceDir(), "pkb") : undefined;
|
|
1690
|
-
const pkbAutoInjectList = pkbRoot
|
|
1691
|
-
? getPkbAutoInjectList(pkbRoot)
|
|
1692
|
-
: undefined;
|
|
1693
|
-
// Pass `ctx` directly — `PkbContextConversation` is structural and
|
|
1694
|
-
// `getInContextPkbPaths` re-reads `conversation.messages` on each call,
|
|
1695
|
-
// so post-compaction re-injects see the updated history.
|
|
1696
|
-
const pkbConversation = pkbActive ? ctx : undefined;
|
|
1697
|
-
// PKB points live under a single workspace sentinel scope.
|
|
1698
|
-
// See `PKB_WORKSPACE_SCOPE` for why.
|
|
1699
|
-
const pkbScopeId = pkbActive ? PKB_WORKSPACE_SCOPE : undefined;
|
|
1700
|
-
|
|
1701
1237
|
// Subagent status injection — gives the parent LLM visibility into active/completed children.
|
|
1702
1238
|
// Skipped when this conversation IS a subagent (no nesting) or has no children.
|
|
1703
1239
|
const subagentStatusBlock = ctx.isSubagent
|
|
@@ -1752,36 +1288,23 @@ export async function runAgentLoopImpl(
|
|
|
1752
1288
|
)
|
|
1753
1289
|
: null;
|
|
1754
1290
|
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
//
|
|
1758
|
-
//
|
|
1759
|
-
//
|
|
1760
|
-
//
|
|
1761
|
-
|
|
1291
|
+
state.reducerCompacted = compactedThisTurn;
|
|
1292
|
+
|
|
1293
|
+
// memory-v3-live: when on, the provider anchors its long-TTL cache
|
|
1294
|
+
// breakpoint on the most recent STABLE user message, since the latest user
|
|
1295
|
+
// message now carries the volatile per-turn `<memory>` block the v3
|
|
1296
|
+
// injector emits. The matching v2-suppression strip is owned by
|
|
1297
|
+
// `applyRuntimeInjections`, which reads the same flag itself. Flag off →
|
|
1298
|
+
// bit-for-bit identical to today's v2 path.
|
|
1299
|
+
const memoryV3Live = isAssistantFeatureFlagEnabled(
|
|
1300
|
+
"memory-v3-live",
|
|
1301
|
+
getConfig(),
|
|
1302
|
+
);
|
|
1762
1303
|
|
|
1763
1304
|
// Shared injection options — reused whenever we need to re-inject after reduction.
|
|
1764
1305
|
const injectionOpts = {
|
|
1765
|
-
diskPressureContext,
|
|
1766
|
-
activeSurface,
|
|
1767
|
-
activeDocuments,
|
|
1768
|
-
workspaceTopLevelContext: shouldInjectWorkspace
|
|
1769
|
-
? ctx.workspaceTopLevelContext
|
|
1770
|
-
: null,
|
|
1771
|
-
channelCapabilities: ctx.channelCapabilities ?? null,
|
|
1772
1306
|
channelCommandContext: ctx.commandIntent ?? null,
|
|
1773
1307
|
unifiedTurnContext: unifiedTurnContextStr,
|
|
1774
|
-
pkbContext,
|
|
1775
|
-
pkbActive,
|
|
1776
|
-
pkbQueryVector,
|
|
1777
|
-
pkbSparseVector,
|
|
1778
|
-
pkbScopeId,
|
|
1779
|
-
pkbConversation,
|
|
1780
|
-
pkbAutoInjectList,
|
|
1781
|
-
pkbRoot,
|
|
1782
|
-
pkbWorkingDir: pkbActive ? ctx.workingDir : undefined,
|
|
1783
|
-
memoryV2Static,
|
|
1784
|
-
nowScratchpad,
|
|
1785
1308
|
voiceCallControlPrompt: ctx.voiceCallControlPrompt ?? null,
|
|
1786
1309
|
transportHints: ctx.transportHints ?? null,
|
|
1787
1310
|
isNonInteractive: !isInteractiveResolved,
|
|
@@ -1803,7 +1326,7 @@ export async function runAgentLoopImpl(
|
|
|
1803
1326
|
|
|
1804
1327
|
const injection = await applyRuntimeInjections(runMessages, {
|
|
1805
1328
|
...injectionOpts,
|
|
1806
|
-
slackChronologicalMessages: reducerCompacted
|
|
1329
|
+
slackChronologicalMessages: state.reducerCompacted
|
|
1807
1330
|
? null
|
|
1808
1331
|
: injectionOpts.slackChronologicalMessages,
|
|
1809
1332
|
mode: currentInjectionMode,
|
|
@@ -1849,18 +1372,7 @@ export async function runAgentLoopImpl(
|
|
|
1849
1372
|
metadataUpdates.memoryV2StaticBlock =
|
|
1850
1373
|
injection.blocks.memoryV2StaticBlock;
|
|
1851
1374
|
}
|
|
1852
|
-
|
|
1853
|
-
"persistence",
|
|
1854
|
-
getMiddlewaresFor("persistence"),
|
|
1855
|
-
defaultPersistenceTerminal,
|
|
1856
|
-
{
|
|
1857
|
-
op: "update",
|
|
1858
|
-
messageId: userMessageId,
|
|
1859
|
-
updates: metadataUpdates,
|
|
1860
|
-
},
|
|
1861
|
-
buildPluginTurnContext(ctx, reqId),
|
|
1862
|
-
DEFAULT_TIMEOUTS.persistence,
|
|
1863
|
-
);
|
|
1375
|
+
updateMessageMetadata(userMessageId, metadataUpdates);
|
|
1864
1376
|
} catch (err) {
|
|
1865
1377
|
rlog.warn({ err }, "Failed to persist injection metadata (non-fatal)");
|
|
1866
1378
|
}
|
|
@@ -1876,51 +1388,18 @@ export async function runAgentLoopImpl(
|
|
|
1876
1388
|
let reducerState: ReducerState | undefined;
|
|
1877
1389
|
|
|
1878
1390
|
const toolTokenBudget = ctx.agentLoop.getToolTokenBudget(runMessages);
|
|
1879
|
-
// Canonical calibration key —
|
|
1880
|
-
//
|
|
1881
|
-
//
|
|
1882
|
-
//
|
|
1883
|
-
// Anthropic → key is `"anthropic"`).
|
|
1391
|
+
// Canonical calibration key — used by the preflight estimate, the
|
|
1392
|
+
// overflow reducer config, and the convergence-path `estimatePromptTokens`
|
|
1393
|
+
// call. Matches the key recorded by `handleUsage` for wrapper providers
|
|
1394
|
+
// (OpenRouter routing to Anthropic → key is `"anthropic"`).
|
|
1884
1395
|
const estimationProviderName = getCalibrationProviderKey(ctx.provider);
|
|
1885
1396
|
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
// trust cascades through per-turn/conversation-level/fallback, and the
|
|
1893
|
-
// context-window handle rides along so any middleware that wants to
|
|
1894
|
-
// reuse the manager (e.g. to compute compaction-aware estimates) can.
|
|
1895
|
-
const pipelineTurnCtx = buildPluginTurnContext(ctx, reqId);
|
|
1896
|
-
|
|
1897
|
-
const runTokenEstimatePipeline = (
|
|
1898
|
-
history: Message[],
|
|
1899
|
-
): Promise<EstimateResult> =>
|
|
1900
|
-
runPipeline<EstimateArgs, EstimateResult>(
|
|
1901
|
-
"tokenEstimate",
|
|
1902
|
-
getMiddlewaresFor("tokenEstimate"),
|
|
1903
|
-
defaultTokenEstimateTerminal,
|
|
1904
|
-
{
|
|
1905
|
-
// Shallow-frozen copies so a misbehaving middleware that mutates
|
|
1906
|
-
// `args.history` or `args.tools` in place (e.g. trims the array
|
|
1907
|
-
// before calling next) can't silently strip prompt context from
|
|
1908
|
-
// the orchestrator's live `runMessages` / resolved-tools arrays.
|
|
1909
|
-
// TypeScript `readonly` on `EstimateArgs` does not prevent
|
|
1910
|
-
// `push`/`splice` at runtime; the frozen wrapper throws in strict
|
|
1911
|
-
// mode and isolates any mutation attempts from the call-site state.
|
|
1912
|
-
history: Object.freeze([...history]) as Message[],
|
|
1913
|
-
systemPrompt: ctx.systemPrompt,
|
|
1914
|
-
tools: Object.freeze([
|
|
1915
|
-
...ctx.agentLoop.getResolvedTools(history),
|
|
1916
|
-
]) as ToolDefinition[],
|
|
1917
|
-
providerName: estimationProviderName,
|
|
1918
|
-
},
|
|
1919
|
-
pipelineTurnCtx,
|
|
1920
|
-
DEFAULT_TIMEOUTS.tokenEstimate,
|
|
1921
|
-
);
|
|
1922
|
-
|
|
1923
|
-
const preflightTokens = await runTokenEstimatePipeline(runMessages);
|
|
1397
|
+
const preflightTokens = estimatePromptTokensWithTools(
|
|
1398
|
+
runMessages,
|
|
1399
|
+
ctx.systemPrompt,
|
|
1400
|
+
ctx.agentLoop.getResolvedTools(runMessages),
|
|
1401
|
+
estimationProviderName,
|
|
1402
|
+
);
|
|
1924
1403
|
|
|
1925
1404
|
if (overflowRecovery.enabled && preflightTokens > preflightBudget) {
|
|
1926
1405
|
rlog.warn(
|
|
@@ -1932,16 +1411,12 @@ export async function runAgentLoopImpl(
|
|
|
1932
1411
|
"Preflight budget exceeded — running overflow reducer before provider call",
|
|
1933
1412
|
);
|
|
1934
1413
|
|
|
1935
|
-
//
|
|
1936
|
-
//
|
|
1937
|
-
//
|
|
1938
|
-
//
|
|
1939
|
-
//
|
|
1940
|
-
//
|
|
1941
|
-
// per iteration (activity emission, compaction application, runtime
|
|
1942
|
-
// injection reassembly, token re-estimation). Registered plugins that
|
|
1943
|
-
// wrap the `overflowReduce` slot see each iteration through their own
|
|
1944
|
-
// middleware `next` callback.
|
|
1414
|
+
// `runOverflowReductionLoop` drives the tier loop — forced compaction →
|
|
1415
|
+
// tool-result truncation → media stubbing → injection downgrade — plus
|
|
1416
|
+
// the re-inject/re-estimate convergence check. The callbacks below are
|
|
1417
|
+
// the orchestrator-specific side effects it coordinates per iteration
|
|
1418
|
+
// (activity emission, compaction application, runtime injection
|
|
1419
|
+
// reassembly, token re-estimation).
|
|
1945
1420
|
const messagesForPreflightOverflowReduction =
|
|
1946
1421
|
slackChronologicalContext?.messages ?? ctx.messages;
|
|
1947
1422
|
const overflowArgs: OverflowReduceArgs = {
|
|
@@ -1955,75 +1430,23 @@ export async function runAgentLoopImpl(
|
|
|
1955
1430
|
maxAttempts: resolveCurrentContextBudget().overflowRecovery.maxAttempts,
|
|
1956
1431
|
abortSignal: abortController.signal,
|
|
1957
1432
|
compactFn: async (msgs, signal, opts) => {
|
|
1958
|
-
//
|
|
1959
|
-
//
|
|
1960
|
-
//
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
// next tier.
|
|
1971
|
-
try {
|
|
1972
|
-
return (await runPipeline<CompactionArgs, CompactionResult>(
|
|
1973
|
-
"compaction",
|
|
1974
|
-
getMiddlewaresFor("compaction"),
|
|
1975
|
-
(args) =>
|
|
1976
|
-
defaultCompactionTerminal(
|
|
1977
|
-
args,
|
|
1978
|
-
buildPluginTurnContext(ctx, reqId),
|
|
1979
|
-
),
|
|
1980
|
-
{
|
|
1981
|
-
messages: msgs,
|
|
1982
|
-
signal,
|
|
1983
|
-
options: {
|
|
1984
|
-
...(opts ?? {}),
|
|
1985
|
-
overrideProfile: resolveCurrentOverrideProfile() ?? null,
|
|
1986
|
-
},
|
|
1987
|
-
},
|
|
1988
|
-
buildPluginTurnContext(ctx, reqId),
|
|
1989
|
-
DEFAULT_TIMEOUTS.compaction,
|
|
1990
|
-
)) as Awaited<
|
|
1991
|
-
ReturnType<typeof ctx.contextWindowManager.maybeCompact>
|
|
1992
|
-
>;
|
|
1993
|
-
} catch (err) {
|
|
1994
|
-
if (err instanceof PluginTimeoutError) {
|
|
1995
|
-
rlog.warn(
|
|
1996
|
-
{ err, phase: "overflow-reducer-forced-compaction" },
|
|
1997
|
-
"Compaction pipeline timed out — falling through to next reducer tier",
|
|
1998
|
-
);
|
|
1999
|
-
await trackCompactionOutcome(ctx, true, onEvent);
|
|
2000
|
-
return {
|
|
2001
|
-
messages: msgs,
|
|
2002
|
-
compacted: false,
|
|
2003
|
-
previousEstimatedInputTokens: 0,
|
|
2004
|
-
estimatedInputTokens: 0,
|
|
2005
|
-
maxInputTokens: 0,
|
|
2006
|
-
thresholdTokens: 0,
|
|
2007
|
-
compactedMessages: 0,
|
|
2008
|
-
compactedPersistedMessages: 0,
|
|
2009
|
-
summaryCalls: 0,
|
|
2010
|
-
summaryInputTokens: 0,
|
|
2011
|
-
summaryOutputTokens: 0,
|
|
2012
|
-
summaryModel: "",
|
|
2013
|
-
summaryText: "",
|
|
2014
|
-
reason: "compaction pipeline timed out",
|
|
2015
|
-
};
|
|
2016
|
-
}
|
|
2017
|
-
throw err;
|
|
2018
|
-
}
|
|
1433
|
+
// Delegate the reducer's forced-compaction tier to the default
|
|
1434
|
+
// compaction plugin, overlaying the turn's resolved inference
|
|
1435
|
+
// profile and actor trust class onto the reducer-supplied options.
|
|
1436
|
+
const reducerOptions = (opts ?? {}) as ContextWindowCompactOptions;
|
|
1437
|
+
return defaultCompact({
|
|
1438
|
+
manager: ctx.contextWindowManager,
|
|
1439
|
+
messages: msgs,
|
|
1440
|
+
signal,
|
|
1441
|
+
...reducerOptions,
|
|
1442
|
+
overrideProfile: resolveCurrentOverrideProfile() ?? null,
|
|
1443
|
+
actorTrustClass: resolveTurnActorTrustClass(ctx),
|
|
1444
|
+
});
|
|
2019
1445
|
},
|
|
2020
1446
|
emitActivityState: () => {
|
|
2021
|
-
ctx.emitActivityState(
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
"assistant_turn",
|
|
2025
|
-
reqId,
|
|
2026
|
-
);
|
|
1447
|
+
ctx.emitActivityState("thinking", "context_compacting", {
|
|
1448
|
+
requestId: reqId,
|
|
1449
|
+
});
|
|
2027
1450
|
},
|
|
2028
1451
|
onCompactionResult: async (result, compactedBasis) => {
|
|
2029
1452
|
// Track circuit-breaker state whenever the reducer invoked
|
|
@@ -2036,11 +1459,13 @@ export async function runAgentLoopImpl(
|
|
|
2036
1459
|
// truncation-only path, etc.) that shouldn't influence the
|
|
2037
1460
|
// breaker.
|
|
2038
1461
|
if (result.summaryFailed !== undefined) {
|
|
2039
|
-
await
|
|
1462
|
+
await ctx.agentLoop.compactionCircuit.recordOutcome(
|
|
1463
|
+
result.summaryFailed,
|
|
1464
|
+
onEvent,
|
|
1465
|
+
);
|
|
2040
1466
|
}
|
|
2041
1467
|
if (result.compacted) {
|
|
2042
1468
|
await applySuccessfulCompaction(result, compactedBasis);
|
|
2043
|
-
shouldInjectWorkspace = true;
|
|
2044
1469
|
}
|
|
2045
1470
|
},
|
|
2046
1471
|
reinjectForMode: async (
|
|
@@ -2051,27 +1476,25 @@ export async function runAgentLoopImpl(
|
|
|
2051
1476
|
) => {
|
|
2052
1477
|
// Mirror the pre-PR-23 behavior: `ctx.messages` must track the
|
|
2053
1478
|
// reducer's latest output before re-injection runs, because other
|
|
2054
|
-
// sites consulted through `injectionOpts` (
|
|
2055
|
-
//
|
|
2056
|
-
// only updates `ctx.messages` on a
|
|
1479
|
+
// sites consulted through `injectionOpts` (slack history, etc.) and
|
|
1480
|
+
// the injectors' own message-presence scans depend on it, and
|
|
1481
|
+
// `applyCompactionResult` only updates `ctx.messages` on a
|
|
1482
|
+
// compaction tier. Assigning here
|
|
2057
1483
|
// keeps non-compaction tiers (tool-result truncation, media
|
|
2058
1484
|
// stubbing, injection downgrade) observable to downstream
|
|
2059
1485
|
// injection assembly on the same turn.
|
|
2060
1486
|
ctx.messages = reducedMessages;
|
|
2061
1487
|
|
|
2062
|
-
// When THIS iteration compacted, it stripped existing
|
|
2063
|
-
//
|
|
2064
|
-
// that only truncates or downgrades must NOT re-force
|
|
1488
|
+
// When THIS iteration compacted, it stripped the existing
|
|
1489
|
+
// memory-static block — so we re-inject current content. A later
|
|
1490
|
+
// iteration that only truncates or downgrades must NOT re-force it,
|
|
2065
1491
|
// or each round would grow the token count.
|
|
2066
1492
|
// Gate: only the iteration that actually compacted re-injects.
|
|
1493
|
+
// (The `<knowledge_base>`, NOW.md, and v2 static `<info>` blocks
|
|
1494
|
+
// self-gate inside their injectors on whether they are already
|
|
1495
|
+
// present in `reducedMessages`.)
|
|
2067
1496
|
const injection = await applyRuntimeInjections(reducedMessages, {
|
|
2068
1497
|
...injectionOpts,
|
|
2069
|
-
...(stepCompacted && { pkbContext: currentPkbContent }),
|
|
2070
|
-
...(stepCompacted && { memoryV2Static: currentMemoryV2Static }),
|
|
2071
|
-
...(stepCompacted && { nowScratchpad: currentNowContent }),
|
|
2072
|
-
workspaceTopLevelContext: shouldInjectWorkspace
|
|
2073
|
-
? ctx.workspaceTopLevelContext
|
|
2074
|
-
: null,
|
|
2075
1498
|
// Once ANY iteration has compacted `ctx.messages`, the captured
|
|
2076
1499
|
// `slackChronologicalMessages` snapshot (built from the full
|
|
2077
1500
|
// persisted transcript) would overwrite the compacted history
|
|
@@ -2097,86 +1520,14 @@ export async function runAgentLoopImpl(
|
|
|
2097
1520
|
}),
|
|
2098
1521
|
};
|
|
2099
1522
|
|
|
2100
|
-
const overflowResult = await
|
|
2101
|
-
OverflowReduceArgs,
|
|
2102
|
-
OverflowReduceResult
|
|
2103
|
-
>(
|
|
2104
|
-
"overflowReduce",
|
|
2105
|
-
getMiddlewaresFor("overflowReduce"),
|
|
2106
|
-
// Terminal — only reached when every registered middleware calls
|
|
2107
|
-
// `next` and delegates past the innermost layer. The default plugin
|
|
2108
|
-
// is a terminal itself (it doesn't call `next`), so in practice
|
|
2109
|
-
// this fallback fires only when the default has been explicitly
|
|
2110
|
-
// deregistered (tests) and no user plugin replaces it. Strict-fail
|
|
2111
|
-
// semantics: throw so the missing terminal surfaces as a visible
|
|
2112
|
-
// error instead of silently returning the history untouched.
|
|
2113
|
-
async () => {
|
|
2114
|
-
throw new PluginExecutionError(
|
|
2115
|
-
"overflowReduce pipeline has no terminal handler — every reducer middleware called next() without providing a replacement",
|
|
2116
|
-
"overflowReduce",
|
|
2117
|
-
);
|
|
2118
|
-
},
|
|
2119
|
-
overflowArgs,
|
|
2120
|
-
buildPluginTurnContext(ctx, reqId),
|
|
2121
|
-
DEFAULT_TIMEOUTS.overflowReduce,
|
|
2122
|
-
);
|
|
1523
|
+
const overflowResult = await runOverflowReductionLoop(overflowArgs);
|
|
2123
1524
|
|
|
2124
1525
|
ctx.messages = overflowResult.messages;
|
|
2125
1526
|
runMessages = overflowResult.runMessages;
|
|
2126
1527
|
currentInjectionMode = overflowResult.injectionMode;
|
|
2127
1528
|
reducerState = overflowResult.reducerState;
|
|
2128
1529
|
if (overflowResult.reducerCompacted) {
|
|
2129
|
-
reducerCompacted = true;
|
|
2130
|
-
}
|
|
2131
|
-
}
|
|
2132
|
-
|
|
2133
|
-
// Pre-run repair — routed through the `historyRepair` plugin pipeline so
|
|
2134
|
-
// plugins can observe or override repair behavior. The default plugin's
|
|
2135
|
-
// middleware is a passthrough; the actual repair runs in the terminal
|
|
2136
|
-
// (`defaultHistoryRepairTerminal`).
|
|
2137
|
-
let preRepairMessages = runMessages;
|
|
2138
|
-
let preRunRepair: HistoryRepairResult | null = null;
|
|
2139
|
-
try {
|
|
2140
|
-
preRunRepair = await runPipeline<HistoryRepairArgs, HistoryRepairResult>(
|
|
2141
|
-
"historyRepair",
|
|
2142
|
-
getMiddlewaresFor("historyRepair"),
|
|
2143
|
-
async (args) => defaultHistoryRepairTerminal(args),
|
|
2144
|
-
{ history: runMessages, provider: ctx.provider.name },
|
|
2145
|
-
buildPluginTurnContext(ctx, reqId),
|
|
2146
|
-
DEFAULT_TIMEOUTS.historyRepair,
|
|
2147
|
-
);
|
|
2148
|
-
} catch (err) {
|
|
2149
|
-
if (err instanceof PluginTimeoutError) {
|
|
2150
|
-
// Pipeline exceeded its budget — likely a misbehaving third-party
|
|
2151
|
-
// middleware. Degrade gracefully by proceeding with the un-repaired
|
|
2152
|
-
// history rather than turn-fatal-erroring; un-repaired history is
|
|
2153
|
-
// strictly better than no turn at all, and the provider call itself
|
|
2154
|
-
// will still error visibly if the drift is unrecoverable.
|
|
2155
|
-
rlog.warn(
|
|
2156
|
-
{ err, phase: "pre_run" },
|
|
2157
|
-
"historyRepair pipeline timed out — proceeding with un-repaired history",
|
|
2158
|
-
);
|
|
2159
|
-
} else {
|
|
2160
|
-
throw err;
|
|
2161
|
-
}
|
|
2162
|
-
}
|
|
2163
|
-
if (preRunRepair !== null) {
|
|
2164
|
-
// Always adopt the pipeline's output history — a user `historyRepair`
|
|
2165
|
-
// middleware may rewrite `messages` (e.g. provider-specific
|
|
2166
|
-
// normalization) without incrementing any of the built-in repair
|
|
2167
|
-
// counters. Gating the assignment on `stats` would silently discard
|
|
2168
|
-
// those edits and send the un-rewritten history to the provider.
|
|
2169
|
-
runMessages = preRunRepair.messages;
|
|
2170
|
-
if (
|
|
2171
|
-
preRunRepair.stats.assistantToolResultsMigrated > 0 ||
|
|
2172
|
-
preRunRepair.stats.missingToolResultsInserted > 0 ||
|
|
2173
|
-
preRunRepair.stats.orphanToolResultsDowngraded > 0 ||
|
|
2174
|
-
preRunRepair.stats.consecutiveSameRoleMerged > 0
|
|
2175
|
-
) {
|
|
2176
|
-
rlog.warn(
|
|
2177
|
-
{ phase: "pre_run", ...preRunRepair.stats },
|
|
2178
|
-
"Repaired runtime history before provider call",
|
|
2179
|
-
);
|
|
1530
|
+
state.reducerCompacted = true;
|
|
2180
1531
|
}
|
|
2181
1532
|
}
|
|
2182
1533
|
|
|
@@ -2195,22 +1546,22 @@ export async function runAgentLoopImpl(
|
|
|
2195
1546
|
}
|
|
2196
1547
|
|
|
2197
1548
|
// user-prompt-submit hook: plugins may transform `runMessages` right
|
|
2198
|
-
// before the agent loop receives them. Fires once per user turn at
|
|
2199
|
-
//
|
|
2200
|
-
//
|
|
2201
|
-
//
|
|
2202
|
-
//
|
|
2203
|
-
//
|
|
1549
|
+
// before the agent loop receives them. Fires once per user turn at the
|
|
1550
|
+
// primary `agentLoop.run` only — the re-entry / retry calls further down
|
|
1551
|
+
// in this function do not refire it (they're not new user submissions).
|
|
1552
|
+
// Plugins may mutate `ctx.latestMessages` in place OR return a new
|
|
1553
|
+
// context with a fresh array; `runHook` forwards whichever the chain
|
|
1554
|
+
// settles on. Order is plugin registration order.
|
|
2204
1555
|
//
|
|
2205
|
-
// Fires BEFORE
|
|
2206
|
-
//
|
|
2207
|
-
//
|
|
2208
|
-
// new-message extraction for persistence — reflects exactly what
|
|
2209
|
-
// `agentLoop.run` receives.
|
|
1556
|
+
// Fires BEFORE the agent loop runs so the hook-emitted messages are part
|
|
1557
|
+
// of the loop's input; the loop then reports its own appended output via
|
|
1558
|
+
// `AgentLoopRunResult.newMessages`, which is what persistence consumes.
|
|
2210
1559
|
const userPromptCtx: UserPromptSubmitContext = {
|
|
2211
1560
|
conversationId: ctx.conversationId,
|
|
1561
|
+
prompt: options?.titleText ?? content,
|
|
2212
1562
|
originalMessages: ctx.messages,
|
|
2213
1563
|
latestMessages: runMessages,
|
|
1564
|
+
logger: rlog,
|
|
2214
1565
|
};
|
|
2215
1566
|
const finalUserPromptCtx = await runHook(
|
|
2216
1567
|
HOOKS.USER_PROMPT_SUBMIT,
|
|
@@ -2218,8 +1569,6 @@ export async function runAgentLoopImpl(
|
|
|
2218
1569
|
);
|
|
2219
1570
|
runMessages = finalUserPromptCtx.latestMessages;
|
|
2220
1571
|
|
|
2221
|
-
let preRunHistoryLength = runMessages.length;
|
|
2222
|
-
|
|
2223
1572
|
const shouldGenerateTitle = isReplaceableTitle(
|
|
2224
1573
|
getConversation(ctx.conversationId)?.title ?? null,
|
|
2225
1574
|
);
|
|
@@ -2233,42 +1582,18 @@ export async function runAgentLoopImpl(
|
|
|
2233
1582
|
rlog,
|
|
2234
1583
|
turnChannelContext: capturedTurnChannelContext,
|
|
2235
1584
|
turnInterfaceContext: capturedTurnInterfaceContext,
|
|
1585
|
+
applyCompaction: applySuccessfulCompaction,
|
|
2236
1586
|
};
|
|
2237
|
-
const eventHandler = (event: AgentEvent) =>
|
|
1587
|
+
const eventHandler = (event: AgentEvent): Promise<void> =>
|
|
2238
1588
|
dispatchAgentEvent(state, deps, event);
|
|
2239
1589
|
emitTerminalExit = async (reason: AgentLoopExitReason): Promise<void> => {
|
|
2240
1590
|
await eventHandler({ type: "agent_loop_exit", reason });
|
|
2241
1591
|
};
|
|
2242
1592
|
|
|
2243
|
-
const onCheckpoint = async (
|
|
2244
|
-
checkpoint: CheckpointInfo,
|
|
2245
|
-
): Promise<CheckpointDecision> => {
|
|
2246
|
-
state.currentTurnToolNames = [];
|
|
2247
|
-
|
|
1593
|
+
const onCheckpoint = async (): Promise<CheckpointDecision> => {
|
|
2248
1594
|
if (ctx.canHandoffAtCheckpoint()) {
|
|
2249
|
-
|
|
2250
|
-
pendingCheckpointYield = "handoff";
|
|
2251
|
-
return "yield";
|
|
1595
|
+
return "handoff";
|
|
2252
1596
|
}
|
|
2253
|
-
|
|
2254
|
-
// Mid-loop token budget check: estimate current context size and
|
|
2255
|
-
// yield if we're approaching the preflight budget. This lets the
|
|
2256
|
-
// conversation-agent-loop run compaction before the provider rejects.
|
|
2257
|
-
if (overflowRecovery.enabled) {
|
|
2258
|
-
const midLoopThreshold =
|
|
2259
|
-
resolveCurrentContextBudget().preflightBudget * 0.85;
|
|
2260
|
-
const estimated = await runTokenEstimatePipeline(checkpoint.history);
|
|
2261
|
-
if (estimated > midLoopThreshold) {
|
|
2262
|
-
rlog.warn(
|
|
2263
|
-
{ phase: "mid-loop", estimated, threshold: midLoopThreshold },
|
|
2264
|
-
"Token estimate approaching budget — yielding for compaction",
|
|
2265
|
-
);
|
|
2266
|
-
yieldedForBudget = true;
|
|
2267
|
-
pendingCheckpointYield = "budget";
|
|
2268
|
-
return "yield";
|
|
2269
|
-
}
|
|
2270
|
-
}
|
|
2271
|
-
|
|
2272
1597
|
return "continue";
|
|
2273
1598
|
};
|
|
2274
1599
|
|
|
@@ -2277,26 +1602,83 @@ export async function runAgentLoopImpl(
|
|
|
2277
1602
|
rlog.info({ callSite: turnCallSite }, "Starting agent loop run");
|
|
2278
1603
|
|
|
2279
1604
|
// Thread the orchestrator's canonical per-turn context into the agent
|
|
2280
|
-
// loop so its internal pipeline invocations (
|
|
2281
|
-
//
|
|
2282
|
-
//
|
|
2283
|
-
// synthesized `"agent-loop"` placeholder. The loop clones this value
|
|
1605
|
+
// loop so its internal pipeline invocations (e.g. compaction) see the
|
|
1606
|
+
// real conversation identity / trust / contextWindowManager instead of
|
|
1607
|
+
// the synthesized `"agent-loop"` placeholder. The loop clones this value
|
|
2284
1608
|
// and overwrites `turnIndex` with its own tool-use iteration counter.
|
|
2285
1609
|
const loopTurnCtx = buildPluginTurnContext(ctx, reqId);
|
|
2286
1610
|
|
|
2287
|
-
|
|
2288
|
-
|
|
2289
|
-
|
|
2290
|
-
|
|
2291
|
-
|
|
2292
|
-
|
|
2293
|
-
|
|
2294
|
-
|
|
2295
|
-
|
|
2296
|
-
|
|
2297
|
-
|
|
2298
|
-
|
|
2299
|
-
|
|
1611
|
+
// Hook for the loop-owned mid-loop compaction. The agent loop owns the
|
|
1612
|
+
// trigger (its budget gate), the `compaction` pipeline call, the result
|
|
1613
|
+
// interpretation (circuit-breaker bookkeeping + the exhaustion decision),
|
|
1614
|
+
// and the inline continue; this callback bridges the injection state the
|
|
1615
|
+
// loop is intentionally blind to. Durable persistence is signalled via
|
|
1616
|
+
// events; re-injection stays orchestrator-supplied for now.
|
|
1617
|
+
const midLoopCompaction: MidLoopCompaction = {
|
|
1618
|
+
postCompactionHook: async ({ history, turnContext }) => {
|
|
1619
|
+
// stripInjectionsForCompaction() unconditionally removed the existing
|
|
1620
|
+
// memory-static block, so re-inject the current content regardless of
|
|
1621
|
+
// whether compaction actually ran. The `<knowledge_base>`, NOW.md, and
|
|
1622
|
+
// v2 static `<info>` blocks self-gate inside their injectors on block
|
|
1623
|
+
// presence.
|
|
1624
|
+
const injection = await postCompactReinject({
|
|
1625
|
+
...injectionOpts,
|
|
1626
|
+
// Suppress the chronological-transcript snapshot once the reducer
|
|
1627
|
+
// has collapsed `ctx.messages`; the captured snapshot reflects the
|
|
1628
|
+
// full persisted transcript and would overwrite compaction.
|
|
1629
|
+
slackChronologicalMessages: state.reducerCompacted
|
|
1630
|
+
? null
|
|
1631
|
+
: injectionOpts.slackChronologicalMessages,
|
|
1632
|
+
mode: currentInjectionMode,
|
|
1633
|
+
turnContext,
|
|
1634
|
+
history,
|
|
1635
|
+
logger: rlog,
|
|
1636
|
+
});
|
|
1637
|
+
return injection.messages;
|
|
1638
|
+
},
|
|
1639
|
+
};
|
|
1640
|
+
|
|
1641
|
+
/**
|
|
1642
|
+
* Shared closure: runs the agent loop with the orchestrator's turn
|
|
1643
|
+
* context and maps the loop's returned checkpoint pause-reason into the
|
|
1644
|
+
* orchestrator's yield bookkeeping. Returns the updated history so call
|
|
1645
|
+
* sites consume it exactly as before. Pass `compaction` only for the
|
|
1646
|
+
* primary run, where the loop compacts in place when its budget gate
|
|
1647
|
+
* trips; reruns omit it and keep yielding for budget.
|
|
1648
|
+
*/
|
|
1649
|
+
const runAgentLoop = async (
|
|
1650
|
+
msgs: Message[],
|
|
1651
|
+
compaction?: MidLoopCompaction,
|
|
1652
|
+
): Promise<Message[]> => {
|
|
1653
|
+
const { history, exitReason, appendedNewMessages, newMessages } =
|
|
1654
|
+
await ctx.agentLoop.run(msgs, eventHandler, {
|
|
1655
|
+
signal: abortController.signal,
|
|
1656
|
+
requestId: reqId,
|
|
1657
|
+
onCheckpoint,
|
|
1658
|
+
callSite: turnCallSite,
|
|
1659
|
+
turnContext: loopTurnCtx,
|
|
1660
|
+
overrideProfile: turnOverrideProfile,
|
|
1661
|
+
resolveOverrideProfile: resolveCurrentOverrideProfile,
|
|
1662
|
+
resolveContextWindow,
|
|
1663
|
+
compaction,
|
|
1664
|
+
// memory-v3-live: the latest user message carries the volatile v3
|
|
1665
|
+
// `<memory>` block, so anchor the provider's long-TTL cache breakpoint
|
|
1666
|
+
// on the most recent stable message instead.
|
|
1667
|
+
mutableLatestUserMessage: memoryV3Live,
|
|
1668
|
+
});
|
|
1669
|
+
lastRunAppendedNewMessages = appendedNewMessages;
|
|
1670
|
+
lastRunNewMessages = newMessages;
|
|
1671
|
+
if (exitReason === "handoff") {
|
|
1672
|
+
yieldedForHandoff = true;
|
|
1673
|
+
pendingCheckpointYield = "handoff";
|
|
1674
|
+
} else if (exitReason === "budget") {
|
|
1675
|
+
yieldedForBudget = true;
|
|
1676
|
+
pendingCheckpointYield = "budget";
|
|
1677
|
+
}
|
|
1678
|
+
return history;
|
|
1679
|
+
};
|
|
1680
|
+
|
|
1681
|
+
let updatedHistory = await runAgentLoop(runMessages, midLoopCompaction);
|
|
2300
1682
|
|
|
2301
1683
|
rlog.info(
|
|
2302
1684
|
{ resultMessageCount: updatedHistory.length },
|
|
@@ -2308,211 +1690,43 @@ export async function runAgentLoopImpl(
|
|
|
2308
1690
|
pendingCheckpointYield = null;
|
|
2309
1691
|
}
|
|
2310
1692
|
|
|
2311
|
-
//
|
|
2312
|
-
//
|
|
2313
|
-
//
|
|
2314
|
-
//
|
|
2315
|
-
//
|
|
2316
|
-
//
|
|
2317
|
-
let midLoopCompactAttempts = 0;
|
|
2318
|
-
while (
|
|
2319
|
-
yieldedForBudget &&
|
|
2320
|
-
midLoopCompactAttempts <
|
|
2321
|
-
resolveCurrentContextBudget().overflowRecovery.maxAttempts &&
|
|
2322
|
-
!state.contextTooLargeDetected &&
|
|
2323
|
-
!abortController.signal.aborted
|
|
2324
|
-
) {
|
|
2325
|
-
midLoopCompactAttempts++;
|
|
2326
|
-
yieldedForBudget = false;
|
|
2327
|
-
pendingCheckpointYield = null;
|
|
2328
|
-
|
|
2329
|
-
rlog.info(
|
|
2330
|
-
{ phase: "mid-loop-compact" },
|
|
2331
|
-
"Running compaction after checkpoint yield",
|
|
2332
|
-
);
|
|
2333
|
-
|
|
2334
|
-
// Strip injected context from updated history before compacting,
|
|
2335
|
-
// so we compact the "raw" persistent messages.
|
|
2336
|
-
const rawHistory = stripInjectionsForCompaction(updatedHistory);
|
|
2337
|
-
ctx.messages = rawHistory;
|
|
2338
|
-
markHistoryStrippedBestEffort(ctx.conversationId, Date.now(), rlog);
|
|
2339
|
-
|
|
2340
|
-
ctx.emitActivityState(
|
|
2341
|
-
"thinking",
|
|
2342
|
-
"context_compacting",
|
|
2343
|
-
"assistant_turn",
|
|
2344
|
-
reqId,
|
|
2345
|
-
"Compacting context",
|
|
2346
|
-
);
|
|
2347
|
-
let midLoopCompact: Awaited<
|
|
2348
|
-
ReturnType<typeof ctx.contextWindowManager.maybeCompact>
|
|
2349
|
-
>;
|
|
2350
|
-
try {
|
|
2351
|
-
midLoopCompact = (await runPipeline<CompactionArgs, CompactionResult>(
|
|
2352
|
-
"compaction",
|
|
2353
|
-
getMiddlewaresFor("compaction"),
|
|
2354
|
-
(args) =>
|
|
2355
|
-
defaultCompactionTerminal(args, buildPluginTurnContext(ctx, reqId)),
|
|
2356
|
-
{
|
|
2357
|
-
messages: ctx.messages,
|
|
2358
|
-
signal: abortController.signal,
|
|
2359
|
-
options: {
|
|
2360
|
-
lastCompactedAt: ctx.contextCompactedAt ?? undefined,
|
|
2361
|
-
force: true,
|
|
2362
|
-
targetInputTokensOverride:
|
|
2363
|
-
resolveCurrentContextBudget().preflightBudget,
|
|
2364
|
-
conversationOriginChannel:
|
|
2365
|
-
getConversationOriginChannel(ctx.conversationId) ?? undefined,
|
|
2366
|
-
overrideProfile: resolveCurrentOverrideProfile() ?? null,
|
|
2367
|
-
},
|
|
2368
|
-
},
|
|
2369
|
-
buildPluginTurnContext(ctx, reqId),
|
|
2370
|
-
DEFAULT_TIMEOUTS.compaction,
|
|
2371
|
-
)) as Awaited<ReturnType<typeof ctx.contextWindowManager.maybeCompact>>;
|
|
2372
|
-
} catch (err) {
|
|
2373
|
-
if (err instanceof PluginTimeoutError) {
|
|
2374
|
-
// Mid-loop compaction timed out. Record the failure for the
|
|
2375
|
-
// circuit breaker and escalate to the convergence loop's more
|
|
2376
|
-
// aggressive reducer tiers (tool-result truncation, media
|
|
2377
|
-
// stubbing, injection downgrade) by flipping the overflow flag
|
|
2378
|
-
// and breaking out of the mid-loop retry. The existing
|
|
2379
|
-
// "exhausted all attempts" block further down handles the
|
|
2380
|
-
// escalation.
|
|
2381
|
-
rlog.warn(
|
|
2382
|
-
{ err, phase: "mid-loop-compact" },
|
|
2383
|
-
"Compaction pipeline timed out — escalating to convergence loop",
|
|
2384
|
-
);
|
|
2385
|
-
await trackCompactionOutcome(ctx, true, onEvent);
|
|
2386
|
-
state.contextTooLargeDetected = true;
|
|
2387
|
-
break;
|
|
2388
|
-
}
|
|
2389
|
-
throw err;
|
|
2390
|
-
}
|
|
2391
|
-
// `force: true` bypasses the cooldown/threshold gates but early returns
|
|
2392
|
-
// for "no eligible messages" / "insufficient messages" still leave
|
|
2393
|
-
// `summaryFailed` undefined. Only track when the summary LLM actually ran.
|
|
2394
|
-
if (midLoopCompact.summaryFailed !== undefined) {
|
|
2395
|
-
await trackCompactionOutcome(
|
|
2396
|
-
ctx,
|
|
2397
|
-
midLoopCompact.summaryFailed,
|
|
2398
|
-
onEvent,
|
|
2399
|
-
);
|
|
2400
|
-
}
|
|
2401
|
-
if (midLoopCompact.compacted) {
|
|
2402
|
-
await applySuccessfulCompaction(midLoopCompact, rawHistory);
|
|
2403
|
-
reducerCompacted = true;
|
|
2404
|
-
shouldInjectWorkspace = true;
|
|
2405
|
-
}
|
|
2406
|
-
|
|
2407
|
-
// Re-inject runtime context and re-enter the agent loop.
|
|
2408
|
-
// stripInjectionsForCompaction() unconditionally removed the existing
|
|
2409
|
-
// NOW.md block from ctx.messages above, so we must always re-inject
|
|
2410
|
-
// the current content regardless of whether compaction actually ran.
|
|
2411
|
-
const injection = await applyRuntimeInjections(ctx.messages, {
|
|
2412
|
-
...injectionOpts,
|
|
2413
|
-
pkbContext: currentPkbContent,
|
|
2414
|
-
memoryV2Static: currentMemoryV2Static,
|
|
2415
|
-
nowScratchpad: currentNowContent,
|
|
2416
|
-
workspaceTopLevelContext: shouldInjectWorkspace
|
|
2417
|
-
? ctx.workspaceTopLevelContext
|
|
2418
|
-
: null,
|
|
2419
|
-
// Suppress the chronological-transcript snapshot once the reducer
|
|
2420
|
-
// has collapsed `ctx.messages`; the captured snapshot reflects the
|
|
2421
|
-
// full persisted transcript and would overwrite compaction.
|
|
2422
|
-
slackChronologicalMessages: reducerCompacted
|
|
2423
|
-
? null
|
|
2424
|
-
: injectionOpts.slackChronologicalMessages,
|
|
2425
|
-
mode: currentInjectionMode,
|
|
2426
|
-
turnContext: buildPluginTurnContext(ctx, reqId),
|
|
2427
|
-
});
|
|
2428
|
-
runMessages = injection.messages;
|
|
2429
|
-
if (isTrustedActor && currentInjectionMode !== "minimal") {
|
|
2430
|
-
ctx.graphMemory.retrackCachedNodes();
|
|
2431
|
-
}
|
|
2432
|
-
const midLoopCompactStrip = stripHistoricalWebSearchResults(runMessages);
|
|
2433
|
-
if (midLoopCompactStrip.stats.blocksStripped > 0) {
|
|
2434
|
-
rlog.info(
|
|
2435
|
-
{ phase: "mid-loop-compact", ...midLoopCompactStrip.stats },
|
|
2436
|
-
"Converted historical web_search_tool_result blocks to text summaries",
|
|
2437
|
-
);
|
|
2438
|
-
runMessages = midLoopCompactStrip.messages;
|
|
2439
|
-
}
|
|
2440
|
-
preRepairMessages = runMessages;
|
|
2441
|
-
preRunHistoryLength = runMessages.length;
|
|
2442
|
-
|
|
2443
|
-
updatedHistory = await ctx.agentLoop.run(
|
|
2444
|
-
runMessages,
|
|
2445
|
-
eventHandler,
|
|
2446
|
-
abortController.signal,
|
|
2447
|
-
reqId,
|
|
2448
|
-
onCheckpoint,
|
|
2449
|
-
turnCallSite,
|
|
2450
|
-
loopTurnCtx,
|
|
2451
|
-
turnOverrideProfile,
|
|
2452
|
-
resolveCurrentMaxInputTokens(),
|
|
2453
|
-
resolveCurrentOverrideProfile,
|
|
2454
|
-
resolveCurrentMaxInputTokens,
|
|
2455
|
-
);
|
|
2456
|
-
}
|
|
2457
|
-
|
|
2458
|
-
// If mid-loop compaction exhausted all attempts but the agent loop
|
|
2459
|
-
// still yielded (yieldedForBudget is true), the turn is incomplete.
|
|
2460
|
-
// Escalate to the convergence loop's more aggressive reducer tiers
|
|
2461
|
-
// (tool-result truncation, media stubbing, injection downgrade)
|
|
2462
|
-
// instead of silently treating an incomplete turn as done.
|
|
1693
|
+
// The loop compacts in place when its budget gate trips and only yields
|
|
1694
|
+
// `exitReason = "budget"` when that inline compaction timed out or
|
|
1695
|
+
// exhausted its retry budget (the `reinject` hook has already restored
|
|
1696
|
+
// runtime context for the productive case). Escalate to the convergence
|
|
1697
|
+
// loop's more aggressive reducer tiers so a half-finished turn doesn't
|
|
1698
|
+
// reach the user.
|
|
2463
1699
|
if (yieldedForBudget && !abortController.signal.aborted) {
|
|
2464
1700
|
rlog.warn(
|
|
2465
|
-
{
|
|
2466
|
-
|
|
2467
|
-
midLoopCompactAttempts,
|
|
2468
|
-
maxAttempts:
|
|
2469
|
-
resolveCurrentContextBudget().overflowRecovery.maxAttempts,
|
|
2470
|
-
},
|
|
2471
|
-
"Mid-loop compaction exhausted all attempts — escalating to convergence loop",
|
|
1701
|
+
{ phase: "mid-loop-compact" },
|
|
1702
|
+
"Inline compaction could not get under budget — escalating to convergence loop",
|
|
2472
1703
|
);
|
|
2473
1704
|
state.contextTooLargeDetected = true;
|
|
2474
1705
|
}
|
|
2475
1706
|
|
|
2476
1707
|
// One-shot ordering error retry
|
|
2477
|
-
if (
|
|
2478
|
-
state.orderingErrorDetected &&
|
|
2479
|
-
updatedHistory.length === preRunHistoryLength
|
|
2480
|
-
) {
|
|
1708
|
+
if (state.orderingErrorDetected && !lastRunAppendedNewMessages) {
|
|
2481
1709
|
rlog.warn(
|
|
2482
1710
|
{ phase: "retry" },
|
|
2483
1711
|
"Provider ordering error detected, attempting one-shot deep-repair retry",
|
|
2484
1712
|
);
|
|
2485
|
-
// Design note: deep-repair intentionally
|
|
2486
|
-
//
|
|
2487
|
-
//
|
|
2488
|
-
//
|
|
2489
|
-
// the original drift. Plugins can
|
|
2490
|
-
// pre-run repair via the
|
|
2491
|
-
//
|
|
2492
|
-
//
|
|
2493
|
-
//
|
|
2494
|
-
const retryRepair = deepRepairHistory(
|
|
1713
|
+
// Design note: deep-repair intentionally stays a direct call rather
|
|
1714
|
+
// than running through the `user-prompt-submit` hook chain. Deep-repair
|
|
1715
|
+
// is a recovery-only path triggered by a provider ordering error — it
|
|
1716
|
+
// must be deterministic and unaffected by user hooks that might have
|
|
1717
|
+
// caused (or be unable to recover from) the original drift. Plugins can
|
|
1718
|
+
// already observe / transform the pre-run repair via the
|
|
1719
|
+
// `user-prompt-submit` hook (the default history-repair plugin runs
|
|
1720
|
+
// `repairHistory` there); widening that surface to deep-repair is
|
|
1721
|
+
// intentionally deferred until there's a concrete plugin-level use case.
|
|
1722
|
+
const retryRepair = deepRepairHistory(updatedHistory);
|
|
2495
1723
|
runMessages = retryRepair.messages;
|
|
2496
1724
|
const retryStrip = stripHistoricalWebSearchResults(runMessages);
|
|
2497
1725
|
runMessages = retryStrip.messages;
|
|
2498
|
-
preRepairMessages = runMessages;
|
|
2499
|
-
preRunHistoryLength = runMessages.length;
|
|
2500
1726
|
state.orderingErrorDetected = false;
|
|
2501
1727
|
state.deferredOrderingError = null;
|
|
2502
1728
|
|
|
2503
|
-
updatedHistory = await
|
|
2504
|
-
runMessages,
|
|
2505
|
-
eventHandler,
|
|
2506
|
-
abortController.signal,
|
|
2507
|
-
reqId,
|
|
2508
|
-
onCheckpoint,
|
|
2509
|
-
turnCallSite,
|
|
2510
|
-
loopTurnCtx,
|
|
2511
|
-
turnOverrideProfile,
|
|
2512
|
-
resolveCurrentMaxInputTokens(),
|
|
2513
|
-
resolveCurrentOverrideProfile,
|
|
2514
|
-
resolveCurrentMaxInputTokens,
|
|
2515
|
-
);
|
|
1729
|
+
updatedHistory = await runAgentLoop(runMessages);
|
|
2516
1730
|
|
|
2517
1731
|
if (state.orderingErrorDetected) {
|
|
2518
1732
|
rlog.error(
|
|
@@ -2561,29 +1775,31 @@ export async function runAgentLoopImpl(
|
|
|
2561
1775
|
}
|
|
2562
1776
|
// Can't resize — replace with a text annotation so the model
|
|
2563
1777
|
// can explain the situation rather than silently dropping context
|
|
2564
|
-
return [
|
|
2565
|
-
{
|
|
2566
|
-
type: "text" as const,
|
|
2567
|
-
text: "(An image was attached but could not be sent — its dimensions exceed the provider limit and automatic resize was not available. Please resize the image and try again.)",
|
|
2568
|
-
},
|
|
2569
|
-
];
|
|
1778
|
+
return [{ type: "text" as const, text: UNSENDABLE_IMAGE_NOTE }];
|
|
2570
1779
|
}),
|
|
2571
1780
|
};
|
|
2572
1781
|
});
|
|
1782
|
+
// The transform above only mutates ctx.messages for the current retry.
|
|
1783
|
+
// Persist the downgrade for images that can never be sent so the rejected
|
|
1784
|
+
// upload doesn't rehydrate from the DB and resurface on later turns. This
|
|
1785
|
+
// is cleanup for future turns, so a persistence failure must never abort
|
|
1786
|
+
// the retry that is about to run — log it and continue.
|
|
1787
|
+
try {
|
|
1788
|
+
const rewritten = persistUnsendableImageDowngrades(ctx.conversationId);
|
|
1789
|
+
if (rewritten > 0) {
|
|
1790
|
+
rlog.info(
|
|
1791
|
+
{ phase: "image-recovery", rewritten },
|
|
1792
|
+
"Persisted unsendable-image downgrades so they cannot resurface",
|
|
1793
|
+
);
|
|
1794
|
+
}
|
|
1795
|
+
} catch (err) {
|
|
1796
|
+
rlog.warn(
|
|
1797
|
+
{ phase: "image-recovery", err },
|
|
1798
|
+
"Failed to persist unsendable-image downgrade; continuing with in-memory recovery",
|
|
1799
|
+
);
|
|
1800
|
+
}
|
|
2573
1801
|
runMessages = ctx.messages;
|
|
2574
|
-
updatedHistory = await
|
|
2575
|
-
runMessages,
|
|
2576
|
-
eventHandler,
|
|
2577
|
-
abortController.signal,
|
|
2578
|
-
reqId,
|
|
2579
|
-
onCheckpoint,
|
|
2580
|
-
turnCallSite,
|
|
2581
|
-
loopTurnCtx,
|
|
2582
|
-
turnOverrideProfile,
|
|
2583
|
-
resolveCurrentMaxInputTokens(),
|
|
2584
|
-
resolveCurrentOverrideProfile,
|
|
2585
|
-
resolveCurrentMaxInputTokens,
|
|
2586
|
-
);
|
|
1802
|
+
updatedHistory = await runAgentLoop(runMessages);
|
|
2587
1803
|
if (state.imageTooLargeDetected) {
|
|
2588
1804
|
rlog.error(
|
|
2589
1805
|
{ phase: "image-recovery" },
|
|
@@ -2610,19 +1826,9 @@ export async function runAgentLoopImpl(
|
|
|
2610
1826
|
// limit), incorporate those new messages into ctx.messages so the
|
|
2611
1827
|
// convergence loop operates on the full (larger) history.
|
|
2612
1828
|
if (state.contextTooLargeDetected) {
|
|
2613
|
-
|
|
2614
|
-
// it needs to be re-injected. Mid-loop compaction (line ~1067) may
|
|
2615
|
-
// have already stripped injections before escalating here, so we
|
|
2616
|
-
// check actual message state rather than tracking mutation sites.
|
|
2617
|
-
let convergenceStripped =
|
|
2618
|
-
findLastInjectedNowContent(ctx.messages) === null;
|
|
2619
|
-
|
|
2620
|
-
if (updatedHistory.length > preRunHistoryLength) {
|
|
1829
|
+
if (lastRunAppendedNewMessages) {
|
|
2621
1830
|
ctx.messages = stripInjectionsForCompaction(updatedHistory);
|
|
2622
|
-
markHistoryStrippedBestEffort(ctx.conversationId
|
|
2623
|
-
convergenceStripped = true;
|
|
2624
|
-
preRepairMessages = updatedHistory;
|
|
2625
|
-
preRunHistoryLength = updatedHistory.length;
|
|
1831
|
+
markHistoryStrippedBestEffort(ctx.conversationId);
|
|
2626
1832
|
}
|
|
2627
1833
|
if (!reducerState) {
|
|
2628
1834
|
reducerState = createInitialReducerState();
|
|
@@ -2703,15 +1909,13 @@ export async function runAgentLoopImpl(
|
|
|
2703
1909
|
"Emergency mid-turn compaction succeeded — bypassing reducer tiers",
|
|
2704
1910
|
);
|
|
2705
1911
|
if (emergencyResult.summaryFailed !== undefined) {
|
|
2706
|
-
await
|
|
2707
|
-
ctx,
|
|
1912
|
+
await ctx.agentLoop.compactionCircuit.recordOutcome(
|
|
2708
1913
|
emergencyResult.summaryFailed,
|
|
2709
1914
|
onEvent,
|
|
2710
1915
|
);
|
|
2711
1916
|
}
|
|
2712
1917
|
if (emergencyResult.compacted) {
|
|
2713
1918
|
await applySuccessfulCompaction(emergencyResult, ctx.messages);
|
|
2714
|
-
shouldInjectWorkspace = true;
|
|
2715
1919
|
}
|
|
2716
1920
|
// Clear the overflow flag and re-run the agent loop with
|
|
2717
1921
|
// the compacted context.
|
|
@@ -2744,12 +1948,9 @@ export async function runAgentLoopImpl(
|
|
|
2744
1948
|
"Context too large — applying next reducer tier",
|
|
2745
1949
|
);
|
|
2746
1950
|
|
|
2747
|
-
ctx.emitActivityState(
|
|
2748
|
-
|
|
2749
|
-
|
|
2750
|
-
"assistant_turn",
|
|
2751
|
-
reqId,
|
|
2752
|
-
);
|
|
1951
|
+
ctx.emitActivityState("thinking", "context_compacting", {
|
|
1952
|
+
requestId: reqId,
|
|
1953
|
+
});
|
|
2753
1954
|
const convergenceCompactionBasis = ctx.messages;
|
|
2754
1955
|
const step = await reduceContextOverflow(
|
|
2755
1956
|
convergenceCompactionBasis,
|
|
@@ -2765,6 +1966,7 @@ export async function runAgentLoopImpl(
|
|
|
2765
1966
|
ctx.contextWindowManager.maybeCompact(msgs, signal!, {
|
|
2766
1967
|
...(opts ?? {}),
|
|
2767
1968
|
overrideProfile: resolveCurrentOverrideProfile() ?? null,
|
|
1969
|
+
actorTrustClass: resolveTurnActorTrustClass(ctx),
|
|
2768
1970
|
}),
|
|
2769
1971
|
abortController.signal,
|
|
2770
1972
|
);
|
|
@@ -2781,8 +1983,7 @@ export async function runAgentLoopImpl(
|
|
|
2781
1983
|
step.compactionResult &&
|
|
2782
1984
|
step.compactionResult.summaryFailed !== undefined
|
|
2783
1985
|
) {
|
|
2784
|
-
await
|
|
2785
|
-
ctx,
|
|
1986
|
+
await ctx.agentLoop.compactionCircuit.recordOutcome(
|
|
2786
1987
|
step.compactionResult.summaryFailed,
|
|
2787
1988
|
onEvent,
|
|
2788
1989
|
);
|
|
@@ -2793,22 +1994,17 @@ export async function runAgentLoopImpl(
|
|
|
2793
1994
|
step.compactionResult,
|
|
2794
1995
|
convergenceCompactionBasis,
|
|
2795
1996
|
);
|
|
2796
|
-
|
|
2797
|
-
reducerCompacted = true;
|
|
1997
|
+
state.reducerCompacted = true;
|
|
2798
1998
|
}
|
|
2799
1999
|
|
|
2800
|
-
// Only re-inject
|
|
2801
|
-
// otherwise the existing
|
|
2802
|
-
// re-injecting would duplicate it.
|
|
2000
|
+
// Only re-inject the memory-static block when ctx.messages was
|
|
2001
|
+
// actually stripped; otherwise the existing block is still present and
|
|
2002
|
+
// re-injecting would duplicate it. (The `<knowledge_base>` and NOW.md
|
|
2003
|
+
// blocks self-gate inside their injectors on whether they are already
|
|
2004
|
+
// present in `ctx.messages`.)
|
|
2803
2005
|
const injection = await applyRuntimeInjections(ctx.messages, {
|
|
2804
2006
|
...injectionOpts,
|
|
2805
|
-
|
|
2806
|
-
memoryV2Static: convergenceStripped ? currentMemoryV2Static : null,
|
|
2807
|
-
nowScratchpad: convergenceStripped ? currentNowContent : null,
|
|
2808
|
-
workspaceTopLevelContext: shouldInjectWorkspace
|
|
2809
|
-
? ctx.workspaceTopLevelContext
|
|
2810
|
-
: null,
|
|
2811
|
-
slackChronologicalMessages: reducerCompacted
|
|
2007
|
+
slackChronologicalMessages: state.reducerCompacted
|
|
2812
2008
|
? null
|
|
2813
2009
|
: injectionOpts.slackChronologicalMessages,
|
|
2814
2010
|
mode: currentInjectionMode,
|
|
@@ -2826,24 +2022,10 @@ export async function runAgentLoopImpl(
|
|
|
2826
2022
|
);
|
|
2827
2023
|
runMessages = convergenceStrip.messages;
|
|
2828
2024
|
}
|
|
2829
|
-
preRepairMessages = runMessages;
|
|
2830
|
-
preRunHistoryLength = runMessages.length;
|
|
2831
2025
|
state.contextTooLargeDetected = false;
|
|
2832
2026
|
yieldedForBudget = false;
|
|
2833
2027
|
|
|
2834
|
-
updatedHistory = await
|
|
2835
|
-
runMessages,
|
|
2836
|
-
eventHandler,
|
|
2837
|
-
abortController.signal,
|
|
2838
|
-
reqId,
|
|
2839
|
-
onCheckpoint,
|
|
2840
|
-
turnCallSite,
|
|
2841
|
-
loopTurnCtx,
|
|
2842
|
-
turnOverrideProfile,
|
|
2843
|
-
resolveCurrentMaxInputTokens(),
|
|
2844
|
-
resolveCurrentOverrideProfile,
|
|
2845
|
-
resolveCurrentMaxInputTokens,
|
|
2846
|
-
);
|
|
2028
|
+
updatedHistory = await runAgentLoop(runMessages);
|
|
2847
2029
|
|
|
2848
2030
|
// If the rerun still yields at checkpoint, the turn is still
|
|
2849
2031
|
// incomplete — continue reducing through the remaining tiers
|
|
@@ -2862,12 +2044,9 @@ export async function runAgentLoopImpl(
|
|
|
2862
2044
|
// Fold rerun progress into ctx.messages so the next reducer
|
|
2863
2045
|
// tier operates on up-to-date history instead of stale
|
|
2864
2046
|
// pre-rerun messages.
|
|
2865
|
-
if (
|
|
2047
|
+
if (lastRunAppendedNewMessages) {
|
|
2866
2048
|
ctx.messages = stripInjectionsForCompaction(updatedHistory);
|
|
2867
|
-
markHistoryStrippedBestEffort(ctx.conversationId
|
|
2868
|
-
convergenceStripped = true;
|
|
2869
|
-
preRepairMessages = updatedHistory;
|
|
2870
|
-
preRunHistoryLength = updatedHistory.length;
|
|
2049
|
+
markHistoryStrippedBestEffort(ctx.conversationId);
|
|
2871
2050
|
}
|
|
2872
2051
|
}
|
|
2873
2052
|
}
|
|
@@ -2884,88 +2063,38 @@ export async function runAgentLoopImpl(
|
|
|
2884
2063
|
|
|
2885
2064
|
if (action === "auto_compress_latest_turn") {
|
|
2886
2065
|
// Auto-compress without asking — users opt out via the "drop" policy.
|
|
2887
|
-
ctx.emitActivityState(
|
|
2888
|
-
|
|
2889
|
-
|
|
2890
|
-
|
|
2891
|
-
|
|
2892
|
-
|
|
2893
|
-
|
|
2894
|
-
|
|
2895
|
-
|
|
2896
|
-
|
|
2897
|
-
|
|
2898
|
-
CompactionArgs,
|
|
2899
|
-
CompactionResult
|
|
2900
|
-
>(
|
|
2901
|
-
"compaction",
|
|
2902
|
-
getMiddlewaresFor("compaction"),
|
|
2903
|
-
(args) =>
|
|
2904
|
-
defaultCompactionTerminal(
|
|
2905
|
-
args,
|
|
2906
|
-
buildPluginTurnContext(ctx, reqId),
|
|
2907
|
-
),
|
|
2908
|
-
{
|
|
2909
|
-
messages: ctx.messages,
|
|
2910
|
-
signal: abortController.signal,
|
|
2911
|
-
options: {
|
|
2912
|
-
lastCompactedAt: ctx.contextCompactedAt ?? undefined,
|
|
2913
|
-
force: true,
|
|
2914
|
-
minKeepRecentUserTurns: 0,
|
|
2915
|
-
targetInputTokensOverride: correctedTarget,
|
|
2916
|
-
overrideProfile: resolveCurrentOverrideProfile() ?? null,
|
|
2917
|
-
},
|
|
2918
|
-
},
|
|
2919
|
-
buildPluginTurnContext(ctx, reqId),
|
|
2920
|
-
DEFAULT_TIMEOUTS.compaction,
|
|
2921
|
-
)) as Awaited<
|
|
2922
|
-
ReturnType<typeof ctx.contextWindowManager.maybeCompact>
|
|
2923
|
-
>;
|
|
2924
|
-
} catch (err) {
|
|
2925
|
-
if (err instanceof PluginTimeoutError) {
|
|
2926
|
-
// Emergency compaction timed out. Record the circuit-breaker
|
|
2927
|
-
// failure and fall through to the graceful-error path below
|
|
2928
|
-
// (the unsuccessful-compaction fallback) rather than hard-
|
|
2929
|
-
// failing the turn.
|
|
2930
|
-
rlog.warn(
|
|
2931
|
-
{ err, phase: "emergency-compaction" },
|
|
2932
|
-
"Emergency compaction pipeline timed out — continuing with overflow fallback",
|
|
2933
|
-
);
|
|
2934
|
-
await trackCompactionOutcome(ctx, true, onEvent);
|
|
2935
|
-
emergencyCompact = null;
|
|
2936
|
-
} else {
|
|
2937
|
-
throw err;
|
|
2938
|
-
}
|
|
2939
|
-
}
|
|
2066
|
+
ctx.emitActivityState("thinking", "context_compacting", {
|
|
2067
|
+
requestId: reqId,
|
|
2068
|
+
});
|
|
2069
|
+
const emergencyCompact = await defaultCompact({
|
|
2070
|
+
manager: ctx.contextWindowManager,
|
|
2071
|
+
messages: ctx.messages,
|
|
2072
|
+
signal: abortController.signal,
|
|
2073
|
+
force: true,
|
|
2074
|
+
minKeepRecentUserTurns: 0,
|
|
2075
|
+
overrideProfile: resolveCurrentOverrideProfile() ?? null,
|
|
2076
|
+
});
|
|
2940
2077
|
// Only track when the summary LLM actually ran; `force: true`
|
|
2941
|
-
// bypasses the
|
|
2942
|
-
if (
|
|
2943
|
-
|
|
2944
|
-
emergencyCompact.summaryFailed !== undefined
|
|
2945
|
-
) {
|
|
2946
|
-
await trackCompactionOutcome(
|
|
2947
|
-
ctx,
|
|
2078
|
+
// bypasses the auto-threshold gate but not the early-return paths.
|
|
2079
|
+
if (emergencyCompact.summaryFailed !== undefined) {
|
|
2080
|
+
await ctx.agentLoop.compactionCircuit.recordOutcome(
|
|
2948
2081
|
emergencyCompact.summaryFailed,
|
|
2949
2082
|
onEvent,
|
|
2950
2083
|
);
|
|
2951
2084
|
}
|
|
2952
|
-
if (emergencyCompact
|
|
2085
|
+
if (emergencyCompact.compacted) {
|
|
2953
2086
|
await applySuccessfulCompaction(emergencyCompact, ctx.messages);
|
|
2954
|
-
reducerCompacted = true;
|
|
2955
|
-
shouldInjectWorkspace = true;
|
|
2087
|
+
state.reducerCompacted = true;
|
|
2956
2088
|
}
|
|
2957
2089
|
|
|
2958
|
-
// Only re-inject
|
|
2959
|
-
// otherwise the existing block is still present.
|
|
2090
|
+
// Only re-inject the memory-static block when ctx.messages was
|
|
2091
|
+
// actually stripped; otherwise the existing block is still present.
|
|
2092
|
+
// (The `<knowledge_base>`, NOW.md, and v2 static `<info>` blocks
|
|
2093
|
+
// self-gate inside their injectors on whether they are already
|
|
2094
|
+
// present in `ctx.messages`.)
|
|
2960
2095
|
const injection = await applyRuntimeInjections(ctx.messages, {
|
|
2961
2096
|
...injectionOpts,
|
|
2962
|
-
|
|
2963
|
-
memoryV2Static: convergenceStripped ? currentMemoryV2Static : null,
|
|
2964
|
-
nowScratchpad: convergenceStripped ? currentNowContent : null,
|
|
2965
|
-
workspaceTopLevelContext: shouldInjectWorkspace
|
|
2966
|
-
? ctx.workspaceTopLevelContext
|
|
2967
|
-
: null,
|
|
2968
|
-
slackChronologicalMessages: reducerCompacted
|
|
2097
|
+
slackChronologicalMessages: state.reducerCompacted
|
|
2969
2098
|
? null
|
|
2970
2099
|
: injectionOpts.slackChronologicalMessages,
|
|
2971
2100
|
mode: currentInjectionMode,
|
|
@@ -2983,23 +2112,9 @@ export async function runAgentLoopImpl(
|
|
|
2983
2112
|
);
|
|
2984
2113
|
runMessages = fallbackStrip.messages;
|
|
2985
2114
|
}
|
|
2986
|
-
preRepairMessages = runMessages;
|
|
2987
|
-
preRunHistoryLength = runMessages.length;
|
|
2988
2115
|
state.contextTooLargeDetected = false;
|
|
2989
2116
|
|
|
2990
|
-
updatedHistory = await
|
|
2991
|
-
runMessages,
|
|
2992
|
-
eventHandler,
|
|
2993
|
-
abortController.signal,
|
|
2994
|
-
reqId,
|
|
2995
|
-
onCheckpoint,
|
|
2996
|
-
turnCallSite,
|
|
2997
|
-
loopTurnCtx,
|
|
2998
|
-
turnOverrideProfile,
|
|
2999
|
-
resolveCurrentMaxInputTokens(),
|
|
3000
|
-
resolveCurrentOverrideProfile,
|
|
3001
|
-
resolveCurrentMaxInputTokens,
|
|
3002
|
-
);
|
|
2117
|
+
updatedHistory = await runAgentLoop(runMessages);
|
|
3003
2118
|
}
|
|
3004
2119
|
// action === "fail_gracefully" falls through to the final error below
|
|
3005
2120
|
}
|
|
@@ -3050,44 +2165,11 @@ export async function runAgentLoopImpl(
|
|
|
3050
2165
|
onEvent(buildConversationErrorMessage(ctx.conversationId, classified));
|
|
3051
2166
|
}
|
|
3052
2167
|
|
|
3053
|
-
//
|
|
3054
|
-
|
|
3055
|
-
|
|
3056
|
-
|
|
3057
|
-
for (const block of msg.content) {
|
|
3058
|
-
if (
|
|
3059
|
-
block.type === "tool_result" &&
|
|
3060
|
-
!state.pendingToolResults.has(block.tool_use_id) &&
|
|
3061
|
-
!state.persistedToolUseIds.has(block.tool_use_id)
|
|
3062
|
-
) {
|
|
3063
|
-
state.pendingToolResults.set(block.tool_use_id, {
|
|
3064
|
-
content: block.content,
|
|
3065
|
-
isError: block.is_error ?? false,
|
|
3066
|
-
});
|
|
3067
|
-
}
|
|
3068
|
-
}
|
|
3069
|
-
}
|
|
3070
|
-
}
|
|
3071
|
-
|
|
3072
|
-
// Flush remaining tool results
|
|
2168
|
+
// Flush remaining tool results. On a normal turn these drain at the next
|
|
2169
|
+
// `message_complete`; an aborted or yielded loop exits with them still
|
|
2170
|
+
// buffered, so finalize the (possibly already on-arrival-reserved) grouped
|
|
2171
|
+
// row here rather than writing a duplicate.
|
|
3073
2172
|
if (state.pendingToolResults.size > 0) {
|
|
3074
|
-
const toolResultBlocks = Array.from(
|
|
3075
|
-
state.pendingToolResults.entries(),
|
|
3076
|
-
).map(([toolUseId, result]) => ({
|
|
3077
|
-
type: "tool_result",
|
|
3078
|
-
tool_use_id: toolUseId,
|
|
3079
|
-
content: redactSecrets(result.content),
|
|
3080
|
-
is_error: result.isError,
|
|
3081
|
-
...(result.contentBlocks
|
|
3082
|
-
? {
|
|
3083
|
-
contentBlocks: result.contentBlocks.map((block) =>
|
|
3084
|
-
block.type === "text"
|
|
3085
|
-
? { ...block, text: redactSecrets(block.text) }
|
|
3086
|
-
: block,
|
|
3087
|
-
),
|
|
3088
|
-
}
|
|
3089
|
-
: {}),
|
|
3090
|
-
}));
|
|
3091
2173
|
const toolResultMetadata = {
|
|
3092
2174
|
...provenanceFromTrustContext(ctx.trustContext),
|
|
3093
2175
|
userMessageChannel: capturedTurnChannelContext.userMessageChannel,
|
|
@@ -3097,21 +2179,12 @@ export async function runAgentLoopImpl(
|
|
|
3097
2179
|
assistantMessageInterface:
|
|
3098
2180
|
capturedTurnInterfaceContext.assistantMessageInterface,
|
|
3099
2181
|
};
|
|
3100
|
-
await
|
|
3101
|
-
|
|
3102
|
-
|
|
3103
|
-
|
|
3104
|
-
|
|
3105
|
-
op: "add",
|
|
3106
|
-
conversationId: ctx.conversationId,
|
|
3107
|
-
role: "user",
|
|
3108
|
-
content: JSON.stringify(toolResultBlocks),
|
|
3109
|
-
metadata: toolResultMetadata,
|
|
3110
|
-
},
|
|
3111
|
-
buildPluginTurnContext(ctx, reqId),
|
|
3112
|
-
DEFAULT_TIMEOUTS.persistence,
|
|
2182
|
+
await finalizePendingToolResultRow(
|
|
2183
|
+
state,
|
|
2184
|
+
ctx.conversationId,
|
|
2185
|
+
toolResultMetadata,
|
|
2186
|
+
rlog,
|
|
3113
2187
|
);
|
|
3114
|
-
state.pendingToolResults.clear();
|
|
3115
2188
|
}
|
|
3116
2189
|
|
|
3117
2190
|
// Persist the budget_yield_unrecovered notice now that any pending
|
|
@@ -3135,24 +2208,13 @@ export async function runAgentLoopImpl(
|
|
|
3135
2208
|
};
|
|
3136
2209
|
let yieldNoticePersistedId: string | null = null;
|
|
3137
2210
|
try {
|
|
3138
|
-
const
|
|
3139
|
-
|
|
3140
|
-
|
|
3141
|
-
|
|
3142
|
-
|
|
3143
|
-
|
|
3144
|
-
|
|
3145
|
-
{
|
|
3146
|
-
op: "add",
|
|
3147
|
-
conversationId: ctx.conversationId,
|
|
3148
|
-
role: "assistant",
|
|
3149
|
-
content: JSON.stringify(yieldNoticeMessage.content),
|
|
3150
|
-
metadata: yieldNoticeMetadata,
|
|
3151
|
-
},
|
|
3152
|
-
buildPluginTurnContext(ctx, reqId),
|
|
3153
|
-
DEFAULT_TIMEOUTS.persistence,
|
|
3154
|
-
)) as PersistAddResult;
|
|
3155
|
-
yieldNoticePersistedId = yieldPersistResult.message.id;
|
|
2211
|
+
const yieldRow = await addMessage(
|
|
2212
|
+
ctx.conversationId,
|
|
2213
|
+
"assistant",
|
|
2214
|
+
JSON.stringify(yieldNoticeMessage.content),
|
|
2215
|
+
{ metadata: yieldNoticeMetadata },
|
|
2216
|
+
);
|
|
2217
|
+
yieldNoticePersistedId = yieldRow.id;
|
|
3156
2218
|
} catch (err) {
|
|
3157
2219
|
// Non-fatal — a DB hiccup must not escalate a budget-yield exit into
|
|
3158
2220
|
// a turn-level throw. The live SSE event was already emitted, so the
|
|
@@ -3208,7 +2270,7 @@ export async function runAgentLoopImpl(
|
|
|
3208
2270
|
}
|
|
3209
2271
|
|
|
3210
2272
|
// Reconstruct history
|
|
3211
|
-
const newMessages =
|
|
2273
|
+
const newMessages = lastRunNewMessages.map((msg) => {
|
|
3212
2274
|
if (msg.role !== "assistant") return msg;
|
|
3213
2275
|
const { cleanedContent } = cleanAssistantContent(msg.content);
|
|
3214
2276
|
const cleanedBlocks = cleanedContent as ContentBlock[];
|
|
@@ -3239,10 +2301,6 @@ export async function runAgentLoopImpl(
|
|
|
3239
2301
|
state.assistantRowAwaitingFinalization &&
|
|
3240
2302
|
state.lastAssistantMessageId
|
|
3241
2303
|
) {
|
|
3242
|
-
// Direct `deleteMessageById` (not via the `persistence` pipeline):
|
|
3243
|
-
// see the same rationale on the matching cleanup in
|
|
3244
|
-
// `handleLlmCallStarted` — an unfinalized reservation has no
|
|
3245
|
-
// observable history for plugins.
|
|
3246
2304
|
try {
|
|
3247
2305
|
deleteMessageById(state.lastAssistantMessageId);
|
|
3248
2306
|
} catch (err) {
|
|
@@ -3264,20 +2322,12 @@ export async function runAgentLoopImpl(
|
|
|
3264
2322
|
const errorAssistantMessage = createAssistantMessage(
|
|
3265
2323
|
state.providerErrorUserMessage,
|
|
3266
2324
|
);
|
|
3267
|
-
const
|
|
3268
|
-
|
|
3269
|
-
|
|
3270
|
-
|
|
3271
|
-
{
|
|
3272
|
-
|
|
3273
|
-
conversationId: ctx.conversationId,
|
|
3274
|
-
role: "assistant",
|
|
3275
|
-
content: JSON.stringify(errorAssistantMessage.content),
|
|
3276
|
-
metadata: errChannelMeta,
|
|
3277
|
-
},
|
|
3278
|
-
buildPluginTurnContext(ctx, reqId),
|
|
3279
|
-
DEFAULT_TIMEOUTS.persistence,
|
|
3280
|
-
)) as PersistAddResult;
|
|
2325
|
+
const errorRow = await addMessage(
|
|
2326
|
+
ctx.conversationId,
|
|
2327
|
+
"assistant",
|
|
2328
|
+
JSON.stringify(errorAssistantMessage.content),
|
|
2329
|
+
{ metadata: errChannelMeta },
|
|
2330
|
+
);
|
|
3281
2331
|
persistedErrorAssistantMessage = true;
|
|
3282
2332
|
// Repoint `lastAssistantMessageId` at the synthetic error row so the
|
|
3283
2333
|
// post-loop sync, attachment resolution, and `message_complete`/
|
|
@@ -3286,7 +2336,7 @@ export async function runAgentLoopImpl(
|
|
|
3286
2336
|
// above. Mark finalization complete so the next LLM call in this run
|
|
3287
2337
|
// (or a downstream handler) doesn't try to clean up an id that
|
|
3288
2338
|
// already corresponds to a finalized row.
|
|
3289
|
-
state.lastAssistantMessageId =
|
|
2339
|
+
state.lastAssistantMessageId = errorRow.id;
|
|
3290
2340
|
state.assistantRowAwaitingFinalization = false;
|
|
3291
2341
|
newMessages.push(errorAssistantMessage);
|
|
3292
2342
|
// Pipe the just-assigned message id into any orphaned LLM request log
|
|
@@ -3300,10 +2350,7 @@ export async function runAgentLoopImpl(
|
|
|
3300
2350
|
// other conversations cannot collide. Non-fatal — a DB hiccup must
|
|
3301
2351
|
// not escalate a provider rejection into a turn-level throw.
|
|
3302
2352
|
try {
|
|
3303
|
-
backfillMessageIdOnLogs(
|
|
3304
|
-
ctx.conversationId,
|
|
3305
|
-
errorPersistResult.message.id,
|
|
3306
|
-
);
|
|
2353
|
+
backfillMessageIdOnLogs(ctx.conversationId, errorRow.id);
|
|
3307
2354
|
} catch (err) {
|
|
3308
2355
|
rlog.warn(
|
|
3309
2356
|
{ err },
|
|
@@ -3316,7 +2363,16 @@ export async function runAgentLoopImpl(
|
|
|
3316
2363
|
// would create a duplicate plain-text bubble below the alert card.
|
|
3317
2364
|
}
|
|
3318
2365
|
|
|
3319
|
-
|
|
2366
|
+
// Base persisted into `ctx.messages` is the loop's own returned history
|
|
2367
|
+
// (minus the tail it appended this run), with the cleaned `newMessages`
|
|
2368
|
+
// re-appended on top. Sourcing the base from the loop keeps it in lockstep
|
|
2369
|
+
// with any in-loop compaction without the orchestrator maintaining a
|
|
2370
|
+
// parallel snapshot across re-entry sites.
|
|
2371
|
+
const loopBase = updatedHistory.slice(
|
|
2372
|
+
0,
|
|
2373
|
+
updatedHistory.length - lastRunNewMessages.length,
|
|
2374
|
+
);
|
|
2375
|
+
let restoredHistory = [...loopBase, ...newMessages];
|
|
3320
2376
|
|
|
3321
2377
|
// Post-turn tool result truncation: save large results to disk and
|
|
3322
2378
|
// replace in-context content with a prefix/suffix stub + file pointer.
|
|
@@ -3389,7 +2445,10 @@ export async function runAgentLoopImpl(
|
|
|
3389
2445
|
// so the client can re-enable the UI without delay.
|
|
3390
2446
|
if (abortController.signal.aborted) {
|
|
3391
2447
|
syncLastAssistantMessageToDisk();
|
|
3392
|
-
ctx.emitActivityState("idle", "generation_cancelled",
|
|
2448
|
+
ctx.emitActivityState("idle", "generation_cancelled", {
|
|
2449
|
+
anchor: "global",
|
|
2450
|
+
requestId: reqId,
|
|
2451
|
+
});
|
|
3393
2452
|
ctx.traceEmitter.emit(
|
|
3394
2453
|
"generation_cancelled",
|
|
3395
2454
|
"Generation cancelled by user",
|
|
@@ -3433,7 +2492,10 @@ export async function runAgentLoopImpl(
|
|
|
3433
2492
|
await emitTerminalExit?.("aborted_after_checkpoint");
|
|
3434
2493
|
pendingCheckpointYield = null;
|
|
3435
2494
|
}
|
|
3436
|
-
ctx.emitActivityState("idle", "generation_cancelled",
|
|
2495
|
+
ctx.emitActivityState("idle", "generation_cancelled", {
|
|
2496
|
+
anchor: "global",
|
|
2497
|
+
requestId: reqId,
|
|
2498
|
+
});
|
|
3437
2499
|
ctx.traceEmitter.emit(
|
|
3438
2500
|
"generation_cancelled",
|
|
3439
2501
|
"Generation cancelled by user",
|
|
@@ -3474,7 +2536,10 @@ export async function runAgentLoopImpl(
|
|
|
3474
2536
|
});
|
|
3475
2537
|
publishLoopMessagesChanged();
|
|
3476
2538
|
} else {
|
|
3477
|
-
ctx.emitActivityState("idle", "message_complete",
|
|
2539
|
+
ctx.emitActivityState("idle", "message_complete", {
|
|
2540
|
+
anchor: "global",
|
|
2541
|
+
requestId: reqId,
|
|
2542
|
+
});
|
|
3478
2543
|
ctx.traceEmitter.emit(
|
|
3479
2544
|
"message_complete",
|
|
3480
2545
|
"Message processing complete",
|
|
@@ -3497,68 +2562,8 @@ export async function runAgentLoopImpl(
|
|
|
3497
2562
|
: {}),
|
|
3498
2563
|
});
|
|
3499
2564
|
publishLoopMessagesChanged();
|
|
3500
|
-
|
|
3501
|
-
// Proactive artifact: fire once when the processed turn was the 4th user message.
|
|
3502
|
-
// Only trigger for real user-authored turns (not subagent/system messages).
|
|
3503
|
-
{
|
|
3504
|
-
const paConv = getConversation(ctx.conversationId);
|
|
3505
|
-
if (
|
|
3506
|
-
paConv &&
|
|
3507
|
-
paConv.conversationType === "standard" &&
|
|
3508
|
-
options?.isUserMessage
|
|
3509
|
-
) {
|
|
3510
|
-
void (async () => {
|
|
3511
|
-
try {
|
|
3512
|
-
if (hasProactiveArtifactCompleted()) return;
|
|
3513
|
-
const userMsg = getMessageById(
|
|
3514
|
-
userMessageId,
|
|
3515
|
-
ctx.conversationId,
|
|
3516
|
-
);
|
|
3517
|
-
if (!userMsg) return;
|
|
3518
|
-
if (!tryClaimProactiveArtifactTrigger(userMsg.createdAt))
|
|
3519
|
-
return;
|
|
3520
|
-
await runProactiveArtifactJob({
|
|
3521
|
-
conversationId: ctx.conversationId,
|
|
3522
|
-
userMessageCutoff: userMsg.createdAt,
|
|
3523
|
-
assistantMessageId: state.lastAssistantMessageId,
|
|
3524
|
-
suppressAppBuild: state.appBuildToolUsedThisRun,
|
|
3525
|
-
broadcastMessage,
|
|
3526
|
-
});
|
|
3527
|
-
} catch (err) {
|
|
3528
|
-
log.warn(
|
|
3529
|
-
{ err, conversationId: ctx.conversationId },
|
|
3530
|
-
"Proactive artifact trigger failed",
|
|
3531
|
-
);
|
|
3532
|
-
}
|
|
3533
|
-
})();
|
|
3534
|
-
}
|
|
3535
|
-
}
|
|
3536
2565
|
}
|
|
3537
2566
|
}
|
|
3538
|
-
|
|
3539
|
-
// Second title pass: after 3 completed turns, re-generate the title
|
|
3540
|
-
// using the last 3 messages for better context. Only fires when the
|
|
3541
|
-
// current title was auto-generated (isAutoTitle = 1) and the user
|
|
3542
|
-
// has not opted out via `conversations.skipAutoRetitling`.
|
|
3543
|
-
if (ctx.turnCount === 2 && !getConfig().conversations.skipAutoRetitling) {
|
|
3544
|
-
// turnCount is 0-indexed, incremented in finally; 2 = about to become 3rd turn
|
|
3545
|
-
queueRegenerateConversationTitle({
|
|
3546
|
-
conversationId: ctx.conversationId,
|
|
3547
|
-
provider: ctx.provider,
|
|
3548
|
-
onTitleUpdated: (title) => {
|
|
3549
|
-
onEvent({
|
|
3550
|
-
type: "conversation_title_updated",
|
|
3551
|
-
conversationId: ctx.conversationId,
|
|
3552
|
-
title,
|
|
3553
|
-
});
|
|
3554
|
-
onEvent({
|
|
3555
|
-
type: "sync_changed",
|
|
3556
|
-
tags: [conversationMetadataSyncTag(ctx.conversationId)],
|
|
3557
|
-
});
|
|
3558
|
-
},
|
|
3559
|
-
signal: abortController.signal,
|
|
3560
|
-
});
|
|
3561
|
-
}
|
|
3562
2567
|
} catch (err) {
|
|
3563
2568
|
const errorCtx = {
|
|
3564
2569
|
phase: "agent_loop" as const,
|
|
@@ -3569,7 +2574,10 @@ export async function runAgentLoopImpl(
|
|
|
3569
2574
|
await emitTerminalExit?.("aborted_after_checkpoint");
|
|
3570
2575
|
pendingCheckpointYield = null;
|
|
3571
2576
|
}
|
|
3572
|
-
ctx.emitActivityState("idle", "generation_cancelled",
|
|
2577
|
+
ctx.emitActivityState("idle", "generation_cancelled", {
|
|
2578
|
+
anchor: "global",
|
|
2579
|
+
requestId: reqId,
|
|
2580
|
+
});
|
|
3573
2581
|
rlog.info("Generation cancelled by user");
|
|
3574
2582
|
ctx.traceEmitter.emit(
|
|
3575
2583
|
"generation_cancelled",
|
|
@@ -3585,7 +2593,10 @@ export async function runAgentLoopImpl(
|
|
|
3585
2593
|
});
|
|
3586
2594
|
publishLoopMessagesChanged();
|
|
3587
2595
|
} else {
|
|
3588
|
-
ctx.emitActivityState("idle", "error_terminal",
|
|
2596
|
+
ctx.emitActivityState("idle", "error_terminal", {
|
|
2597
|
+
anchor: "global",
|
|
2598
|
+
requestId: reqId,
|
|
2599
|
+
});
|
|
3589
2600
|
const message = err instanceof Error ? err.message : String(err);
|
|
3590
2601
|
const errorClass = err instanceof Error ? err.constructor.name : "Error";
|
|
3591
2602
|
rlog.error({ err }, "Conversation processing error");
|
|
@@ -3612,8 +2623,6 @@ export async function runAgentLoopImpl(
|
|
|
3612
2623
|
}
|
|
3613
2624
|
} finally {
|
|
3614
2625
|
if (turnStarted) {
|
|
3615
|
-
cleanupBootstrapAfterTurnThreshold(ctx.conversationId);
|
|
3616
|
-
|
|
3617
2626
|
ctx.turnCount++;
|
|
3618
2627
|
const config = getConfig();
|
|
3619
2628
|
const maxWait = config.workspaceGit?.turnCommitMaxWaitMs ?? 4000;
|
|
@@ -3651,7 +2660,7 @@ export async function runAgentLoopImpl(
|
|
|
3651
2660
|
ctx.profiler.emitSummary(ctx.traceEmitter, reqId);
|
|
3652
2661
|
|
|
3653
2662
|
ctx.abortController = null;
|
|
3654
|
-
ctx.
|
|
2663
|
+
ctx.setProcessing(false);
|
|
3655
2664
|
ctx.onConfirmationOutcome = undefined;
|
|
3656
2665
|
ctx.surfaceActionRequestIds.delete(ctx.currentRequestId ?? "");
|
|
3657
2666
|
ctx.approvedViaPromptThisTurn = false;
|
|
@@ -3798,7 +2807,7 @@ export async function applyCompactionResult(
|
|
|
3798
2807
|
result.summaryText,
|
|
3799
2808
|
ctx.contextCompactedMessageCount,
|
|
3800
2809
|
);
|
|
3801
|
-
markHistoryStrippedBestEffort(ctx.conversationId
|
|
2810
|
+
markHistoryStrippedBestEffort(ctx.conversationId);
|
|
3802
2811
|
if (options.slackContextCompactionWatermarkTs) {
|
|
3803
2812
|
updateConversationSlackContextWatermark(
|
|
3804
2813
|
ctx.conversationId,
|